diff --git a/Cargo.lock b/Cargo.lock index 1d0079d..a452a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,12 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bitflags" version = "2.6.0" @@ -125,6 +131,7 @@ dependencies = [ "serde", "serde_ignored", "serde_json", + "serial_test", "sha2", "smallvec", "tar", @@ -321,6 +328,83 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -383,9 +467,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" -version = "0.2.160" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libredox" @@ -404,6 +488,22 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" @@ -473,12 +573,47 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.31" @@ -540,6 +675,27 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c1f7fc6deb21665a9060dfc7d271be784669295a31babdcd4dd2c79ae8cbfb" +dependencies = [ + "sdd", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" + [[package]] name = "semver" version = "1.0.23" @@ -580,9 +736,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "6dbcf9b78a125ee667ae19388837dd12294b858d101fdd393cb9d5501ef09eb2" dependencies = [ "itoa", "memchr", @@ -599,6 +755,31 @@ dependencies = [ "serde", ] +[[package]] +name = "serial_test" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha2" version = "0.10.8" @@ -616,6 +797,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" diff --git a/Cargo.toml b/Cargo.toml index ce3c8f6..e697a49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,3 +44,4 @@ vendored-openssl = ["openssl/vendored"] [dev-dependencies] once_cell = "1.17.1" +serial_test = "3.1.1" diff --git a/README.md b/README.md index 7065359..77b23c9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,14 @@ However, it doesn't offer any filtering; today cargo includes all platforms, but some projects only care about Linux for example. -More information: https://github.com/rust-lang/cargo/issues/7058 +More information: + +Additionally some projects are not interested by vendoring test code +or development dependencies of used crates and these filters +are also not supported with no development planned yet. + +More information: +or ## Generating a vendor/ directory with filtering @@ -26,13 +33,15 @@ $ cargo vendor-filterer --tier=2 Currently this will drop out crates such as `redox_syscall`. You can also declaratively specify the desired vendor configuration via the [Cargo metadata](https://doc.rust-lang.org/cargo/reference/manifest.html#the-metadata-table) -key `package.metadata.vendor-filter`. In this example, we include only tier 1 and 2 Linux platforms, and additionally remove some vendored C sources and `tests` folders from all crates: +key `package.metadata.vendor-filter`. In this example, we include only tier 1 and 2 Linux platforms, and additionally remove some vendored C sources, `tests` folders +and development dependencies from all crates: ```toml [package.metadata.vendor-filter] platforms = ["*-unknown-linux-gnu"] tier = "2" all-features = true +keep-dep-kinds = "no-dev" exclude-crate-paths = [ { name = "curl-sys", exclude = "curl" }, { name = "libz-sys", exclude = "src/zlib" }, { name = "libz-sys", exclude = "src/smoke.c" }, @@ -51,6 +60,8 @@ key `workspace.metadata.vendor-filter`. and `*` wildcards are supported. For example, `*-unknown-linux-gnu`. - `tier`: This can be either "1" or "2". It may be specified in addition to `platforms`. - `all-features`: Enable all features of the current crate when vendoring. +- `keep-dep-kinds`: Specify which dependencies kinds to keep. + Can be one of: all, normal, build, dev, no-normal, no-build, no-dev - `exclude-crate-paths`: Remove files and directories from target crates. A key use case for this is removing the vendored copy of C libraries embedded in crates like `libz-sys`, when you only want to support dynamically linking. diff --git a/src/dep_kinds_filtering.rs b/src/dep_kinds_filtering.rs new file mode 100644 index 0000000..8e26110 --- /dev/null +++ b/src/dep_kinds_filtering.rs @@ -0,0 +1,262 @@ +use crate::{Args, VendorFilter}; +use anyhow::{Context, Result}; +use camino::Utf8Path; +use clap::{builder::PossibleValue, ValueEnum}; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, +}; + +/// Kinds of dependencies that shall be included. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum DepKinds { + All, + Normal, + Build, + Dev, + NoNormal, + NoBuild, + NoDev, +} + +impl ValueEnum for DepKinds { + fn value_variants<'a>() -> &'a [Self] { + &[ + Self::All, + Self::Normal, + Self::Build, + Self::Dev, + Self::NoNormal, + Self::NoBuild, + Self::NoDev, + ] + } + + fn to_possible_value(&self) -> Option { + Some(match self { + Self::All => PossibleValue::new("all"), + Self::Normal => PossibleValue::new("normal"), + Self::Build => PossibleValue::new("build"), + Self::Dev => PossibleValue::new("dev"), + Self::NoNormal => PossibleValue::new("no-normal"), + Self::NoBuild => PossibleValue::new("no-build"), + Self::NoDev => PossibleValue::new("no-dev"), + }) + } +} + +impl std::fmt::Display for DepKinds { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.to_possible_value() + .expect("No values are skipped") + .get_name() + .fmt(f) + } +} + +/// Filter out unwanted dependency kinds. +/// +/// Replicates logic from add_packages_for_platform() but uses cargo tree +/// because cargo metadata does not implement dependency kinds filtering. +/// Ref: +/// Cargo tree is NOT intended for automatic processing so this function +/// explicitly does not replace the add_packages_for_platform() entirely. +pub(crate) fn filter_dep_kinds( + args: &Args, + config: &VendorFilter, + packages: &mut HashMap, + platform: Option<&str>, +) -> Result<()> { + // exit early when no dependency kinds filtering is requested + match config.keep_dep_kinds { + None | Some(DepKinds::All) => return Ok(()), + Some(_) => (), + }; + + let required_packages = get_required_packages( + &args.get_all_manifest_paths(), + args.offline, + config, + platform, + )?; + + packages.retain(|_, package| { + required_packages.contains(&( + Cow::Borrowed(&package.name), + Cow::Borrowed(&package.version), + )) + }); + Ok(()) +} + +/// Returns the set of required packages to satisfy filters specified in config +fn get_required_packages<'a>( + manifest_paths: &[Option<&Utf8Path>], + offline: bool, + config: &VendorFilter, + platform: Option<&str>, +) -> Result, Cow<'a, cargo_metadata::semver::Version>)>> { + let keep_dep_kinds = config.keep_dep_kinds.expect("keep_dep_kinds not set"); + let mut required_packages = HashSet::new(); + for manifest_path in manifest_paths { + let mut cargo_tree = std::process::Command::new("cargo"); + cargo_tree + .arg("tree") + .args(["--quiet", "--prefix", "none"]) // ignore non-relevant output + .args(["--edges", &keep_dep_kinds.to_string()]); // key filter not available with metadata + if offline { + cargo_tree.arg("--offline"); + } + if let Some(manifest_path) = manifest_path { + cargo_tree.args(["--manifest-path", manifest_path.as_str()]); + } + if config.all_features { + cargo_tree.arg("--all-features"); + } + if config.no_default_features { + cargo_tree.arg("--no-default-features"); + } + if !config.features.is_empty() { + cargo_tree.arg("--features").args(&config.features); + } + match platform { + Some(platform) => cargo_tree.arg(format!("--target={platform}")), + None => { + // different than in cargo metadata the default is current platform only + cargo_tree.arg("--target=all") + } + }; + let output = cargo_tree.output()?; + if !output.status.success() { + anyhow::bail!( + "Failed to execute cargo tree: {:?}", + String::from_utf8(output.stderr).expect("Invalid cargo tree output") + ); + } + let output_str = String::from_utf8(output.stdout).expect("Invalid cargo tree output"); + for line in output_str.lines() { + let tokens: Vec<&str> = line.split(' ').collect(); + let [package, version, ..] = tokens.as_slice() else { + anyhow::bail!("Invalid output received from cargo tree: {line}"); + }; + if version.len() < 5 || version.contains("feature") { + continue; // skip invalid entries and "feature" list + } + // need to remove the initial "v" character that the cargo tree is printing in package name + // Ref: + // The PR requesting the v to be removed (or configurable) was closed: + // + let version = version + .strip_prefix('v') + .with_context(|| format!("Invalid version: {}", tokens[1]))?; + let version = cargo_metadata::semver::Version::parse(version) + .with_context(|| format!("Cannot parse version {version} for {package}"))?; + required_packages.insert((Cow::Owned(package.to_string()), Cow::Owned(version))); + } + } + Ok(required_packages) +} + +#[cfg(test)] +mod tests { + use super::*; + use camino::Utf8PathBuf; + use serde_json::json; + + #[test] + fn test_dep_kind_dev_only() { + let mut own_cargo_toml = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")); + own_cargo_toml.push("Cargo.toml"); + let rp = get_required_packages( + &[Some(&own_cargo_toml)], + false, + &serde_json::from_value(json!({ "keep-dep-kinds": "dev"})).unwrap(), + Some("x86_64-pc-windows-gnu"), + ); + match rp { + Ok(rp) => assert_eq!(rp.len(), 3), // own package + once_cell + serial_test dev dependencies + Err(e) => panic!("Got error: {e:?}"), + } + } + + #[test] + fn test_dep_kind_all_number() { + let mut own_cargo_toml = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")); + own_cargo_toml.push("Cargo.toml"); + let rp = get_required_packages( + &[Some(&own_cargo_toml)], + false, + &serde_json::from_value(json!({ "keep-dep-kinds": "all", "--all-features": true})) + .unwrap(), + None, // all platforms + ); + match rp { + Ok(rp) => assert!(rp.len() > 90), // all features, all platforms list is long + Err(e) => panic!("Got error: {e:?}"), + } + } + + #[test] + fn test_dep_kind_normal_vs_no_build() { + let mut own_cargo_toml = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")); + own_cargo_toml.push("Cargo.toml"); + + let rp_normal = get_required_packages( + &[Some(&own_cargo_toml)], + false, + &serde_json::from_value(json!({ "keep-dep-kinds": "normal"})).unwrap(), + Some("x86_64-pc-windows-gnu"), + ); + + // no-build => normal + dev dependencies, so including once_call, serial_test... + let rp_no_build = get_required_packages( + &[Some(&own_cargo_toml)], + false, + &serde_json::from_value(json!({ "keep-dep-kinds": "no-build"})).unwrap(), + Some("x86_64-pc-windows-gnu"), + ); + + // if once_cell is also a normal dependency, it is not removed from the list + match (rp_normal, rp_no_build) { + (Ok(rp_normal), Ok(rp_no_build)) => assert!( + rp_normal.len() < rp_no_build.len(), + "Filtering does not work. Got {} normal and {} no-build dependencies", + rp_normal.len(), + rp_no_build.len() + ), + _ => panic!("One of get_required_packages() calls failed"), + } + } + + #[test] + fn test_dep_kind_build_vs_no_dev() { + let mut own_cargo_toml = Utf8PathBuf::from(env!("CARGO_MANIFEST_DIR")); + own_cargo_toml.push("Cargo.toml"); + + let rp_build = get_required_packages( + &[Some(&own_cargo_toml)], + false, + &serde_json::from_value(json!({ "keep-dep-kinds": "build"})).unwrap(), + Some("x86_64-unknown-linux-gnu"), + ); + + // no-dev => build + normal so the list shall be larger + let rp_no_dev = get_required_packages( + &[Some(&own_cargo_toml)], + false, + &serde_json::from_value(json!({ "keep-dep-kinds": "no-dev"})).unwrap(), + Some("x86_64-unknown-linux-gnu"), + ); + match (rp_build, rp_no_dev) { + (Ok(rp_build), Ok(rp_no_dev)) => assert!( + rp_build.len() < rp_no_dev.len(), + "Filtering does not work. Got {} build and {} no-dev dependencies", + rp_build.len(), + rp_no_dev.len() + ), + _ => panic!("One of get_required_packages() calls failed"), + } + } +} diff --git a/src/main.rs b/src/main.rs index e3adc29..47a641e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use std::vec; use cargo_vendor_filterer::*; +mod dep_kinds_filtering; mod tiers; /// This is the .cargo-checksum.json in a crate/package. @@ -131,6 +132,7 @@ struct VendorFilter { #[serde(skip_serializing_if = "Vec::is_empty", default)] features: Vec, exclude_crate_paths: Option>, + keep_dep_kinds: Option, } #[derive(Parser, Debug)] @@ -179,6 +181,12 @@ struct Args { #[arg(long, short = 'F')] features: Vec, + /// Dependencies kinds you want to keep: normal, build and/or development (dev). + /// Possible values: all (default), normal, build, dev, no-normal, no-build, no-dev + /// Ref: + #[arg(long)] + keep_dep_kinds: Option, + /// Pick the output format. #[arg(long, default_value = "dir")] format: OutputTarget, @@ -337,7 +345,8 @@ impl VendorFilter { && !args.all_features && !args.no_default_features && args.features.is_empty() - && args.exclude_crate_path.is_none(); + && args.exclude_crate_path.is_none() + && args.keep_dep_kinds.is_none(); let exclude_crate_paths = args .exclude_crate_path .as_ref() @@ -357,6 +366,7 @@ impl VendorFilter { no_default_features: args.no_default_features, features: args.features.clone(), exclude_crate_paths, + keep_dep_kinds: args.keep_dep_kinds, }); Ok(r) } @@ -862,10 +872,12 @@ fn run() -> Result<()> { &mut packages, Some(platform), )?; + dep_kinds_filtering::filter_dep_kinds(&args, &config, &mut packages, Some(platform))?; } expanded_platforms = Some(platforms); } else { add_packages_for_platform(&args, &config, &all_packages, &mut packages, None)?; + dep_kinds_filtering::filter_dep_kinds(&args, &config, &mut packages, None)?; } // Run `cargo vendor` which will capture all dependencies. @@ -939,6 +951,9 @@ fn run() -> Result<()> { } else if let Some(platforms) = expanded_platforms { eprintln!("Filtered to target platforms: {:?}", platforms); } + if let Some(keep_dep_kinds) = config.keep_dep_kinds { + eprintln!("Filtered to dependency kinds: {keep_dep_kinds}"); + } eprintln!("Generated: {final_output_path}"); Ok(()) @@ -961,12 +976,12 @@ fn test_parse_config() { let valid = vec![ json!({}), json!({ "platforms": ["aarch64-unknown-linux-gnu"]}), - json!({ "platforms": ["aarch64-unknown-linux-gnu"], "no-default-features": true}), - json!({ "platforms": ["*-unknown-linux-gnu"], "tier": "2", "no-default-features": false}), - json!({ "platforms": ["*-unknown-linux-gnu"], "tier": "Two", "no-default-features": false}), - json!({ "platforms": ["aarch64-unknown-linux-gnu"], "all-features": true, "no-default-features": false}), - json!({ "platforms": ["aarch64-unknown-linux-gnu"], "no-default-features": true}), - json!({ "platforms": ["aarch64-unknown-linux-gnu"], "no-default-features": true, "features": ["first-feature", "second-feature"]}), + json!({ "platforms": ["aarch64-unknown-linux-gnu"], "no-default-features": true, "keep-dep-kinds": "all"}), + json!({ "platforms": ["*-unknown-linux-gnu"], "tier": "2", "no-default-features": false, "keep-dep-kinds": "normal"}), + json!({ "platforms": ["*-unknown-linux-gnu"], "tier": "Two", "no-default-features": false, "keep-dep-kinds": "build"}), + json!({ "platforms": ["aarch64-unknown-linux-gnu"], "all-features": true, "no-default-features": false, "keep-dep-kinds": "dev"}), + json!({ "platforms": ["aarch64-unknown-linux-gnu"], "no-default-features": true, "keep-dep-kinds": "no-build"}), + json!({ "platforms": ["aarch64-unknown-linux-gnu"], "no-default-features": true, "features": ["first-feature", "second-feature"], "keep-dep-kinds": "no-build"}), ]; for case in valid { let _: VendorFilter = serde_json::from_value(case).unwrap(); diff --git a/tests/vendor_filterer/common.rs b/tests/vendor_filterer/common.rs index c214fd8..53c3ef5 100644 --- a/tests/vendor_filterer/common.rs +++ b/tests/vendor_filterer/common.rs @@ -69,6 +69,7 @@ pub(crate) struct VendorOptions<'a, 'b, 'c, 'd, 'e> { pub manifest_path: Option<&'d Utf8Path>, pub sync: Vec<&'e Utf8Path>, pub versioned_dirs: bool, + pub keep_dep_kinds: Option<&'static str>, } /// Run a vendoring process @@ -105,6 +106,9 @@ pub(crate) fn vendor(options: VendorOptions) -> Result { for s in options.sync { cmd.arg(format!("--sync={s}")); } + if let Some(keep_dep_kinds) = options.keep_dep_kinds { + cmd.args(["--keep-dep-kinds", keep_dep_kinds]); + } if let Some(output) = options.output { cmd.arg(output); } diff --git a/tests/vendor_filterer/exclude.rs b/tests/vendor_filterer/exclude.rs index 3815ed0..090975f 100644 --- a/tests/vendor_filterer/exclude.rs +++ b/tests/vendor_filterer/exclude.rs @@ -1,6 +1,7 @@ use super::common::{tempdir, vendor, verify_no_windows, VendorOptions}; #[test] +#[serial_test::parallel] fn linux_multiple_platforms() { let (_td, mut test_folder) = tempdir().unwrap(); test_folder.push("vendor"); @@ -18,3 +19,22 @@ fn linux_multiple_platforms() { test_folder.push("../tests"); assert!(!test_folder.exists()); } + +#[test] +#[serial_test::parallel] +fn windows_with_dep_kind_filter_normal() { + let (_td, mut test_folder) = tempdir().unwrap(); + test_folder.push("vendor-test2"); + let output = vendor(VendorOptions { + output: Some(&test_folder), + platforms: Some(&["x86_64-pc-windows-gnu"]), + keep_dep_kinds: Some("normal"), + ..Default::default() + }) + .unwrap(); + assert!(output.status.success()); + test_folder.push("serial_test/tests"); // crate replaced with a stub, so tests folder is removed + assert!(!test_folder.exists()); + test_folder.push("../openssl/examples"); // openssl removed because defined only for non-windows + assert!(!test_folder.exists()); +}