From 8178f22ee901b6bca67eea2f044e5105fbe4af01 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Wed, 24 Feb 2021 14:14:43 -0800 Subject: [PATCH] Support [patch] in .cargo/config files This patch adds support for `[patch]` sections in `.cargo/config.toml` files. Patches from config files defer to `[patch]` in `Cargo.toml` if both provide a patch for the same crate. The current implementation merge config patches into the workspace manifest patches. It's unclear if that's the right long-term plan, or whether these patches should be stored separately (though likely still in the manifest). Regardless, they _should_ likely continue to be parsed when the manifest is parsed so that errors and such occur in the same place regardless of where a patch is specified. Fixes #5539. --- src/cargo/core/features.rs | 2 + src/cargo/util/toml/mod.rs | 26 ++++- tests/testsuite/patch.rs | 217 +++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 2 deletions(-) diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 377cf25fb58..d50a28828e6 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -551,6 +551,7 @@ pub struct CliUnstable { pub extra_link_arg: bool, pub credential_process: bool, pub configurable_env: bool, + pub patch_in_config: bool, } const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \ @@ -707,6 +708,7 @@ impl CliUnstable { "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?, "configurable-env" => self.configurable_env = parse_empty(k, v)?, + "patch-in-config" => self.patch_in_config = parse_empty(k, v)?, "features" => { // For now this is still allowed (there are still some // unstable options like "compare"). This should be removed at diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 683f5e7c825..d31c7114d18 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -1531,9 +1531,12 @@ impl TomlManifest { Ok(replace) } - fn patch(&self, cx: &mut Context<'_, '_>) -> CargoResult>> { + fn patch_( + table: Option<&BTreeMap>>, + cx: &mut Context<'_, '_>, + ) -> CargoResult>> { let mut patch = HashMap::new(); - for (url, deps) in self.patch.iter().flatten() { + for (url, deps) in table.into_iter().flatten() { let url = match &url[..] { CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(), _ => cx @@ -1554,6 +1557,25 @@ impl TomlManifest { Ok(patch) } + fn patch(&self, cx: &mut Context<'_, '_>) -> CargoResult>> { + let from_manifest = Self::patch_(self.patch.as_ref(), cx)?; + + let config_patch: Option>> = + cx.config.get("patch")?; + + if config_patch.is_some() && !cx.config.cli_unstable().patch_in_config { + cx.warnings.push("`[patch]` in .cargo/config.toml ignored, the -Zpatch-in-config command-line flag is required".to_owned()); + return Ok(from_manifest); + } + + let mut from_config = Self::patch_(config_patch.as_ref(), cx)?; + if from_config.is_empty() { + return Ok(from_manifest); + } + from_config.extend(from_manifest); + Ok(from_config) + } + /// Returns the path to the build script if one exists for this crate. fn maybe_custom_build( &self, diff --git a/tests/testsuite/patch.rs b/tests/testsuite/patch.rs index 0890b1e8565..12f23050661 100644 --- a/tests/testsuite/patch.rs +++ b/tests/testsuite/patch.rs @@ -66,6 +66,91 @@ fn replace() { p.cargo("build").with_stderr("[FINISHED] [..]").run(); } +#[cargo_test] +fn from_config_without_z() { + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1.0" + "#, + ) + .file( + ".cargo/config.toml", + r#" + [patch.crates-io] + bar = { path = 'bar' } + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1")) + .file("bar/src/lib.rs", r#""#) + .build(); + + p.cargo("build") + .with_stderr( + "\ +[WARNING] `[patch]` in .cargo/config.toml ignored, the -Zpatch-in-config command-line flag is required +[UPDATING] `[ROOT][..]` index +[DOWNLOADING] crates ... +[DOWNLOADED] bar v0.1.0 ([..]) +[COMPILING] bar v0.1.0 +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} +#[cargo_test] +fn from_config() { + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1.0" + "#, + ) + .file( + ".cargo/config.toml", + r#" + [patch.crates-io] + bar = { path = 'bar' } + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.1")) + .file("bar/src/lib.rs", r#""#) + .build(); + + p.cargo("build -Zpatch-in-config") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[UPDATING] `[ROOT][..]` index +[COMPILING] bar v0.1.1 ([..]) +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); +} + #[cargo_test] fn nonexistent() { Package::new("baz", "0.1.0").publish(); @@ -268,6 +353,78 @@ fn unused() { ); } +#[cargo_test] +fn unused_from_config() { + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1.0" + "#, + ) + .file( + ".cargo/config.toml", + r#" + [patch.crates-io] + bar = { path = "bar" } + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.2.0")) + .file("bar/src/lib.rs", "not rust code") + .build(); + + p.cargo("build -Zpatch-in-config") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[UPDATING] `[ROOT][..]` index +[WARNING] Patch `bar v0.2.0 ([CWD]/bar)` was not used in the crate graph. +[..] +[..] +[..] +[..] +[DOWNLOADING] crates ... +[DOWNLOADED] bar v0.1.0 [..] +[COMPILING] bar v0.1.0 +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.cargo("build -Zpatch-in-config") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[WARNING] Patch `bar v0.2.0 ([CWD]/bar)` was not used in the crate graph. +[..] +[..] +[..] +[..] +[FINISHED] [..] +", + ) + .run(); + + // unused patch should be in the lock file + let lock = p.read_lockfile(); + let toml: toml::Value = toml::from_str(&lock).unwrap(); + assert_eq!(toml["patch"]["unused"].as_array().unwrap().len(), 1); + assert_eq!(toml["patch"]["unused"][0]["name"].as_str(), Some("bar")); + assert_eq!( + toml["patch"]["unused"][0]["version"].as_str(), + Some("0.2.0") + ); +} + #[cargo_test] fn unused_git() { Package::new("bar", "0.1.0").publish(); @@ -395,6 +552,66 @@ fn add_patch() { p.cargo("build").with_stderr("[FINISHED] [..]").run(); } +#[cargo_test] +fn add_patch_from_config() { + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + bar = "0.1.0" + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", r#""#) + .build(); + + p.cargo("build") + .with_stderr( + "\ +[UPDATING] `[ROOT][..]` index +[DOWNLOADING] crates ... +[DOWNLOADED] bar v0.1.0 [..] +[COMPILING] bar v0.1.0 +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.cargo("build").with_stderr("[FINISHED] [..]").run(); + + p.change_file( + ".cargo/config.toml", + r#" + [patch.crates-io] + bar = { path = 'bar' } + "#, + ); + + p.cargo("build -Zpatch-in-config") + .masquerade_as_nightly_cargo() + .with_stderr( + "\ +[COMPILING] bar v0.1.0 ([CWD]/bar) +[COMPILING] foo v0.0.1 ([CWD]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +", + ) + .run(); + p.cargo("build -Zpatch-in-config") + .masquerade_as_nightly_cargo() + .with_stderr("[FINISHED] [..]") + .run(); +} + #[cargo_test] fn add_ignored_patch() { Package::new("bar", "0.1.0").publish();