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