Skip to content

Commit

Permalink
Add config-based path shorthands
Browse files Browse the repository at this point in the history
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" }
```
  • Loading branch information
Jon Gjengset committed Feb 6, 2021
1 parent 56e0df6 commit e1ac615
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/bin/cargo/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]'"
);
Expand Down
2 changes: 2 additions & 0 deletions src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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),
Expand Down
35 changes: 33 additions & 2 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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::<Option<ConfigRelativePath>>(&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 {
Expand Down
121 changes: 121 additions & 0 deletions tests/testsuite/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit e1ac615

Please sign in to comment.