| //! Tools for rendering and writing BUILD and other Starlark files |
| |
| mod template_engine; |
| |
| use std::collections::{BTreeMap, BTreeSet}; |
| use std::fs; |
| use std::path::PathBuf; |
| use std::str::FromStr; |
| |
| use anyhow::{bail, Context as AnyhowContext, Result}; |
| use itertools::Itertools; |
| |
| use crate::config::{AliasRule, RenderConfig, VendorMode}; |
| use crate::context::crate_context::{CrateContext, CrateDependency, Rule}; |
| use crate::context::{Context, TargetAttributes}; |
| use crate::rendering::template_engine::TemplateEngine; |
| use crate::select::Select; |
| use crate::splicing::default_splicing_package_crate_id; |
| use crate::utils::starlark::{ |
| self, Alias, CargoBuildScript, CommonAttrs, Data, ExportsFiles, Filegroup, Glob, Label, Load, |
| Package, RustBinary, RustLibrary, RustProcMacro, SelectDict, SelectList, SelectScalar, |
| SelectSet, Starlark, TargetCompatibleWith, |
| }; |
| use crate::utils::target_triple::TargetTriple; |
| use crate::utils::{self, sanitize_repository_name}; |
| |
| // Configuration remapper used to convert from cfg expressions like "cfg(unix)" |
| // to platform labels like "@rules_rust//rust/platform:x86_64-unknown-linux-gnu". |
| pub(crate) type Platforms = BTreeMap<String, BTreeSet<String>>; |
| |
| pub(crate) struct Renderer { |
| config: RenderConfig, |
| supported_platform_triples: BTreeSet<TargetTriple>, |
| engine: TemplateEngine, |
| } |
| |
| impl Renderer { |
| pub(crate) fn new( |
| config: RenderConfig, |
| supported_platform_triples: BTreeSet<TargetTriple>, |
| ) -> Self { |
| let engine = TemplateEngine::new(&config); |
| Self { |
| config, |
| supported_platform_triples, |
| engine, |
| } |
| } |
| |
| pub(crate) fn render(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> { |
| let mut output = BTreeMap::new(); |
| |
| let platforms = self.render_platform_labels(context); |
| output.extend(self.render_build_files(context, &platforms)?); |
| output.extend(self.render_crates_module(context, &platforms)?); |
| |
| if let Some(vendor_mode) = &self.config.vendor_mode { |
| match vendor_mode { |
| crate::config::VendorMode::Local => { |
| // Nothing to do for local vendor crate |
| } |
| crate::config::VendorMode::Remote => { |
| output.extend(self.render_vendor_support_files(context)?); |
| } |
| } |
| } |
| |
| Ok(output) |
| } |
| |
| fn render_platform_labels(&self, context: &Context) -> BTreeMap<String, BTreeSet<String>> { |
| context |
| .conditions |
| .iter() |
| .map(|(cfg, target_triples)| { |
| ( |
| cfg.clone(), |
| target_triples |
| .iter() |
| .map(|target_triple| { |
| render_platform_constraint_label( |
| &self.config.platforms_template, |
| target_triple, |
| ) |
| }) |
| .collect(), |
| ) |
| }) |
| .collect() |
| } |
| |
| fn render_crates_module( |
| &self, |
| context: &Context, |
| platforms: &Platforms, |
| ) -> Result<BTreeMap<PathBuf, String>> { |
| let module_label = render_module_label(&self.config.crates_module_template, "defs.bzl") |
| .context("Failed to resolve string to module file label")?; |
| let module_build_label = |
| render_module_label(&self.config.crates_module_template, "BUILD.bazel") |
| .context("Failed to resolve string to module file label")?; |
| let module_alias_rules_label = |
| render_module_label(&self.config.crates_module_template, "alias_rules.bzl") |
| .context("Failed to resolve string to module file label")?; |
| |
| let mut map = BTreeMap::new(); |
| map.insert( |
| Renderer::label_to_path(&module_label), |
| self.engine.render_module_bzl(context, platforms)?, |
| ); |
| map.insert( |
| Renderer::label_to_path(&module_build_label), |
| self.render_module_build_file(context)?, |
| ); |
| map.insert( |
| Renderer::label_to_path(&module_alias_rules_label), |
| include_str!(concat!( |
| env!("CARGO_MANIFEST_DIR"), |
| "/src/rendering/verbatim/alias_rules.bzl" |
| )) |
| .to_owned(), |
| ); |
| |
| Ok(map) |
| } |
| |
| fn render_module_build_file(&self, context: &Context) -> Result<String> { |
| let mut starlark = Vec::new(); |
| |
| // Banner comment for top of the file. |
| let header = self.engine.render_header()?; |
| starlark.push(Starlark::Verbatim(header)); |
| |
| // Load any `alias_rule`s. |
| let mut loads: BTreeMap<String, BTreeSet<String>> = BTreeMap::new(); |
| for alias_rule in Iterator::chain( |
| std::iter::once(&self.config.default_alias_rule), |
| context |
| .workspace_member_deps() |
| .iter() |
| .flat_map(|dep| &context.crates[&dep.id].alias_rule), |
| ) { |
| if let Some(bzl) = alias_rule.bzl() { |
| loads.entry(bzl).or_default().insert(alias_rule.rule()); |
| } |
| } |
| for (bzl, items) in loads { |
| starlark.push(Starlark::Load(Load { bzl, items })) |
| } |
| |
| // Package visibility, exported bzl files. |
| let package = Package::default_visibility_public(BTreeSet::new()); |
| starlark.push(Starlark::Package(package)); |
| |
| let mut exports_files = ExportsFiles { |
| paths: BTreeSet::from(["cargo-bazel.json".to_owned(), "defs.bzl".to_owned()]), |
| globs: Glob { |
| allow_empty: true, |
| include: BTreeSet::from(["*.bazel".to_owned()]), |
| exclude: BTreeSet::new(), |
| }, |
| }; |
| if let Some(VendorMode::Remote) = self.config.vendor_mode { |
| exports_files.paths.insert("crates.bzl".to_owned()); |
| } |
| starlark.push(Starlark::ExportsFiles(exports_files)); |
| |
| let filegroup = Filegroup { |
| name: "srcs".to_owned(), |
| srcs: Glob { |
| allow_empty: true, |
| include: BTreeSet::from(["*.bazel".to_owned(), "*.bzl".to_owned()]), |
| exclude: BTreeSet::new(), |
| }, |
| }; |
| starlark.push(Starlark::Filegroup(filegroup)); |
| |
| // An `alias` for each direct dependency of a workspace member crate. |
| let mut dependencies = Vec::new(); |
| for dep in context.workspace_member_deps() { |
| let krate = &context.crates[&dep.id]; |
| let alias_rule = krate |
| .alias_rule |
| .as_ref() |
| .unwrap_or(&self.config.default_alias_rule); |
| |
| if let Some(library_target_name) = &krate.library_target_name { |
| let rename = dep.alias.as_ref().unwrap_or(&krate.name); |
| dependencies.push(Alias { |
| rule: alias_rule.rule(), |
| // If duplicates exist, include version to disambiguate them. |
| name: if context.has_duplicate_workspace_member_dep(&dep) { |
| format!("{}-{}", rename, krate.version) |
| } else { |
| rename.clone() |
| }, |
| actual: self.crate_label( |
| &krate.name, |
| &krate.version.to_string(), |
| library_target_name, |
| ), |
| tags: BTreeSet::from(["manual".to_owned()]), |
| }); |
| } |
| |
| for (alias, target) in &krate.extra_aliased_targets { |
| dependencies.push(Alias { |
| rule: alias_rule.rule(), |
| name: alias.clone(), |
| actual: self.crate_label(&krate.name, &krate.version.to_string(), target), |
| tags: BTreeSet::from(["manual".to_owned()]), |
| }); |
| } |
| } |
| |
| let duplicates: Vec<_> = dependencies |
| .iter() |
| .map(|alias| &alias.name) |
| .duplicates() |
| .sorted() |
| .collect(); |
| |
| assert!( |
| duplicates.is_empty(), |
| "Found duplicate aliases that must be changed (Check your `extra_aliased_targets`): {:#?}", |
| duplicates |
| ); |
| |
| if !dependencies.is_empty() { |
| let comment = "# Workspace Member Dependencies".to_owned(); |
| starlark.push(Starlark::Verbatim(comment)); |
| starlark.extend(dependencies.into_iter().map(Starlark::Alias)); |
| } |
| |
| // An `alias` for each binary dependency. |
| let mut binaries = Vec::new(); |
| for crate_id in &context.binary_crates { |
| let krate = &context.crates[crate_id]; |
| for rule in &krate.targets { |
| if let Rule::Binary(bin) = rule { |
| binaries.push(Alias { |
| rule: AliasRule::default().rule(), |
| // If duplicates exist, include version to disambiguate them. |
| name: if context.has_duplicate_binary_crate(crate_id) { |
| format!("{}-{}__{}", krate.name, krate.version, bin.crate_name) |
| } else { |
| format!("{}__{}", krate.name, bin.crate_name) |
| }, |
| actual: self.crate_label( |
| &krate.name, |
| &krate.version.to_string(), |
| &format!("{}__bin", bin.crate_name), |
| ), |
| tags: BTreeSet::from(["manual".to_owned()]), |
| }); |
| } |
| } |
| } |
| if !binaries.is_empty() { |
| let comment = "# Binaries".to_owned(); |
| starlark.push(Starlark::Verbatim(comment)); |
| starlark.extend(binaries.into_iter().map(Starlark::Alias)); |
| } |
| |
| let starlark = starlark::serialize(&starlark)?; |
| Ok(starlark) |
| } |
| |
| fn render_build_files( |
| &self, |
| context: &Context, |
| platforms: &Platforms, |
| ) -> Result<BTreeMap<PathBuf, String>> { |
| let default_splicing_package_id = default_splicing_package_crate_id(); |
| context |
| .crates |
| .keys() |
| // Do not render the default splicing package |
| .filter(|id| *id != &default_splicing_package_id) |
| // Do not render local packages |
| .filter(|id| !context.workspace_members.contains_key(id)) |
| .map(|id| { |
| let label = match render_build_file_template( |
| &self.config.build_file_template, |
| &id.name, |
| &id.version.to_string(), |
| ) { |
| Ok(label) => label, |
| Err(e) => bail!(e), |
| }; |
| |
| let filename = Renderer::label_to_path(&label); |
| let content = self.render_one_build_file(platforms, &context.crates[id])?; |
| Ok((filename, content)) |
| }) |
| .collect() |
| } |
| |
| fn render_one_build_file(&self, platforms: &Platforms, krate: &CrateContext) -> Result<String> { |
| let mut starlark = Vec::new(); |
| |
| // Banner comment for top of the file. |
| let header = self.engine.render_header()?; |
| starlark.push(Starlark::Verbatim(header)); |
| |
| // Loads: map of bzl file to set of items imported from that file. These |
| // get inserted into `starlark` at the bottom of this function. |
| let mut loads: BTreeMap<String, BTreeSet<String>> = BTreeMap::new(); |
| let mut load = |bzl: &str, item: &str| { |
| loads |
| .entry(bzl.to_owned()) |
| .or_default() |
| .insert(item.to_owned()) |
| }; |
| |
| let disable_visibility = "# buildifier: disable=bzl-visibility".to_owned(); |
| starlark.push(Starlark::Verbatim(disable_visibility)); |
| starlark.push(Starlark::Load(Load { |
| bzl: "@rules_rust//crate_universe/private:selects.bzl".to_owned(), |
| items: BTreeSet::from(["selects".to_owned()]), |
| })); |
| |
| if self.config.generate_rules_license_metadata { |
| let has_license_ids = !krate.license_ids.is_empty(); |
| let mut package_metadata = BTreeSet::from([Label::Relative { |
| target: "package_info".to_owned(), |
| }]); |
| |
| starlark.push(Starlark::Load(Load { |
| bzl: "@rules_license//rules:package_info.bzl".to_owned(), |
| items: BTreeSet::from(["package_info".to_owned()]), |
| })); |
| |
| if has_license_ids { |
| starlark.push(Starlark::Load(Load { |
| bzl: "@rules_license//rules:license.bzl".to_owned(), |
| items: BTreeSet::from(["license".to_owned()]), |
| })); |
| package_metadata.insert(Label::Relative { |
| target: "license".to_owned(), |
| }); |
| } |
| |
| let package = Package::default_visibility_public(package_metadata); |
| starlark.push(Starlark::Package(package)); |
| |
| starlark.push(Starlark::PackageInfo(starlark::PackageInfo { |
| name: "package_info".to_owned(), |
| package_name: krate.name.clone(), |
| package_url: krate.package_url.clone().unwrap_or_default(), |
| package_version: krate.version.to_string(), |
| })); |
| |
| if has_license_ids { |
| let mut license_kinds = BTreeSet::new(); |
| |
| krate.license_ids.clone().into_iter().for_each(|lic| { |
| license_kinds.insert("@rules_license//licenses/spdx:".to_owned() + &lic); |
| }); |
| |
| starlark.push(Starlark::License(starlark::License { |
| name: "license".to_owned(), |
| license_kinds, |
| license_text: krate.license_file.clone().unwrap_or_default(), |
| })); |
| } |
| } else { |
| // Package visibility. |
| let package = Package::default_visibility_public(BTreeSet::new()); |
| starlark.push(Starlark::Package(package)); |
| } |
| |
| for rule in &krate.targets { |
| if let Some(override_target) = krate.override_targets.get(rule.override_target_key()) { |
| starlark.push(Starlark::Alias(Alias { |
| rule: AliasRule::default().rule(), |
| name: rule.crate_name().to_owned(), |
| actual: override_target.clone(), |
| tags: BTreeSet::from(["manual".to_owned()]), |
| })); |
| } else { |
| match rule { |
| Rule::BuildScript(target) => { |
| load("@rules_rust//cargo:defs.bzl", "cargo_build_script"); |
| let cargo_build_script = |
| self.make_cargo_build_script(platforms, krate, target)?; |
| starlark.push(Starlark::CargoBuildScript(cargo_build_script)); |
| starlark.push(Starlark::Alias(Alias { |
| rule: AliasRule::default().rule(), |
| name: target.crate_name.clone(), |
| actual: Label::from_str("_bs").unwrap(), |
| tags: BTreeSet::from(["manual".to_owned()]), |
| })); |
| } |
| Rule::ProcMacro(target) => { |
| load("@rules_rust//rust:defs.bzl", "rust_proc_macro"); |
| let rust_proc_macro = |
| self.make_rust_proc_macro(platforms, krate, target)?; |
| starlark.push(Starlark::RustProcMacro(rust_proc_macro)); |
| } |
| Rule::Library(target) => { |
| load("@rules_rust//rust:defs.bzl", "rust_library"); |
| let rust_library = self.make_rust_library(platforms, krate, target)?; |
| starlark.push(Starlark::RustLibrary(rust_library)); |
| } |
| Rule::Binary(target) => { |
| load("@rules_rust//rust:defs.bzl", "rust_binary"); |
| let rust_binary = self.make_rust_binary(platforms, krate, target)?; |
| starlark.push(Starlark::RustBinary(rust_binary)); |
| } |
| } |
| } |
| } |
| |
| if let Some(additive_build_file_content) = &krate.additive_build_file_content { |
| let comment = "# Additive BUILD file content".to_owned(); |
| starlark.push(Starlark::Verbatim(comment)); |
| starlark.push(Starlark::Verbatim(additive_build_file_content.clone())); |
| } |
| |
| // Insert all the loads immediately after the header banner comment. |
| let loads = loads |
| .into_iter() |
| .map(|(bzl, items)| Starlark::Load(Load { bzl, items })); |
| starlark.splice(1..1, loads); |
| |
| let starlark = starlark::serialize(&starlark)?; |
| Ok(starlark) |
| } |
| |
| fn make_cargo_build_script( |
| &self, |
| platforms: &Platforms, |
| krate: &CrateContext, |
| target: &TargetAttributes, |
| ) -> Result<CargoBuildScript> { |
| let attrs = krate.build_script_attrs.as_ref(); |
| |
| const COMPILE_DATA_GLOB_EXCLUDES: &[&str] = &["**/*.rs"]; |
| |
| Ok(CargoBuildScript { |
| // Because `cargo_build_script` does some invisible target name |
| // mutating to determine the package and crate name for a build |
| // script, the Bazel target name of any build script cannot be the |
| // Cargo canonical name of "cargo_build_script" without losing out |
| // on having certain Cargo environment variables set. |
| // |
| // Do not change this name to "cargo_build_script". |
| // |
| // This is set to a short name to avoid long path name issues on windows. |
| name: "_bs".to_string(), |
| aliases: SelectDict::new(self.make_aliases(krate, true, false), platforms), |
| build_script_env: SelectDict::new( |
| attrs |
| .map(|attrs| attrs.build_script_env.clone()) |
| .unwrap_or_default(), |
| platforms, |
| ), |
| compile_data: make_data_with_exclude( |
| platforms, |
| attrs |
| .map(|attrs| attrs.compile_data_glob.clone()) |
| .unwrap_or_default(), |
| COMPILE_DATA_GLOB_EXCLUDES |
| .iter() |
| .map(|&pattern| pattern.to_owned()) |
| .collect(), |
| attrs |
| .map(|attrs| attrs.compile_data.clone()) |
| .unwrap_or_default(), |
| ), |
| crate_features: SelectSet::new(krate.common_attrs.crate_features.clone(), platforms), |
| crate_name: utils::sanitize_module_name(&target.crate_name), |
| crate_root: target.crate_root.clone(), |
| data: make_data( |
| platforms, |
| attrs |
| .map(|attrs| attrs.data_glob.clone()) |
| .unwrap_or_default(), |
| attrs.map(|attrs| attrs.data.clone()).unwrap_or_default(), |
| ), |
| deps: SelectSet::new( |
| self.make_deps( |
| attrs.map(|attrs| attrs.deps.clone()).unwrap_or_default(), |
| attrs |
| .map(|attrs| attrs.extra_deps.clone()) |
| .unwrap_or_default(), |
| ), |
| platforms, |
| ), |
| link_deps: SelectSet::new( |
| self.make_deps( |
| attrs |
| .map(|attrs| attrs.link_deps.clone()) |
| .unwrap_or_default(), |
| attrs |
| .map(|attrs| attrs.extra_link_deps.clone()) |
| .unwrap_or_default(), |
| ), |
| platforms, |
| ), |
| edition: krate.common_attrs.edition.clone(), |
| linker_script: krate.common_attrs.linker_script.clone(), |
| links: attrs.and_then(|attrs| attrs.links.clone()), |
| pkg_name: Some(krate.name.clone()), |
| proc_macro_deps: SelectSet::new( |
| self.make_deps( |
| attrs |
| .map(|attrs| attrs.proc_macro_deps.clone()) |
| .unwrap_or_default(), |
| attrs |
| .map(|attrs| attrs.extra_proc_macro_deps.clone()) |
| .unwrap_or_default(), |
| ), |
| platforms, |
| ), |
| rundir: SelectScalar::new( |
| attrs.map(|attrs| attrs.rundir.clone()).unwrap_or_default(), |
| platforms, |
| ), |
| rustc_env: SelectDict::new( |
| attrs |
| .map(|attrs| attrs.rustc_env.clone()) |
| .unwrap_or_default(), |
| platforms, |
| ), |
| rustc_env_files: SelectSet::new( |
| attrs |
| .map(|attrs| attrs.rustc_env_files.clone()) |
| .unwrap_or_default(), |
| platforms, |
| ), |
| rustc_flags: SelectList::new( |
| // In most cases, warnings in 3rd party crates are not |
| // interesting as they're out of the control of consumers. The |
| // flag here silences warnings. For more details see: |
| // https://doc.rust-lang.org/rustc/lints/levels.html |
| Select::merge( |
| Select::from_value(Vec::from(["--cap-lints=allow".to_owned()])), |
| attrs |
| .map(|attrs| attrs.rustc_flags.clone()) |
| .unwrap_or_default(), |
| ), |
| platforms, |
| ), |
| srcs: target.srcs.clone(), |
| tags: { |
| let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned()); |
| tags.insert("cargo-bazel".to_owned()); |
| tags.insert("manual".to_owned()); |
| tags.insert("noclippy".to_owned()); |
| tags.insert("norustfmt".to_owned()); |
| tags.insert(format!("crate-name={}", krate.name)); |
| tags |
| }, |
| tools: SelectSet::new( |
| attrs.map(|attrs| attrs.tools.clone()).unwrap_or_default(), |
| platforms, |
| ), |
| toolchains: attrs.map_or_else(BTreeSet::new, |attrs| attrs.toolchains.clone()), |
| version: krate.common_attrs.version.clone(), |
| visibility: BTreeSet::from(["//visibility:private".to_owned()]), |
| }) |
| } |
| |
| fn make_rust_proc_macro( |
| &self, |
| platforms: &Platforms, |
| krate: &CrateContext, |
| target: &TargetAttributes, |
| ) -> Result<RustProcMacro> { |
| Ok(RustProcMacro { |
| name: target.crate_name.clone(), |
| deps: SelectSet::new( |
| self.make_deps( |
| krate.common_attrs.deps.clone(), |
| krate.common_attrs.extra_deps.clone(), |
| ), |
| platforms, |
| ), |
| proc_macro_deps: SelectSet::new( |
| self.make_deps( |
| krate.common_attrs.proc_macro_deps.clone(), |
| krate.common_attrs.extra_proc_macro_deps.clone(), |
| ), |
| platforms, |
| ), |
| aliases: SelectDict::new(self.make_aliases(krate, false, false), platforms), |
| common: self.make_common_attrs(platforms, krate, target)?, |
| }) |
| } |
| |
| fn make_rust_library( |
| &self, |
| platforms: &Platforms, |
| krate: &CrateContext, |
| target: &TargetAttributes, |
| ) -> Result<RustLibrary> { |
| Ok(RustLibrary { |
| name: target.crate_name.clone(), |
| deps: SelectSet::new( |
| self.make_deps( |
| krate.common_attrs.deps.clone(), |
| krate.common_attrs.extra_deps.clone(), |
| ), |
| platforms, |
| ), |
| proc_macro_deps: SelectSet::new( |
| self.make_deps( |
| krate.common_attrs.proc_macro_deps.clone(), |
| krate.common_attrs.extra_proc_macro_deps.clone(), |
| ), |
| platforms, |
| ), |
| aliases: SelectDict::new(self.make_aliases(krate, false, false), platforms), |
| common: self.make_common_attrs(platforms, krate, target)?, |
| disable_pipelining: krate.disable_pipelining, |
| }) |
| } |
| |
| fn make_rust_binary( |
| &self, |
| platforms: &Platforms, |
| krate: &CrateContext, |
| target: &TargetAttributes, |
| ) -> Result<RustBinary> { |
| Ok(RustBinary { |
| name: format!("{}__bin", target.crate_name), |
| deps: { |
| let mut deps = self.make_deps( |
| krate.common_attrs.deps.clone(), |
| krate.common_attrs.extra_deps.clone(), |
| ); |
| if let Some(library_target_name) = &krate.library_target_name { |
| deps.insert( |
| Label::from_str(&format!(":{library_target_name}")).unwrap(), |
| None, |
| ); |
| } |
| SelectSet::new(deps, platforms) |
| }, |
| proc_macro_deps: SelectSet::new( |
| self.make_deps( |
| krate.common_attrs.proc_macro_deps.clone(), |
| krate.common_attrs.extra_proc_macro_deps.clone(), |
| ), |
| platforms, |
| ), |
| aliases: SelectDict::new(self.make_aliases(krate, false, false), platforms), |
| common: self.make_common_attrs(platforms, krate, target)?, |
| }) |
| } |
| |
| fn make_common_attrs( |
| &self, |
| platforms: &Platforms, |
| krate: &CrateContext, |
| target: &TargetAttributes, |
| ) -> Result<CommonAttrs> { |
| Ok(CommonAttrs { |
| compile_data: make_data( |
| platforms, |
| krate.common_attrs.compile_data_glob.clone(), |
| krate.common_attrs.compile_data.clone(), |
| ), |
| crate_features: SelectSet::new(krate.common_attrs.crate_features.clone(), platforms), |
| crate_root: target.crate_root.clone(), |
| data: make_data( |
| platforms, |
| krate.common_attrs.data_glob.clone(), |
| krate.common_attrs.data.clone(), |
| ), |
| edition: krate.common_attrs.edition.clone(), |
| linker_script: krate.common_attrs.linker_script.clone(), |
| rustc_env: SelectDict::new(krate.common_attrs.rustc_env.clone(), platforms), |
| rustc_env_files: SelectSet::new(krate.common_attrs.rustc_env_files.clone(), platforms), |
| rustc_flags: SelectList::new( |
| // In most cases, warnings in 3rd party crates are not |
| // interesting as they're out of the control of consumers. The |
| // flag here silences warnings. For more details see: |
| // https://doc.rust-lang.org/rustc/lints/levels.html |
| Select::merge( |
| Select::from_value(Vec::from(["--cap-lints=allow".to_owned()])), |
| krate.common_attrs.rustc_flags.clone(), |
| ), |
| platforms, |
| ), |
| srcs: target.srcs.clone(), |
| tags: { |
| let mut tags = BTreeSet::from_iter(krate.common_attrs.tags.iter().cloned()); |
| tags.insert("cargo-bazel".to_owned()); |
| tags.insert("manual".to_owned()); |
| tags.insert("noclippy".to_owned()); |
| tags.insert("norustfmt".to_owned()); |
| tags.insert(format!("crate-name={}", krate.name)); |
| tags |
| }, |
| target_compatible_with: self.config.generate_target_compatible_with.then(|| { |
| TargetCompatibleWith::new( |
| self.supported_platform_triples |
| .iter() |
| .map(|target_triple| { |
| render_platform_constraint_label( |
| &self.config.platforms_template, |
| target_triple, |
| ) |
| }) |
| .collect(), |
| ) |
| }), |
| version: krate.common_attrs.version.clone(), |
| }) |
| } |
| |
| /// Filter a crate's dependencies to only ones with aliases |
| fn make_aliases( |
| &self, |
| krate: &CrateContext, |
| build: bool, |
| include_dev: bool, |
| ) -> Select<BTreeMap<Label, String>> { |
| let mut dependency_selects = Vec::new(); |
| if build { |
| if let Some(build_script_attrs) = &krate.build_script_attrs { |
| dependency_selects.push(&build_script_attrs.deps); |
| dependency_selects.push(&build_script_attrs.proc_macro_deps); |
| } |
| } else { |
| dependency_selects.push(&krate.common_attrs.deps); |
| dependency_selects.push(&krate.common_attrs.proc_macro_deps); |
| if include_dev { |
| dependency_selects.push(&krate.common_attrs.deps_dev); |
| dependency_selects.push(&krate.common_attrs.proc_macro_deps_dev); |
| } |
| } |
| |
| let mut aliases: Select<BTreeMap<Label, String>> = Select::default(); |
| for dependency_select in dependency_selects.iter() { |
| for (configuration, dependency) in dependency_select.items().into_iter() { |
| if let Some(alias) = &dependency.alias { |
| let label = self.crate_label( |
| &dependency.id.name, |
| &dependency.id.version.to_string(), |
| &dependency.target, |
| ); |
| aliases.insert((label, alias.clone()), configuration.clone()); |
| } |
| } |
| } |
| aliases |
| } |
| |
| fn make_deps( |
| &self, |
| deps: Select<BTreeSet<CrateDependency>>, |
| extra_deps: Select<BTreeSet<Label>>, |
| ) -> Select<BTreeSet<Label>> { |
| Select::merge( |
| deps.map(|dep| { |
| self.crate_label(&dep.id.name, &dep.id.version.to_string(), &dep.target) |
| }), |
| extra_deps, |
| ) |
| } |
| |
| fn render_vendor_support_files(&self, context: &Context) -> Result<BTreeMap<PathBuf, String>> { |
| let module_label = render_module_label(&self.config.crates_module_template, "crates.bzl") |
| .context("Failed to resolve string to module file label")?; |
| |
| let mut map = BTreeMap::new(); |
| map.insert( |
| Renderer::label_to_path(&module_label), |
| self.engine.render_vendor_module_file(context)?, |
| ); |
| |
| Ok(map) |
| } |
| |
| fn label_to_path(label: &Label) -> PathBuf { |
| match &label.package() { |
| Some(package) if !package.is_empty() => { |
| PathBuf::from(format!("{}/{}", package, label.target())) |
| } |
| Some(_) | None => PathBuf::from(label.target()), |
| } |
| } |
| |
| fn crate_label(&self, name: &str, version: &str, target: &str) -> Label { |
| Label::from_str(&sanitize_repository_name(&render_crate_bazel_label( |
| &self.config.crate_label_template, |
| &self.config.repository_name, |
| name, |
| version, |
| target, |
| ))) |
| .unwrap() |
| } |
| } |
| |
| /// Write a set of [crate::context::crate_context::CrateContext] to disk. |
| pub(crate) fn write_outputs(outputs: BTreeMap<PathBuf, String>, dry_run: bool) -> Result<()> { |
| if dry_run { |
| for (path, content) in outputs { |
| println!( |
| "===============================================================================" |
| ); |
| println!("{}", path.display()); |
| println!( |
| "===============================================================================" |
| ); |
| println!("{content}\n"); |
| } |
| } else { |
| for (path, content) in outputs { |
| // Ensure the output directory exists |
| fs::create_dir_all( |
| path.parent() |
| .expect("All file paths should have valid directories"), |
| )?; |
| fs::write(&path, content.as_bytes()) |
| .context(format!("Failed to write file to disk: {}", path.display()))?; |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// Render the Bazel label of a crate |
| pub(crate) fn render_crate_bazel_label( |
| template: &str, |
| repository_name: &str, |
| name: &str, |
| version: &str, |
| target: &str, |
| ) -> String { |
| template |
| .replace("{repository}", repository_name) |
| .replace("{name}", name) |
| .replace("{version}", version) |
| .replace("{target}", target) |
| } |
| |
| /// Render the Bazel label of a crate |
| pub(crate) fn render_crate_bazel_repository( |
| template: &str, |
| repository_name: &str, |
| name: &str, |
| version: &str, |
| ) -> String { |
| template |
| .replace("{repository}", repository_name) |
| .replace("{name}", name) |
| .replace("{version}", version) |
| } |
| |
| /// Render the Bazel label of a crate |
| pub(crate) fn render_crate_build_file(template: &str, name: &str, version: &str) -> String { |
| template |
| .replace("{name}", name) |
| .replace("{version}", version) |
| } |
| |
| /// Render the Bazel label of a vendor module label |
| pub(crate) fn render_module_label(template: &str, name: &str) -> Result<Label> { |
| Label::from_str(&template.replace("{file}", name)) |
| } |
| |
| /// Render the Bazel label of a platform triple |
| fn render_platform_constraint_label(template: &str, target_triple: &TargetTriple) -> String { |
| template.replace("{triple}", &target_triple.to_bazel()) |
| } |
| |
| fn render_build_file_template(template: &str, name: &str, version: &str) -> Result<Label> { |
| Label::from_str( |
| &template |
| .replace("{name}", name) |
| .replace("{version}", version), |
| ) |
| } |
| |
| fn make_data_with_exclude( |
| platforms: &Platforms, |
| include: BTreeSet<String>, |
| exclude: BTreeSet<String>, |
| select: Select<BTreeSet<Label>>, |
| ) -> Data { |
| const COMMON_GLOB_EXCLUDES: &[&str] = &[ |
| "**/* *", |
| "BUILD.bazel", |
| "BUILD", |
| "WORKSPACE.bazel", |
| "WORKSPACE", |
| ".tmp_git_root/**/*", |
| ]; |
| |
| Data { |
| glob: Glob { |
| allow_empty: true, |
| include, |
| exclude: COMMON_GLOB_EXCLUDES |
| .iter() |
| .map(|&glob| glob.to_owned()) |
| .chain(exclude) |
| .collect(), |
| }, |
| select: SelectSet::new(select, platforms), |
| } |
| } |
| |
| fn make_data( |
| platforms: &Platforms, |
| glob: BTreeSet<String>, |
| select: Select<BTreeSet<Label>>, |
| ) -> Data { |
| make_data_with_exclude(platforms, glob, BTreeSet::new(), select) |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| |
| use indoc::indoc; |
| |
| use crate::config::{Config, CrateId}; |
| use crate::context::{BuildScriptAttributes, CommonAttributes}; |
| use crate::metadata::Annotations; |
| use crate::test; |
| use crate::utils::normalize_cargo_file_paths; |
| |
| const VERSION_ZERO_ONE_ZERO: semver::Version = semver::Version::new(0, 1, 0); |
| |
| fn mock_target_attributes() -> TargetAttributes { |
| TargetAttributes { |
| crate_name: "mock_crate".to_owned(), |
| crate_root: Some("src/root.rs".to_owned()), |
| ..TargetAttributes::default() |
| } |
| } |
| |
| fn mock_render_config(vendor_mode: Option<VendorMode>) -> RenderConfig { |
| RenderConfig { |
| repository_name: "test_rendering".to_owned(), |
| regen_command: "cargo_bazel_regen_command".to_owned(), |
| vendor_mode, |
| ..RenderConfig::default() |
| } |
| } |
| |
| fn mock_supported_platform_triples() -> BTreeSet<TargetTriple> { |
| BTreeSet::from([ |
| TargetTriple::from_bazel("aarch64-apple-darwin".to_owned()), |
| TargetTriple::from_bazel("aarch64-apple-ios".to_owned()), |
| TargetTriple::from_bazel("aarch64-linux-android".to_owned()), |
| TargetTriple::from_bazel("aarch64-pc-windows-msvc".to_owned()), |
| TargetTriple::from_bazel("aarch64-unknown-linux-gnu".to_owned()), |
| TargetTriple::from_bazel("arm-unknown-linux-gnueabi".to_owned()), |
| TargetTriple::from_bazel("armv7-unknown-linux-gnueabi".to_owned()), |
| TargetTriple::from_bazel("i686-apple-darwin".to_owned()), |
| TargetTriple::from_bazel("i686-linux-android".to_owned()), |
| TargetTriple::from_bazel("i686-pc-windows-msvc".to_owned()), |
| TargetTriple::from_bazel("i686-unknown-freebsd".to_owned()), |
| TargetTriple::from_bazel("i686-unknown-linux-gnu".to_owned()), |
| TargetTriple::from_bazel("powerpc-unknown-linux-gnu".to_owned()), |
| TargetTriple::from_bazel("s390x-unknown-linux-gnu".to_owned()), |
| TargetTriple::from_bazel("wasm32-unknown-unknown".to_owned()), |
| TargetTriple::from_bazel("wasm32-wasi".to_owned()), |
| TargetTriple::from_bazel("x86_64-apple-darwin".to_owned()), |
| TargetTriple::from_bazel("x86_64-apple-ios".to_owned()), |
| TargetTriple::from_bazel("x86_64-linux-android".to_owned()), |
| TargetTriple::from_bazel("x86_64-pc-windows-msvc".to_owned()), |
| TargetTriple::from_bazel("x86_64-unknown-freebsd".to_owned()), |
| TargetTriple::from_bazel("x86_64-unknown-linux-gnu".to_owned()), |
| ]) |
| } |
| |
| #[test] |
| fn render_rust_library() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| assert!(build_file_content.contains("rust_library(")); |
| assert!(build_file_content.contains("name = \"mock_crate\"")); |
| assert!(build_file_content.contains("\"crate-name=mock_crate\"")); |
| } |
| |
| #[test] |
| fn test_disable_pipelining() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: true, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| assert!(build_file_content.contains("disable_pipelining = True")); |
| } |
| |
| #[test] |
| fn render_cargo_build_script() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::BuildScript(TargetAttributes { |
| crate_name: "build_script_build".to_owned(), |
| crate_root: Some("build.rs".to_owned()), |
| ..TargetAttributes::default() |
| })]), |
| // Build script attributes are required. |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: Some(BuildScriptAttributes::default()), |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| assert!(build_file_content.contains("cargo_build_script(")); |
| assert!(build_file_content.contains("name = \"build_script_build\"")); |
| assert!(build_file_content.contains("\"crate-name=mock_crate\"")); |
| assert!(build_file_content.contains("compile_data = glob(")); |
| |
| // Ensure `cargo_build_script` requirements are met |
| assert!(build_file_content.contains("name = \"_bs\"")); |
| } |
| |
| #[test] |
| fn render_proc_macro() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::ProcMacro(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| assert!(build_file_content.contains("rust_proc_macro(")); |
| assert!(build_file_content.contains("name = \"mock_crate\"")); |
| assert!(build_file_content.contains("\"crate-name=mock_crate\"")); |
| } |
| |
| #[test] |
| fn render_binary() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Binary(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| assert!(build_file_content.contains("rust_binary(")); |
| assert!(build_file_content.contains("name = \"mock_crate__bin\"")); |
| assert!(build_file_content.contains("\"crate-name=mock_crate\"")); |
| } |
| |
| #[test] |
| fn render_additive_build_contents() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Binary(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: Some( |
| "# Hello World from additive section!".to_owned(), |
| ), |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| assert!(build_file_content.contains("# Hello World from additive section!")); |
| } |
| |
| #[test] |
| fn render_aliases() { |
| let config = Config { |
| generate_binaries: true, |
| ..Config::default() |
| }; |
| let annotations = |
| Annotations::new(test::metadata::alias(), test::lockfile::alias(), config).unwrap(); |
| let context = Context::new(annotations, false).unwrap(); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output.get(&PathBuf::from("BUILD.bazel")).unwrap(); |
| |
| assert!(build_file_content.contains(r#"name = "names-0.12.1-dev__names","#)); |
| assert!(build_file_content.contains(r#"name = "names-0.13.0__names","#)); |
| } |
| |
| #[test] |
| fn render_crate_repositories() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap(); |
| |
| assert!(defs_module.contains("def crate_repositories():")); |
| } |
| |
| #[test] |
| fn remote_remote_vendor_mode() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| // Enable remote vendor mode |
| let renderer = Renderer::new( |
| mock_render_config(Some(VendorMode::Remote)), |
| mock_supported_platform_triples(), |
| ); |
| let output = renderer.render(&context).unwrap(); |
| |
| let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap(); |
| assert!(defs_module.contains("def crate_repositories():")); |
| |
| let crates_module = output.get(&PathBuf::from("crates.bzl")).unwrap(); |
| assert!(crates_module.contains("def crate_repositories():")); |
| } |
| |
| #[test] |
| fn remote_local_vendor_mode() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| // Enable local vendor mode |
| let renderer = Renderer::new( |
| mock_render_config(Some(VendorMode::Local)), |
| mock_supported_platform_triples(), |
| ); |
| let output = renderer.render(&context).unwrap(); |
| |
| // Local vendoring does not produce a `crate_repositories` macro |
| let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap(); |
| assert!(!defs_module.contains("def crate_repositories():")); |
| |
| // Local vendoring does not produce a `crates.bzl` file. |
| assert!(!output.contains_key(&PathBuf::from("crates.bzl"))); |
| } |
| |
| #[test] |
| fn duplicate_rustc_flags() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| |
| let rustc_flags = vec![ |
| "-l".to_owned(), |
| "dylib=ssl".to_owned(), |
| "-l".to_owned(), |
| "dylib=crypto".to_owned(), |
| ]; |
| |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes { |
| rustc_flags: Select::from_value(rustc_flags.clone()), |
| ..CommonAttributes::default() |
| }, |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| // Enable local vendor mode |
| let renderer = Renderer::new( |
| mock_render_config(Some(VendorMode::Local)), |
| mock_supported_platform_triples(), |
| ); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| // Strip all spaces from the generated BUILD file and ensure it has the flags |
| // represented by `rustc_flags` in the same order. |
| assert!(build_file_content.replace(' ', "").contains( |
| &rustc_flags |
| .iter() |
| .map(|s| format!("\"{s}\",")) |
| .collect::<Vec<String>>() |
| .join("\n") |
| )); |
| } |
| |
| #[test] |
| fn test_render_build_file_deps() { |
| let config: Config = serde_json::from_value(serde_json::json!({ |
| "generate_binaries": false, |
| "generate_build_scripts": false, |
| "rendering": { |
| "repository_name": "multi_cfg_dep", |
| "regen_command": "bazel test //crate_universe:unit_test", |
| }, |
| "supported_platform_triples": [ |
| "x86_64-apple-darwin", |
| "x86_64-unknown-linux-gnu", |
| "aarch64-apple-darwin", |
| "aarch64-unknown-linux-gnu", |
| ], |
| })) |
| .unwrap(); |
| let metadata = test::metadata::multi_cfg_dep(); |
| let lockfile = test::lockfile::multi_cfg_dep(); |
| |
| let annotations = Annotations::new(metadata, lockfile, config.clone()).unwrap(); |
| let context = Context::new(annotations, false).unwrap(); |
| |
| let renderer = Renderer::new(config.rendering, config.supported_platform_triples); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.cpufeatures-0.2.7.bazel")) |
| .unwrap(); |
| |
| // This is unfortunately somewhat brittle. Alas. Ultimately we wish to demonstrate that the |
| // original cfg(...) strings are preserved in the `deps` list for ease of debugging. |
| let expected = indoc! {r#" |
| deps = select({ |
| "@rules_rust//rust/platform:aarch64-apple-darwin": [ |
| "@multi_cfg_dep__libc-0.2.117//:libc", # cfg(all(target_arch = "aarch64", target_vendor = "apple")) |
| ], |
| "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [ |
| "@multi_cfg_dep__libc-0.2.117//:libc", # cfg(all(target_arch = "aarch64", target_os = "linux")) |
| ], |
| "//conditions:default": [], |
| }), |
| "#}; |
| |
| assert!( |
| build_file_content.contains(&expected.replace('\n', "\n ")), |
| "{}", |
| build_file_content, |
| ); |
| } |
| |
| #[test] |
| fn crate_features_by_target() { |
| let mut context = Context { |
| conditions: mock_supported_platform_triples() |
| .iter() |
| .map(|platform| (platform.to_bazel(), BTreeSet::from([platform.clone()]))) |
| .collect(), |
| ..Context::default() |
| }; |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| let mut crate_features: Select<BTreeSet<String>> = Select::default(); |
| crate_features.insert("foo".to_owned(), Some("aarch64-apple-darwin".to_owned())); |
| crate_features.insert("bar".to_owned(), None); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes { |
| crate_features, |
| ..CommonAttributes::default() |
| }, |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let renderer = Renderer::new(mock_render_config(None), mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| let expected = indoc! {r#" |
| crate_features = [ |
| "bar", |
| ] + select({ |
| "@rules_rust//rust/platform:aarch64-apple-darwin": [ |
| "foo", # aarch64-apple-darwin |
| ], |
| "//conditions:default": [], |
| }), |
| "#}; |
| assert!(build_file_content |
| .replace(' ', "") |
| .contains(&expected.replace(' ', ""))); |
| } |
| |
| #[test] |
| fn crate_package_metadata_without_license_ids() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: Some("http://www.mock_crate.com/".to_owned()), |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let mut render_config = mock_render_config(None); |
| render_config.generate_rules_license_metadata = true; |
| let renderer = Renderer::new(render_config, mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| let expected = indoc! {r#" |
| package( |
| default_package_metadata = [":package_info"], |
| default_visibility = ["//visibility:public"], |
| ) |
| |
| package_info( |
| name = "package_info", |
| package_name = "mock_crate", |
| package_version = "0.1.0", |
| package_url = "http://www.mock_crate.com/", |
| ) |
| "#}; |
| assert!(build_file_content |
| .replace(' ', "") |
| .contains(&expected.replace(' ', ""))); |
| } |
| |
| #[test] |
| fn crate_package_metadata_with_license_ids() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: Some("http://www.mock_crate.com/".to_owned()), |
| license_ids: BTreeSet::from(["Apache-2.0".to_owned(), "MIT".to_owned()]), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| repository: None, |
| license: None, |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let mut render_config = mock_render_config(None); |
| render_config.generate_rules_license_metadata = true; |
| let renderer = Renderer::new(render_config, mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| let expected = indoc! {r#" |
| package( |
| default_package_metadata = [ |
| ":license", |
| ":package_info", |
| ], |
| default_visibility = ["//visibility:public"], |
| ) |
| |
| package_info( |
| name = "package_info", |
| package_name = "mock_crate", |
| package_version = "0.1.0", |
| package_url = "http://www.mock_crate.com/", |
| ) |
| |
| license( |
| name = "license", |
| license_kinds = [ |
| "@rules_license//licenses/spdx:Apache-2.0", |
| "@rules_license//licenses/spdx:MIT", |
| ], |
| ) |
| "#}; |
| assert!(build_file_content |
| .replace(' ', "") |
| .contains(&expected.replace(' ', ""))); |
| } |
| |
| #[test] |
| fn crate_package_metadata_with_license_ids_and_file() { |
| let mut context = Context::default(); |
| let crate_id = CrateId::new("mock_crate".to_owned(), VERSION_ZERO_ONE_ZERO); |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: Some("http://www.mock_crate.com/".to_owned()), |
| license_ids: BTreeSet::from(["Apache-2.0".to_owned(), "MIT".to_owned()]), |
| license_file: Some("LICENSE.txt".to_owned()), |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| repository: None, |
| license: None, |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let mut render_config = mock_render_config(None); |
| render_config.generate_rules_license_metadata = true; |
| let renderer = Renderer::new(render_config, mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| |
| let build_file_content = output |
| .get(&PathBuf::from("BUILD.mock_crate-0.1.0.bazel")) |
| .unwrap(); |
| |
| let expected = indoc! {r#" |
| package( |
| default_package_metadata = [ |
| ":license", |
| ":package_info", |
| ], |
| default_visibility = ["//visibility:public"], |
| ) |
| |
| package_info( |
| name = "package_info", |
| package_name = "mock_crate", |
| package_version = "0.1.0", |
| package_url = "http://www.mock_crate.com/", |
| ) |
| |
| license( |
| name = "license", |
| license_kinds = [ |
| "@rules_license//licenses/spdx:Apache-2.0", |
| "@rules_license//licenses/spdx:MIT", |
| ], |
| license_text = "LICENSE.txt", |
| ) |
| "#}; |
| assert!(build_file_content |
| .replace(' ', "") |
| .contains(&expected.replace(' ', ""))); |
| } |
| |
| #[test] |
| fn write_outputs_semver_metadata() { |
| let mut context = Context::default(); |
| // generate crate for libbpf-sys-1.4.0-v1.4.0 |
| let mut version = semver::Version::new(1, 4, 0); |
| version.build = semver::BuildMetadata::new("v1.4.0").unwrap(); |
| // ensure metadata has a + |
| assert!(version.to_string().contains('+')); |
| let crate_id = CrateId::new("libbpf-sys".to_owned(), version); |
| |
| context.crates.insert( |
| crate_id.clone(), |
| CrateContext { |
| name: crate_id.name, |
| version: crate_id.version, |
| package_url: None, |
| repository: None, |
| targets: BTreeSet::from([Rule::Library(mock_target_attributes())]), |
| library_target_name: None, |
| common_attrs: CommonAttributes::default(), |
| build_script_attrs: None, |
| license: None, |
| license_ids: BTreeSet::default(), |
| license_file: None, |
| additive_build_file_content: None, |
| disable_pipelining: false, |
| extra_aliased_targets: BTreeMap::default(), |
| alias_rule: None, |
| override_targets: BTreeMap::default(), |
| }, |
| ); |
| |
| let mut config = mock_render_config(Some(VendorMode::Local)); |
| // change templates so it matches local vendor |
| config.build_file_template = "//{name}-{version}:BUILD.bazel".into(); |
| |
| // Enable local vendor mode |
| let renderer = Renderer::new(config, mock_supported_platform_triples()); |
| let output = renderer.render(&context).unwrap(); |
| eprintln!("output before {:?}", output.keys()); |
| // Local vendoring does not produce a `crate_repositories` macro |
| let defs_module = output.get(&PathBuf::from("defs.bzl")).unwrap(); |
| assert!(!defs_module.contains("def crate_repositories():")); |
| |
| // Local vendoring does not produce a `crates.bzl` file. |
| assert!(!output.contains_key(&PathBuf::from("crates.bzl"))); |
| |
| // create tempdir to write to |
| let outdir = tempfile::tempdir().unwrap(); |
| |
| // create dir to mimic cargo vendor |
| let _ = std::fs::create_dir_all(outdir.path().join("libbpf-sys-1.4.0+v1.4.0")); |
| |
| let normalized_outputs = normalize_cargo_file_paths(output, outdir.path()); |
| eprintln!( |
| "Normalized outputs are {:?}", |
| normalized_outputs.clone().into_keys() |
| ); |
| |
| write_outputs(normalized_outputs, false).unwrap(); |
| let expected = outdir.path().join("libbpf-sys-1.4.0-v1.4.0"); |
| let mut found = false; |
| // ensure no files paths have a + sign |
| for entry in fs::read_dir(outdir.path()).unwrap() { |
| let path_str = entry.as_ref().unwrap().path().to_str().unwrap().to_string(); |
| if entry.unwrap().path() == expected { |
| found = true; |
| } |
| assert!(!path_str.contains('+')); |
| } |
| assert!(found); |
| } |
| } |