From e1ac61541bdd6eec8aa561584b81676f146347c8 Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Fri, 5 Feb 2021 17:10:40 -0800 Subject: [PATCH] Add config-based path shorthands When doing local development, users may wish to specify many `path` dependencies that all live in the same local directory. If that local directory is not a short distance from the `Cargo.toml`, this can get unwieldy. This patch introduces the notion of path "prefixes", which are defined in `.cargo/config.toml`: ```toml [path] devdir = "/home/jon/dev/rust/" ``` Which can then be used as the root for path dependencies. For example: ```toml foo = { path = "devdir::foo" } bar = { path = "devdir::bar" } baz = { path = "devdir::ws/baz" } ``` would fetch `foo` from `/home/jon/dev/rust/foo`, `bar` from `/home/jon/dev/rust/bar`, and `baz` from `/home/jon/dev/rust/ws/baz`. This feature also serves as a convenient way for external build systems to mask build-dependent paths from the user. Consider a build system that vendors first-party dependencies into ``` /home/user/workplace/feature-1/build/first-party-package/first-party-package-1.0/x86_64/dev/build/private/rust-vendored/ ``` Instead of requiring users to hard-code that path, or the relative equivalent, into their `Cargo.toml`, the build-system can instead produce a project-local `.cargo/config.toml` that defines that path as `path.vendored`, and the user can then use vendored dependencies using ```toml foo = { path = "vendored::foo" } ``` --- src/bin/cargo/cli.rs | 1 + src/cargo/core/features.rs | 2 + src/cargo/util/toml/mod.rs | 35 ++++++++++- tests/testsuite/path.rs | 121 +++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 2 deletions(-) diff --git a/src/bin/cargo/cli.rs b/src/bin/cargo/cli.rs index 243f6ac0773..aa83f0c1e23 100644 --- a/src/bin/cargo/cli.rs +++ b/src/bin/cargo/cli.rs @@ -45,6 +45,7 @@ Available unstable (nightly-only) flags: -Z terminal-width -- Provide a terminal width to rustc for error truncation -Z namespaced-features -- Allow features with `dep:` prefix -Z weak-dep-features -- Allow `dep_name?/feature` feature syntax + -Z path-prefixes -- Allow paths that use prefixes from config Run with 'cargo -Z [FLAG] [SUBCOMMAND]'" ); diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 494e3399300..dff7710e34d 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -444,6 +444,7 @@ pub struct CliUnstable { pub weak_dep_features: bool, pub extra_link_arg: bool, pub credential_process: bool, + pub path_prefixes: bool, } const STABILIZED_COMPILE_PROGRESS: &str = "The progress bar is now always \ @@ -627,6 +628,7 @@ impl CliUnstable { "weak-dep-features" => self.weak_dep_features = parse_empty(k, v)?, "extra-link-arg" => self.extra_link_arg = parse_empty(k, v)?, "credential-process" => self.credential_process = parse_empty(k, v)?, + "path-prefixes" => self.path_prefixes = parse_empty(k, v)?, "compile-progress" => stabilized_warn(k, "1.30", STABILIZED_COMPILE_PROGRESS), "offline" => stabilized_err(k, "1.36", STABILIZED_OFFLINE)?, "cache-messages" => stabilized_warn(k, "1.40", STABILIZED_CACHE_MESSAGES), diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index d208340e320..c12fc731971 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -24,7 +24,9 @@ use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, Worksp use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY}; use crate::util::errors::{CargoResult, CargoResultExt, ManifestError}; use crate::util::interning::InternedString; -use crate::util::{self, paths, validate_package_name, Config, IntoUrl}; +use crate::util::{ + self, config::ConfigRelativePath, paths, validate_package_name, Config, IntoUrl, +}; mod targets; use self::targets::targets; @@ -1765,7 +1767,36 @@ impl DetailedTomlDependency { // always end up hashing to the same value no matter where it's // built from. if cx.source_id.is_path() { - let path = cx.root.join(path); + let mut with_prefix = path.splitn(2, "::"); + let prefix = with_prefix.next().expect("split always yields once"); + let path = if let Some(path) = with_prefix.next() { + if !cx.config.cli_unstable().path_prefixes { + bail!("Usage of path prefixes requires `-Z path-prefixes`"); + } + + // We have prefix::path. + // Look up the relevant prefix in the Config and use that as the root. + if let Some(prefix_path) = cx + .config + .get::>(&format!("path.{}", prefix))? + { + let prefix_path = prefix_path.resolve_path(cx.config); + prefix_path.join(path) + } else { + bail!( + "path prefix `{}` is undefined in path `{}::{}`. \ + You must specify a path for `path.{}` in your cargo configuration.", + prefix, + prefix, + path, + prefix + ); + } + } else { + // This is a standard path with no prefix. + let path = prefix; + cx.root.join(path) + }; let path = util::normalize_path(&path); SourceId::for_path(&path)? } else { diff --git a/tests/testsuite/path.rs b/tests/testsuite/path.rs index 34cdffb3e13..132f0afe21f 100644 --- a/tests/testsuite/path.rs +++ b/tests/testsuite/path.rs @@ -529,6 +529,127 @@ Caused by: .run(); } +#[cargo_test] +fn prefix_not_stable() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + fs::create_dir(&paths::root().join(".cargo")).unwrap(); + fs::write( + &paths::root().join(".cargo/config"), + &format!( + "[path]\ntest = '{}'", + bar.root().parent().unwrap().display() + ), + ) + .unwrap(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies.bar] + path = 'test::bar' + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build") + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]/foo/Cargo.toml` + +Caused by: + Usage of path prefixes requires `-Z path-prefixes` +", + ) + .run(); +} + +#[cargo_test] +fn prefixed_path() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.5.0")) + .file("src/lib.rs", "") + .build(); + + fs::create_dir(&paths::root().join(".cargo")).unwrap(); + fs::write( + &paths::root().join(".cargo/config"), + &format!( + "[path]\ntest = '{}'", + bar.root().parent().unwrap().display() + ), + ) + .unwrap(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies.bar] + path = 'test::bar' + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v -Zpath-prefixes") + .masquerade_as_nightly_cargo() + .run(); +} + +#[cargo_test] +fn unknown_prefix() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + + name = "foo" + version = "0.5.0" + authors = ["wycats@example.com"] + + [dependencies.bar] + path = 'test::bar' + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -Zpath-prefixes") + .masquerade_as_nightly_cargo() + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]/foo/Cargo.toml` + +Caused by: + path prefix `test` is undefined in path `test::bar`. You must specify a path for `path.test` in your cargo configuration. +", + ) + .run(); +} + #[cargo_test] fn override_relative() { let bar = project()