// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::{ error::RazeError, metadata::{MetadataFetcher, DEFAULT_CRATE_INDEX_URL, DEFAULT_CRATE_REGISTRY_URL}, util, }; use anyhow::{anyhow, bail, Context, Result}; use camino::{Utf8Path, Utf8PathBuf}; use cargo_metadata::{Metadata, MetadataCommand, Package}; use semver::VersionReq; use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, BTreeSet, HashMap, HashSet}, hash::Hash, }; pub type CrateSettingsPerVersion = HashMap<VersionReq, CrateSettings>; /// The configuration settings for `cargo-raze`, included in a projects Cargo metadata #[derive(Debug, Clone, Deserialize)] pub struct RazeSettings { /// The path to write BUILD file outputs to. /// /// This may be a workspace-relative path (e.g. `//foo/bar`) or a path relative to the `Cargo.toml` file's directory /// (e.g. if the Cargo metadata file is `//foo:Cargo.toml`, this could be `third_party` to put /// outputs in `//foo/third_party`.) pub workspace_path: String, /// The relative path within each workspace member directory where aliases the member's dependencies should be rendered. /// /// By default, a new directory will be created next to the `Cargo.toml` file named `cargo` for users to refer to them /// as. For example, the toml file `//my/package:Cargo.toml` will have aliases rendered as something like /// `//my/package/cargo:dependency`. Note that setting this value to `"."` will cause the BUILD file in the same package /// as the Cargo.toml file to be overwritten. #[serde(default = "default_package_aliases_dir")] pub package_aliases_dir: String, /// If true, package alises will be rendered based on the functionality described by `package_aliases_dir`. #[serde(default = "default_render_package_aliases")] pub render_package_aliases: bool, /// The platform target to generate BUILD rules for. /// /// This comes in the form of a "triple", such as "x86_64-unknown-linux-gnu" #[serde(default)] pub target: Option<String>, /// A list of targets to generate BUILD rules for. /// /// Each item comes in the form of a "triple", such as "x86_64-unknown-linux-gnu" #[serde(default)] pub targets: Option<HashSet<String>>, /// A list of binary dependencies. #[serde(default)] pub binary_deps: HashMap<String, cargo_toml::Dependency>, /// Any crate-specific configuration. See CrateSettings for details. #[serde(default)] pub crates: HashMap<String, CrateSettingsPerVersion>, // TODO(acmcarther): Does this have a non-bazel analogue? /// Prefix for generated Bazel workspaces (from workspace_rules) /// /// This is only useful with remote genmode. It prefixes the names of the workspaces for /// dependencies (@PREFIX_crateName_crateVersion) as well as the name of the repository function /// generated in crates.bzl (PREFIX_fetch_remote_crates()). #[serde(default = "default_raze_settings_field_gen_workspace_prefix")] pub gen_workspace_prefix: String, /// How to generate the dependencies. See GenMode for details. #[serde(default = "default_raze_settings_field_genmode")] pub genmode: GenMode, /// The name of the output BUILD files when `genmode == "Vendored"` /// /// Default: BUILD.bazel #[serde(default = "default_raze_settings_field_output_buildfile_suffix")] pub output_buildfile_suffix: String, /// Default value for per-crate gen_buildrs setting if it's not explicitly for a crate. /// /// See [crate::settings::CrateSettings::gen_buildrs] for more information. #[serde(default = "default_raze_settings_field_gen_buildrs")] pub default_gen_buildrs: bool, /// The default crates registry. /// /// The patterns `{crate}` and `{version}` will be used to fill /// in the package's name (eg: rand) and version (eg: 0.7.1). /// See https://doc.rust-lang.org/cargo/reference/registries.html#index-format #[serde(default = "default_raze_settings_registry")] pub registry: String, /// The index url to use for Binary dependencies #[serde(default = "default_raze_settings_index_url")] pub index_url: String, /// The name of the [rules_rust](https://github.com/bazelbuild/rules_rust) repository /// used in the generated workspace. #[serde(default = "default_raze_settings_rust_rules_workspace_name")] pub rust_rules_workspace_name: String, /// The expected path relative to the `Cargo.toml` file where vendored sources can /// be found. This should match the path passed to the `cargo vendor` command. eg: /// `cargo vendor -q --versioned-dirs "cargo/vendor" #[serde(default = "default_raze_settings_vendor_dir")] pub vendor_dir: String, /** * If true, an experimetnal API for accessing crates will be rendered into * `crates.bzl` for both Remote and Vendored genmodes. */ #[serde(default = "default_raze_settings_experimental_api")] pub experimental_api: bool, } impl RazeSettings { /// Returns all of the supported targets that are enabled. pub fn enabled_targets(&self) -> BTreeSet<String> { let mut result: BTreeSet<String> = BTreeSet::new(); if let Some(target) = &self.target { result.insert(target.into()); } else { result.extend(util::get_enabled_targets(&self.targets).map(String::from)); } result } } /// Override settings for individual crates (as part of `RazeSettings`). #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct CrateSettings { /// Dependencies to be added to a crate. /// /// Importantly, the format of dependency references depends on the genmode. /// Remote: @{gen_workspace_prefix}__{dep_name}__{dep_version_sanitized}/:{dep_name} /// Vendored: //{workspace_path}/vendor/{dep_name}-{dep_version}:{dep_name} /// /// In addition, the added deps must be accessible from a remote workspace under Remote GenMode. /// Usually, this means they _also_ need to be remote, but a "local" build path prefixed with /// "@", in the form "@//something_local" may work. #[serde(default)] pub additional_deps: Vec<String>, /// Dependencies to be removed from a crate, in the form "{dep-name}-{dep-version}" /// /// This is applied during Cargo analysis, so it uses Cargo-style labeling #[serde(default)] pub skipped_deps: Vec<String>, /// Library targets that should be aliased in the root BUILD file. /// /// This is useful to facilitate using binary utility crates, such as bindgen, as part of genrules. #[serde(default)] pub extra_aliased_targets: Vec<String>, /// Flags to be added to the crate compilation process, in the form "--flag". #[serde(default)] pub additional_flags: Vec<String>, /// Environment variables to be added to the crate compilation process. #[serde(default)] pub additional_env: BTreeMap<String, String>, /// Whether or not to generate the build script that goes with this crate. /// /// Many build scripts will not function, as they will still be built hermetically. However, build /// scripts that merely generate files into OUT_DIR may be fully functional. #[serde(default = "default_crate_settings_field_gen_buildrs")] pub gen_buildrs: Option<bool>, // N.B. Build scripts are always provided all crate files for their `data` attr. /// The verbatim `data` clause to be included for the generated build targets. #[serde(default = "default_crate_settings_field_data_attr")] pub data_attr: Option<String>, /// A list of targets for the `data` attribute of a Rust target #[serde(default)] pub data_dependencies: Vec<String>, /// The verbatim `compile_data` clause to be included for the generated build targets. #[serde(default)] pub compile_data_attr: Option<String>, /// The `data` attribute for buildrs targets #[serde(default)] pub build_data_dependencies: Vec<String>, /// The `tools` attribute for buildrs targets #[serde(default)] pub build_tools_dependencies: Vec<String>, /// Additional environment variables to add when running the build script. #[serde(default)] pub buildrs_additional_environment_variables: BTreeMap<String, String>, /// Additional dependencies for buildrs targets. See `additional_deps` #[serde(default)] pub buildrs_additional_deps: Vec<String>, /// The arguments given to the patch tool. /// /// Defaults to `-p0`, however `-p1` will usually be needed for patches generated by git. /// /// If multiple `-p` arguments are specified, the last one will take effect. /// If arguments other than `-p` are specified, Bazel will fall back to use patch command line /// tool instead of the Bazel-native patch implementation. /// /// When falling back to `patch` command line tool and `patch_tool` attribute is not specified, /// `patch` will be used. #[serde(default)] pub patch_args: Vec<String>, /// Sequence of Bash commands to be applied on Linux/Macos after patches are applied. #[serde(default)] pub patch_cmds: Vec<String>, /// Sequence of Powershell commands to be applied on Windows after patches are applied. /// /// If this attribute is not set, patch_cmds will be executed on Windows, which requires Bash /// binary to exist. #[serde(default)] pub patch_cmds_win: Vec<String>, /// The `patch(1)` utility to use. /// /// If this is specified, Bazel will use the specified patch tool instead of the Bazel-native patch /// implementation. #[serde(default)] pub patch_tool: Option<String>, /// A list of files that are to be applied as patches after extracting the archive. /// /// By default, it uses the Bazel-native patch implementation which doesn't support fuzz match and /// binary patch, but Bazel will fall back to use patch command line tool if `patch_tool` /// attribute is specified or there are arguments other than `-p` in `patch_args` attribute. #[serde(default)] pub patches: Vec<String>, /// Path to a file to be included as part of the generated BUILD file. /// /// For example, some crates include non-Rust code typically built through a build.rs script. They /// can be made compatible by manually writing appropriate Bazel targets, and including them into /// the crate through a combination of additional_build_file and additional_deps. /// /// Note: This field should be a path to a file relative to the Cargo workspace root. For more /// context, see https://doc.rust-lang.org/cargo/reference/workspaces.html#root-package #[serde(default)] pub additional_build_file: Option<Utf8PathBuf>, } /// Describes how dependencies should be managed in tree. #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] pub enum GenMode { /// This mode assumes that files are vendored (into vendor/), and generates BUILD files /// accordingly Vendored, /// This mode assumes that files are not locally vendored, and generates a workspace-level /// function that can bring them in. Remote, /// A representation of a GenMode that has not yet been specified Unspecified, } impl Default for CrateSettings { fn default() -> Self { Self { additional_deps: Vec::new(), skipped_deps: Vec::new(), extra_aliased_targets: Vec::new(), additional_flags: Vec::new(), additional_env: BTreeMap::new(), gen_buildrs: default_crate_settings_field_gen_buildrs(), data_attr: default_crate_settings_field_data_attr(), data_dependencies: Vec::new(), compile_data_attr: None, build_data_dependencies: Vec::new(), build_tools_dependencies: Vec::new(), buildrs_additional_deps: Vec::new(), buildrs_additional_environment_variables: BTreeMap::new(), patch_args: Vec::new(), patch_cmds: Vec::new(), patch_cmds_win: Vec::new(), patch_tool: None, patches: Vec::new(), additional_build_file: None, } } } fn default_raze_settings_field_gen_workspace_prefix() -> String { "raze".to_owned() } fn default_raze_settings_field_genmode() -> GenMode { GenMode::Unspecified } fn default_raze_settings_field_output_buildfile_suffix() -> String { "BUILD.bazel".to_owned() } fn default_raze_settings_field_gen_buildrs() -> bool { true } fn default_raze_settings_registry() -> String { format!( "{}/{}", DEFAULT_CRATE_REGISTRY_URL, "api/v1/crates/{crate}/{version}/download" ) } fn default_raze_settings_index_url() -> String { DEFAULT_CRATE_INDEX_URL.to_string() } fn default_raze_settings_rust_rules_workspace_name() -> String { "rules_rust".to_owned() } fn default_raze_settings_vendor_dir() -> String { "vendor".to_owned() } fn default_raze_settings_experimental_api() -> bool { false } fn default_crate_settings_field_gen_buildrs() -> Option<bool> { None } fn default_crate_settings_field_data_attr() -> Option<String> { None } fn default_package_aliases_dir() -> String { "cargo".to_owned() } fn default_render_package_aliases() -> bool { true } /// Formats a registry url to include the name and version fo the target package pub fn format_registry_url(registry_url: &str, name: &str, version: &str) -> String { registry_url .replace("{crate}", name) .replace("{version}", version) } /// Check that the the `additional_build_file` represents a path to a file from the cargo workspace root fn validate_crate_setting_additional_build_file( additional_build_file: &Utf8Path, cargo_workspace_root: &Utf8Path, ) -> Result<()> { let additional_build_file = cargo_workspace_root.join(additional_build_file); if !additional_build_file.exists() { return Err(anyhow!( "File not found. `{}` should be a relative path from the cargo workspace root: {}", additional_build_file, cargo_workspace_root )); } Ok(()) } /// Ensures crate settings associatd with the parsed [RazeSettings](crate::settings::RazeSettings) have valid crate settings fn validate_crate_settings( settings: &RazeSettings, cargo_workspace_root: &Utf8Path, ) -> Result<(), RazeError> { let mut errors = Vec::new(); for (crate_name, crate_settings) in settings.crates.iter() { for (version, crate_settings) in crate_settings.iter() { if crate_settings.additional_build_file.is_none() { continue; } let result = validate_crate_setting_additional_build_file( // UNWRAP: Safe due to check above crate_settings.additional_build_file.as_ref().unwrap(), cargo_workspace_root, ); if let Some(err) = result.err() { errors.push(RazeError::Config { field_path_opt: Some(format!( "raze.crates.{}.{}.additional_build_file", crate_name, version )), message: err.to_string(), }); } } } // Surface all errors if !errors.is_empty() { return Err(RazeError::Config { field_path_opt: None, message: format!("{:?}", errors), }); } Ok(()) } /// Verifies that the provided settings make sense. fn validate_settings( settings: &mut RazeSettings, cargo_workspace_path: &Utf8Path, ) -> Result<(), RazeError> { if !settings.workspace_path.starts_with("//") { return Err(RazeError::Config { field_path_opt: Some("raze.workspace_path".to_owned()), message: concat!( "Path must start with \"//\". Paths into local repositories (such as ", "@local//path) are currently unsupported." ) .to_owned(), }); } if settings.workspace_path != "//" && settings.workspace_path.ends_with('/') { settings.workspace_path.pop(); } if settings.genmode == GenMode::Unspecified { eprintln!( "WARNING: The [raze] setting `genmode` is unspecified. Not specifying `genmode` is \ deprecated. Please explicitly set it to either \"Remote\" or \"Vendored\"" ); settings.genmode = GenMode::Vendored; } validate_crate_settings(settings, cargo_workspace_path)?; Ok(()) } /// The intermediate configuration settings for `cargo-raze`, included in a project's Cargo metadata /// /// Note that this struct should contain only `Option` and match all public fields of /// [RazeSettings](crate::settings::RazeSettings) #[derive(Debug, Clone, Deserialize)] #[serde(deny_unknown_fields)] struct RawRazeSettings { #[serde(default)] pub workspace_path: Option<String>, #[serde(default)] pub package_aliases_dir: Option<String>, #[serde(default)] pub render_package_aliases: Option<bool>, #[serde(default)] pub target: Option<String>, #[serde(default)] pub targets: Option<Vec<String>>, #[serde(default)] pub binary_deps: HashMap<String, cargo_toml::Dependency>, #[serde(default)] pub crates: HashMap<String, CrateSettingsPerVersion>, #[serde(default)] pub gen_workspace_prefix: Option<String>, #[serde(default)] pub genmode: Option<GenMode>, #[serde(default)] pub output_buildfile_suffix: Option<String>, #[serde(default)] pub default_gen_buildrs: Option<bool>, #[serde(default)] pub registry: Option<String>, #[serde(default)] pub index_url: Option<String>, #[serde(default)] pub rust_rules_workspace_name: Option<String>, #[serde(default)] pub vendor_dir: Option<String>, #[serde(default)] pub experimental_api: Option<bool>, } impl RawRazeSettings { /// Checks whether or not the settings have non-package specific settings specified fn contains_primary_options(&self) -> bool { self.workspace_path.is_some() || self.package_aliases_dir.is_some() || self.render_package_aliases.is_some() || self.target.is_some() || self.targets.is_some() || self.gen_workspace_prefix.is_some() || self.genmode.is_some() || self.output_buildfile_suffix.is_some() || self.default_gen_buildrs.is_some() || self.registry.is_some() || self.index_url.is_some() || self.rust_rules_workspace_name.is_some() || self.vendor_dir.is_some() || self.experimental_api.is_some() } fn print_notices_and_warnings(&self) { if self.target.is_some() { eprintln!( "WARNING: `[*.raze.target]` is deprecated. Please update your project to use \ `[*.raze.targets]`." ); } } } /// Grows a list with duplicate keys between two maps fn extend_duplicates<K: Hash + Eq + Clone, V>( extended_list: &mut Vec<K>, main_map: &HashMap<K, V>, input_map: &HashMap<K, V>, ) { extended_list.extend( input_map .iter() .filter_map(|(key, _value)| { // Log the key if it exists in both the main and input maps if main_map.contains_key(key) { Some(key) } else { None } }) .cloned() .collect::<Vec<K>>(), ); } /// Parse [RazeSettings](crate::settings::RazeSettings) from workspace metadata fn parse_raze_settings_workspace( metadata_value: &serde_json::value::Value, metadata: &Metadata, ) -> Result<RazeSettings> { RawRazeSettings::deserialize(metadata_value)?.print_notices_and_warnings(); let mut settings = RazeSettings::deserialize(metadata_value)?; let workspace_packages: Vec<&Package> = metadata .packages .iter() .filter(|pkg| metadata.workspace_members.contains(&pkg.id)) .collect(); let mut duplicate_binary_deps = Vec::new(); let mut duplicate_crate_settings = Vec::new(); for package in workspace_packages.iter() { if let Some(pkg_value) = package.metadata.get("raze") { let pkg_settings = RawRazeSettings::deserialize(pkg_value)?; if pkg_settings.contains_primary_options() { return Err(anyhow!( "The package '{}' contains Primary raze settings, please move these to the \ `[workspace.metadata.raze]`", package.name )); } // Log duplicate binary dependencies extend_duplicates( &mut duplicate_binary_deps, &settings.binary_deps, &pkg_settings.binary_deps, ); settings .binary_deps .extend(pkg_settings.binary_deps.into_iter()); // Log duplicate crate settings extend_duplicates( &mut duplicate_crate_settings, &settings.crates, &pkg_settings.crates, ); settings.crates.extend(pkg_settings.crates.into_iter()); } } // Check for duplication errors if !duplicate_binary_deps.is_empty() { return Err(anyhow!( "Duplicate `raze.binary_deps` values detected across various crates: {:?}", duplicate_binary_deps )); } if !duplicate_crate_settings.is_empty() { return Err(anyhow!( "Duplicate `raze.crates.*` values detected across various crates: {:?}", duplicate_crate_settings )); } Ok(settings) } /// Parse [RazeSettings](crate::settings::RazeSettings) from a project's root package's metadata fn parse_raze_settings_root_package( metadata_value: &serde_json::value::Value, root_package: &Package, ) -> Result<RazeSettings> { RawRazeSettings::deserialize(metadata_value)?.print_notices_and_warnings(); RazeSettings::deserialize(metadata_value).with_context(|| { format!( "Failed to load raze settings from root package: {}", root_package.name, ) }) } /// Parse [RazeSettings](crate::settings::RazeSettings) from any workspace member's metadata fn parse_raze_settings_any_package(metadata: &Metadata) -> Result<RazeSettings> { let mut settings_packages = Vec::new(); for package in metadata.packages.iter() { if let Some(pkg_value) = package.metadata.get("raze") { let pkg_settings = RawRazeSettings::deserialize(pkg_value)?; if pkg_settings.contains_primary_options() { settings_packages.push(package); } } } // There should only be one package with raze let settings_count = settings_packages.len(); if settings_count == 0 { bail!( "No raze settings were specified in the Cargo.toml file, see README.md for details on \ expected fields" ); } else if settings_count > 1 { bail!( "Multiple packages contain primary raze settings: {:?}", settings_packages .iter() .map(|pkg| &pkg.name) .collect::<Vec<&String>>() ); } // UNWRAP: Safe due to checks above let settings_value = settings_packages[0].metadata.get("raze").unwrap(); RawRazeSettings::deserialize(settings_value)?.print_notices_and_warnings(); RazeSettings::deserialize(settings_value) .with_context(|| format!("Failed to deserialize raze settings: {:?}", settings_value)) } /// A struct only to deserialize a Cargo.toml to in search of the legacy syntax for [RazeSettings](crate::settings::RazeSettings) #[derive(Debug, Clone, Deserialize)] pub struct LegacyCargoToml { pub raze: RazeSettings, } /// Parse [RazeSettings](crate::settings::RazeSettings) from a Cargo.toml file using the legacy syntax `[raze]` fn parse_raze_settings_legacy(metadata: &Metadata) -> Result<RazeSettings> { let root_toml = metadata.workspace_root.join("Cargo.toml"); let toml_contents = std::fs::read_to_string(&root_toml)?; let data = toml::from_str::<LegacyCargoToml>(&toml_contents) .with_context(|| format!("Failed to read `[raze]` settings from {}", root_toml))?; Ok(data.raze) } /// Parses raze settings from the contents of a `Cargo.toml` file fn parse_raze_settings(metadata: &Metadata) -> Result<RazeSettings> { // Workspace takes precedence let workspace_level_settings = metadata.workspace_metadata.get("raze"); if let Some(value) = workspace_level_settings { return parse_raze_settings_workspace(value, metadata); } // Root packages are the next priority if let Some(root_package) = metadata.root_package() { if let Some(value) = root_package.metadata.get("raze") { return parse_raze_settings_root_package(value, root_package); } } // Attempt to load legacy settings but do not allow failures to propagate if let Ok(settings) = parse_raze_settings_legacy(metadata) { eprintln!( "WARNING: The top-level `[raze]` key is deprecated. Please set `[workspace.metadata.raze]` \ or `[package.metadata.raze]` instead." ); return Ok(settings); } // Finally check any package for settings parse_raze_settings_any_package(metadata) } /// A cargo command wrapper for gathering cargo metadata used to parse [RazeSettings](crate::settings::RazeSettings) pub struct SettingsMetadataFetcher { pub cargo_bin_path: Utf8PathBuf, } impl Default for SettingsMetadataFetcher { fn default() -> SettingsMetadataFetcher { SettingsMetadataFetcher { cargo_bin_path: util::cargo_bin_path(), } } } impl MetadataFetcher for SettingsMetadataFetcher { fn fetch_metadata(&self, working_dir: &Utf8Path, _include_deps: bool) -> Result<Metadata> { // This fetch does not require network access. MetadataCommand::new() .cargo_path(&self.cargo_bin_path) .no_deps() .current_dir(working_dir.as_std_path()) .other_options(vec!["--offline".to_owned()]) .exec() .with_context(|| { format!( "Failed to fetch Metadata with `{}` from `{}`", &self.cargo_bin_path, working_dir ) }) } } /// Load settings from a given Cargo manifest pub fn load_settings_from_manifest<T: AsRef<Utf8Path>>( cargo_toml_path: T, cargo_bin_path: Option<String>, ) -> Result<RazeSettings, RazeError> { // Get the path to the cargo binary from either an optional Cargo binary // path or a fallback expected to be found on the system. let bin_path: Utf8PathBuf = if let Some(path) = cargo_bin_path { path.into() } else { util::cargo_bin_path() }; // Create a MetadataFetcher let fetcher = SettingsMetadataFetcher { cargo_bin_path: bin_path, }; let cargo_toml_dir = cargo_toml_path.as_ref().parent().ok_or_else(|| { RazeError::Generic(format!( "Failed to find parent directory for cargo toml file: {:?}", cargo_toml_path.as_ref(), )) })?; let metadata = { let result = fetcher.fetch_metadata(cargo_toml_dir, false); if result.is_err() { return Err(RazeError::Generic(result.err().unwrap().to_string())); } // UNWRAP: safe due to check above result.unwrap() }; load_settings(&metadata) } /// Load settings used to configure the functionality of Cargo Raze pub fn load_settings(metadata: &Metadata) -> Result<RazeSettings, RazeError> { let mut settings = { let result = parse_raze_settings(metadata); if result.is_err() { return Err(RazeError::Generic(result.err().unwrap().to_string())); } // UNWRAP: safe due to check above result.unwrap() }; validate_settings(&mut settings, metadata.workspace_root.as_ref())?; Ok(settings) } #[cfg(test)] pub mod tests { use crate::testing::{make_workspace, named_toml_contents}; use super::*; use indoc::{formatdoc, indoc}; use tempfile::TempDir; pub fn dummy_raze_settings() -> RazeSettings { RazeSettings { workspace_path: "//cargo".to_owned(), package_aliases_dir: "cargo".to_owned(), render_package_aliases: default_render_package_aliases(), target: Some("x86_64-unknown-linux-gnu".to_owned()), targets: None, crates: HashMap::new(), gen_workspace_prefix: "raze_test".to_owned(), genmode: GenMode::Remote, output_buildfile_suffix: "BUILD".to_owned(), default_gen_buildrs: default_raze_settings_field_gen_buildrs(), binary_deps: HashMap::new(), registry: default_raze_settings_registry(), index_url: default_raze_settings_index_url(), rust_rules_workspace_name: default_raze_settings_rust_rules_workspace_name(), vendor_dir: default_raze_settings_vendor_dir(), experimental_api: default_raze_settings_experimental_api(), } } #[test] fn test_loading_without_package_settings() { let toml_contents = indoc! { r#" [package] name = "test" version = "0.0.1" [dependencies] "# }; let temp_workspace_dir = TempDir::new() .ok() .expect("Failed to set up temporary directory"); let cargo_toml_path = Utf8PathBuf::from_path_buf(temp_workspace_dir.path().join("Cargo.toml")).unwrap(); std::fs::write(&cargo_toml_path, toml_contents).unwrap(); assert!(load_settings_from_manifest(cargo_toml_path, None).is_err()); } #[test] fn test_loading_package_settings() { let toml_contents = indoc! { r#" [package] name = "load_settings_test" version = "0.1.0" [lib] path = "not_a_file.rs" [dependencies] actix-web = "2.0.0" actix-rt = "1.0.0" [target.x86_64-apple-ios.dependencies] [target.x86_64-linux-android.dependencies] bitflags = "1.2.1" [package.metadata.raze] workspace_path = "//workspace_path/raze" genmode = "Remote" [package.metadata.raze.binary_deps] wasm-bindgen-cli = "0.2.68" "# }; let temp_workspace_dir = TempDir::new() .ok() .expect("Failed to set up temporary directory"); let cargo_toml_path = Utf8PathBuf::from_path_buf(temp_workspace_dir.path().join("Cargo.toml")).unwrap(); std::fs::write(&cargo_toml_path, toml_contents).unwrap(); let settings = load_settings_from_manifest(cargo_toml_path, None).unwrap(); assert!(!settings.binary_deps.is_empty()); } #[test] fn test_loading_settings_legacy() { let toml_contents = indoc! { r#" [package] name = "load_settings_test" version = "0.1.0" [lib] path = "not_a_file.rs" [dependencies] actix-web = "2.0.0" actix-rt = "1.0.0" [target.x86_64-apple-ios.dependencies] [target.x86_64-linux-android.dependencies] bitflags = "1.2.1" [raze] workspace_path = "//workspace_path/raze" genmode = "Remote" [raze.binary_deps] wasm-bindgen-cli = "0.2.68" "# }; let temp_workspace_dir = TempDir::new() .ok() .expect("Failed to set up temporary directory"); let cargo_toml_path = Utf8PathBuf::from_path_buf(temp_workspace_dir.path().join("Cargo.toml")).unwrap(); std::fs::write(&cargo_toml_path, toml_contents).unwrap(); let settings = load_settings_from_manifest(cargo_toml_path, /*cargo_bin_path=*/ None).unwrap(); assert!(!settings.binary_deps.is_empty()); } #[test] fn test_loading_workspace_settings() { let toml_contents = indoc! { r#" [workspace] members = [ "crate_a", "crate_b", ] [workspace.metadata.raze] workspace_path = "//workspace_path/raze" genmode = "Remote" "# }; let dir = make_workspace(toml_contents, None); for member in vec!["crate_a", "crate_b"].iter() { let crate_toml = dir.as_ref().join(member).join("Cargo.toml"); std::fs::create_dir_all(crate_toml.parent().unwrap()).unwrap(); let toml_contents = formatdoc! { r#" {named_contents} [package.metadata.raze.crates.settings-test-{name}.'*'] additional_flags = [ "--cfg={name}" ] "#, named_contents = named_toml_contents(member, "0.0.1"), name = member }; std::fs::write(crate_toml, toml_contents).unwrap(); } let settings = load_settings_from_manifest( Utf8PathBuf::from_path_buf(dir.as_ref().join("Cargo.toml")).unwrap(), None, ) .unwrap(); assert_eq!(&settings.workspace_path, "//workspace_path/raze"); assert_eq!(settings.genmode, GenMode::Remote); assert_eq!(settings.crates.len(), 2); } #[test] fn test_formatting_registry_url() { assert_eq!( format_registry_url(&default_raze_settings_registry(), "foo", "0.0.1"), "https://crates.io/api/v1/crates/foo/0.0.1/download" ); assert_eq!( format_registry_url( "https://registry.io/{crate}/{crate}/{version}/{version}", "foo", "0.0.1" ), "https://registry.io/foo/foo/0.0.1/0.0.1" ); } }