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