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