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 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 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 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 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 }
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
243type ValueKey = (String, Ident);
245struct ValueEntry {
247 attributes: Vec<Arc<String>>,
248 global_name: String,
249 trait_idents: Vec<Ident>,
250}
251
252struct QueueEntry {
253 file_path: PathBuf,
255 mod_path: String,
258 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 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 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 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 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 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 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}