Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support [patch] in .cargo/config files #9204

Merged
merged 7 commits into from
Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/cargo/core/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ pub struct CliUnstable {
pub namespaced_features: bool,
pub weak_dep_features: bool,
pub extra_link_arg: bool,
pub patch_in_config: bool,
pub credential_process: bool,
pub configurable_env: bool,
pub enable_future_incompat_feature: bool,
Expand Down Expand Up @@ -719,6 +720,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
Expand Down
8 changes: 4 additions & 4 deletions src/cargo/core/resolver/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ impl EncodableResolve {
/// primary uses is to be used with `resolve_with_previous` to guide the
/// resolver to create a complete Resolve.
pub fn into_resolve(self, original: &str, ws: &Workspace<'_>) -> CargoResult<Resolve> {
let path_deps = build_path_deps(ws);
let path_deps = build_path_deps(ws)?;
let mut checksums = HashMap::new();

let mut version = match self.version {
Expand Down Expand Up @@ -402,7 +402,7 @@ impl EncodableResolve {
}
}

fn build_path_deps(ws: &Workspace<'_>) -> HashMap<String, SourceId> {
fn build_path_deps(ws: &Workspace<'_>) -> CargoResult<HashMap<String, SourceId>> {
// If a crate is **not** a path source, then we're probably in a situation
// such as `cargo install` with a lock file from a remote dependency. In
// that case we don't need to fixup any path dependencies (as they're not
Expand All @@ -424,7 +424,7 @@ fn build_path_deps(ws: &Workspace<'_>) -> HashMap<String, SourceId> {
for member in members.iter() {
build_pkg(member, ws, &mut ret, &mut visited);
}
for deps in ws.root_patch().values() {
for deps in ws.root_patch()?.values() {
for dep in deps {
build_dep(dep, ws, &mut ret, &mut visited);
}
Expand All @@ -433,7 +433,7 @@ fn build_path_deps(ws: &Workspace<'_>) -> HashMap<String, SourceId> {
build_dep(dep, ws, &mut ret, &mut visited);
}

return ret;
return Ok(ret);

fn build_pkg(
pkg: &Package,
Expand Down
101 changes: 96 additions & 5 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ use crate::core::resolver::ResolveBehavior;
use crate::core::{Dependency, Edition, PackageId, PackageIdSpec};
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
use crate::ops;
use crate::sources::PathSource;
use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
use crate::util::errors::{CargoResult, CargoResultExt, ManifestError};
use crate::util::interning::InternedString;
use crate::util::paths;
use crate::util::toml::{read_manifest, TomlProfiles};
use crate::util::{Config, Filesystem};
use crate::util::toml::{read_manifest, TomlDependency, TomlProfiles};
use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl};

/// The core abstraction in Cargo for working with a workspace of crates.
///
Expand Down Expand Up @@ -362,14 +362,105 @@ impl<'cfg> Workspace<'cfg> {
}
}

fn config_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
let config_patch: Option<
BTreeMap<String, BTreeMap<String, TomlDependency<ConfigRelativePath>>>,
> = self.config.get("patch")?;

if config_patch.is_some() && !self.config.cli_unstable().patch_in_config {
self.config.shell().warn("`[patch]` in cargo config was ignored, the -Zpatch-in-config command-line flag is required".to_owned())?;
return Ok(HashMap::new());
}

let source = SourceId::for_path(self.root())?;

let mut warnings = Vec::new();
let mut nested_paths = Vec::new();

let mut patch = HashMap::new();
for (url, deps) in config_patch.into_iter().flatten() {
let url = match &url[..] {
CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
url => self
.config
.get_registry_index(url)
.or_else(|_| url.into_url())
.chain_err(|| {
format!("[patch] entry `{}` should be a URL or registry name", url)
})?,
};
patch.insert(
url,
deps.iter()
.map(|(name, dep)| {
dep.to_dependency_split(
name,
/* pkg_id */ None,
source,
&mut nested_paths,
self.config,
&mut warnings,
/* platform */ None,
// NOTE: Since we use ConfigRelativePath, this root isn't used as
// any relative paths are resolved before they'd be joined with root.
&Path::new("unused-relative-path"),
self.unstable_features(),
/* kind */ None,
)
})
.collect::<CargoResult<Vec<_>>>()?,
);
}

for message in warnings {
self.config
.shell()
.warn(format!("[patch] in cargo config: {}", message))?
}

Ok(patch)
}

/// Returns the root `[patch]` section of this workspace.
///
/// This may be from a virtual crate or an actual crate.
pub fn root_patch(&self) -> &HashMap<Url, Vec<Dependency>> {
match self.root_maybe() {
pub fn root_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
let from_manifest = match self.root_maybe() {
MaybePackage::Package(p) => p.manifest().patch(),
MaybePackage::Virtual(vm) => vm.patch(),
};

let from_config = self.config_patch()?;
if from_config.is_empty() {
return Ok(from_manifest.clone());
}
if from_manifest.is_empty() {
return Ok(from_config.clone());
}

// We could just chain from_manifest and from_config,
// but that's not quite right as it won't deal with overlaps.
let mut combined = from_manifest.clone();
for (url, cdeps) in from_config {
if let Some(deps) = combined.get_mut(&url) {
// We want from_manifest to take precedence for each patched name.
// NOTE: This is inefficient if the number of patches is large!
let mut left = cdeps.clone();
for dep in &mut *deps {
if let Some(i) = left.iter().position(|cdep| {
// XXX: should this also take into account version numbers?
dep.name_in_toml() == cdep.name_in_toml()
}) {
left.swap_remove(i);
}
}
// Whatever is left does not exist in manifest dependencies.
deps.extend(left);
} else {
combined.insert(url.clone(), cdeps.clone());
}
}
Ok(combined)
}

/// Returns an iterator over all packages in this workspace
Expand Down
2 changes: 1 addition & 1 deletion src/cargo/ops/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ pub fn resolve_with_previous<'cfg>(
// locked.
let mut avoid_patch_ids = HashSet::new();
if register_patches {
for (url, patches) in ws.root_patch() {
for (url, patches) in ws.root_patch()?.iter() {
let previous = match previous {
Some(r) => r,
None => {
Expand Down
104 changes: 89 additions & 15 deletions src/cargo/util/toml/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::fmt;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::str;
Expand All @@ -22,7 +23,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 @@ -199,25 +202,25 @@ type TomlBenchTarget = TomlTarget;

#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum TomlDependency {
pub enum TomlDependency<P = String> {
/// In the simple format, only a version is specified, eg.
/// `package = "<version>"`
Simple(String),
/// The simple format is equivalent to a detailed dependency
/// specifying only a version, eg.
/// `package = { version = "<version>" }`
Detailed(DetailedTomlDependency),
Detailed(DetailedTomlDependency<P>),
}

impl<'de> de::Deserialize<'de> for TomlDependency {
impl<'de, P: Deserialize<'de>> de::Deserialize<'de> for TomlDependency<P> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
struct TomlDependencyVisitor;
struct TomlDependencyVisitor<P>(PhantomData<P>);

impl<'de> de::Visitor<'de> for TomlDependencyVisitor {
type Value = TomlDependency;
impl<'de, P: Deserialize<'de>> de::Visitor<'de> for TomlDependencyVisitor<P> {
type Value = TomlDependency<P>;

fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(
Expand All @@ -242,13 +245,29 @@ impl<'de> de::Deserialize<'de> for TomlDependency {
}
}

deserializer.deserialize_any(TomlDependencyVisitor)
deserializer.deserialize_any(TomlDependencyVisitor(PhantomData))
}
}

#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub trait ResolveToPath {
fn resolve(&self, config: &Config) -> PathBuf;
}

impl ResolveToPath for String {
fn resolve(&self, _: &Config) -> PathBuf {
self.into()
}
}

impl ResolveToPath for ConfigRelativePath {
fn resolve(&self, c: &Config) -> PathBuf {
self.resolve_path(c)
}
}

#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct DetailedTomlDependency {
pub struct DetailedTomlDependency<P = String> {
version: Option<String>,
registry: Option<String>,
/// The URL of the `registry` field.
Expand All @@ -258,7 +277,9 @@ pub struct DetailedTomlDependency {
/// registry names configured, so Cargo can't rely on just the name for
/// crates published by other users.
registry_index: Option<String>,
path: Option<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.
path: Option<P>,
git: Option<String>,
branch: Option<String>,
tag: Option<String>,
Expand All @@ -272,6 +293,28 @@ pub struct DetailedTomlDependency {
public: Option<bool>,
}

// Explicit implementation so we avoid pulling in P: Default
impl<P> Default for DetailedTomlDependency<P> {
fn default() -> Self {
Self {
version: Default::default(),
registry: Default::default(),
registry_index: Default::default(),
path: Default::default(),
git: Default::default(),
branch: Default::default(),
tag: Default::default(),
rev: Default::default(),
features: Default::default(),
optional: Default::default(),
default_features: Default::default(),
default_features2: Default::default(),
package: Default::default(),
public: Default::default(),
}
}
}

/// This type is used to deserialize `Cargo.toml` files.
#[derive(Debug, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
Expand Down Expand Up @@ -1627,15 +1670,45 @@ fn unique_build_targets(targets: &[Target], package_root: &Path) -> Result<(), S
Ok(())
}

impl TomlDependency {
impl<P: ResolveToPath> TomlDependency<P> {
pub(crate) fn to_dependency_split(
&self,
name: &str,
pkgid: Option<PackageId>,
source_id: SourceId,
nested_paths: &mut Vec<PathBuf>,
config: &Config,
warnings: &mut Vec<String>,
platform: Option<Platform>,
root: &Path,
features: &Features,
kind: Option<DepKind>,
) -> CargoResult<Dependency> {
self.to_dependency(
name,
&mut Context {
pkgid,
deps: &mut Vec::new(),
source_id,
nested_paths,
config,
warnings,
platform,
root,
features,
},
kind,
)
}

fn to_dependency(
&self,
name: &str,
cx: &mut Context<'_, '_>,
kind: Option<DepKind>,
) -> CargoResult<Dependency> {
match *self {
TomlDependency::Simple(ref version) => DetailedTomlDependency {
TomlDependency::Simple(ref version) => DetailedTomlDependency::<P> {
version: Some(version.clone()),
..Default::default()
}
Expand All @@ -1652,7 +1725,7 @@ impl TomlDependency {
}
}

impl DetailedTomlDependency {
impl<P: ResolveToPath> DetailedTomlDependency<P> {
fn to_dependency(
&self,
name_in_toml: &str,
Expand Down Expand Up @@ -1762,7 +1835,8 @@ impl DetailedTomlDependency {
SourceId::for_git(&loc, reference)?
}
(None, Some(path), _, _) => {
cx.nested_paths.push(PathBuf::from(path));
let path = path.resolve(cx.config);
cx.nested_paths.push(path.clone());
// If the source ID for the package we're parsing is a path
// source, then we normalize the path here to get rid of
// components like `..`.
Expand Down
Loading