Skip to content

Commit

Permalink
Implement support for base paths
Browse files Browse the repository at this point in the history
  • Loading branch information
dpaoliello committed Aug 5, 2024
1 parent fa64658 commit b75dd6b
Show file tree
Hide file tree
Showing 7 changed files with 596 additions and 28 deletions.
2 changes: 2 additions & 0 deletions crates/cargo-util-schemas/src/manifest/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ pub struct TomlDetailedDependency<P: Clone = String> {
// `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to
// that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file.
pub path: Option<P>,
pub base: Option<String>,
pub git: Option<String>,
pub branch: Option<String>,
pub tag: Option<String>,
Expand Down Expand Up @@ -815,6 +816,7 @@ impl<P: Clone> Default for TomlDetailedDependency<P> {
registry: Default::default(),
registry_index: Default::default(),
path: Default::default(),
base: Default::default(),
git: Default::default(),
branch: Default::default(),
tag: Default::default(),
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 @@ -776,6 +776,7 @@ unstable_cli_options!(
no_index_update: bool = ("Do not update the registry index even if the cache is outdated"),
package_workspace: bool = ("Handle intra-workspace dependencies when packaging"),
panic_abort_tests: bool = ("Enable support to run tests with -Cpanic=abort"),
path_bases: bool = ("Allow paths that resolve relatively to a base specified in the config"),
profile_rustflags: bool = ("Enable the `rustflags` option in profiles in .cargo/config.toml file"),
public_dependency: bool = ("Respect a dependency's `public` field in Cargo.toml to control public/private dependencies"),
publish_timeout: bool = ("Enable the `publish.timeout` key in .cargo/config.toml file"),
Expand Down Expand Up @@ -1280,6 +1281,7 @@ impl CliUnstable {
"package-workspace" => self.package_workspace= parse_empty(k, v)?,
"panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
"public-dependency" => self.public_dependency = parse_empty(k, v)?,
"path-bases" => self.path_bases = parse_empty(k, v)?,
"profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?,
"trim-paths" => self.trim_paths = parse_empty(k, v)?,
"publish-timeout" => self.publish_timeout = parse_empty(k, v)?,
Expand Down
46 changes: 40 additions & 6 deletions src/cargo/sources/path.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::fmt::{self, Debug, Formatter};
use std::fs;
Expand All @@ -14,7 +15,7 @@ use crate::sources::IndexSummary;
use crate::util::errors::CargoResult;
use crate::util::important_paths::find_project_manifest_exact;
use crate::util::internal;
use crate::util::toml::read_manifest;
use crate::util::toml::{lookup_path_base, read_manifest};
use crate::util::GlobalContext;
use anyhow::Context as _;
use cargo_util::paths;
Expand Down Expand Up @@ -878,7 +879,7 @@ fn read_packages(
}
}

fn nested_paths(manifest: &Manifest) -> Vec<PathBuf> {
fn nested_paths(manifest: &Manifest) -> Vec<(String, PathBuf, Option<String>)> {
let mut nested_paths = Vec::new();
let normalized = manifest.normalized_toml();
let dependencies = normalized
Expand All @@ -900,7 +901,7 @@ fn nested_paths(manifest: &Manifest) -> Vec<PathBuf> {
}),
);
for dep_table in dependencies {
for dep in dep_table.values() {
for (name, dep) in dep_table.iter() {
let cargo_util_schemas::manifest::InheritableDependency::Value(dep) = dep else {
continue;
};
Expand All @@ -910,7 +911,11 @@ fn nested_paths(manifest: &Manifest) -> Vec<PathBuf> {
let Some(path) = dep.path.as_ref() else {
continue;
};
nested_paths.push(PathBuf::from(path.as_str()));
nested_paths.push((
name.to_string(),
PathBuf::from(path.as_str()),
dep.base.clone(),
));
}
}
nested_paths
Expand Down Expand Up @@ -1000,8 +1005,36 @@ fn read_nested_packages(
//
// TODO: filesystem/symlink implications?
if !source_id.is_registry() {
for p in nested.iter() {
let path = paths::normalize_path(&path.join(p));
let mut manifest_gctx = None;

for (name, p, base) in nested.iter() {
let p = if let Some(base) = base {
// If the dependency has a path base, then load the global context for the current
// manifest and use it to resolve the path base.
let manifest_gctx = match manifest_gctx {
Some(ref gctx) => gctx,
None => {
let mut new_manifest_gctx = match GlobalContext::default() {
Ok(gctx) => gctx,
Err(err) => return Err(err),
};
if let Err(err) = new_manifest_gctx.reload_rooted_at(&manifest_path) {
return Err(err);
}
manifest_gctx.insert(new_manifest_gctx)
}
};
match lookup_path_base(base, name, manifest_gctx, manifest_path.parent().unwrap()) {
Ok(base) => Cow::Owned(base.join(p)),
Err(err) => {
errors.push(err);
continue;
}
}
} else {
Cow::Borrowed(p)
};
let path = paths::normalize_path(&path.join(p.as_path()));
let result =
read_nested_packages(&path, all_packages, source_id, gctx, visited, errors);
// Ignore broken manifests found on git repositories.
Expand All @@ -1019,6 +1052,7 @@ fn read_nested_packages(
);
errors.push(err);
} else {
trace!("Failed to manifest: {:?}", err);
return Err(err);
}
}
Expand Down
62 changes: 54 additions & 8 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -901,13 +901,17 @@ impl InheritableFields {
};
let mut dep = dep.clone();
if let manifest::TomlDependency::Detailed(detailed) = &mut dep {
if let Some(rel_path) = &detailed.path {
detailed.path = Some(resolve_relative_path(
name,
self.ws_root(),
package_root,
rel_path,
)?);
if detailed.base.is_none() {
// If this is a path dependency without a base, then update the path to be relative
// to the workspace root instead.
if let Some(rel_path) = &detailed.path {
detailed.path = Some(resolve_relative_path(
name,
self.ws_root(),
package_root,
rel_path,
)?);
}
}
}
Ok(dep)
Expand Down Expand Up @@ -2135,7 +2139,17 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
// always end up hashing to the same value no matter where it's
// built from.
if manifest_ctx.source_id.is_path() {
let path = manifest_ctx.root.join(path);
let path = if let Some(base) = orig.base.as_ref() {
if !manifest_ctx.gctx.cli_unstable().path_bases {
bail!("usage of path bases requires `-Z path-bases`");
}

lookup_path_base(&base, name_in_toml, manifest_ctx.gctx, manifest_ctx.root)?
.join(path)
} else {
// This is a standard path with no prefix.
manifest_ctx.root.join(path)
};
let path = paths::normalize_path(&path);
SourceId::for_path(&path)
} else {
Expand All @@ -2151,6 +2165,37 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
}
}

pub(crate) fn lookup_path_base(
base: &str,
name_in_toml: &str,
gctx: &GlobalContext,
manifest_root: &Path,
) -> CargoResult<PathBuf> {
// Look up the relevant base in the Config and use that as the root.
if let Some(path_bases) =
gctx.get::<Option<ConfigRelativePath>>(&format!("path-bases.{base}"))?
{
Ok(path_bases.resolve_path(gctx))
} else {
// Otherwise, check the built-in bases.
match base {
"workspace" => {
if let Some(workspace_root) = find_workspace_root(manifest_root, gctx)? {
Ok(workspace_root.parent().unwrap().to_path_buf())
} else {
bail!(
"dependency ({name_in_toml}) is using the `workspace` built-in path base outside of a workspace."
)
}
}
_ => bail!(
"dependency ({name_in_toml}) uses an undefined path base `{base}`. \
You must add an entry for `{base}` in the Cargo configuration [path-bases] table."
),
}
}
}

pub trait ResolveToPath {
fn resolve(&self, gctx: &GlobalContext) -> PathBuf;
}
Expand Down Expand Up @@ -2865,6 +2910,7 @@ fn prepare_toml_for_publish(
let mut d = d.clone();
// Path dependencies become crates.io deps.
d.path.take();
d.base.take();
// Same with git dependencies.
d.git.take();
d.branch.take();
Expand Down
30 changes: 16 additions & 14 deletions tests/testsuite/cargo/z_help/stdout.term.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
101 changes: 101 additions & 0 deletions tests/testsuite/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,107 @@ hello world
.run();
}

#[cargo_test]
fn dependency_in_submodule_via_path_base() {
// Using a submodule prevents the dependency from being discovered during the directory walk,
// so Cargo will need to follow the path dependency to discover it.

let git_project = git::new("dep1", |project| {
project
.file(".cargo/config.toml", "[path-bases]\nsubmodules = 'submods'")
.file(
"Cargo.toml",
r#"
[package]
name = "dep1"
version = "0.5.0"
edition = "2015"
authors = ["[email protected]"]
[dependencies.dep2]
version = "0.5.0"
path = "dep2"
base = "submodules"
[lib]
name = "dep1"
"#,
)
.file(
"src/dep1.rs",
r#"
extern crate dep2;
pub fn hello() -> &'static str {
dep2::hello()
}
"#,
)
});

let git_project2 = git::new("dep2", |project| {
project
.file("Cargo.toml", &basic_lib_manifest("dep2"))
.file(
"src/dep2.rs",
r#"
pub fn hello() -> &'static str {
"hello world"
}
"#,
)
});

let repo = git2::Repository::open(&git_project.root()).unwrap();
let url = git_project2.root().to_url().to_string();
git::add_submodule(&repo, &url, Path::new("submods/dep2"));
git::commit(&repo);

let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.5.0"
edition = "2015"
authors = ["[email protected]"]
[dependencies.dep1]
version = "0.5.0"
git = '{}'
[[bin]]
name = "foo"
"#,
git_project.url()
),
)
.file(
"src/foo.rs",
&main_file(r#""{}", dep1::hello()"#, &["dep1"]),
)
.build();

p.cargo("build").run();

assert!(p.bin("foo").is_file());

p.process(&p.bin("foo"))
.with_stdout_data(str![[r#"
hello world
"#]])
.run();
}

#[cargo_test]
fn cargo_compile_with_malformed_nested_paths() {
let git_project = git::new("dep1", |project| {
Expand Down
Loading

0 comments on commit b75dd6b

Please sign in to comment.