Skip to content

Commit 695465c

Browse files
committed
Auto merge of #9204 - jonhoo:patch-in-config, r=alexcrichton
Support [patch] in .cargo/config files This patch adds support for `[patch]` sections in `.cargo/config.toml` files. Patches from config files defer to `[patch]` in `Cargo.toml` if both provide a patch for the same crate. The current implementation merge config patches into the workspace manifest patches. It's unclear if that's the right long-term plan, or whether these patches should be stored separately (though likely still in the manifest). Regardless, they _should_ likely continue to be parsed when the manifest is parsed so that errors and such occur in the same place regardless of where a patch is specified. Fixes #5539.
2 parents bbda175 + b50cde8 commit 695465c

File tree

6 files changed

+497
-25
lines changed

6 files changed

+497
-25
lines changed

src/cargo/core/features.rs

+2
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ pub struct CliUnstable {
556556
pub namespaced_features: bool,
557557
pub weak_dep_features: bool,
558558
pub extra_link_arg: bool,
559+
pub patch_in_config: bool,
559560
pub credential_process: bool,
560561
pub configurable_env: bool,
561562
pub enable_future_incompat_feature: bool,
@@ -719,6 +720,7 @@ impl CliUnstable {
719720
"panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?,
720721
"jobserver-per-rustc" => self.jobserver_per_rustc = parse_empty(k, v)?,
721722
"configurable-env" => self.configurable_env = parse_empty(k, v)?,
723+
"patch-in-config" => self.patch_in_config = parse_empty(k, v)?,
722724
"features" => {
723725
// For now this is still allowed (there are still some
724726
// unstable options like "compare"). This should be removed at

src/cargo/core/resolver/encode.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ impl EncodableResolve {
154154
/// primary uses is to be used with `resolve_with_previous` to guide the
155155
/// resolver to create a complete Resolve.
156156
pub fn into_resolve(self, original: &str, ws: &Workspace<'_>) -> CargoResult<Resolve> {
157-
let path_deps = build_path_deps(ws);
157+
let path_deps = build_path_deps(ws)?;
158158
let mut checksums = HashMap::new();
159159

160160
let mut version = match self.version {
@@ -402,7 +402,7 @@ impl EncodableResolve {
402402
}
403403
}
404404

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

436-
return ret;
436+
return Ok(ret);
437437

438438
fn build_pkg(
439439
pkg: &Package,

src/cargo/core/workspace.rs

+96-5
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ use crate::core::resolver::ResolveBehavior;
1616
use crate::core::{Dependency, Edition, PackageId, PackageIdSpec};
1717
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
1818
use crate::ops;
19-
use crate::sources::PathSource;
19+
use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
2020
use crate::util::errors::{CargoResult, CargoResultExt, ManifestError};
2121
use crate::util::interning::InternedString;
2222
use crate::util::paths;
23-
use crate::util::toml::{read_manifest, TomlProfiles};
24-
use crate::util::{Config, Filesystem};
23+
use crate::util::toml::{read_manifest, TomlDependency, TomlProfiles};
24+
use crate::util::{config::ConfigRelativePath, Config, Filesystem, IntoUrl};
2525

2626
/// The core abstraction in Cargo for working with a workspace of crates.
2727
///
@@ -362,14 +362,105 @@ impl<'cfg> Workspace<'cfg> {
362362
}
363363
}
364364

365+
fn config_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
366+
let config_patch: Option<
367+
BTreeMap<String, BTreeMap<String, TomlDependency<ConfigRelativePath>>>,
368+
> = self.config.get("patch")?;
369+
370+
if config_patch.is_some() && !self.config.cli_unstable().patch_in_config {
371+
self.config.shell().warn("`[patch]` in cargo config was ignored, the -Zpatch-in-config command-line flag is required".to_owned())?;
372+
return Ok(HashMap::new());
373+
}
374+
375+
let source = SourceId::for_path(self.root())?;
376+
377+
let mut warnings = Vec::new();
378+
let mut nested_paths = Vec::new();
379+
380+
let mut patch = HashMap::new();
381+
for (url, deps) in config_patch.into_iter().flatten() {
382+
let url = match &url[..] {
383+
CRATES_IO_REGISTRY => CRATES_IO_INDEX.parse().unwrap(),
384+
url => self
385+
.config
386+
.get_registry_index(url)
387+
.or_else(|_| url.into_url())
388+
.chain_err(|| {
389+
format!("[patch] entry `{}` should be a URL or registry name", url)
390+
})?,
391+
};
392+
patch.insert(
393+
url,
394+
deps.iter()
395+
.map(|(name, dep)| {
396+
dep.to_dependency_split(
397+
name,
398+
/* pkg_id */ None,
399+
source,
400+
&mut nested_paths,
401+
self.config,
402+
&mut warnings,
403+
/* platform */ None,
404+
// NOTE: Since we use ConfigRelativePath, this root isn't used as
405+
// any relative paths are resolved before they'd be joined with root.
406+
&Path::new("unused-relative-path"),
407+
self.unstable_features(),
408+
/* kind */ None,
409+
)
410+
})
411+
.collect::<CargoResult<Vec<_>>>()?,
412+
);
413+
}
414+
415+
for message in warnings {
416+
self.config
417+
.shell()
418+
.warn(format!("[patch] in cargo config: {}", message))?
419+
}
420+
421+
Ok(patch)
422+
}
423+
365424
/// Returns the root `[patch]` section of this workspace.
366425
///
367426
/// This may be from a virtual crate or an actual crate.
368-
pub fn root_patch(&self) -> &HashMap<Url, Vec<Dependency>> {
369-
match self.root_maybe() {
427+
pub fn root_patch(&self) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
428+
let from_manifest = match self.root_maybe() {
370429
MaybePackage::Package(p) => p.manifest().patch(),
371430
MaybePackage::Virtual(vm) => vm.patch(),
431+
};
432+
433+
let from_config = self.config_patch()?;
434+
if from_config.is_empty() {
435+
return Ok(from_manifest.clone());
436+
}
437+
if from_manifest.is_empty() {
438+
return Ok(from_config.clone());
439+
}
440+
441+
// We could just chain from_manifest and from_config,
442+
// but that's not quite right as it won't deal with overlaps.
443+
let mut combined = from_manifest.clone();
444+
for (url, cdeps) in from_config {
445+
if let Some(deps) = combined.get_mut(&url) {
446+
// We want from_manifest to take precedence for each patched name.
447+
// NOTE: This is inefficient if the number of patches is large!
448+
let mut left = cdeps.clone();
449+
for dep in &mut *deps {
450+
if let Some(i) = left.iter().position(|cdep| {
451+
// XXX: should this also take into account version numbers?
452+
dep.name_in_toml() == cdep.name_in_toml()
453+
}) {
454+
left.swap_remove(i);
455+
}
456+
}
457+
// Whatever is left does not exist in manifest dependencies.
458+
deps.extend(left);
459+
} else {
460+
combined.insert(url.clone(), cdeps.clone());
461+
}
372462
}
463+
Ok(combined)
373464
}
374465

375466
/// Returns an iterator over all packages in this workspace

src/cargo/ops/resolve.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ pub fn resolve_with_previous<'cfg>(
240240
// locked.
241241
let mut avoid_patch_ids = HashSet::new();
242242
if register_patches {
243-
for (url, patches) in ws.root_patch() {
243+
for (url, patches) in ws.root_patch()?.iter() {
244244
let previous = match previous {
245245
Some(r) => r,
246246
None => {

src/cargo/util/toml/mod.rs

+89-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
22
use std::fmt;
3+
use std::marker::PhantomData;
34
use std::path::{Path, PathBuf};
45
use std::rc::Rc;
56
use std::str;
@@ -22,7 +23,9 @@ use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, Worksp
2223
use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY};
2324
use crate::util::errors::{CargoResult, CargoResultExt, ManifestError};
2425
use crate::util::interning::InternedString;
25-
use crate::util::{self, paths, validate_package_name, Config, IntoUrl};
26+
use crate::util::{
27+
self, config::ConfigRelativePath, paths, validate_package_name, Config, IntoUrl,
28+
};
2629

2730
mod targets;
2831
use self::targets::targets;
@@ -199,25 +202,25 @@ type TomlBenchTarget = TomlTarget;
199202

200203
#[derive(Clone, Debug, Serialize)]
201204
#[serde(untagged)]
202-
pub enum TomlDependency {
205+
pub enum TomlDependency<P = String> {
203206
/// In the simple format, only a version is specified, eg.
204207
/// `package = "<version>"`
205208
Simple(String),
206209
/// The simple format is equivalent to a detailed dependency
207210
/// specifying only a version, eg.
208211
/// `package = { version = "<version>" }`
209-
Detailed(DetailedTomlDependency),
212+
Detailed(DetailedTomlDependency<P>),
210213
}
211214

212-
impl<'de> de::Deserialize<'de> for TomlDependency {
215+
impl<'de, P: Deserialize<'de>> de::Deserialize<'de> for TomlDependency<P> {
213216
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
214217
where
215218
D: de::Deserializer<'de>,
216219
{
217-
struct TomlDependencyVisitor;
220+
struct TomlDependencyVisitor<P>(PhantomData<P>);
218221

219-
impl<'de> de::Visitor<'de> for TomlDependencyVisitor {
220-
type Value = TomlDependency;
222+
impl<'de, P: Deserialize<'de>> de::Visitor<'de> for TomlDependencyVisitor<P> {
223+
type Value = TomlDependency<P>;
221224

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

245-
deserializer.deserialize_any(TomlDependencyVisitor)
248+
deserializer.deserialize_any(TomlDependencyVisitor(PhantomData))
246249
}
247250
}
248251

249-
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
252+
pub trait ResolveToPath {
253+
fn resolve(&self, config: &Config) -> PathBuf;
254+
}
255+
256+
impl ResolveToPath for String {
257+
fn resolve(&self, _: &Config) -> PathBuf {
258+
self.into()
259+
}
260+
}
261+
262+
impl ResolveToPath for ConfigRelativePath {
263+
fn resolve(&self, c: &Config) -> PathBuf {
264+
self.resolve_path(c)
265+
}
266+
}
267+
268+
#[derive(Deserialize, Serialize, Clone, Debug)]
250269
#[serde(rename_all = "kebab-case")]
251-
pub struct DetailedTomlDependency {
270+
pub struct DetailedTomlDependency<P = String> {
252271
version: Option<String>,
253272
registry: Option<String>,
254273
/// The URL of the `registry` field.
@@ -258,7 +277,9 @@ pub struct DetailedTomlDependency {
258277
/// registry names configured, so Cargo can't rely on just the name for
259278
/// crates published by other users.
260279
registry_index: Option<String>,
261-
path: Option<String>,
280+
// `path` is relative to the file it appears in. If that's a `Cargo.toml`, it'll be relative to
281+
// that TOML file, and if it's a `.cargo/config` file, it'll be relative to that file.
282+
path: Option<P>,
262283
git: Option<String>,
263284
branch: Option<String>,
264285
tag: Option<String>,
@@ -272,6 +293,28 @@ pub struct DetailedTomlDependency {
272293
public: Option<bool>,
273294
}
274295

296+
// Explicit implementation so we avoid pulling in P: Default
297+
impl<P> Default for DetailedTomlDependency<P> {
298+
fn default() -> Self {
299+
Self {
300+
version: Default::default(),
301+
registry: Default::default(),
302+
registry_index: Default::default(),
303+
path: Default::default(),
304+
git: Default::default(),
305+
branch: Default::default(),
306+
tag: Default::default(),
307+
rev: Default::default(),
308+
features: Default::default(),
309+
optional: Default::default(),
310+
default_features: Default::default(),
311+
default_features2: Default::default(),
312+
package: Default::default(),
313+
public: Default::default(),
314+
}
315+
}
316+
}
317+
275318
/// This type is used to deserialize `Cargo.toml` files.
276319
#[derive(Debug, Deserialize, Serialize)]
277320
#[serde(rename_all = "kebab-case")]
@@ -1627,15 +1670,45 @@ fn unique_build_targets(targets: &[Target], package_root: &Path) -> Result<(), S
16271670
Ok(())
16281671
}
16291672

1630-
impl TomlDependency {
1673+
impl<P: ResolveToPath> TomlDependency<P> {
1674+
pub(crate) fn to_dependency_split(
1675+
&self,
1676+
name: &str,
1677+
pkgid: Option<PackageId>,
1678+
source_id: SourceId,
1679+
nested_paths: &mut Vec<PathBuf>,
1680+
config: &Config,
1681+
warnings: &mut Vec<String>,
1682+
platform: Option<Platform>,
1683+
root: &Path,
1684+
features: &Features,
1685+
kind: Option<DepKind>,
1686+
) -> CargoResult<Dependency> {
1687+
self.to_dependency(
1688+
name,
1689+
&mut Context {
1690+
pkgid,
1691+
deps: &mut Vec::new(),
1692+
source_id,
1693+
nested_paths,
1694+
config,
1695+
warnings,
1696+
platform,
1697+
root,
1698+
features,
1699+
},
1700+
kind,
1701+
)
1702+
}
1703+
16311704
fn to_dependency(
16321705
&self,
16331706
name: &str,
16341707
cx: &mut Context<'_, '_>,
16351708
kind: Option<DepKind>,
16361709
) -> CargoResult<Dependency> {
16371710
match *self {
1638-
TomlDependency::Simple(ref version) => DetailedTomlDependency {
1711+
TomlDependency::Simple(ref version) => DetailedTomlDependency::<P> {
16391712
version: Some(version.clone()),
16401713
..Default::default()
16411714
}
@@ -1652,7 +1725,7 @@ impl TomlDependency {
16521725
}
16531726
}
16541727

1655-
impl DetailedTomlDependency {
1728+
impl<P: ResolveToPath> DetailedTomlDependency<P> {
16561729
fn to_dependency(
16571730
&self,
16581731
name_in_toml: &str,
@@ -1762,7 +1835,8 @@ impl DetailedTomlDependency {
17621835
SourceId::for_git(&loc, reference)?
17631836
}
17641837
(None, Some(path), _, _) => {
1765-
cx.nested_paths.push(PathBuf::from(path));
1838+
let path = path.resolve(cx.config);
1839+
cx.nested_paths.push(path.clone());
17661840
// If the source ID for the package we're parsing is a path
17671841
// source, then we normalize the path here to get rid of
17681842
// components like `..`.

0 commit comments

Comments
 (0)