xref: /aosp_15_r20/external/bazelbuild-rules_rust/crate_universe/src/context/crate_context.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1 //! Crate specific information embedded into [crate::context::Context] objects.
2 
3 use std::collections::{BTreeMap, BTreeSet};
4 
5 use cargo_metadata::{Node, Package, PackageId};
6 use serde::{Deserialize, Serialize};
7 
8 use crate::config::{AliasRule, CrateId, GenBinaries};
9 use crate::metadata::{
10     CrateAnnotation, Dependency, PairedExtras, SourceAnnotation, TreeResolverMetadata,
11 };
12 use crate::select::Select;
13 use crate::utils::sanitize_module_name;
14 use crate::utils::starlark::{Glob, Label};
15 
16 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
17 pub struct CrateDependency {
18     /// The [CrateId] of the dependency
19     pub id: CrateId,
20 
21     /// The target name of the dependency. Note this may differ from the
22     /// dependency's package name in cases such as build scripts.
23     pub target: String,
24 
25     /// Some dependencies are assigned aliases. This is tracked here
26     #[serde(default, skip_serializing_if = "Option::is_none")]
27     pub alias: Option<String>,
28 }
29 
30 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
31 #[serde(default)]
32 pub(crate) struct TargetAttributes {
33     /// The module name of the crate (notably, not the package name).
34     //
35     // This must be the first field of `TargetAttributes` to make it the
36     // lexicographically first thing the derived `Ord` implementation will sort
37     // by. The `Ord` impl controls the order of multiple rules of the same type
38     // in the same BUILD file. In particular, this makes packages with multiple
39     // bin crates generate those `rust_binary` targets in alphanumeric order.
40     pub(crate) crate_name: String,
41 
42     /// The path to the crate's root source file, relative to the manifest.
43     pub(crate) crate_root: Option<String>,
44 
45     /// A glob pattern of all source files required by the target
46     pub(crate) srcs: Glob,
47 }
48 
49 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Clone)]
50 pub(crate) enum Rule {
51     /// `rust_library`
52     Library(TargetAttributes),
53 
54     /// `rust_proc_macro`
55     ProcMacro(TargetAttributes),
56 
57     /// `rust_binary`
58     Binary(TargetAttributes),
59 
60     /// `cargo_build_script`
61     BuildScript(TargetAttributes),
62 }
63 
64 impl Rule {
65     /// The keys that can be used in override_targets to override these Rule sources.
66     /// These intentionally match the accepted `Target.kind`s returned by cargo-metadata.
override_target_key(&self) -> &'static str67     pub(crate) fn override_target_key(&self) -> &'static str {
68         match self {
69             Self::Library(..) => "lib",
70             Self::ProcMacro(..) => "proc-macro",
71             Self::Binary(..) => "bin",
72             Self::BuildScript(..) => "custom-build",
73         }
74     }
75 
crate_name(&self) -> &str76     pub(crate) fn crate_name(&self) -> &str {
77         match self {
78             Self::Library(attrs)
79             | Self::ProcMacro(attrs)
80             | Self::Binary(attrs)
81             | Self::BuildScript(attrs) => &attrs.crate_name,
82         }
83     }
84 }
85 
86 /// A set of attributes common to most `rust_library`, `rust_proc_macro`, and other
87 /// [core rules of `rules_rust`](https://bazelbuild.github.io/rules_rust/defs.html).
88 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89 #[serde(default)]
90 pub(crate) struct CommonAttributes {
91     #[serde(skip_serializing_if = "Select::is_empty")]
92     pub(crate) compile_data: Select<BTreeSet<Label>>,
93 
94     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
95     pub(crate) compile_data_glob: BTreeSet<String>,
96 
97     #[serde(skip_serializing_if = "Select::is_empty")]
98     pub(crate) crate_features: Select<BTreeSet<String>>,
99 
100     #[serde(skip_serializing_if = "Select::is_empty")]
101     pub(crate) data: Select<BTreeSet<Label>>,
102 
103     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
104     pub(crate) data_glob: BTreeSet<String>,
105 
106     #[serde(skip_serializing_if = "Select::is_empty")]
107     pub(crate) deps: Select<BTreeSet<CrateDependency>>,
108 
109     #[serde(skip_serializing_if = "Select::is_empty")]
110     pub(crate) extra_deps: Select<BTreeSet<Label>>,
111 
112     #[serde(skip_serializing_if = "Select::is_empty")]
113     pub(crate) deps_dev: Select<BTreeSet<CrateDependency>>,
114 
115     pub(crate) edition: String,
116 
117     #[serde(skip_serializing_if = "Option::is_none")]
118     pub(crate) linker_script: Option<String>,
119 
120     #[serde(skip_serializing_if = "Select::is_empty")]
121     pub(crate) proc_macro_deps: Select<BTreeSet<CrateDependency>>,
122 
123     #[serde(skip_serializing_if = "Select::is_empty")]
124     pub(crate) extra_proc_macro_deps: Select<BTreeSet<Label>>,
125 
126     #[serde(skip_serializing_if = "Select::is_empty")]
127     pub(crate) proc_macro_deps_dev: Select<BTreeSet<CrateDependency>>,
128 
129     #[serde(skip_serializing_if = "Select::is_empty")]
130     pub(crate) rustc_env: Select<BTreeMap<String, String>>,
131 
132     #[serde(skip_serializing_if = "Select::is_empty")]
133     pub(crate) rustc_env_files: Select<BTreeSet<String>>,
134 
135     #[serde(skip_serializing_if = "Select::is_empty")]
136     pub(crate) rustc_flags: Select<Vec<String>>,
137 
138     pub(crate) version: String,
139 
140     #[serde(skip_serializing_if = "Vec::is_empty")]
141     pub(crate) tags: Vec<String>,
142 }
143 
144 impl Default for CommonAttributes {
default() -> Self145     fn default() -> Self {
146         Self {
147             compile_data: Default::default(),
148             // Generated targets include all files in their package by default
149             compile_data_glob: BTreeSet::from(["**".to_owned()]),
150             crate_features: Default::default(),
151             data: Default::default(),
152             data_glob: Default::default(),
153             deps: Default::default(),
154             extra_deps: Default::default(),
155             deps_dev: Default::default(),
156             edition: Default::default(),
157             linker_script: Default::default(),
158             proc_macro_deps: Default::default(),
159             extra_proc_macro_deps: Default::default(),
160             proc_macro_deps_dev: Default::default(),
161             rustc_env: Default::default(),
162             rustc_env_files: Default::default(),
163             rustc_flags: Default::default(),
164             version: Default::default(),
165             tags: Default::default(),
166         }
167     }
168 }
169 
170 // Build script attributes. See
171 // https://bazelbuild.github.io/rules_rust/cargo.html#cargo_build_script
172 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173 #[serde(default)]
174 pub(crate) struct BuildScriptAttributes {
175     #[serde(skip_serializing_if = "Select::is_empty")]
176     pub(crate) compile_data: Select<BTreeSet<Label>>,
177 
178     #[serde(skip_serializing_if = "Select::is_empty")]
179     pub(crate) data: Select<BTreeSet<Label>>,
180 
181     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
182     pub(crate) data_glob: BTreeSet<String>,
183 
184     #[serde(skip_serializing_if = "Select::is_empty")]
185     pub(crate) deps: Select<BTreeSet<CrateDependency>>,
186 
187     #[serde(skip_serializing_if = "Select::is_empty")]
188     pub(crate) extra_deps: Select<BTreeSet<Label>>,
189 
190     // TODO: refactor a crate with a build.rs file from two into three bazel
191     // rules in order to deduplicate link_dep information. Currently as the
192     // crate depends upon the build.rs file, the build.rs cannot find the
193     // information for the normal dependencies of the crate. This could be
194     // solved by switching the dependency graph from:
195     //
196     //   rust_library -> cargo_build_script
197     //
198     // to:
199     //
200     //   rust_library ->-+-->------------------->--+
201     //                   |                         |
202     //                   +--> cargo_build_script --+--> crate dependencies
203     //
204     // in which either all of the deps are in crate dependencies, or just the
205     // normal dependencies. This could be handled a special rule, or just using
206     // a `filegroup`.
207     #[serde(skip_serializing_if = "Select::is_empty")]
208     pub(crate) link_deps: Select<BTreeSet<CrateDependency>>,
209 
210     #[serde(skip_serializing_if = "Select::is_empty")]
211     pub(crate) extra_link_deps: Select<BTreeSet<Label>>,
212 
213     #[serde(skip_serializing_if = "Select::is_empty")]
214     pub(crate) build_script_env: Select<BTreeMap<String, String>>,
215 
216     #[serde(skip_serializing_if = "Select::is_empty")]
217     pub(crate) rundir: Select<String>,
218 
219     #[serde(skip_serializing_if = "Select::is_empty")]
220     pub(crate) extra_proc_macro_deps: Select<BTreeSet<Label>>,
221 
222     #[serde(skip_serializing_if = "Select::is_empty")]
223     pub(crate) proc_macro_deps: Select<BTreeSet<CrateDependency>>,
224 
225     #[serde(skip_serializing_if = "Select::is_empty")]
226     pub(crate) rustc_env: Select<BTreeMap<String, String>>,
227 
228     #[serde(skip_serializing_if = "Select::is_empty")]
229     pub(crate) rustc_flags: Select<Vec<String>>,
230 
231     #[serde(skip_serializing_if = "Select::is_empty")]
232     pub(crate) rustc_env_files: Select<BTreeSet<String>>,
233 
234     #[serde(skip_serializing_if = "Select::is_empty")]
235     pub(crate) tools: Select<BTreeSet<Label>>,
236 
237     #[serde(skip_serializing_if = "Option::is_none")]
238     pub(crate) links: Option<String>,
239 
240     #[serde(skip_serializing_if = "BTreeSet::is_empty")]
241     pub(crate) toolchains: BTreeSet<Label>,
242 }
243 
244 impl Default for BuildScriptAttributes {
default() -> Self245     fn default() -> Self {
246         Self {
247             compile_data: Default::default(),
248             data: Default::default(),
249             // Build scripts include all sources by default
250             data_glob: BTreeSet::from(["**".to_owned()]),
251             deps: Default::default(),
252             extra_deps: Default::default(),
253             link_deps: Default::default(),
254             extra_link_deps: Default::default(),
255             build_script_env: Default::default(),
256             rundir: Default::default(),
257             extra_proc_macro_deps: Default::default(),
258             proc_macro_deps: Default::default(),
259             rustc_env: Default::default(),
260             rustc_flags: Default::default(),
261             rustc_env_files: Default::default(),
262             tools: Default::default(),
263             links: Default::default(),
264             toolchains: Default::default(),
265         }
266     }
267 }
268 
269 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270 pub(crate) struct CrateContext {
271     /// The package name of the current crate
272     pub(crate) name: String,
273 
274     /// The full version of the current crate
275     pub(crate) version: semver::Version,
276 
277     /// The package URL of the current crate
278     #[serde(default)]
279     pub(crate) package_url: Option<String>,
280 
281     /// Optional source annotations if they were discoverable in the
282     /// lockfile. Workspace Members will not have source annotations and
283     /// potentially others.
284     #[serde(default)]
285     pub(crate) repository: Option<SourceAnnotation>,
286 
287     /// A list of all targets (lib, proc-macro, bin) associated with this package
288     #[serde(default)]
289     pub(crate) targets: BTreeSet<Rule>,
290 
291     /// The name of the crate's root library target. This is the target that a dependent
292     /// would get if they were to depend on `{crate_name}`.
293     #[serde(default)]
294     pub(crate) library_target_name: Option<String>,
295 
296     /// A set of attributes common to most [Rule] types or target types.
297     #[serde(default)]
298     pub(crate) common_attrs: CommonAttributes,
299 
300     /// Optional attributes for build scripts. This field is only populated if
301     /// a build script (`custom-build`) target is defined for the crate.
302     #[serde(skip_serializing_if = "Option::is_none")]
303     #[serde(default)]
304     pub(crate) build_script_attrs: Option<BuildScriptAttributes>,
305 
306     /// The license used by the crate
307     #[serde(default)]
308     pub(crate) license: Option<String>,
309 
310     /// The SPDX licence IDs
311     /// #[serde(default)]
312     pub(crate) license_ids: BTreeSet<String>,
313 
314     /// The license file
315     #[serde(default)]
316     pub(crate) license_file: Option<String>,
317 
318     /// Additional text to add to the generated BUILD file.
319     #[serde(skip_serializing_if = "Option::is_none")]
320     #[serde(default)]
321     pub(crate) additive_build_file_content: Option<String>,
322 
323     /// If true, disables pipelining for library targets generated for this crate
324     #[serde(skip_serializing_if = "std::ops::Not::not")]
325     #[serde(default)]
326     pub(crate) disable_pipelining: bool,
327 
328     /// Extra targets that should be aliased.
329     #[serde(skip_serializing_if = "BTreeMap::is_empty")]
330     #[serde(default)]
331     pub(crate) extra_aliased_targets: BTreeMap<String, String>,
332 
333     /// Transition rule to use instead of `alias`.
334     #[serde(skip_serializing_if = "Option::is_none")]
335     #[serde(default)]
336     pub(crate) alias_rule: Option<AliasRule>,
337 
338     /// Targets to use instead of the default target for the crate.
339     #[serde(skip_serializing_if = "BTreeMap::is_empty")]
340     #[serde(default)]
341     pub(crate) override_targets: BTreeMap<String, Label>,
342 }
343 
344 impl CrateContext {
345     #[allow(clippy::too_many_arguments)]
new( annotation: &CrateAnnotation, packages: &BTreeMap<PackageId, Package>, source_annotations: &BTreeMap<PackageId, SourceAnnotation>, extras: &BTreeMap<CrateId, PairedExtras>, resolver_data: &TreeResolverMetadata, include_binaries: bool, include_build_scripts: bool, sources_are_present: bool, ) -> Self346     pub(crate) fn new(
347         annotation: &CrateAnnotation,
348         packages: &BTreeMap<PackageId, Package>,
349         source_annotations: &BTreeMap<PackageId, SourceAnnotation>,
350         extras: &BTreeMap<CrateId, PairedExtras>,
351         resolver_data: &TreeResolverMetadata,
352         include_binaries: bool,
353         include_build_scripts: bool,
354         sources_are_present: bool,
355     ) -> Self {
356         let package: &Package = &packages[&annotation.node.id];
357         let current_crate_id = CrateId::new(package.name.clone(), package.version.clone());
358 
359         let new_crate_dep = |dep: Dependency| -> CrateDependency {
360             let pkg = &packages[&dep.package_id];
361 
362             // Unfortunately, The package graph and resolve graph of cargo metadata have different representations
363             // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this
364             // content to align when rendering, the dependency target needs to be explicitly sanitized.
365             let target = sanitize_module_name(&dep.target_name);
366 
367             CrateDependency {
368                 id: CrateId::new(pkg.name.clone(), pkg.version.clone()),
369                 target,
370                 alias: dep.alias,
371             }
372         };
373 
374         // Convert the dependencies into renderable strings
375         let deps = annotation.deps.normal_deps.clone().map(new_crate_dep);
376         let deps_dev = annotation.deps.normal_dev_deps.clone().map(new_crate_dep);
377         let proc_macro_deps = annotation.deps.proc_macro_deps.clone().map(new_crate_dep);
378         let proc_macro_deps_dev = annotation
379             .deps
380             .proc_macro_dev_deps
381             .clone()
382             .map(new_crate_dep);
383 
384         let crate_features = resolver_data
385             .get(&current_crate_id)
386             .map(|tree_data| {
387                 let mut select = Select::<BTreeSet<String>>::new();
388                 for (config, data) in tree_data.items() {
389                     for feature in data.features {
390                         select.insert(feature, config.clone());
391                     }
392                 }
393                 select
394             })
395             .unwrap_or_default();
396 
397         // Gather all "common" attributes
398         let mut common_attrs = CommonAttributes {
399             crate_features,
400             deps,
401             deps_dev,
402             edition: package.edition.as_str().to_string(),
403             proc_macro_deps,
404             proc_macro_deps_dev,
405             version: package.version.to_string(),
406             ..Default::default()
407         };
408 
409         // Locate extra settings for the current package.
410         let package_extra = extras
411             .iter()
412             .find(|(_, settings)| settings.package_id == package.id);
413 
414         let include_build_scripts =
415             Self::crate_includes_build_script(package_extra, include_build_scripts);
416 
417         let gen_none = GenBinaries::Some(BTreeSet::new());
418         let gen_binaries = package_extra
419             .and_then(|(_, settings)| settings.crate_extra.gen_binaries.as_ref())
420             .unwrap_or(if include_binaries {
421                 &GenBinaries::All
422             } else {
423                 &gen_none
424             });
425 
426         // Iterate over each target and produce a Bazel target for all supported "kinds"
427         let targets = Self::collect_targets(
428             &annotation.node,
429             packages,
430             gen_binaries,
431             include_build_scripts,
432             sources_are_present,
433         );
434 
435         // Parse the library crate name from the set of included targets
436         let library_target_name = {
437             let lib_targets: Vec<&TargetAttributes> = targets
438                 .iter()
439                 .filter_map(|t| match t {
440                     Rule::ProcMacro(attrs) => Some(attrs),
441                     Rule::Library(attrs) => Some(attrs),
442                     _ => None,
443                 })
444                 .collect();
445 
446             // TODO: There should only be at most 1 library target. This case
447             // should be handled in a more intelligent way.
448             assert!(lib_targets.len() <= 1);
449             lib_targets
450                 .iter()
451                 .last()
452                 .map(|attr| attr.crate_name.clone())
453         };
454 
455         // Gather any build-script related attributes
456         let build_script_target = targets.iter().find_map(|r| match r {
457             Rule::BuildScript(attr) => Some(attr),
458             _ => None,
459         });
460 
461         let build_script_attrs = if let Some(target) = build_script_target {
462             // Track the build script dependency
463             common_attrs.deps.insert(
464                 CrateDependency {
465                     id: current_crate_id,
466                     target: target.crate_name.clone(),
467                     alias: None,
468                 },
469                 None,
470             );
471 
472             let build_deps = annotation.deps.build_deps.clone().map(new_crate_dep);
473             let build_link_deps = annotation.deps.build_link_deps.clone().map(new_crate_dep);
474             let build_proc_macro_deps = annotation
475                 .deps
476                 .build_proc_macro_deps
477                 .clone()
478                 .map(new_crate_dep);
479 
480             Some(BuildScriptAttributes {
481                 deps: build_deps,
482                 link_deps: build_link_deps,
483                 proc_macro_deps: build_proc_macro_deps,
484                 links: package.links.clone(),
485                 ..Default::default()
486             })
487         } else {
488             None
489         };
490 
491         // Save the repository information for the current crate
492         let repository = source_annotations.get(&package.id).cloned();
493 
494         // Identify the license type
495         let mut license_ids: BTreeSet<String> = BTreeSet::new();
496         if let Some(license) = &package.license {
497             if let Ok(parse_result) = spdx::Expression::parse_mode(license, spdx::ParseMode::LAX) {
498                 parse_result.requirements().for_each(|er| {
499                     if let Some(license_id) = er.req.license.id() {
500                         license_ids.insert(license_id.name.to_string());
501                     }
502                 });
503             }
504         }
505 
506         let license_file = Self::locate_license_file(package);
507 
508         let package_url: Option<String> = match package.repository {
509             Some(..) => package.repository.clone(),
510             None => package.homepage.clone(),
511         };
512 
513         // Create the crate's context and apply extra settings
514         CrateContext {
515             name: package.name.clone(),
516             version: package.version.clone(),
517             license: package.license.clone(),
518             license_ids,
519             license_file,
520             package_url,
521             repository,
522             targets,
523             library_target_name,
524             common_attrs,
525             build_script_attrs,
526             additive_build_file_content: None,
527             disable_pipelining: false,
528             extra_aliased_targets: BTreeMap::new(),
529             alias_rule: None,
530             override_targets: BTreeMap::new(),
531         }
532         .with_overrides(extras)
533     }
534 
with_overrides(mut self, extras: &BTreeMap<CrateId, PairedExtras>) -> Self535     fn with_overrides(mut self, extras: &BTreeMap<CrateId, PairedExtras>) -> Self {
536         let id = CrateId::new(self.name.clone(), self.version.clone());
537 
538         // Insert all overrides/extras
539         if let Some(paired_override) = extras.get(&id) {
540             let crate_extra = &paired_override.crate_extra;
541 
542             // Deps
543             if let Some(extra) = &crate_extra.deps {
544                 self.common_attrs.extra_deps =
545                     Select::merge(self.common_attrs.extra_deps, extra.clone());
546             }
547 
548             // Proc macro deps
549             if let Some(extra) = &crate_extra.proc_macro_deps {
550                 self.common_attrs.extra_proc_macro_deps =
551                     Select::merge(self.common_attrs.extra_proc_macro_deps, extra.clone());
552             }
553 
554             // Compile data
555             if let Some(extra) = &crate_extra.compile_data {
556                 self.common_attrs.compile_data =
557                     Select::merge(self.common_attrs.compile_data, extra.clone());
558             }
559 
560             // Compile data glob
561             if let Some(extra) = &crate_extra.compile_data_glob {
562                 self.common_attrs.compile_data_glob.extend(extra.clone());
563             }
564 
565             // Crate features
566             if let Some(extra) = &crate_extra.crate_features {
567                 self.common_attrs.crate_features =
568                     Select::merge(self.common_attrs.crate_features, extra.clone());
569             }
570 
571             // Data
572             if let Some(extra) = &crate_extra.data {
573                 self.common_attrs.data = Select::merge(self.common_attrs.data, extra.clone());
574             }
575 
576             // Data glob
577             if let Some(extra) = &crate_extra.data_glob {
578                 self.common_attrs.data_glob.extend(extra.clone());
579             }
580 
581             // Disable pipelining
582             if crate_extra.disable_pipelining {
583                 self.disable_pipelining = true;
584             }
585 
586             // Rustc flags
587             if let Some(extra) = &crate_extra.rustc_flags {
588                 self.common_attrs.rustc_flags =
589                     Select::merge(self.common_attrs.rustc_flags, extra.clone());
590             }
591 
592             // Rustc env
593             if let Some(extra) = &crate_extra.rustc_env {
594                 self.common_attrs.rustc_env =
595                     Select::merge(self.common_attrs.rustc_env, extra.clone());
596             }
597 
598             // Rustc env files
599             if let Some(extra) = &crate_extra.rustc_env_files {
600                 self.common_attrs.rustc_env_files =
601                     Select::merge(self.common_attrs.rustc_env_files, extra.clone());
602             }
603 
604             // Build script Attributes
605             if let Some(attrs) = &mut self.build_script_attrs {
606                 // Deps
607                 if let Some(extra) = &crate_extra.build_script_deps {
608                     attrs.extra_deps = Select::merge(attrs.extra_deps.clone(), extra.clone());
609                 }
610 
611                 // Proc macro deps
612                 if let Some(extra) = &crate_extra.build_script_proc_macro_deps {
613                     attrs.extra_proc_macro_deps =
614                         Select::merge(attrs.extra_proc_macro_deps.clone(), extra.clone());
615                 }
616 
617                 // Data
618                 if let Some(extra) = &crate_extra.build_script_data {
619                     attrs.data = Select::merge(attrs.data.clone(), extra.clone());
620                 }
621 
622                 // Tools
623                 if let Some(extra) = &crate_extra.build_script_tools {
624                     attrs.tools = Select::merge(attrs.tools.clone(), extra.clone());
625                 }
626 
627                 // Toolchains
628                 if let Some(extra) = &crate_extra.build_script_toolchains {
629                     attrs.toolchains.extend(extra.iter().cloned());
630                 }
631 
632                 // Data glob
633                 if let Some(extra) = &crate_extra.build_script_data_glob {
634                     attrs.data_glob.extend(extra.clone());
635                 }
636 
637                 // Rustc env
638                 if let Some(extra) = &crate_extra.build_script_rustc_env {
639                     attrs.rustc_env = Select::merge(attrs.rustc_env.clone(), extra.clone());
640                 }
641 
642                 // Build script env
643                 if let Some(extra) = &crate_extra.build_script_env {
644                     attrs.build_script_env =
645                         Select::merge(attrs.build_script_env.clone(), extra.clone());
646                 }
647 
648                 if let Some(rundir) = &crate_extra.build_script_rundir {
649                     attrs.rundir = Select::merge(attrs.rundir.clone(), rundir.clone());
650                 }
651             }
652 
653             // Extra build contents
654             self.additive_build_file_content = crate_extra
655                 .additive_build_file_content
656                 .as_ref()
657                 .map(|content| {
658                     // For prettier rendering, dedent the build contents
659                     textwrap::dedent(content)
660                 });
661 
662             // Extra aliased targets
663             if let Some(extra) = &crate_extra.extra_aliased_targets {
664                 self.extra_aliased_targets.append(&mut extra.clone());
665             }
666 
667             // Transition alias
668             if let Some(alias_rule) = &crate_extra.alias_rule {
669                 self.alias_rule.get_or_insert(alias_rule.clone());
670             }
671 
672             // Git shallow_since
673             if let Some(SourceAnnotation::Git { shallow_since, .. }) = &mut self.repository {
674                 shallow_since.clone_from(&crate_extra.shallow_since);
675             }
676 
677             // Patch attributes
678             if let Some(repository) = &mut self.repository {
679                 match repository {
680                     SourceAnnotation::Git {
681                         patch_args,
682                         patch_tool,
683                         patches,
684                         ..
685                     } => {
686                         patch_args.clone_from(&crate_extra.patch_args);
687                         patch_tool.clone_from(&crate_extra.patch_tool);
688                         patches.clone_from(&crate_extra.patches);
689                     }
690                     SourceAnnotation::Http {
691                         patch_args,
692                         patch_tool,
693                         patches,
694                         ..
695                     } => {
696                         patch_args.clone_from(&crate_extra.patch_args);
697                         patch_tool.clone_from(&crate_extra.patch_tool);
698                         patches.clone_from(&crate_extra.patches);
699                     }
700                 }
701             }
702 
703             if let Some(override_targets) = &crate_extra.override_targets {
704                 self.override_targets.extend(override_targets.clone());
705             }
706         }
707 
708         self
709     }
710 
locate_license_file(package: &Package) -> Option<String>711     fn locate_license_file(package: &Package) -> Option<String> {
712         if let Some(license_file_path) = &package.license_file {
713             return Some(license_file_path.to_string());
714         }
715         let package_root = package
716             .manifest_path
717             .as_std_path()
718             .parent()
719             .expect("Every manifest should have a parent directory");
720         if package_root.exists() {
721             let mut paths: Vec<_> = package_root
722                 .read_dir()
723                 .unwrap()
724                 .map(|r| r.unwrap())
725                 .collect();
726             paths.sort_by_key(|dir| dir.path());
727             for path in paths {
728                 if let Some(file_name) = path.file_name().to_str() {
729                     if file_name.to_uppercase().starts_with("LICENSE") {
730                         return Some(file_name.to_string());
731                     }
732                 }
733             }
734         }
735         None
736     }
737 
738     /// Determine whether or not a crate __should__ include a build script
739     /// (build.rs) if it happens to have one.
crate_includes_build_script( package_extra: Option<(&CrateId, &PairedExtras)>, default_generate_build_script: bool, ) -> bool740     fn crate_includes_build_script(
741         package_extra: Option<(&CrateId, &PairedExtras)>,
742         default_generate_build_script: bool,
743     ) -> bool {
744         // If the crate has extra settings, which explicitly set `gen_build_script`, always use
745         // this value, otherwise, fallback to the provided default.
746         package_extra
747             .and_then(|(_, settings)| settings.crate_extra.gen_build_script)
748             .unwrap_or(default_generate_build_script)
749     }
750 
751     /// Collect all Bazel targets that should be generated for a particular Package
collect_targets( node: &Node, packages: &BTreeMap<PackageId, Package>, gen_binaries: &GenBinaries, include_build_scripts: bool, sources_are_present: bool, ) -> BTreeSet<Rule>752     fn collect_targets(
753         node: &Node,
754         packages: &BTreeMap<PackageId, Package>,
755         gen_binaries: &GenBinaries,
756         include_build_scripts: bool,
757         sources_are_present: bool,
758     ) -> BTreeSet<Rule> {
759         let package = &packages[&node.id];
760 
761         let package_root = package
762             .manifest_path
763             .as_std_path()
764             .parent()
765             .expect("Every manifest should have a parent directory");
766 
767         package
768             .targets
769             .iter()
770             .flat_map(|target| {
771                 target.kind.iter().filter_map(move |kind| {
772                     // Unfortunately, The package graph and resolve graph of cargo metadata have different representations
773                     // for the crate names (resolve graph sanitizes names to match module names) so to get the rest of this
774                     // content to align when rendering, the package target names are always sanitized.
775                     let crate_name = sanitize_module_name(&target.name);
776 
777                     // Locate the crate's root source file relative to the package root normalized for unix
778                     let crate_root = pathdiff::diff_paths(&target.src_path, package_root).map(
779                         // Normalize the path so that it always renders the same regardless of platform
780                         |root| root.to_string_lossy().replace('\\', "/"),
781                     );
782 
783                     // Conditionally check to see if the dependencies is a build-script target
784                     if include_build_scripts && kind == "custom-build" {
785                         return Some(Rule::BuildScript(TargetAttributes {
786                             crate_name,
787                             crate_root,
788                             srcs: Glob::new_rust_srcs(!sources_are_present),
789                         }));
790                     }
791 
792                     // Check to see if the dependencies is a proc-macro target
793                     if kind == "proc-macro" {
794                         return Some(Rule::ProcMacro(TargetAttributes {
795                             crate_name,
796                             crate_root,
797                             srcs: Glob::new_rust_srcs(!sources_are_present),
798                         }));
799                     }
800 
801                     // Check to see if the dependencies is a library target
802                     if ["lib", "rlib"].contains(&kind.as_str()) {
803                         return Some(Rule::Library(TargetAttributes {
804                             crate_name,
805                             crate_root,
806                             srcs: Glob::new_rust_srcs(!sources_are_present),
807                         }));
808                     }
809 
810                     // Check if the target kind is binary and is one of the ones included in gen_binaries
811                     if kind == "bin"
812                         && match gen_binaries {
813                             GenBinaries::All => true,
814                             GenBinaries::Some(set) => set.contains(&target.name),
815                         }
816                     {
817                         return Some(Rule::Binary(TargetAttributes {
818                             crate_name: target.name.clone(),
819                             crate_root,
820                             srcs: Glob::new_rust_srcs(!sources_are_present),
821                         }));
822                     }
823 
824                     None
825                 })
826             })
827             .collect()
828     }
829 }
830 
831 #[cfg(test)]
832 mod test {
833     use super::*;
834 
835     use semver::Version;
836 
837     use crate::config::CrateAnnotations;
838     use crate::metadata::{Annotations, CargoTreeEntry};
839 
common_annotations() -> Annotations840     fn common_annotations() -> Annotations {
841         Annotations::new(
842             crate::test::metadata::common(),
843             crate::test::lockfile::common(),
844             crate::config::Config::default(),
845         )
846         .unwrap()
847     }
848 
849     #[test]
new_context()850     fn new_context() {
851         let annotations = common_annotations();
852 
853         let crate_annotation = &annotations.metadata.crates[&PackageId {
854             repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(),
855         }];
856 
857         let include_binaries = false;
858         let include_build_scripts = false;
859         let are_sources_present = false;
860         let context = CrateContext::new(
861             crate_annotation,
862             &annotations.metadata.packages,
863             &annotations.lockfile.crates,
864             &annotations.pairred_extras,
865             &annotations.metadata.workspace_metadata.tree_metadata,
866             include_binaries,
867             include_build_scripts,
868             are_sources_present,
869         );
870 
871         assert_eq!(context.name, "common");
872         assert_eq!(
873             context.targets,
874             BTreeSet::from([Rule::Library(TargetAttributes {
875                 crate_name: "common".to_owned(),
876                 crate_root: Some("lib.rs".to_owned()),
877                 srcs: Glob::new_rust_srcs(!are_sources_present),
878             })]),
879         );
880     }
881 
882     #[test]
context_with_overrides()883     fn context_with_overrides() {
884         let annotations = common_annotations();
885 
886         let package_id = PackageId {
887             repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(),
888         };
889 
890         let crate_annotation = &annotations.metadata.crates[&package_id];
891 
892         let mut pairred_extras = BTreeMap::new();
893         pairred_extras.insert(
894             CrateId::new("common".to_owned(), semver::Version::new(0, 1, 0)),
895             PairedExtras {
896                 package_id,
897                 crate_extra: CrateAnnotations {
898                     gen_binaries: Some(GenBinaries::All),
899                     data_glob: Some(BTreeSet::from(["**/data_glob/**".to_owned()])),
900                     ..CrateAnnotations::default()
901                 },
902             },
903         );
904 
905         let include_binaries = false;
906         let include_build_scripts = false;
907         let are_sources_present = false;
908         let context = CrateContext::new(
909             crate_annotation,
910             &annotations.metadata.packages,
911             &annotations.lockfile.crates,
912             &pairred_extras,
913             &annotations.metadata.workspace_metadata.tree_metadata,
914             include_binaries,
915             include_build_scripts,
916             are_sources_present,
917         );
918 
919         assert_eq!(context.name, "common");
920         assert_eq!(
921             context.targets,
922             BTreeSet::from([
923                 Rule::Library(TargetAttributes {
924                     crate_name: "common".to_owned(),
925                     crate_root: Some("lib.rs".to_owned()),
926                     srcs: Glob::new_rust_srcs(!are_sources_present),
927                 }),
928                 Rule::Binary(TargetAttributes {
929                     crate_name: "common-bin".to_owned(),
930                     crate_root: Some("main.rs".to_owned()),
931                     srcs: Glob::new_rust_srcs(!are_sources_present),
932                 }),
933             ]),
934         );
935         assert_eq!(
936             context.common_attrs.data_glob,
937             BTreeSet::from(["**/data_glob/**".to_owned()])
938         );
939     }
940 
build_script_annotations() -> Annotations941     fn build_script_annotations() -> Annotations {
942         Annotations::new(
943             crate::test::metadata::build_scripts(),
944             crate::test::lockfile::build_scripts(),
945             crate::config::Config::default(),
946         )
947         .unwrap()
948     }
949 
crate_type_annotations() -> Annotations950     fn crate_type_annotations() -> Annotations {
951         Annotations::new(
952             crate::test::metadata::crate_types(),
953             crate::test::lockfile::crate_types(),
954             crate::config::Config::default(),
955         )
956         .unwrap()
957     }
958 
959     #[test]
context_with_build_script()960     fn context_with_build_script() {
961         let annotations = build_script_annotations();
962 
963         let package_id = PackageId {
964             repr: "registry+https://github.com/rust-lang/crates.io-index#[email protected]"
965                 .to_owned(),
966         };
967 
968         let crate_annotation = &annotations.metadata.crates[&package_id];
969 
970         let include_binaries = false;
971         let include_build_scripts = true;
972         let are_sources_present = false;
973         let context = CrateContext::new(
974             crate_annotation,
975             &annotations.metadata.packages,
976             &annotations.lockfile.crates,
977             &annotations.pairred_extras,
978             &annotations.metadata.workspace_metadata.tree_metadata,
979             include_binaries,
980             include_build_scripts,
981             are_sources_present,
982         );
983 
984         assert_eq!(context.name, "openssl-sys");
985         assert!(context.build_script_attrs.is_some());
986         assert_eq!(
987             context.targets,
988             BTreeSet::from([
989                 Rule::Library(TargetAttributes {
990                     crate_name: "openssl_sys".to_owned(),
991                     crate_root: Some("src/lib.rs".to_owned()),
992                     srcs: Glob::new_rust_srcs(!are_sources_present),
993                 }),
994                 Rule::BuildScript(TargetAttributes {
995                     crate_name: "build_script_main".to_owned(),
996                     crate_root: Some("build/main.rs".to_owned()),
997                     srcs: Glob::new_rust_srcs(!are_sources_present),
998                 })
999             ]),
1000         );
1001 
1002         // Cargo build scripts should include all sources
1003         assert!(context.build_script_attrs.unwrap().data_glob.contains("**"));
1004     }
1005 
1006     #[test]
context_disabled_build_script()1007     fn context_disabled_build_script() {
1008         let annotations = build_script_annotations();
1009 
1010         let package_id = PackageId {
1011             repr: "registry+https://github.com/rust-lang/crates.io-index#[email protected]"
1012                 .to_owned(),
1013         };
1014 
1015         let crate_annotation = &annotations.metadata.crates[&package_id];
1016 
1017         let include_binaries = false;
1018         let include_build_scripts = false;
1019         let are_sources_present = false;
1020         let context = CrateContext::new(
1021             crate_annotation,
1022             &annotations.metadata.packages,
1023             &annotations.lockfile.crates,
1024             &annotations.pairred_extras,
1025             &annotations.metadata.workspace_metadata.tree_metadata,
1026             include_binaries,
1027             include_build_scripts,
1028             are_sources_present,
1029         );
1030 
1031         assert_eq!(context.name, "openssl-sys");
1032         assert!(context.build_script_attrs.is_none());
1033         assert_eq!(
1034             context.targets,
1035             BTreeSet::from([Rule::Library(TargetAttributes {
1036                 crate_name: "openssl_sys".to_owned(),
1037                 crate_root: Some("src/lib.rs".to_owned()),
1038                 srcs: Glob::new_rust_srcs(!are_sources_present),
1039             })]),
1040         );
1041     }
1042 
1043     #[test]
context_rlib_crate_type()1044     fn context_rlib_crate_type() {
1045         let annotations = crate_type_annotations();
1046 
1047         let package_id = PackageId {
1048             repr: "registry+https://github.com/rust-lang/crates.io-index#[email protected]".to_owned(),
1049         };
1050 
1051         let crate_annotation = &annotations.metadata.crates[&package_id];
1052 
1053         let include_binaries = false;
1054         let include_build_scripts = false;
1055         let are_sources_present = false;
1056         let context = CrateContext::new(
1057             crate_annotation,
1058             &annotations.metadata.packages,
1059             &annotations.lockfile.crates,
1060             &annotations.pairred_extras,
1061             &annotations.metadata.workspace_metadata.tree_metadata,
1062             include_binaries,
1063             include_build_scripts,
1064             are_sources_present,
1065         );
1066 
1067         assert_eq!(context.name, "sysinfo");
1068         assert!(context.build_script_attrs.is_none());
1069         assert_eq!(
1070             context.targets,
1071             BTreeSet::from([Rule::Library(TargetAttributes {
1072                 crate_name: "sysinfo".to_owned(),
1073                 crate_root: Some("src/lib.rs".to_owned()),
1074                 srcs: Glob::new_rust_srcs(!are_sources_present),
1075             })]),
1076         );
1077     }
1078 
package_context_test( set_package: fn(package: &mut Package), check_context: fn(context: CrateContext), )1079     fn package_context_test(
1080         set_package: fn(package: &mut Package),
1081         check_context: fn(context: CrateContext),
1082     ) {
1083         let mut annotations = common_annotations();
1084         let crate_annotation = &annotations.metadata.crates[&PackageId {
1085             repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(),
1086         }];
1087         let include_binaries = false;
1088         let include_build_scripts = false;
1089         let are_sources_present = false;
1090 
1091         let package = annotations
1092             .metadata
1093             .packages
1094             .get_mut(&crate_annotation.node.id)
1095             .unwrap();
1096         set_package(package);
1097 
1098         let context = CrateContext::new(
1099             crate_annotation,
1100             &annotations.metadata.packages,
1101             &annotations.lockfile.crates,
1102             &annotations.pairred_extras,
1103             &annotations.metadata.workspace_metadata.tree_metadata,
1104             include_binaries,
1105             include_build_scripts,
1106             are_sources_present,
1107         );
1108 
1109         assert_eq!(context.name, "common");
1110         check_context(context);
1111     }
1112 
1113     #[test]
context_with_parsable_license()1114     fn context_with_parsable_license() {
1115         package_context_test(
1116             |package| {
1117                 package.license = Some("MIT OR Apache-2.0".to_owned());
1118             },
1119             |context| {
1120                 assert_eq!(
1121                     context.license_ids,
1122                     BTreeSet::from(["MIT".to_owned(), "Apache-2.0".to_owned(),]),
1123                 );
1124             },
1125         );
1126     }
1127 
1128     #[test]
context_with_unparsable_license()1129     fn context_with_unparsable_license() {
1130         package_context_test(
1131             |package| {
1132                 package.license = Some("NonSPDXLicenseID".to_owned());
1133             },
1134             |context| {
1135                 assert_eq!(context.license_ids, BTreeSet::default());
1136             },
1137         );
1138     }
1139 
1140     #[test]
context_with_license_file()1141     fn context_with_license_file() {
1142         package_context_test(
1143             |package| {
1144                 package.license_file = Some("LICENSE.txt".into());
1145             },
1146             |context| {
1147                 assert_eq!(context.license_file, Some("LICENSE.txt".to_owned()));
1148             },
1149         );
1150     }
1151 
1152     #[test]
context_package_url_with_only_repository()1153     fn context_package_url_with_only_repository() {
1154         package_context_test(
1155             |package| {
1156                 package.repository = Some("http://www.repostiory.com/".to_owned());
1157                 package.homepage = None;
1158             },
1159             |context| {
1160                 assert_eq!(
1161                     context.package_url,
1162                     Some("http://www.repostiory.com/".to_owned())
1163                 );
1164             },
1165         );
1166     }
1167 
1168     #[test]
context_package_url_with_only_homepage()1169     fn context_package_url_with_only_homepage() {
1170         package_context_test(
1171             |package| {
1172                 package.repository = None;
1173                 package.homepage = Some("http://www.homepage.com/".to_owned());
1174             },
1175             |context| {
1176                 assert_eq!(
1177                     context.package_url,
1178                     Some("http://www.homepage.com/".to_owned())
1179                 );
1180             },
1181         );
1182     }
1183 
1184     #[test]
context_package_url_prefers_repository()1185     fn context_package_url_prefers_repository() {
1186         package_context_test(
1187             |package| {
1188                 package.repository = Some("http://www.repostiory.com/".to_owned());
1189                 package.homepage = Some("http://www.homepage.com/".to_owned());
1190             },
1191             |context| {
1192                 assert_eq!(
1193                     context.package_url,
1194                     Some("http://www.repostiory.com/".to_owned())
1195                 );
1196             },
1197         );
1198     }
1199 
1200     #[test]
crate_context_features_from_annotations()1201     fn crate_context_features_from_annotations() {
1202         let mut annotations = common_annotations();
1203 
1204         // Crate a fake feature to track
1205         let mut select = Select::new();
1206         select.insert(
1207             CargoTreeEntry {
1208                 features: BTreeSet::from(["unique_feature".to_owned()]),
1209                 deps: BTreeSet::new(),
1210             },
1211             // The common config
1212             None,
1213         );
1214         annotations
1215             .metadata
1216             .workspace_metadata
1217             .tree_metadata
1218             .insert(
1219                 CrateId::new("common".to_owned(), Version::new(0, 1, 0)),
1220                 select,
1221             );
1222 
1223         let crate_annotation = &annotations.metadata.crates[&PackageId {
1224             repr: "path+file://{TEMP_DIR}/common#0.1.0".to_owned(),
1225         }];
1226         let include_binaries = false;
1227         let include_build_scripts = false;
1228         let are_sources_present = false;
1229 
1230         let context = CrateContext::new(
1231             crate_annotation,
1232             &annotations.metadata.packages,
1233             &annotations.lockfile.crates,
1234             &annotations.pairred_extras,
1235             &annotations.metadata.workspace_metadata.tree_metadata,
1236             include_binaries,
1237             include_build_scripts,
1238             are_sources_present,
1239         );
1240 
1241         let mut expected = Select::new();
1242         expected.insert("unique_feature".to_owned(), None);
1243 
1244         assert_eq!(context.common_attrs.crate_features, expected);
1245     }
1246 }
1247