• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Tools for rendering and writing BUILD and other Starlark files
2 
3 mod template_engine;
4 
5 use std::collections::{BTreeMap, BTreeSet};
6 use std::fs;
7 use std::iter::FromIterator;
8 use std::path::{Path, PathBuf};
9 use std::str::FromStr;
10 
11 use anyhow::{bail, Context as AnyhowContext, Result};
12 use itertools::Itertools;
13 
14 use crate::config::{AliasRule, RenderConfig, VendorMode};
15 use crate::context::crate_context::{CrateContext, CrateDependency, Rule};
16 use crate::context::{Context, TargetAttributes};
17 use crate::rendering::template_engine::TemplateEngine;
18 use crate::select::Select;
19 use crate::splicing::default_splicing_package_crate_id;
20 use crate::utils::starlark::{
21     self, Alias, CargoBuildScript, CommonAttrs, Data, ExportsFiles, Filegroup, Glob, Label, Load,
22     Package, RustBinary, RustLibrary, RustProcMacro, SelectDict, SelectList, SelectScalar,
23     SelectSet, Starlark, TargetCompatibleWith,
24 };
25 use crate::utils::target_triple::TargetTriple;
26 use crate::utils::{self, sanitize_repository_name};
27 
28 // Configuration remapper used to convert from cfg expressions like "cfg(unix)"
29 // to platform labels like "@rules_rust//rust/platform:x86_64-unknown-linux-gnu".
30 pub(crate) type Platforms = BTreeMap<String, BTreeSet<String>>;
31 
32 pub(crate) struct Renderer {
33     config: RenderConfig,
34     supported_platform_triples: BTreeSet<TargetTriple>,
35     engine: TemplateEngine,
36 }
37 
38 impl Renderer {
new( config: RenderConfig, supported_platform_triples: BTreeSet<TargetTriple>, ) -> Self39     pub(crate) fn new(
40         config: RenderConfig,
41         supported_platform_triples: BTreeSet<TargetTriple>,
42     ) -> Self {
43         let engine = TemplateEngine::new(&config);
44         Self {
45             config,
46             supported_platform_triples,
47             engine,
48         }
49     }
50 
render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>>51     pub(crate) fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
52         let mut output = BTreeMap::new();
53 
54         let platforms = self.render_platform_labels(context);
55         output.extend(self.render_build_files(context, &platforms)?);
56         output.extend(self.render_crates_module(context, &platforms)?);
57 
58         if let Some(vendor_mode) = &self.config.vendor_mode {
59             match vendor_mode {
60                 crate::config::VendorMode::Local => {
61                     // Nothing to do for local vendor crate
62                 }
63                 crate::config::VendorMode::Remote => {
64                     output.extend(self.render_vendor_support_files(context)?);
65                 }
66             }
67         }
68 
69         Ok(output)
70     }
71 
render_platform_labels(&self, context: &Context) -> BTreeMap<String, BTreeSet<String>>72     fn render_platform_labels(&self, context: &Context) -> BTreeMap<String, BTreeSet<String>> {
73         context
74             .conditions
75             .iter()
76             .map(|(cfg, target_triples)| {
77                 (
78                     cfg.clone(),
79                     target_triples
80                         .iter()
81                         .map(|target_triple| {
82                             render_platform_constraint_label(
83                                 &self.config.platforms_template,
84                                 target_triple,
85                             )
86                         })
87                         .collect(),
88                 )
89             })
90             .collect()
91     }
92 
render_crates_module( &self, context: &Context, platforms: &Platforms, ) -> Result<BTreeMap<PathBuf, String>>93     fn render_crates_module(
94         &self,
95         context: &Context,
96         platforms: &Platforms,
97     ) -> Result<BTreeMap<PathBuf, String>> {
98         let module_label = render_module_label(&self.config.crates_module_template, "defs.bzl")
99             .context("Failed to resolve string to module file label")?;
100         let module_build_label =
101             render_module_label(&self.config.crates_module_template, "BUILD.bazel")
102                 .context("Failed to resolve string to module file label")?;
103         let module_alias_rules_label =
104             render_module_label(&self.config.crates_module_template, "alias_rules.bzl")
105                 .context("Failed to resolve string to module file label")?;
106 
107         let mut map = BTreeMap::new();
108         map.insert(
109             Renderer::label_to_path(&module_label),
110             self.engine.render_module_bzl(context, platforms)?,
111         );
112         map.insert(
113             Renderer::label_to_path(&module_build_label),
114             self.render_module_build_file(context)?,
115         );
116         map.insert(
117             Renderer::label_to_path(&module_alias_rules_label),
118             include_str!(concat!(
119                 env!("CARGO_MANIFEST_DIR"),
120                 "/src/rendering/verbatim/alias_rules.bzl"
121             ))
122             .to_owned(),
123         );
124 
125         Ok(map)
126     }
127 
render_module_build_file(&self, context: &Context) -> Result<String>128     fn render_module_build_file(&self, context: &Context) -> Result<String> {
129         let mut starlark = Vec::new();
130 
131         // Banner comment for top of the file.
132         let header = self.engine.render_header()?;
133         starlark.push(Starlark::Verbatim(header));
134 
135         // Load any `alias_rule`s.
136         let mut loads: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
137         for alias_rule in Iterator::chain(
138             std::iter::once(&self.config.default_alias_rule),
139             context
140                 .workspace_member_deps()
141                 .iter()
142                 .flat_map(|dep| &context.crates[&dep.id].alias_rule),
143         ) {
144             if let Some(bzl) = alias_rule.bzl() {
145                 loads.entry(bzl).or_default().insert(alias_rule.rule());
146             }
147         }
148         for (bzl, items) in loads {
149             starlark.push(Starlark::Load(Load { bzl, items }))
150         }
151 
152         // Package visibility, exported bzl files.
153         let package = Package::default_visibility_public(BTreeSet::new());
154         starlark.push(Starlark::Package(package));
155 
156         let mut exports_files = ExportsFiles {
157             paths: BTreeSet::from(["cargo-bazel.json".to_owned(), "defs.bzl".to_owned()]),
158             globs: Glob {
159                 allow_empty: true,
160                 include: BTreeSet::from(["*.bazel".to_owned()]),
161                 exclude: BTreeSet::new(),
162             },
163         };
164         if let Some(VendorMode::Remote) = self.config.vendor_mode {
165             exports_files.paths.insert("crates.bzl".to_owned());
166         }
167         starlark.push(Starlark::ExportsFiles(exports_files));
168 
169         let filegroup = Filegroup {
170             name: "srcs".to_owned(),
171             srcs: Glob {
172                 allow_empty: true,
173                 include: BTreeSet::from(["*.bazel".to_owned(), "*.bzl".to_owned()]),
174                 exclude: BTreeSet::new(),
175             },
176         };
177         starlark.push(Starlark::Filegroup(filegroup));
178 
179         // An `alias` for each direct dependency of a workspace member crate.
180         let mut dependencies = Vec::new();
181         for dep in context.workspace_member_deps() {
182             let krate = &context.crates[&dep.id];
183             let alias_rule = krate
184                 .alias_rule
185                 .as_ref()
186                 .unwrap_or(&self.config.default_alias_rule);
187 
188             if let Some(library_target_name) = &krate.library_target_name {
189                 let rename = dep.alias.as_ref().unwrap_or(&krate.name);
190                 dependencies.push(Alias {
191                     rule: alias_rule.rule(),
192                     // If duplicates exist, include version to disambiguate them.
193                     name: if context.has_duplicate_workspace_member_dep(&dep) {
194                         format!("{}-{}", rename, krate.version)
195                     } else {
196                         rename.clone()
197                     },
198                     actual: self.crate_label(
199                         &krate.name,
200                         &krate.version.to_string(),
201                         library_target_name,
202                     ),
203                     tags: BTreeSet::from(["manual".to_owned()]),
204                 });
205             }
206 
207             for (alias, target) in &krate.extra_aliased_targets {
208                 dependencies.push(Alias {
209                     rule: alias_rule.rule(),
210                     name: alias.clone(),
211                     actual: self.crate_label(&krate.name, &krate.version.to_string(), target),
212                     tags: BTreeSet::from(["manual".to_owned()]),
213                 });
214             }
215         }
216 
217         let duplicates: Vec<_> = dependencies
218             .iter()
219             .map(|alias| &alias.name)
220             .duplicates()
221             .sorted()
222             .collect();
223 
224         assert!(
225             duplicates.is_empty(),
226             "Found duplicate aliases that must be changed (Check your `extra_aliased_targets`): {:#?}",
227             duplicates
228         );
229 
230         if !dependencies.is_empty() {
231             let comment = "# Workspace Member Dependencies".to_owned();
232             starlark.push(Starlark::Verbatim(comment));
233             starlark.extend(dependencies.into_iter().map(Starlark::Alias));
234         }
235 
236         // An `alias` for each binary dependency.
237         let mut binaries = Vec::new();
238         for crate_id in &context.binary_crates {
239             let krate = &context.crates[crate_id];
240             for rule in &krate.targets {
241                 if let Rule::Binary(bin) = rule {
242                     binaries.push(Alias {
243                         rule: AliasRule::default().rule(),
244                         // If duplicates exist, include version to disambiguate them.
245                         name: if context.has_duplicate_binary_crate(crate_id) {
246                             format!("{}-{}__{}", krate.name, krate.version, bin.crate_name)
247                         } else {
248                             format!("{}__{}", krate.name, bin.crate_name)
249                         },
250                         actual: self.crate_label(
251                             &krate.name,
252                             &krate.version.to_string(),
253                             &format!("{}__bin", bin.crate_name),
254                         ),
255                         tags: BTreeSet::from(["manual".to_owned()]),
256                     });
257                 }
258             }
259         }
260         if !binaries.is_empty() {
261             let comment = "# Binaries".to_owned();
262             starlark.push(Starlark::Verbatim(comment));
263             starlark.extend(binaries.into_iter().map(Starlark::Alias));
264         }
265 
266         let starlark = starlark::serialize(&starlark)?;
267         Ok(starlark)
268     }
269 
render_build_files( &self, context: &Context, platforms: &Platforms, ) -> Result<BTreeMap<PathBuf, String>>270     fn render_build_files(
271         &self,
272         context: &Context,
273         platforms: &Platforms,
274     ) -> Result<BTreeMap<PathBuf, String>> {
275         let default_splicing_package_id = default_splicing_package_crate_id();
276         context
277             .crates
278             .keys()
279             // Do not render the default splicing package
280             .filter(|id| *id != &default_splicing_package_id)
281             // Do not render local packages
282             .filter(|id| !context.workspace_members.contains_key(id))
283             .map(|id| {
284                 let label = match render_build_file_template(
285                     &self.config.build_file_template,
286                     &id.name,
287                     &id.version.to_string(),
288                 ) {
289                     Ok(label) => label,
290                     Err(e) => bail!(e),
291                 };
292 
293                 let filename = Renderer::label_to_path(&label);
294                 let content = self.render_one_build_file(platforms, &context.crates[id])?;
295                 Ok((filename, content))
296             })
297             .collect()
298     }
299 
render_one_build_file(&self, platforms: &Platforms, krate: &CrateContext) -> Result<String>300     fn render_one_build_file(&self, platforms: &Platforms, krate: &CrateContext) -> Result<String> {
301         let mut starlark = Vec::new();
302 
303         // Banner comment for top of the file.
304         let header = self.engine.render_header()?;
305         starlark.push(Starlark::Verbatim(header));
306 
307         // Loads: map of bzl file to set of items imported from that file. These
308         // get inserted into `starlark` at the bottom of this function.
309         let mut loads: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
310         let mut load = |bzl: &str, item: &str| {
311             loads
312                 .entry(bzl.to_owned())
313                 .or_default()
314                 .insert(item.to_owned())
315         };
316 
317         let disable_visibility = "# buildifier: disable=bzl-visibility".to_owned();
318         starlark.push(Starlark::Verbatim(disable_visibility));
319         starlark.push(Starlark::Load(Load {
320             bzl: "@rules_rust//crate_universe/private:selects.bzl".to_owned(),
321             items: BTreeSet::from(["selects".to_owned()]),
322         }));
323 
324         if self.config.generate_rules_license_metadata {
325             let has_license_ids = !krate.license_ids.is_empty();
326             let mut package_metadata = BTreeSet::from([Label::Relative {
327                 target: "package_info".to_owned(),
328             }]);
329 
330             starlark.push(Starlark::Load(Load {
331                 bzl: "@rules_license//rules:package_info.bzl".to_owned(),
332                 items: BTreeSet::from(["package_info".to_owned()]),
333             }));
334 
335             if has_license_ids {
336                 starlark.push(Starlark::Load(Load {
337                     bzl: "@rules_license//rules:license.bzl".to_owned(),
338                     items: BTreeSet::from(["license".to_owned()]),
339                 }));
340                 package_metadata.insert(Label::Relative {
341                     target: "license".to_owned(),
342                 });
343             }
344 
345             let package = Package::default_visibility_public(package_metadata);
346             starlark.push(Starlark::Package(package));
347 
348             starlark.push(Starlark::PackageInfo(starlark::PackageInfo {
349                 name: "package_info".to_owned(),
350                 package_name: krate.name.clone(),
351                 package_url: krate.package_url.clone().unwrap_or_default(),
352                 package_version: krate.version.to_string(),
353             }));
354 
355             if has_license_ids {
356                 let mut license_kinds = BTreeSet::new();
357 
358                 krate.license_ids.clone().into_iter().for_each(|lic| {
359                     license_kinds.insert("@rules_license//licenses/spdx:".to_owned() + &lic);
360                 });
361 
362                 starlark.push(Starlark::License(starlark::License {
363                     name: "license".to_owned(),
364                     license_kinds,
365                     license_text: krate.license_file.clone().unwrap_or_default(),
366                 }));
367             }
368         } else {
369             // Package visibility.
370             let package = Package::default_visibility_public(BTreeSet::new());
371             starlark.push(Starlark::Package(package));
372         }
373 
374         for rule in &krate.targets {
375             match rule {
376                 Rule::BuildScript(target) => {
377                     load("@rules_rust//cargo:defs.bzl", "cargo_build_script");
378                     let cargo_build_script =
379                         self.make_cargo_build_script(platforms, krate, target)?;
380                     starlark.push(Starlark::CargoBuildScript(cargo_build_script));
381                     starlark.push(Starlark::Alias(Alias {
382                         rule: AliasRule::default().rule(),
383                         name: target.crate_name.clone(),
384                         actual: Label::from_str(&format!(":{}_bs", krate.name)).unwrap(),
385                         tags: BTreeSet::from(["manual".to_owned()]),
386                     }));
387                 }
388                 Rule::ProcMacro(target) => {
389                     load("@rules_rust//rust:defs.bzl", "rust_proc_macro");
390                     let rust_proc_macro = self.make_rust_proc_macro(platforms, krate, target)?;
391                     starlark.push(Starlark::RustProcMacro(rust_proc_macro));
392                 }
393                 Rule::Library(target) => {
394                     load("@rules_rust//rust:defs.bzl", "rust_library");
395                     let rust_library = self.make_rust_library(platforms, krate, target)?;
396                     starlark.push(Starlark::RustLibrary(rust_library));
397                 }
398                 Rule::Binary(target) => {
399                     load("@rules_rust//rust:defs.bzl", "rust_binary");
400                     let rust_binary = self.make_rust_binary(platforms, krate, target)?;
401                     starlark.push(Starlark::RustBinary(rust_binary));
402                 }
403             }
404         }
405 
406         if let Some(additive_build_file_content) = &krate.additive_build_file_content {
407             let comment = "# Additive BUILD file content".to_owned();
408             starlark.push(Starlark::Verbatim(comment));
409             starlark.push(Starlark::Verbatim(additive_build_file_content.clone()));
410         }
411 
412         // Insert all the loads immediately after the header banner comment.
413         let loads = loads
414             .into_iter()
415             .map(|(bzl, items)| Starlark::Load(Load { bzl, items }));
416         starlark.splice(1..1, loads);
417 
418         let starlark = starlark::serialize(&starlark)?;
419         Ok(starlark)
420     }
421 
make_cargo_build_script( &self, platforms: &Platforms, krate: &CrateContext, target: &TargetAttributes, ) -> Result<CargoBuildScript>422     fn make_cargo_build_script(
423         &self,
424         platforms: &Platforms,
425         krate: &CrateContext,
426         target: &TargetAttributes,
427     ) -> Result<CargoBuildScript> {
428         let attrs = krate.build_script_attrs.as_ref();
429 
430         Ok(CargoBuildScript {
431             // Because `cargo_build_script` does some invisible target name
432             // mutating to determine the package and crate name for a build
433             // script, the Bazel target name of any build script cannot be the
434             // Cargo canonical name of "cargo_build_script" without losing out
435             // on having certain Cargo environment variables set.
436             //
437             // Do not change this name to "cargo_build_script".
438             //
439             // This is set to a short suffix to avoid long path name issues on windows.
440             name: format!("{}_bs", krate.name),
441             aliases: SelectDict::new(self.make_aliases(krate, true, false), platforms),
442             build_script_env: SelectDict::new(
443                 attrs
444                     .map(|attrs| attrs.build_script_env.clone())
445                     .unwrap_or_default(),
446                 platforms,
447             ),
448             compile_data: make_data(
449                 platforms,
450                 Default::default(),
451                 attrs
452                     .map(|attrs| attrs.compile_data.clone())
453                     .unwrap_or_default(),
454             ),
455             crate_features: SelectSet::new(krate.common_attrs.crate_features.clone(), platforms),
456             crate_name: utils::sanitize_module_name(&target.crate_name),
457             crate_root: target.crate_root.clone(),
458             data: make_data(
459                 platforms,
460                 attrs
461                     .map(|attrs| attrs.data_glob.clone())
462                     .unwrap_or_default(),
463                 attrs.map(|attrs| attrs.data.clone()).unwrap_or_default(),
464             ),
465             deps: SelectSet::new(
466                 self.make_deps(
467                     attrs.map(|attrs| attrs.deps.clone()).unwrap_or_default(),
468                     attrs
469                         .map(|attrs| attrs.extra_deps.clone())
470                         .unwrap_or_default(),
471                 ),
472                 platforms,
473             ),
474             link_deps: SelectSet::new(
475                 self.make_deps(
476                     attrs
477                         .map(|attrs| attrs.link_deps.clone())
478                         .unwrap_or_default(),
479                     attrs
480                         .map(|attrs| attrs.extra_link_deps.clone())
481                         .unwrap_or_default(),
482                 ),
483                 platforms,
484             ),
485             edition: krate.common_attrs.edition.clone(),
486             linker_script: krate.common_attrs.linker_script.clone(),
487             links: attrs.and_then(|attrs| attrs.links.clone()),
488             proc_macro_deps: SelectSet::new(
489                 self.make_deps(
490                     attrs
491                         .map(|attrs| attrs.proc_macro_deps.clone())
492                         .unwrap_or_default(),
493                     attrs
494                         .map(|attrs| attrs.extra_proc_macro_deps.clone())
495                         .unwrap_or_default(),
496                 ),
497                 platforms,
498             ),
499             rundir: SelectScalar::new(
500                 attrs.map(|attrs| attrs.rundir.clone()).unwrap_or_default(),
501                 platforms,
502             ),
503             rustc_env: SelectDict::new(
504                 attrs
505                     .map(|attrs| attrs.rustc_env.clone())
506                     .unwrap_or_default(),
507                 platforms,
508             ),
509             rustc_env_files: SelectSet::new(
510                 attrs
511                     .map(|attrs| attrs.rustc_env_files.clone())
512                     .unwrap_or_default(),
513                 platforms,
514             ),
515             rustc_flags: SelectList::new(
516                 // In most cases, warnings in 3rd party crates are not
517                 // interesting as they're out of the control of consumers. The
518                 // flag here silences warnings. For more details see:
519                 // https://doc.rust-lang.org/rustc/lints/levels.html
520                 Select::merge(
521                     Select::from_value(Vec::from(["--cap-lints=allow".to_owned()])),
522                     attrs
523                         .map(|attrs| attrs.rustc_flags.clone())
524                         .unwrap_or_default(),
525                 ),
526                 platforms,
527             ),
528             srcs: target.srcs.clone(),
529             tags: {
530                 let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned());
531                 tags.insert("cargo-bazel".to_owned());
532                 tags.insert("manual".to_owned());
533                 tags.insert("noclippy".to_owned());
534                 tags.insert("norustfmt".to_owned());
535                 tags.insert(format!("crate-name={}", krate.name));
536                 tags
537             },
538             tools: SelectSet::new(
539                 attrs.map(|attrs| attrs.tools.clone()).unwrap_or_default(),
540                 platforms,
541             ),
542             toolchains: attrs.map_or_else(BTreeSet::new, |attrs| attrs.toolchains.clone()),
543             version: krate.common_attrs.version.clone(),
544             visibility: BTreeSet::from(["//visibility:private".to_owned()]),
545         })
546     }
547 
make_rust_proc_macro( &self, platforms: &Platforms, krate: &CrateContext, target: &TargetAttributes, ) -> Result<RustProcMacro>548     fn make_rust_proc_macro(
549         &self,
550         platforms: &Platforms,
551         krate: &CrateContext,
552         target: &TargetAttributes,
553     ) -> Result<RustProcMacro> {
554         Ok(RustProcMacro {
555             name: target.crate_name.clone(),
556             deps: SelectSet::new(
557                 self.make_deps(
558                     krate.common_attrs.deps.clone(),
559                     krate.common_attrs.extra_deps.clone(),
560                 ),
561                 platforms,
562             ),
563             proc_macro_deps: SelectSet::new(
564                 self.make_deps(
565                     krate.common_attrs.proc_macro_deps.clone(),
566                     krate.common_attrs.extra_proc_macro_deps.clone(),
567                 ),
568                 platforms,
569             ),
570             aliases: SelectDict::new(self.make_aliases(krate, false, false), platforms),
571             common: self.make_common_attrs(platforms, krate, target)?,
572         })
573     }
574 
make_rust_library( &self, platforms: &Platforms, krate: &CrateContext, target: &TargetAttributes, ) -> Result<RustLibrary>575     fn make_rust_library(
576         &self,
577         platforms: &Platforms,
578         krate: &CrateContext,
579         target: &TargetAttributes,
580     ) -> Result<RustLibrary> {
581         Ok(RustLibrary {
582             name: target.crate_name.clone(),
583             deps: SelectSet::new(
584                 self.make_deps(
585                     krate.common_attrs.deps.clone(),
586                     krate.common_attrs.extra_deps.clone(),
587                 ),
588                 platforms,
589             ),
590             proc_macro_deps: SelectSet::new(
591                 self.make_deps(
592                     krate.common_attrs.proc_macro_deps.clone(),
593                     krate.common_attrs.extra_proc_macro_deps.clone(),
594                 ),
595                 platforms,
596             ),
597             aliases: SelectDict::new(self.make_aliases(krate, false, false), platforms),
598             common: self.make_common_attrs(platforms, krate, target)?,
599             disable_pipelining: krate.disable_pipelining,
600         })
601     }
602 
make_rust_binary( &self, platforms: &Platforms, krate: &CrateContext, target: &TargetAttributes, ) -> Result<RustBinary>603     fn make_rust_binary(
604         &self,
605         platforms: &Platforms,
606         krate: &CrateContext,
607         target: &TargetAttributes,
608     ) -> Result<RustBinary> {
609         Ok(RustBinary {
610             name: format!("{}__bin", target.crate_name),
611             deps: {
612                 let mut deps = self.make_deps(
613                     krate.common_attrs.deps.clone(),
614                     krate.common_attrs.extra_deps.clone(),
615                 );
616                 if let Some(library_target_name) = &krate.library_target_name {
617                     deps.insert(
618                         Label::from_str(&format!(":{library_target_name}")).unwrap(),
619                         None,
620                     );
621                 }
622                 SelectSet::new(deps, platforms)
623             },
624             proc_macro_deps: SelectSet::new(
625                 self.make_deps(
626                     krate.common_attrs.proc_macro_deps.clone(),
627                     krate.common_attrs.extra_proc_macro_deps.clone(),
628                 ),
629                 platforms,
630             ),
631             aliases: SelectDict::new(self.make_aliases(krate, false, false), platforms),
632             common: self.make_common_attrs(platforms, krate, target)?,
633         })
634     }
635 
make_common_attrs( &self, platforms: &Platforms, krate: &CrateContext, target: &TargetAttributes, ) -> Result<CommonAttrs>636     fn make_common_attrs(
637         &self,
638         platforms: &Platforms,
639         krate: &CrateContext,
640         target: &TargetAttributes,
641     ) -> Result<CommonAttrs> {
642         Ok(CommonAttrs {
643             compile_data: make_data(
644                 platforms,
645                 krate.common_attrs.compile_data_glob.clone(),
646                 krate.common_attrs.compile_data.clone(),
647             ),
648             crate_features: SelectSet::new(krate.common_attrs.crate_features.clone(), platforms),
649             crate_root: target.crate_root.clone(),
650             data: make_data(
651                 platforms,
652                 krate.common_attrs.data_glob.clone(),
653                 krate.common_attrs.data.clone(),
654             ),
655             edition: krate.common_attrs.edition.clone(),
656             linker_script: krate.common_attrs.linker_script.clone(),
657             rustc_env: SelectDict::new(krate.common_attrs.rustc_env.clone(), platforms),
658             rustc_env_files: SelectSet::new(krate.common_attrs.rustc_env_files.clone(), platforms),
659             rustc_flags: SelectList::new(
660                 // In most cases, warnings in 3rd party crates are not
661                 // interesting as they're out of the control of consumers. The
662                 // flag here silences warnings. For more details see:
663                 // https://doc.rust-lang.org/rustc/lints/levels.html
664                 Select::merge(
665                     Select::from_value(Vec::from(["--cap-lints=allow".to_owned()])),
666                     krate.common_attrs.rustc_flags.clone(),
667                 ),
668                 platforms,
669             ),
670             srcs: target.srcs.clone(),
671             tags: {
672                 let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned());
673                 tags.insert("cargo-bazel".to_owned());
674                 tags.insert("manual".to_owned());
675                 tags.insert("noclippy".to_owned());
676                 tags.insert("norustfmt".to_owned());
677                 tags.insert(format!("crate-name={}", krate.name));
678                 tags
679             },
680             target_compatible_with: self.config.generate_target_compatible_with.then(|| {
681                 TargetCompatibleWith::new(
682                     self.supported_platform_triples
683                         .iter()
684                         .map(|target_triple| {
685                             render_platform_constraint_label(
686                                 &self.config.platforms_template,
687                                 target_triple,
688                             )
689                         })
690                         .collect(),
691                 )
692             }),
693             version: krate.common_attrs.version.clone(),
694         })
695     }
696 
697     /// Filter a crate's dependencies to only ones with aliases
make_aliases( &self, krate: &CrateContext, build: bool, include_dev: bool, ) -> Select<BTreeMap<Label, String>>698     fn make_aliases(
699         &self,
700         krate: &CrateContext,
701         build: bool,
702         include_dev: bool,
703     ) -> Select<BTreeMap<Label, String>> {
704         let mut dependency_selects = Vec::new();
705         if build {
706             if let Some(build_script_attrs) = &krate.build_script_attrs {
707                 dependency_selects.push(&build_script_attrs.deps);
708                 dependency_selects.push(&build_script_attrs.proc_macro_deps);
709             }
710         } else {
711             dependency_selects.push(&krate.common_attrs.deps);
712             dependency_selects.push(&krate.common_attrs.proc_macro_deps);
713             if include_dev {
714                 dependency_selects.push(&krate.common_attrs.deps_dev);
715                 dependency_selects.push(&krate.common_attrs.proc_macro_deps_dev);
716             }
717         }
718 
719         let mut aliases: Select<BTreeMap<Label, String>> = Select::default();
720         for dependency_select in dependency_selects.iter() {
721             for (configuration, dependency) in dependency_select.items().into_iter() {
722                 if let Some(alias) = &dependency.alias {
723                     let label = self.crate_label(
724                         &dependency.id.name,
725                         &dependency.id.version.to_string(),
726                         &dependency.target,
727                     );
728                     aliases.insert((label, alias.clone()), configuration.clone());
729                 }
730             }
731         }
732         aliases
733     }
734 
make_deps( &self, deps: Select<BTreeSet<CrateDependency>>, extra_deps: Select<BTreeSet<Label>>, ) -> Select<BTreeSet<Label>>735     fn make_deps(
736         &self,
737         deps: Select<BTreeSet<CrateDependency>>,
738         extra_deps: Select<BTreeSet<Label>>,
739     ) -> Select<BTreeSet<Label>> {
740         Select::merge(
741             deps.map(|dep| {
742                 self.crate_label(&dep.id.name, &dep.id.version.to_string(), &dep.target)
743             }),
744             extra_deps,
745         )
746     }
747 
render_vendor_support_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>>748     fn render_vendor_support_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> {
749         let module_label = render_module_label(&self.config.crates_module_template, "crates.bzl")
750             .context("Failed to resolve string to module file label")?;
751 
752         let mut map = BTreeMap::new();
753         map.insert(
754             Renderer::label_to_path(&module_label),
755             self.engine.render_vendor_module_file(context)?,
756         );
757 
758         Ok(map)
759     }
760 
label_to_path(label: &Label) -> PathBuf761     fn label_to_path(label: &Label) -> PathBuf {
762         match &label.package() {
763             Some(package) if !package.is_empty() => {
764                 PathBuf::from(format!("{}/{}", package, label.target()))
765             }
766             Some(_) | None => PathBuf::from(label.target()),
767         }
768     }
769 
crate_label(&self, name: &str, version: &str, target: &str) -> Label770     fn crate_label(&self, name: &str, version: &str, target: &str) -> Label {
771         Label::from_str(&sanitize_repository_name(&render_crate_bazel_label(
772             &self.config.crate_label_template,
773             &self.config.repository_name,
774             name,
775             version,
776             target,
777         )))
778         .unwrap()
779     }
780 }
781 
782 /// Write a set of [crate::context::crate_context::CrateContext] to disk.
write_outputs( outputs: BTreeMap<PathBuf, String>, out_dir: &Path, dry_run: bool, ) -> Result<()>783 pub(crate) fn write_outputs(
784     outputs: BTreeMap<PathBuf, String>,
785     out_dir: &Path,
786     dry_run: bool,
787 ) -> Result<()> {
788     let outputs: BTreeMap<PathBuf, String> = outputs
789         .into_iter()
790         .map(|(path, content)| (out_dir.join(path), content))
791         .collect();
792 
793     if dry_run {
794         for (path, content) in outputs {
795             println!(
796                 "==============================================================================="
797             );
798             println!("{}", path.display());
799             println!(
800                 "==============================================================================="
801             );
802             println!("{content}\n");
803         }
804     } else {
805         for (path, content) in outputs {
806             // Ensure the output directory exists
807             fs::create_dir_all(
808                 path.parent()
809                     .expect("All file paths should have valid directories"),
810             )?;
811 
812             fs::write(&path, content.as_bytes())
813                 .context(format!("Failed to write file to disk: {}", path.display()))?;
814         }
815     }
816 
817     Ok(())
818 }
819 
820 /// Render the Bazel label of a crate
render_crate_bazel_label( template: &str, repository_name: &str, name: &str, version: &str, target: &str, ) -> String821 pub(crate) fn render_crate_bazel_label(
822     template: &str,
823     repository_name: &str,
824     name: &str,
825     version: &str,
826     target: &str,
827 ) -> String {
828     template
829         .replace("{repository}", repository_name)
830         .replace("{name}", name)
831         .replace("{version}", version)
832         .replace("{target}", target)
833 }
834 
835 /// Render the Bazel label of a crate
render_crate_bazel_repository( template: &str, repository_name: &str, name: &str, version: &str, ) -> String836 pub(crate) fn render_crate_bazel_repository(
837     template: &str,
838     repository_name: &str,
839     name: &str,
840     version: &str,
841 ) -> String {
842     template
843         .replace("{repository}", repository_name)
844         .replace("{name}", name)
845         .replace("{version}", version)
846 }
847 
848 /// Render the Bazel label of a crate
render_crate_build_file(template: &str, name: &str, version: &str) -> String849 pub(crate) fn render_crate_build_file(template: &str, name: &str, version: &str) -> String {
850     template
851         .replace("{name}", name)
852         .replace("{version}", version)
853 }
854 
855 /// Render the Bazel label of a vendor module label
render_module_label(template: &str, name: &str) -> Result<Label>856 pub(crate) fn render_module_label(template: &str, name: &str) -> Result<Label> {
857     Label::from_str(&template.replace("{file}", name))
858 }
859 
860 /// Render the Bazel label of a platform triple
render_platform_constraint_label(template: &str, target_triple: &TargetTriple) -> String861 fn render_platform_constraint_label(template: &str, target_triple: &TargetTriple) -> String {
862     template.replace("{triple}", &target_triple.to_bazel())
863 }
864 
render_build_file_template(template: &str, name: &str, version: &str) -> Result<Label>865 fn render_build_file_template(template: &str, name: &str, version: &str) -> Result<Label> {
866     Label::from_str(
867         &template
868             .replace("{name}", name)
869             .replace("{version}", version),
870     )
871 }
872 
make_data( platforms: &Platforms, glob: BTreeSet<String>, select: Select<BTreeSet<Label>>, ) -> Data873 fn make_data(
874     platforms: &Platforms,
875     glob: BTreeSet<String>,
876     select: Select<BTreeSet<Label>>,
877 ) -> Data {
878     const COMMON_GLOB_EXCLUDES: &[&str] = &[
879         "**/* *",
880         "BUILD.bazel",
881         "BUILD",
882         "WORKSPACE.bazel",
883         "WORKSPACE",
884         ".tmp_git_root/**/*",
885     ];
886 
887     Data {
888         glob: Glob {
889             allow_empty: true,
890             include: glob,
891             exclude: COMMON_GLOB_EXCLUDES
892                 .iter()
893                 .map(|&glob| glob.to_owned())
894                 .collect(),
895         },
896         select: SelectSet::new(select, platforms),
897     }
898 }
899 
900 #[cfg(test)]
901 mod test {
902     use super::*;
903 
904     use indoc::indoc;
905     use std::collections::BTreeSet;
906 
907     use crate::config::{Config, CrateId, VendorMode};
908     use crate::context::crate_context::{CrateContext, Rule};
909     use crate::context::{BuildScriptAttributes, CommonAttributes, Context, TargetAttributes};
910     use crate::metadata::Annotations;
911     use crate::test;
912 
913     const VERSION_ZERO_ONE_ZERO: semver::Version = semver::Version::new(0, 1, 0);
914 
mock_target_attributes() -> TargetAttributes915     fn mock_target_attributes() -> TargetAttributes {
916         TargetAttributes {
917             crate_name: "mock_crate".to_owned(),
918             crate_root: Some("src/root.rs".to_owned()),
919             ..TargetAttributes::default()
920         }
921     }
922 
mock_render_config(vendor_mode: Option<VendorMode>) -> RenderConfig923     fn mock_render_config(vendor_mode: Option<VendorMode>) -> RenderConfig {
924         RenderConfig {
925             repository_name: "test_rendering".to_owned(),
926             regen_command: "cargo_bazel_regen_command".to_owned(),
927             vendor_mode,
928             ..RenderConfig::default()
929         }
930     }
931 
mock_supported_platform_triples() -> BTreeSet<TargetTriple>932     fn mock_supported_platform_triples() -> BTreeSet<TargetTriple> {
933         BTreeSet::from([
934             TargetTriple::from_bazel("aarch64-apple-darwin".to_owned()),
935             TargetTriple::from_bazel("aarch64-apple-ios".to_owned()),
936             TargetTriple::from_bazel("aarch64-linux-android".to_owned()),
937             TargetTriple::from_bazel("aarch64-pc-windows-msvc".to_owned()),
938             TargetTriple::from_bazel("aarch64-unknown-linux-gnu".to_owned()),
939             TargetTriple::from_bazel("arm-unknown-linux-gnueabi".to_owned()),
940             TargetTriple::from_bazel("armv7-unknown-linux-gnueabi".to_owned()),
941             TargetTriple::from_bazel("i686-apple-darwin".to_owned()),
942             TargetTriple::from_bazel("i686-linux-android".to_owned()),
943             TargetTriple::from_bazel("i686-pc-windows-msvc".to_owned()),
944             TargetTriple::from_bazel("i686-unknown-freebsd".to_owned()),
945             TargetTriple::from_bazel("i686-unknown-linux-gnu".to_owned()),
946             TargetTriple::from_bazel("powerpc-unknown-linux-gnu".to_owned()),
947             TargetTriple::from_bazel("s390x-unknown-linux-gnu".to_owned()),
948             TargetTriple::from_bazel("wasm32-unknown-unknown".to_owned()),
949             TargetTriple::from_bazel("wasm32-wasi".to_owned()),
950             TargetTriple::from_bazel("x86_64-apple-darwin".to_owned()),
951             TargetTriple::from_bazel("x86_64-apple-ios".to_owned()),
952             TargetTriple::from_bazel("x86_64-linux-android".to_owned()),
953             TargetTriple::from_bazel("x86_64-pc-windows-msvc".to_owned()),
954             TargetTriple::from_bazel("x86_64-unknown-freebsd".to_owned()),
955             TargetTriple::from_bazel("x86_64-unknown-linux-gnu".to_owned()),
956         ])
957     }
958 
959     #[test]
render_rust_library()960     fn render_rust_library() {
961         let mut context = Context::default();
962         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
963         context.crates.insert(
964             crate_id.clone(),
965             CrateContext {
966                 name: crate_id.name,
967                 version: crate_id.version,
968                 package_url: None,
969                 repository: None,
970                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
971                 library_target_name: None,
972                 common_attrs: CommonAttributes::default(),
973                 build_script_attrs: None,
974                 license: None,
975                 license_ids: BTreeSet::default(),
976                 license_file: None,
977                 additive_build_file_content: None,
978                 disable_pipelining: false,
979                 extra_aliased_targets: BTreeMap::default(),
980                 alias_rule: None,
981             },
982         );
983 
984         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
985         let output = renderer.render(&context).unwrap();
986 
987         let build_file_content = output
988             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
989             .unwrap();
990 
991         assert!(build_file_content.contains("rust_library("));
992         assert!(build_file_content.contains("name = \"mock_crate\""));
993         assert!(build_file_content.contains("\"crate-name=mock_crate\""));
994     }
995 
996     #[test]
test_disable_pipelining()997     fn test_disable_pipelining() {
998         let mut context = Context::default();
999         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1000         context.crates.insert(
1001             crate_id.clone(),
1002             CrateContext {
1003                 name: crate_id.name,
1004                 version: crate_id.version,
1005                 package_url: None,
1006                 repository: None,
1007                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1008                 library_target_name: None,
1009                 common_attrs: CommonAttributes::default(),
1010                 build_script_attrs: None,
1011                 license: None,
1012                 license_ids: BTreeSet::default(),
1013                 license_file: None,
1014                 additive_build_file_content: None,
1015                 disable_pipelining: true,
1016                 extra_aliased_targets: BTreeMap::default(),
1017                 alias_rule: None,
1018             },
1019         );
1020 
1021         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1022         let output = renderer.render(&context).unwrap();
1023 
1024         let build_file_content = output
1025             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1026             .unwrap();
1027 
1028         assert!(build_file_content.contains("disable_pipelining = True"));
1029     }
1030 
1031     #[test]
render_cargo_build_script()1032     fn render_cargo_build_script() {
1033         let mut context = Context::default();
1034         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1035         context.crates.insert(
1036             crate_id.clone(),
1037             CrateContext {
1038                 name: crate_id.name,
1039                 version: crate_id.version,
1040                 package_url: None,
1041                 repository: None,
1042                 targets: BTreeSet::from([Rule::BuildScript(TargetAttributes {
1043                     crate_name: "build_script_build".to_owned(),
1044                     crate_root: Some("build.rs".to_owned()),
1045                     ..TargetAttributes::default()
1046                 })]),
1047                 // Build script attributes are required.
1048                 library_target_name: None,
1049                 common_attrs: CommonAttributes::default(),
1050                 build_script_attrs: Some(BuildScriptAttributes::default()),
1051                 license: None,
1052                 license_ids: BTreeSet::default(),
1053                 license_file: None,
1054                 additive_build_file_content: None,
1055                 disable_pipelining: false,
1056                 extra_aliased_targets: BTreeMap::default(),
1057                 alias_rule: None,
1058             },
1059         );
1060 
1061         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1062         let output = renderer.render(&context).unwrap();
1063 
1064         let build_file_content = output
1065             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1066             .unwrap();
1067 
1068         assert!(build_file_content.contains("cargo_build_script("));
1069         assert!(build_file_content.contains("name = \"build_script_build\""));
1070         assert!(build_file_content.contains("\"crate-name=mock_crate\""));
1071 
1072         // Ensure `cargo_build_script` requirements are met
1073         assert!(build_file_content.contains("name = \"mock_crate_bs\""));
1074     }
1075 
1076     #[test]
render_proc_macro()1077     fn render_proc_macro() {
1078         let mut context = Context::default();
1079         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1080         context.crates.insert(
1081             crate_id.clone(),
1082             CrateContext {
1083                 name: crate_id.name,
1084                 version: crate_id.version,
1085                 package_url: None,
1086                 repository: None,
1087                 targets: BTreeSet::from([Rule::ProcMacro(mock_target_attributes())]),
1088                 library_target_name: None,
1089                 common_attrs: CommonAttributes::default(),
1090                 build_script_attrs: None,
1091                 license: None,
1092                 license_ids: BTreeSet::default(),
1093                 license_file: None,
1094                 additive_build_file_content: None,
1095                 disable_pipelining: false,
1096                 extra_aliased_targets: BTreeMap::default(),
1097                 alias_rule: None,
1098             },
1099         );
1100 
1101         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1102         let output = renderer.render(&context).unwrap();
1103 
1104         let build_file_content = output
1105             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1106             .unwrap();
1107 
1108         assert!(build_file_content.contains("rust_proc_macro("));
1109         assert!(build_file_content.contains("name = \"mock_crate\""));
1110         assert!(build_file_content.contains("\"crate-name=mock_crate\""));
1111     }
1112 
1113     #[test]
render_binary()1114     fn render_binary() {
1115         let mut context = Context::default();
1116         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1117         context.crates.insert(
1118             crate_id.clone(),
1119             CrateContext {
1120                 name: crate_id.name,
1121                 version: crate_id.version,
1122                 package_url: None,
1123                 repository: None,
1124                 targets: BTreeSet::from([Rule::Binary(mock_target_attributes())]),
1125                 library_target_name: None,
1126                 common_attrs: CommonAttributes::default(),
1127                 build_script_attrs: None,
1128                 license: None,
1129                 license_ids: BTreeSet::default(),
1130                 license_file: None,
1131                 additive_build_file_content: None,
1132                 disable_pipelining: false,
1133                 extra_aliased_targets: BTreeMap::default(),
1134                 alias_rule: None,
1135             },
1136         );
1137 
1138         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1139         let output = renderer.render(&context).unwrap();
1140 
1141         let build_file_content = output
1142             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1143             .unwrap();
1144 
1145         assert!(build_file_content.contains("rust_binary("));
1146         assert!(build_file_content.contains("name = \"mock_crate__bin\""));
1147         assert!(build_file_content.contains("\"crate-name=mock_crate\""));
1148     }
1149 
1150     #[test]
render_additive_build_contents()1151     fn render_additive_build_contents() {
1152         let mut context = Context::default();
1153         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1154         context.crates.insert(
1155             crate_id.clone(),
1156             CrateContext {
1157                 name: crate_id.name,
1158                 version: crate_id.version,
1159                 package_url: None,
1160                 repository: None,
1161                 targets: BTreeSet::from([Rule::Binary(mock_target_attributes())]),
1162                 library_target_name: None,
1163                 common_attrs: CommonAttributes::default(),
1164                 build_script_attrs: None,
1165                 license: None,
1166                 license_ids: BTreeSet::default(),
1167                 license_file: None,
1168                 additive_build_file_content: Some(
1169                     "# Hello World from additive section!".to_owned(),
1170                 ),
1171                 disable_pipelining: false,
1172                 extra_aliased_targets: BTreeMap::default(),
1173                 alias_rule: None,
1174             },
1175         );
1176 
1177         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1178         let output = renderer.render(&context).unwrap();
1179 
1180         let build_file_content = output
1181             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1182             .unwrap();
1183 
1184         assert!(build_file_content.contains("# Hello World from additive section!"));
1185     }
1186 
1187     #[test]
render_aliases()1188     fn render_aliases() {
1189         let config = Config {
1190             generate_binaries: true,
1191             ..Config::default()
1192         };
1193         let annotations =
1194             Annotations::new(test::metadata::alias(), test::lockfile::alias(), config).unwrap();
1195         let context = Context::new(annotations).unwrap();
1196 
1197         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1198         let output = renderer.render(&context).unwrap();
1199 
1200         let build_file_content = output.get(&PathBuf::from("BUILD.bazel")).unwrap();
1201 
1202         assert!(build_file_content.contains(r#"name = "names-0.12.1-dev__names","#));
1203         assert!(build_file_content.contains(r#"name = "names-0.13.0__names","#));
1204     }
1205 
1206     #[test]
render_crate_repositories()1207     fn render_crate_repositories() {
1208         let mut context = Context::default();
1209         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1210         context.crates.insert(
1211             crate_id.clone(),
1212             CrateContext {
1213                 name: crate_id.name,
1214                 version: crate_id.version,
1215                 package_url: None,
1216                 repository: None,
1217                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1218                 library_target_name: None,
1219                 common_attrs: CommonAttributes::default(),
1220                 build_script_attrs: None,
1221                 license: None,
1222                 license_ids: BTreeSet::default(),
1223                 license_file: None,
1224                 additive_build_file_content: None,
1225                 disable_pipelining: false,
1226                 extra_aliased_targets: BTreeMap::default(),
1227                 alias_rule: None,
1228             },
1229         );
1230 
1231         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1232         let output = renderer.render(&context).unwrap();
1233 
1234         let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
1235 
1236         assert!(defs_module.contains("def crate_repositories():"));
1237     }
1238 
1239     #[test]
remote_remote_vendor_mode()1240     fn remote_remote_vendor_mode() {
1241         let mut context = Context::default();
1242         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1243         context.crates.insert(
1244             crate_id.clone(),
1245             CrateContext {
1246                 name: crate_id.name,
1247                 version: crate_id.version,
1248                 package_url: None,
1249                 repository: None,
1250                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1251                 library_target_name: None,
1252                 common_attrs: CommonAttributes::default(),
1253                 build_script_attrs: None,
1254                 license: None,
1255                 license_ids: BTreeSet::default(),
1256                 license_file: None,
1257                 additive_build_file_content: None,
1258                 disable_pipelining: false,
1259                 extra_aliased_targets: BTreeMap::default(),
1260                 alias_rule: None,
1261             },
1262         );
1263 
1264         // Enable remote vendor mode
1265         let renderer = Renderer::new(
1266             mock_render_config(Some(VendorMode::Remote)),
1267             mock_supported_platform_triples(),
1268         );
1269         let output = renderer.render(&context).unwrap();
1270 
1271         let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
1272         assert!(defs_module.contains("def crate_repositories():"));
1273 
1274         let crates_module = output.get(&PathBuf::from("crates.bzl")).unwrap();
1275         assert!(crates_module.contains("def crate_repositories():"));
1276     }
1277 
1278     #[test]
remote_local_vendor_mode()1279     fn remote_local_vendor_mode() {
1280         let mut context = Context::default();
1281         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1282         context.crates.insert(
1283             crate_id.clone(),
1284             CrateContext {
1285                 name: crate_id.name,
1286                 version: crate_id.version,
1287                 package_url: None,
1288                 repository: None,
1289                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1290                 library_target_name: None,
1291                 common_attrs: CommonAttributes::default(),
1292                 build_script_attrs: None,
1293                 license: None,
1294                 license_ids: BTreeSet::default(),
1295                 license_file: None,
1296                 additive_build_file_content: None,
1297                 disable_pipelining: false,
1298                 extra_aliased_targets: BTreeMap::default(),
1299                 alias_rule: None,
1300             },
1301         );
1302 
1303         // Enable local vendor mode
1304         let renderer = Renderer::new(
1305             mock_render_config(Some(VendorMode::Local)),
1306             mock_supported_platform_triples(),
1307         );
1308         let output = renderer.render(&context).unwrap();
1309 
1310         // Local vendoring does not produce a `crate_repositories` macro
1311         let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap();
1312         assert!(!defs_module.contains("def crate_repositories():"));
1313 
1314         // Local vendoring does not produce a `crates.bzl` file.
1315         assert!(output.get(&PathBuf::from("crates.bzl")).is_none());
1316     }
1317 
1318     #[test]
duplicate_rustc_flags()1319     fn duplicate_rustc_flags() {
1320         let mut context = Context::default();
1321         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1322 
1323         let rustc_flags = vec![
1324             "-l".to_owned(),
1325             "dylib=ssl".to_owned(),
1326             "-l".to_owned(),
1327             "dylib=crypto".to_owned(),
1328         ];
1329 
1330         context.crates.insert(
1331             crate_id.clone(),
1332             CrateContext {
1333                 name: crate_id.name,
1334                 version: crate_id.version,
1335                 package_url: None,
1336                 repository: None,
1337                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1338                 library_target_name: None,
1339                 common_attrs: CommonAttributes {
1340                     rustc_flags: Select::from_value(rustc_flags.clone()),
1341                     ..CommonAttributes::default()
1342                 },
1343                 build_script_attrs: None,
1344                 license: None,
1345                 license_ids: BTreeSet::default(),
1346                 license_file: None,
1347                 additive_build_file_content: None,
1348                 disable_pipelining: false,
1349                 extra_aliased_targets: BTreeMap::default(),
1350                 alias_rule: None,
1351             },
1352         );
1353 
1354         // Enable local vendor mode
1355         let renderer = Renderer::new(
1356             mock_render_config(Some(VendorMode::Local)),
1357             mock_supported_platform_triples(),
1358         );
1359         let output = renderer.render(&context).unwrap();
1360 
1361         let build_file_content = output
1362             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1363             .unwrap();
1364 
1365         // Strip all spaces from the generated BUILD file and ensure it has the flags
1366         // represented by `rustc_flags` in the same order.
1367         assert!(build_file_content.replace(' ', "").contains(
1368             &rustc_flags
1369                 .iter()
1370                 .map(|s| format!("\"{s}\","))
1371                 .collect::<Vec<String>>()
1372                 .join("\n")
1373         ));
1374     }
1375 
1376     #[test]
test_render_build_file_deps()1377     fn test_render_build_file_deps() {
1378         let config: Config = serde_json::from_value(serde_json::json!({
1379             "generate_binaries": false,
1380             "generate_build_scripts": false,
1381             "rendering": {
1382                 "repository_name": "multi_cfg_dep",
1383                 "regen_command": "bazel test //crate_universe:unit_test",
1384             },
1385             "supported_platform_triples": [
1386                 "x86_64-apple-darwin",
1387                 "x86_64-unknown-linux-gnu",
1388                 "aarch64-apple-darwin",
1389                 "aarch64-unknown-linux-gnu",
1390             ],
1391         }))
1392         .unwrap();
1393         let metadata = test::metadata::multi_cfg_dep();
1394         let lockfile = test::lockfile::multi_cfg_dep();
1395 
1396         let annotations = Annotations::new(metadata, lockfile, config.clone()).unwrap();
1397         let context = Context::new(annotations).unwrap();
1398 
1399         let renderer = Renderer::new(config.rendering, config.supported_platform_triples);
1400         let output = renderer.render(&context).unwrap();
1401 
1402         let build_file_content = output
1403             .get(&PathBuf::from("BUILD.cpufeatures-0.2.7.bazel"))
1404             .unwrap();
1405 
1406         // This is unfortunately somewhat brittle. Alas. Ultimately we wish to demonstrate that the
1407         // original cfg(...) strings are preserved in the `deps` list for ease of debugging.
1408         let expected = indoc! {r#"
1409             deps = select({
1410                 "@rules_rust//rust/platform:aarch64-apple-darwin": [
1411                     "@multi_cfg_dep__libc-0.2.117//:libc",  # cfg(all(target_arch = "aarch64", target_vendor = "apple"))
1412                 ],
1413                 "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [
1414                     "@multi_cfg_dep__libc-0.2.117//:libc",  # cfg(all(target_arch = "aarch64", target_os = "linux"))
1415                 ],
1416                 "//conditions:default": [],
1417             }),
1418         "#};
1419 
1420         assert!(
1421             build_file_content.contains(&expected.replace('\n', "\n    ")),
1422             "{}",
1423             build_file_content,
1424         );
1425     }
1426 
1427     #[test]
crate_features_by_target()1428     fn crate_features_by_target() {
1429         let mut context = Context {
1430             conditions: mock_supported_platform_triples()
1431                 .iter()
1432                 .map(|platform| (platform.to_bazel(), BTreeSet::from([platform.clone()])))
1433                 .collect(),
1434             ..Context::default()
1435         };
1436         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1437         let mut crate_features: Select<BTreeSet<String>> = Select::default();
1438         crate_features.insert("foo".to_owned(), Some("aarch64-apple-darwin".to_owned()));
1439         crate_features.insert("bar".to_owned(), None);
1440         context.crates.insert(
1441             crate_id.clone(),
1442             CrateContext {
1443                 name: crate_id.name,
1444                 version: crate_id.version,
1445                 package_url: None,
1446                 repository: None,
1447                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1448                 library_target_name: None,
1449                 common_attrs: CommonAttributes {
1450                     crate_features,
1451                     ..CommonAttributes::default()
1452                 },
1453                 build_script_attrs: None,
1454                 license: None,
1455                 license_ids: BTreeSet::default(),
1456                 license_file: None,
1457                 additive_build_file_content: None,
1458                 disable_pipelining: false,
1459                 extra_aliased_targets: BTreeMap::default(),
1460                 alias_rule: None,
1461             },
1462         );
1463 
1464         let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples());
1465         let output = renderer.render(&context).unwrap();
1466 
1467         let build_file_content = output
1468             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1469             .unwrap();
1470         let expected = indoc! {r#"
1471             crate_features = [
1472                 "bar",
1473             ] + select({
1474                 "@rules_rust//rust/platform:aarch64-apple-darwin": [
1475                     "foo",  # aarch64-apple-darwin
1476                 ],
1477                 "//conditions:default": [],
1478             }),
1479         "#};
1480         assert!(build_file_content
1481             .replace(' ', "")
1482             .contains(&expected.replace(' ', "")));
1483     }
1484 
1485     #[test]
crate_package_metadata_without_license_ids()1486     fn crate_package_metadata_without_license_ids() {
1487         let mut context = Context::default();
1488         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1489         context.crates.insert(
1490             crate_id.clone(),
1491             CrateContext {
1492                 name: crate_id.name,
1493                 version: crate_id.version,
1494                 package_url: Some("http://www.mock_crate.com/".to_owned()),
1495                 repository: None,
1496                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1497                 library_target_name: None,
1498                 common_attrs: CommonAttributes::default(),
1499                 build_script_attrs: None,
1500                 license: None,
1501                 license_ids: BTreeSet::default(),
1502                 license_file: None,
1503                 additive_build_file_content: None,
1504                 disable_pipelining: false,
1505                 extra_aliased_targets: BTreeMap::default(),
1506                 alias_rule: None,
1507             },
1508         );
1509 
1510         let mut render_config = mock_render_config(None);
1511         render_config.generate_rules_license_metadata = true;
1512         let renderer = Renderer::new(render_config, mock_supported_platform_triples());
1513         let output = renderer.render(&context).unwrap();
1514 
1515         let build_file_content = output
1516             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1517             .unwrap();
1518 
1519         let expected = indoc! {r#"
1520             package(
1521                 default_package_metadata = [":package_info"],
1522                 default_visibility = ["//visibility:public"],
1523             )
1524 
1525             package_info(
1526                 name = "package_info",
1527                 package_name = "mock_crate",
1528                 package_version = "0.1.0",
1529                 package_url = "http://www.mock_crate.com/",
1530             )
1531         "#};
1532         assert!(build_file_content
1533             .replace(' ', "")
1534             .contains(&expected.replace(' ', "")));
1535     }
1536 
1537     #[test]
crate_package_metadata_with_license_ids()1538     fn crate_package_metadata_with_license_ids() {
1539         let mut context = Context::default();
1540         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1541         context.crates.insert(
1542             crate_id.clone(),
1543             CrateContext {
1544                 name: crate_id.name,
1545                 version: crate_id.version,
1546                 package_url: Some("http://www.mock_crate.com/".to_owned()),
1547                 license_ids: BTreeSet::from(["Apache-2.0".to_owned(), "MIT".to_owned()]),
1548                 license_file: None,
1549                 additive_build_file_content: None,
1550                 disable_pipelining: false,
1551                 extra_aliased_targets: BTreeMap::default(),
1552                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1553                 library_target_name: None,
1554                 common_attrs: CommonAttributes::default(),
1555                 build_script_attrs: None,
1556                 repository: None,
1557                 license: None,
1558                 alias_rule: None,
1559             },
1560         );
1561 
1562         let mut render_config = mock_render_config(None);
1563         render_config.generate_rules_license_metadata = true;
1564         let renderer = Renderer::new(render_config, mock_supported_platform_triples());
1565         let output = renderer.render(&context).unwrap();
1566 
1567         let build_file_content = output
1568             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1569             .unwrap();
1570 
1571         let expected = indoc! {r#"
1572             package(
1573                 default_package_metadata = [
1574                     ":license",
1575                     ":package_info",
1576                 ],
1577                 default_visibility = ["//visibility:public"],
1578             )
1579 
1580             package_info(
1581                 name = "package_info",
1582                 package_name = "mock_crate",
1583                 package_version = "0.1.0",
1584                 package_url = "http://www.mock_crate.com/",
1585             )
1586 
1587             license(
1588                 name = "license",
1589                 license_kinds = [
1590                     "@rules_license//licenses/spdx:Apache-2.0",
1591                     "@rules_license//licenses/spdx:MIT",
1592                 ],
1593             )
1594         "#};
1595         assert!(build_file_content
1596             .replace(' ', "")
1597             .contains(&expected.replace(' ', "")));
1598     }
1599 
1600     #[test]
crate_package_metadata_with_license_ids_and_file()1601     fn crate_package_metadata_with_license_ids_and_file() {
1602         let mut context = Context::default();
1603         let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO);
1604         context.crates.insert(
1605             crate_id.clone(),
1606             CrateContext {
1607                 name: crate_id.name,
1608                 version: crate_id.version,
1609                 package_url: Some("http://www.mock_crate.com/".to_owned()),
1610                 license_ids: BTreeSet::from(["Apache-2.0".to_owned(), "MIT".to_owned()]),
1611                 license_file: Some("LICENSE.txt".to_owned()),
1612                 additive_build_file_content: None,
1613                 disable_pipelining: false,
1614                 extra_aliased_targets: BTreeMap::default(),
1615                 targets: BTreeSet::from([Rule::Library(mock_target_attributes())]),
1616                 library_target_name: None,
1617                 common_attrs: CommonAttributes::default(),
1618                 build_script_attrs: None,
1619                 repository: None,
1620                 license: None,
1621                 alias_rule: None,
1622             },
1623         );
1624 
1625         let mut render_config = mock_render_config(None);
1626         render_config.generate_rules_license_metadata = true;
1627         let renderer = Renderer::new(render_config, mock_supported_platform_triples());
1628         let output = renderer.render(&context).unwrap();
1629 
1630         let build_file_content = output
1631             .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel"))
1632             .unwrap();
1633 
1634         let expected = indoc! {r#"
1635             package(
1636                 default_package_metadata = [
1637                     ":license",
1638                     ":package_info",
1639                 ],
1640                 default_visibility = ["//visibility:public"],
1641             )
1642 
1643             package_info(
1644                 name = "package_info",
1645                 package_name = "mock_crate",
1646                 package_version = "0.1.0",
1647                 package_url = "http://www.mock_crate.com/",
1648             )
1649 
1650             license(
1651                 name = "license",
1652                 license_kinds = [
1653                     "@rules_license//licenses/spdx:Apache-2.0",
1654                     "@rules_license//licenses/spdx:MIT",
1655                 ],
1656                 license_text = "LICENSE.txt",
1657             )
1658         "#};
1659         assert!(build_file_content
1660             .replace(' ', "")
1661             .contains(&expected.replace(' ', "")));
1662     }
1663 }
1664