turbo_tasks_build/
lib.rs

1use std::{
2    env::{self, current_dir},
3    fmt::{Display, Write},
4    fs::read_dir,
5    path::{MAIN_SEPARATOR as PATH_SEP, PathBuf},
6    sync::Arc,
7};
8
9use anyhow::{Context, Result};
10use glob::glob;
11use quote::ToTokens;
12use rustc_hash::{FxHashMap, FxHashSet};
13use syn::{
14    Attribute, Expr, Ident, Item, ItemEnum, ItemFn, ItemImpl, ItemMacro, ItemMod, ItemStruct,
15    ItemTrait, Lit, Meta, TraitItem, TraitItemFn, parse_quote,
16};
17use turbo_tasks_macros_shared::{
18    GenericTypeInput, PrimitiveInput, get_impl_function_ident, get_native_function_ident,
19    get_path_ident, get_register_trait_impls_ident, get_register_trait_methods_ident,
20    get_register_value_type_ident, get_trait_default_impl_function_ident,
21    get_trait_impl_function_ident, get_trait_type_ident, get_type_ident,
22};
23
24pub fn generate_register() {
25    println!("cargo:rerun-if-changed=build.rs");
26
27    let crate_dir = current_dir().unwrap();
28    let workspace_dir = env::var_os("CARGO_WORKSPACE_DIR")
29        .map(PathBuf::from)
30        .unwrap_or_else(|| crate_dir.clone());
31    let crate_name = env::var("CARGO_PKG_NAME").unwrap();
32    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
33
34    let src_dir = crate_dir.join("src");
35    let examples_dir = crate_dir.join("examples");
36    let tests_dir = crate_dir.join("tests");
37    let fuzz_dir = crate_dir.join("fuzz_targets");
38    let afl_dir = crate_dir.join("afl_targets");
39    let benches_dir = crate_dir.join("benches");
40    let cargo_lock_path = workspace_dir.join("Cargo.lock");
41
42    // TODO: use (ask @sokra)
43    let _lock = cargo_lock::Lockfile::load(cargo_lock_path).ok();
44
45    let mut entries = Vec::new();
46
47    let lib_entry = src_dir.join("lib.rs");
48    if lib_entry.exists() {
49        entries.push(("register.rs".to_string(), lib_entry));
50    } else {
51        let bin_entry = src_dir.join("main.rs");
52        if bin_entry.exists() {
53            entries.push(("register.rs".to_string(), bin_entry));
54        }
55    }
56
57    if examples_dir.exists() {
58        for item in read_dir(examples_dir).unwrap() {
59            let item = item.unwrap();
60            let file_type = &item.file_type().unwrap();
61            if file_type.is_file() || file_type.is_symlink() {
62                let name = item.file_name();
63                let name = name.to_string_lossy();
64                if name.ends_with(".rs") {
65                    entries.push((format!("register_example_{name}"), item.path()));
66                }
67            }
68        }
69    }
70
71    if tests_dir.exists() {
72        for item in read_dir(tests_dir).unwrap() {
73            let item = item.unwrap();
74            let file_type = &item.file_type().unwrap();
75            if file_type.is_file() || file_type.is_symlink() {
76                let name = item.file_name();
77                let name = name.to_string_lossy();
78                if name.ends_with(".rs") {
79                    entries.push((format!("register_test_{name}"), item.path()));
80                }
81            }
82        }
83    }
84
85    if afl_dir.exists() {
86        for item in read_dir(afl_dir).unwrap() {
87            let item = item.unwrap();
88            let file_type = &item.file_type().unwrap();
89            if file_type.is_file() || file_type.is_symlink() {
90                let name = item.file_name();
91                let name = name.to_string_lossy();
92                if name.ends_with(".rs") {
93                    entries.push((format!("register_afl_{name}"), item.path()));
94                }
95            }
96        }
97    }
98
99    if fuzz_dir.exists() {
100        for item in read_dir(fuzz_dir).unwrap() {
101            let item = item.unwrap();
102            let file_type = &item.file_type().unwrap();
103            if file_type.is_file() || file_type.is_symlink() {
104                let name = item.file_name();
105                let name = name.to_string_lossy();
106                if name.ends_with(".rs") {
107                    entries.push((format!("register_fuzz_{name}"), item.path()));
108                }
109            }
110        }
111    }
112
113    if benches_dir.exists() {
114        let bench_mod = benches_dir.join("mod.rs");
115        if bench_mod.is_file() || bench_mod.is_symlink() {
116            let name = bench_mod.file_name().unwrap();
117            let name = name.to_string_lossy();
118            if name.ends_with(".rs") {
119                entries.push(("register_benches.rs".to_string(), bench_mod));
120            }
121        }
122    }
123
124    for (filename, entry) in entries {
125        // TODO hash src dir
126        let hash = "TODO";
127
128        let prefix = format!("{crate_name}@{hash}::");
129
130        let mut register_code = String::new();
131        let mut values = FxHashMap::default();
132
133        let out_file = out_dir.join(filename);
134
135        let mut queue = vec![QueueEntry {
136            file_path: entry,
137            mod_path: "".to_string(),
138            attributes: Vec::new(),
139        }];
140
141        while let Some(QueueEntry {
142            file_path,
143            mod_path,
144            attributes,
145        }) = queue.pop()
146        {
147            println!("cargo:rerun-if-changed={}", file_path.to_string_lossy());
148            let src = std::fs::read_to_string(&file_path).unwrap();
149
150            let mut ctx = RegisterContext {
151                queue: &mut queue,
152
153                file_path: &file_path,
154                prefix: &prefix,
155                mod_path,
156                attributes,
157
158                register: &mut register_code,
159                values: &mut values,
160            };
161
162            match syn::parse_file(&src)
163                .with_context(|| format!("failed to parse {}", file_path.display()))
164            {
165                Ok(file) => {
166                    for item in file.items {
167                        ctx.process_item(&item).unwrap();
168                    }
169                }
170                Err(err) => println!("{err}"),
171            }
172        }
173
174        let mut values_code = String::new();
175        for ((mod_path, ident), entry) in values {
176            for attribute in &entry.attributes {
177                values_code.push_str(attribute);
178            }
179            writeln!(
180                values_code,
181                "crate{}::{}({}, #[allow(unused_variables)] |value| {{",
182                mod_path,
183                get_register_value_type_ident(&ident),
184                entry.global_name,
185            )
186            .unwrap();
187            // Register all the trait items for each impl so we can dispatch to them as turbotasks
188            for trait_ident in &entry.trait_idents {
189                writeln!(
190                    values_code,
191                    "    crate{}::{}(value);",
192                    mod_path,
193                    get_register_trait_methods_ident(trait_ident, &ident),
194                )
195                .unwrap();
196            }
197            writeln!(values_code, "}}, #[allow(unused_variables)] |value_id| {{").unwrap();
198            // Register all the vtables for the impls so we can dispatch to them as normal indirect
199            // trait calls.
200            for trait_ident in &entry.trait_idents {
201                writeln!(
202                    values_code,
203                    "    crate{}::{}(value_id);",
204                    mod_path,
205                    get_register_trait_impls_ident(trait_ident, &ident),
206                )
207                .unwrap();
208            }
209            writeln!(values_code, "}});").unwrap();
210        }
211
212        let code = format!(
213            "{{\nstatic ONCE: std::sync::Once = std::sync::Once::new();\nONCE.call_once(|| \
214             {{\n{register_code}{values_code}}});\n}}\n"
215        );
216        std::fs::write(out_file, code).unwrap();
217
218        // println!("cargo:warning={}", out_file.display());
219        // for line in code.lines() {
220        //     println!("cargo:warning={line}");
221        // }
222    }
223}
224
225pub fn rerun_if_glob(globs: &str, root: &str) {
226    let cwd = env::current_dir().unwrap();
227    let globs = cwd.join(globs.replace('/', PATH_SEP.to_string().as_str()));
228    let root = cwd.join(root.replace('/', PATH_SEP.to_string().as_str()));
229    println!("cargo:rerun-if-changed={}", root.display());
230    let mut seen = FxHashSet::from_iter([root]);
231    for entry in glob(globs.to_str().unwrap()).unwrap() {
232        let path = entry.unwrap();
233        for ancestor in path.ancestors() {
234            if seen.insert(ancestor.to_owned()) {
235                println!("cargo:rerun-if-changed={}", ancestor.display());
236            } else {
237                break;
238            }
239        }
240    }
241}
242
243/// (mod_path, type_ident)
244type ValueKey = (String, Ident);
245/// (global_name, trait_register_fns)
246struct ValueEntry {
247    attributes: Vec<Arc<String>>,
248    global_name: String,
249    trait_idents: Vec<Ident>,
250}
251
252struct QueueEntry {
253    /// The on-disk path to the file representing this module.
254    file_path: PathBuf,
255    /// The `syn::Path`-style representation of the module. Each component is
256    /// separated by `::`.
257    mod_path: String,
258    /// Attributes (`#[cfg(...)]`) applied to the `ItemMod`.
259    attributes: Vec<Arc<String>>,
260}
261
262struct RegisterContext<'a> {
263    queue: &'a mut Vec<QueueEntry>,
264
265    file_path: &'a PathBuf,
266    mod_path: String,
267    attributes: Vec<Arc<String>>,
268    prefix: &'a str,
269
270    register: &'a mut String,
271    values: &'a mut FxHashMap<ValueKey, ValueEntry>,
272}
273
274impl RegisterContext<'_> {
275    fn process_item(&mut self, item: &Item) -> Result<()> {
276        match item {
277            Item::Enum(enum_item) => self.process_enum(enum_item),
278            Item::Fn(fn_item) => self.process_fn(fn_item),
279            Item::Impl(impl_item) => self.process_impl(impl_item),
280            Item::Mod(mod_item) => self.process_mod(mod_item),
281            Item::Struct(struct_item) => self.process_struct(struct_item),
282            Item::Trait(trait_item) => self.process_trait(trait_item),
283            Item::Macro(macro_item) => self.process_macro(macro_item),
284            _ => Ok(()),
285        }
286    }
287
288    fn process_enum(&mut self, item: &ItemEnum) -> Result<()> {
289        self.with_cfg_attrs(&item.attrs, move |this| this.process_enum_inner(item))
290    }
291
292    fn process_enum_inner(&mut self, enum_item: &ItemEnum) -> Result<()> {
293        if has_turbo_attribute(&enum_item.attrs, "value") {
294            self.add_value(&enum_item.ident);
295            self.add_value_debug_impl(&enum_item.ident);
296        }
297        Ok(())
298    }
299
300    fn process_fn(&mut self, item: &ItemFn) -> Result<()> {
301        self.with_cfg_attrs(&item.attrs, move |this| this.process_fn_inner(item))
302    }
303
304    fn process_fn_inner(&mut self, fn_item: &ItemFn) -> Result<()> {
305        if has_turbo_attribute(&fn_item.attrs, "function") {
306            let ident = &fn_item.sig.ident;
307            let type_ident = get_native_function_ident(ident);
308
309            self.register(type_ident, self.get_global_name(&[ident]))?;
310        }
311        Ok(())
312    }
313
314    fn process_impl(&mut self, item: &ItemImpl) -> Result<()> {
315        self.with_cfg_attrs(&item.attrs, move |this| this.process_impl_inner(item))
316    }
317
318    fn process_impl_inner(&mut self, impl_item: &ItemImpl) -> Result<()> {
319        if has_turbo_attribute(&impl_item.attrs, "value_impl") {
320            let struct_ident = get_type_ident(&impl_item.self_ty).unwrap();
321
322            let trait_ident = impl_item
323                .trait_
324                .as_ref()
325                .map(|(_, trait_path, _)| get_path_ident(trait_path));
326
327            if let Some(trait_ident) = &trait_ident {
328                self.add_value_trait(&struct_ident, trait_ident);
329            }
330
331            for item in &impl_item.items {
332                if let syn::ImplItem::Fn(method_item) = item
333                    && method_item
334                        .attrs
335                        .iter()
336                        .any(|a| is_turbo_attribute(a, "function"))
337                {
338                    let method_ident = &method_item.sig.ident;
339                    let function_type_ident = if let Some(trait_ident) = &trait_ident {
340                        get_trait_impl_function_ident(&struct_ident, trait_ident, method_ident)
341                    } else {
342                        get_impl_function_ident(&struct_ident, method_ident)
343                    };
344
345                    let global_name = if let Some(trait_ident) = &trait_ident {
346                        self.get_global_name(&[&struct_ident, trait_ident, method_ident])
347                    } else {
348                        self.get_global_name(&[&struct_ident, method_ident])
349                    };
350
351                    self.register(function_type_ident, global_name)?;
352                }
353            }
354        }
355        Ok(())
356    }
357
358    fn process_mod(&mut self, item: &ItemMod) -> Result<()> {
359        self.with_cfg_attrs(&item.attrs, move |this| this.process_mod_inner(item))
360    }
361
362    fn process_mod_inner(&mut self, mod_item: &ItemMod) -> Result<()> {
363        let child_mod_name = mod_item.ident.to_string();
364        let child_mod_path = format!("{}::{}", self.mod_path, child_mod_name);
365        if let Some((_, items)) = &mod_item.content {
366            let parent_mod_path = std::mem::replace(&mut self.mod_path, child_mod_path);
367            for item in items {
368                self.process_item(item)?;
369            }
370            self.mod_path = parent_mod_path;
371        } else {
372            let parent_file_path = self.file_path.parent().unwrap();
373            if let Some(path) = mod_item.attrs.iter().find_map(|attr| {
374                let Meta::NameValue(pair) = &attr.meta else {
375                    return None;
376                };
377                if !pair.path.is_ident("path") {
378                    return None;
379                }
380                let Expr::Lit(lit) = &pair.value else {
381                    return None;
382                };
383                let Lit::Str(str) = &lit.lit else {
384                    return None;
385                };
386                let path = str.value();
387                let path = path.replace('/', &format!("{PATH_SEP}"));
388                let path = parent_file_path.join(path);
389                Some(path)
390            }) {
391                self.queue.push(QueueEntry {
392                    file_path: path,
393                    mod_path: child_mod_path,
394                    attributes: self.attributes.clone(),
395                });
396            } else {
397                let direct = parent_file_path.join(format!("{child_mod_name}.rs"));
398                if direct.exists() {
399                    self.queue.push(QueueEntry {
400                        file_path: direct,
401                        mod_path: child_mod_path,
402                        attributes: self.attributes.clone(),
403                    });
404                } else {
405                    let nested = parent_file_path.join(&child_mod_name).join("mod.rs");
406                    if nested.exists() {
407                        self.queue.push(QueueEntry {
408                            file_path: nested,
409                            mod_path: child_mod_path,
410                            attributes: self.attributes.clone(),
411                        });
412                    }
413                }
414            }
415        }
416        Ok(())
417    }
418
419    fn process_struct(&mut self, item: &ItemStruct) -> Result<()> {
420        self.with_cfg_attrs(&item.attrs, move |this| this.process_struct_inner(item))
421    }
422
423    fn process_struct_inner(&mut self, struct_item: &ItemStruct) -> Result<()> {
424        if has_turbo_attribute(&struct_item.attrs, "value") {
425            self.add_value(&struct_item.ident);
426            self.add_value_debug_impl(&struct_item.ident);
427        }
428        Ok(())
429    }
430
431    fn process_trait(&mut self, item: &ItemTrait) -> Result<()> {
432        self.with_cfg_attrs(&item.attrs, move |this| this.process_trait_inner(item))
433    }
434
435    fn process_trait_inner(&mut self, trait_item: &ItemTrait) -> Result<()> {
436        if trait_item
437            .attrs
438            .iter()
439            .any(|a| is_turbo_attribute(a, "value_trait"))
440        {
441            let trait_ident = &trait_item.ident;
442
443            for item in &trait_item.items {
444                if let TraitItem::Fn(TraitItemFn {
445                    default: Some(_),
446                    sig,
447                    attrs,
448                    ..
449                }) = item
450                    && attrs.iter().any(|a| is_turbo_attribute(a, "function"))
451                {
452                    let method_ident = &sig.ident;
453                    let function_type_ident =
454                        get_trait_default_impl_function_ident(trait_ident, method_ident);
455
456                    self.register(
457                        function_type_ident,
458                        self.get_global_name(&[trait_ident, method_ident]),
459                    )?;
460                }
461            }
462
463            let trait_type_ident = get_trait_type_ident(trait_ident);
464            self.register(trait_type_ident, self.get_global_name(&[trait_ident]))?;
465        }
466        Ok(())
467    }
468
469    fn process_macro(&mut self, item: &ItemMacro) -> Result<()> {
470        self.with_cfg_attrs(&item.attrs, move |this| this.process_macro_inner(item))
471    }
472
473    fn process_macro_inner(&mut self, macro_item: &ItemMacro) -> Result<()> {
474        if macro_item
475            .mac
476            .path
477            .is_ident("__turbo_tasks_internal_primitive")
478        {
479            let input = macro_item.mac.tokens.clone();
480            let input = syn::parse2::<PrimitiveInput>(input).unwrap();
481
482            let ty = input.ty;
483            let Some(ident) = get_type_ident(&ty) else {
484                return Ok(());
485            };
486
487            self.add_value(&ident);
488            self.add_value_debug_impl(&ident);
489            self.add_value_default_impl(&ident);
490        } else if macro_item
491            .mac
492            .path
493            .is_ident("__turbo_tasks_internal_generic_type")
494        {
495            let input = macro_item.mac.tokens.clone();
496            let input = syn::parse2::<GenericTypeInput>(input).unwrap();
497
498            let ty = input.ty;
499            let Some(ident) = get_type_ident(&ty) else {
500                return Ok(());
501            };
502
503            // Generic types must implement `ValueDebug` manually, as there's currently no
504            // easy way to automate the process.
505            self.add_value(&ident);
506        }
507
508        Ok(())
509    }
510}
511
512impl RegisterContext<'_> {
513    fn get_global_name(&self, parts: &[&Ident]) -> String {
514        format!(
515            "r##\"{}{}::{}\"##",
516            self.prefix,
517            self.mod_path,
518            parts
519                .iter()
520                .map(ToString::to_string)
521                .collect::<Vec<_>>()
522                .join("::")
523        )
524    }
525
526    fn add_value(&mut self, ident: &Ident) {
527        let key: ValueKey = (self.mod_path.clone(), ident.clone());
528        let value = ValueEntry {
529            attributes: self.attributes.clone(),
530            global_name: self.get_global_name(&[ident]),
531            trait_idents: Vec::new(),
532        };
533
534        assert!(
535            self.values.insert(key, value).is_none(),
536            "{ident} is declared more than once"
537        );
538    }
539
540    fn add_value_debug_impl(&mut self, ident: &Ident) {
541        // register default debug impl generated by proc macro
542        self.register_debug_impl(ident).unwrap();
543        self.add_value_trait(
544            ident,
545            &get_type_ident(&parse_quote! {
546                turbo_tasks::debug::ValueDebug
547            })
548            .unwrap(),
549        );
550    }
551
552    fn add_value_default_impl(&mut self, ident: &Ident) {
553        // register default ValueDefault impl generated by proc macro
554        self.register_default_impl(ident).unwrap();
555        self.add_value_trait(
556            ident,
557            &get_type_ident(&parse_quote! {
558                turbo_tasks::ValueDefault
559            })
560            .unwrap(),
561        );
562    }
563
564    fn add_value_trait(&mut self, ident: &Ident, trait_ident: &Ident) {
565        let key: ValueKey = (self.mod_path.clone(), ident.clone());
566
567        let entry = self.values.get_mut(&key);
568        if entry.is_none() {
569            panic!(
570                "failed to add value trait {} to {} in {}. Did you try to implement a trait on a \
571                 Vc instead of its value?",
572                trait_ident,
573                ident,
574                self.file_path.display()
575            );
576        }
577        entry.unwrap().trait_idents.push(trait_ident.clone());
578    }
579
580    fn register(
581        &mut self,
582        type_ident: impl Display,
583        global_name: impl Display,
584    ) -> std::fmt::Result {
585        for attribute in &self.attributes {
586            self.register.push_str(attribute);
587        }
588        writeln!(
589            self.register,
590            "crate{}::{}.register({});",
591            self.mod_path, type_ident, global_name
592        )
593    }
594
595    /// Declares a derive of the given trait and its methods.
596    fn register_impl(
597        &mut self,
598        ident: &Ident,
599        trait_ident: &Ident,
600        fn_names: &[&'static str],
601    ) -> std::fmt::Result {
602        for fn_name in fn_names {
603            let fn_ident = Ident::new(fn_name, ident.span());
604
605            let (impl_fn_ident, global_name) = (
606                get_trait_impl_function_ident(ident, trait_ident, &fn_ident),
607                self.get_global_name(&[ident, trait_ident, &fn_ident]),
608            );
609
610            self.register(impl_fn_ident, global_name)?;
611        }
612
613        Ok(())
614    }
615
616    /// Declares the default derive of the `ValueDebug` trait.
617    fn register_debug_impl(&mut self, ident: &Ident) -> std::fmt::Result {
618        self.register_impl(
619            ident,
620            &get_type_ident(&parse_quote! {
621                turbo_tasks::debug::ValueDebug
622            })
623            .unwrap(),
624            &["dbg", "dbg_depth"],
625        )
626    }
627
628    /// Declares the default derive of the `ValueDefault` trait.
629    fn register_default_impl(&mut self, ident: &Ident) -> std::fmt::Result {
630        self.register_impl(
631            ident,
632            &get_type_ident(&parse_quote! {
633                turbo_tasks::ValueDefault
634            })
635            .unwrap(),
636            &["value_default"],
637        )
638    }
639
640    fn with_cfg_attrs<T>(&mut self, attrs: &[Attribute], func: impl FnOnce(&mut Self) -> T) -> T {
641        let orig_len = self.attributes.len();
642        for attr in attrs.iter().filter(|a| is_cfg_attribute(a)) {
643            self.attributes
644                .push(Arc::new(attr.to_token_stream().to_string()));
645        }
646        let ret = func(self);
647        self.attributes.truncate(orig_len);
648        ret
649    }
650}
651
652fn has_turbo_attribute(attrs: &[Attribute], name: &str) -> bool {
653    attrs.iter().any(|a| is_turbo_attribute(a, name))
654}
655
656fn is_turbo_attribute(attr: &Attribute, name: &str) -> bool {
657    let path = attr.path();
658    if path.leading_colon.is_some() {
659        return false;
660    }
661    let mut iter = path.segments.iter();
662    match iter.next() {
663        Some(seg) if seg.arguments.is_empty() && seg.ident == "turbo_tasks" => match iter.next() {
664            Some(seg) if seg.arguments.is_empty() && seg.ident == name => iter.next().is_none(),
665            _ => false,
666        },
667        _ => false,
668    }
669}
670
671fn is_cfg_attribute(attr: &Attribute) -> bool {
672    attr.path()
673        .get_ident()
674        .is_some_and(|ident| ident == "cfg" || ident == "cfg_attr")
675}