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