Skip to content

Commit 0736fa6

Browse files
committed
Implement base paths (RFC 3529) 1/n: path dep and patch support
1 parent 403bc5b commit 0736fa6

File tree

7 files changed

+515
-22
lines changed

7 files changed

+515
-22
lines changed

crates/cargo-util-schemas/src/manifest/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,7 @@ pub struct TomlDetailedDependency<P: Clone = String> {
776776
// `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to
777777
// that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file.
778778
pub path: Option<P>,
779+
pub base: Option<String>,
779780
pub git: Option<String>,
780781
pub branch: Option<String>,
781782
pub tag: Option<String>,
@@ -815,6 +816,7 @@ impl<P: Clone> Default for TomlDetailedDependency<P> {
815816
registry: Default::default(),
816817
registry_index: Default::default(),
817818
path: Default::default(),
819+
base: Default::default(),
818820
git: Default::default(),
819821
branch: Default::default(),
820822
tag: Default::default(),

src/cargo/core/features.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,7 @@ unstable_cli_options!(
776776
no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
777777
package_workspace: bool = ("Handle intra-workspace dependencies when packaging"),
778778
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
779+
path_bases: bool = ("Allow paths that resolve relatively to a base specified in the config"),
779780
profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
780781
public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
781782
publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
@@ -1280,6 +1281,7 @@ impl CliUnstable {
12801281
"package-workspace" => self.package_workspace= parse_empty(k, v)?,
12811282
"panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
12821283
"public-dependency" => self.public_dependency = parse_empty(k, v)?,
1284+
"path-bases" => self.path_bases = parse_empty(k, v)?,
12831285
"profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
12841286
"trim-paths" => self.trim_paths = parse_empty(k, v)?,
12851287
"publish-timeout" => self.publish_timeout = parse_empty(k, v)?,

src/cargo/util/toml/mod.rs

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -901,13 +901,17 @@ impl InheritableFields {
901901
};
902902
let mut dep = dep.clone();
903903
if let manifest::TomlDependency::Detailed(detailed) = &mut dep {
904-
if let Some(rel_path) = &detailed.path {
905-
detailed.path = Some(resolve_relative_path(
906-
name,
907-
self.ws_root(),
908-
package_root,
909-
rel_path,
910-
)?);
904+
if detailed.base.is_none() {
905+
// If this is a path dependency without a base, then update the path to be relative
906+
// to the workspace root instead.
907+
if let Some(rel_path) = &detailed.path {
908+
detailed.path = Some(resolve_relative_path(
909+
name,
910+
self.ws_root(),
911+
package_root,
912+
rel_path,
913+
)?);
914+
}
911915
}
912916
}
913917
Ok(dep)
@@ -2135,7 +2139,16 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
21352139
// always end up hashing to the same value no matter where it's
21362140
// built from.
21372141
if manifest_ctx.source_id.is_path() {
2138-
let path = manifest_ctx.root.join(path);
2142+
let path = if let Some(base) = orig.base.as_ref() {
2143+
lookup_path_base(&base, manifest_ctx.gctx, manifest_ctx.root)
2144+
.with_context(|| {
2145+
format!("resolving path base for dependency ({name_in_toml})")
2146+
})?
2147+
.join(path)
2148+
} else {
2149+
// This is a standard path with no prefix.
2150+
manifest_ctx.root.join(path)
2151+
};
21392152
let path = paths::normalize_path(&path);
21402153
SourceId::for_path(&path)
21412154
} else {
@@ -2151,6 +2164,40 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
21512164
}
21522165
}
21532166

2167+
pub(crate) fn lookup_path_base(
2168+
base: &str,
2169+
gctx: &GlobalContext,
2170+
manifest_root: &Path,
2171+
) -> CargoResult<PathBuf> {
2172+
if !gctx.cli_unstable().path_bases {
2173+
bail!("usage of path bases requires `-Z path-bases`");
2174+
}
2175+
2176+
// Look up the relevant base in the Config and use that as the root.
2177+
if let Some(path_bases) =
2178+
gctx.get::<Option<ConfigRelativePath>>(&format!("path-bases.{base}"))?
2179+
{
2180+
Ok(path_bases.resolve_path(gctx))
2181+
} else {
2182+
// Otherwise, check the built-in bases.
2183+
match base {
2184+
"workspace" => {
2185+
if let Some(workspace_root) = find_workspace_root(manifest_root, gctx)? {
2186+
Ok(workspace_root.parent().unwrap().to_path_buf())
2187+
} else {
2188+
bail!(
2189+
"the `workspace` built-in path base cannot be used outside of a workspace."
2190+
)
2191+
}
2192+
}
2193+
_ => bail!(
2194+
"path base `{base}` is undefined. \
2195+
You must add an entry for `{base}` in the Cargo configuration [path-bases] table."
2196+
),
2197+
}
2198+
}
2199+
}
2200+
21542201
pub trait ResolveToPath {
21552202
fn resolve(&self, gctx: &GlobalContext) -> PathBuf;
21562203
}
@@ -2865,6 +2912,7 @@ fn prepare_toml_for_publish(
28652912
let mut d = d.clone();
28662913
// Path dependencies become crates.io deps.
28672914
d.path.take();
2915+
d.base.take();
28682916
// Same with git dependencies.
28692917
d.git.take();
28702918
d.branch.take();

src/doc/src/reference/unstable.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Each new feature described below should explain how to use it.
101101
* [Edition 2024](#edition-2024) — Adds support for the 2024 Edition.
102102
* [Profile `trim-paths` option](#profile-trim-paths-option) --- Control the sanitization of file paths in build outputs.
103103
* [`[lints.cargo]`](#lintscargo) --- Allows configuring lints for Cargo.
104+
* [path bases](#path-bases) --- Named base directories for path dependencies.
104105
* Information and metadata
105106
* [Build-plan](#build-plan) --- Emits JSON information on which commands will be run.
106107
* [unit-graph](#unit-graph) --- Emits JSON for Cargo's internal graph structure.
@@ -1570,6 +1571,54 @@ implicit-features = "warn"
15701571
workspace = true
15711572
```
15721573

1574+
## Path Bases
1575+
1576+
* Tracking Issue: [#14355](https://github.com/rust-lang/cargo/issues/14355)
1577+
1578+
A `path` dependency may optionally specify a base by setting the `base` key to
1579+
the name of a path base from the `[path-bases]` table in either the
1580+
[configuration](config.md) or one of the [built-in path bases](#built-in-path-bases).
1581+
The value of that path base is prepended to the `path` value (along with a path
1582+
separator if necessary) to produce the actual location where Cargo will look for
1583+
the dependency.
1584+
1585+
For example, if the `Cargo.toml` contains:
1586+
1587+
```toml
1588+
[dependencies]
1589+
foo = { path = "foo", base = "dev" }
1590+
```
1591+
1592+
Given a `[path-bases]` table in the configuration that contains:
1593+
1594+
```toml
1595+
[path-bases]
1596+
dev = "/home/user/dev/rust/libraries/"
1597+
```
1598+
1599+
This will produce a `path` dependency `foo` located at
1600+
`/home/user/dev/rust/libraries/foo`.
1601+
1602+
Path bases can be either absolute or relative. Relative path bases are relative
1603+
to the parent directory of the configuration file that declared that path base.
1604+
1605+
If the name of path base used in a dependency is neither in the configuration
1606+
nor one of the built-in path base, then Cargo will raise an error.
1607+
1608+
#### Built-in path bases
1609+
1610+
Cargo provides implicit path bases that can be used without the need to specify
1611+
them in a `[path-bases]` table.
1612+
1613+
* `workspace` - If a project is [a workspace or workspace member](workspaces.md)
1614+
then this path base is defined as the parent directory of the root `Cargo.toml`
1615+
of the workspace.
1616+
1617+
If a built-in path base name is also declared in the configuration, then Cargo
1618+
will prefer the value in the configuration. The allows Cargo to add new built-in
1619+
path bases without compatibility issues (as existing uses will shadow the
1620+
built-in name).
1621+
15731622
# Stabilized and removed features
15741623

15751624
## Compile progress

tests/testsuite/cargo/z_help/stdout.term.svg

Lines changed: 16 additions & 14 deletions
Loading

tests/testsuite/patch.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3028,3 +3028,45 @@ foo v0.0.0 ([ROOT]/foo)
30283028

30293029
assert_eq!(p.read_file("Cargo.lock"), p.read_file("Cargo.lock.orig"));
30303030
}
3031+
3032+
#[cargo_test]
3033+
fn patch_with_base() {
3034+
let bar = project()
3035+
.at("bar")
3036+
.file("Cargo.toml", &basic_manifest("bar", "0.5.0"))
3037+
.file("src/lib.rs", "pub fn hello() {}")
3038+
.build();
3039+
Package::new("bar", "0.5.0").publish();
3040+
3041+
let p = project()
3042+
.file(
3043+
".cargo/config.toml",
3044+
&format!(
3045+
"[path-bases]\ntest = '{}'",
3046+
bar.root().parent().unwrap().display()
3047+
),
3048+
)
3049+
.file(
3050+
"Cargo.toml",
3051+
r#"
3052+
[package]
3053+
name = "foo"
3054+
version = "0.5.0"
3055+
authors = ["[email protected]"]
3056+
edition = "2018"
3057+
3058+
[dependencies]
3059+
bar = "0.5.0"
3060+
3061+
[patch.crates-io.bar]
3062+
path = 'bar'
3063+
base = 'test'
3064+
"#,
3065+
)
3066+
.file("src/lib.rs", "use bar::hello as _;")
3067+
.build();
3068+
3069+
p.cargo("build -v -Zpath-bases")
3070+
.masquerade_as_nightly_cargo(&["path-bases"])
3071+
.run();
3072+
}

0 commit comments

Comments
 (0)