Skip to content

Commit 35dc025

Browse files
committed
feat: parse dep.patches to construct patched source
1 parent ff129fd commit 35dc025

File tree

2 files changed

+122
-21
lines changed

2 files changed

+122
-21
lines changed

src/cargo/core/workspace.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ impl<'gctx> Workspace<'gctx> {
438438
})?,
439439
};
440440
patch.insert(
441-
url,
441+
url.clone(),
442442
deps.iter()
443443
.map(|(name, dep)| {
444444
crate::util::toml::to_dependency(
@@ -452,6 +452,7 @@ impl<'gctx> Workspace<'gctx> {
452452
// any relative paths are resolved before they'd be joined with root.
453453
Path::new("unused-relative-path"),
454454
/* kind */ None,
455+
&url,
455456
)
456457
})
457458
.collect::<CargoResult<Vec<_>>>()?,

src/cargo/util/toml/mod.rs

Lines changed: 120 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use annotate_snippets::{Level, Renderer, Snippet};
2+
use cargo_util_schemas::core::PatchInfo;
23
use std::collections::{BTreeMap, BTreeSet, HashMap};
34
use std::ffi::OsStr;
45
use std::path::{Path, PathBuf};
@@ -27,6 +28,7 @@ use crate::core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, Worksp
2728
use crate::sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY};
2829
use crate::util::errors::{CargoResult, ManifestError};
2930
use crate::util::interning::InternedString;
31+
use crate::util::CanonicalUrl;
3032
use crate::util::{self, context::ConfigRelativePath, GlobalContext, IntoUrl, OptVersionReq};
3133

3234
mod embedded;
@@ -1185,7 +1187,7 @@ fn to_real_manifest(
11851187
)?;
11861188
}
11871189
let replace = replace(&resolved_toml, &mut manifest_ctx)?;
1188-
let patch = patch(&resolved_toml, &mut manifest_ctx)?;
1190+
let patch = patch(&resolved_toml, &mut manifest_ctx, &features)?;
11891191

11901192
{
11911193
let mut names_sources = BTreeMap::new();
@@ -1448,7 +1450,7 @@ fn to_virtual_manifest(
14481450
};
14491451
(
14501452
replace(&original_toml, &mut manifest_ctx)?,
1451-
patch(&original_toml, &mut manifest_ctx)?,
1453+
patch(&original_toml, &mut manifest_ctx, &features)?,
14521454
)
14531455
};
14541456
if let Some(profiles) = &original_toml.profile {
@@ -1527,7 +1529,7 @@ fn gather_dependencies(
15271529

15281530
for (n, v) in dependencies.iter() {
15291531
let resolved = v.resolved().expect("previously resolved");
1530-
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind)?;
1532+
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind, None)?;
15311533
manifest_ctx.deps.push(dep);
15321534
}
15331535
Ok(())
@@ -1561,7 +1563,7 @@ fn replace(
15611563
);
15621564
}
15631565

1564-
let mut dep = dep_to_dependency(replacement, spec.name(), manifest_ctx, None)?;
1566+
let mut dep = dep_to_dependency(replacement, spec.name(), manifest_ctx, None, None)?;
15651567
let version = spec.version().ok_or_else(|| {
15661568
anyhow!(
15671569
"replacements must specify a version \
@@ -1584,7 +1586,9 @@ fn replace(
15841586
fn patch(
15851587
me: &manifest::TomlManifest,
15861588
manifest_ctx: &mut ManifestContext<'_, '_>,
1589+
features: &Features,
15871590
) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
1591+
let patch_files_enabled = features.require(Feature::patch_files()).is_ok();
15881592
let mut patch = HashMap::new();
15891593
for (toml_url, deps) in me.patch.iter().flatten() {
15901594
let url = match &toml_url[..] {
@@ -1601,7 +1605,7 @@ fn patch(
16011605
})?,
16021606
};
16031607
patch.insert(
1604-
url,
1608+
url.clone(),
16051609
deps.iter()
16061610
.map(|(name, dep)| {
16071611
unused_dep_keys(
@@ -1610,14 +1614,21 @@ fn patch(
16101614
dep.unused_keys(),
16111615
&mut manifest_ctx.warnings,
16121616
);
1613-
dep_to_dependency(dep, name, manifest_ctx, None)
1617+
dep_to_dependency(
1618+
dep,
1619+
name,
1620+
manifest_ctx,
1621+
None,
1622+
Some((&url, patch_files_enabled)),
1623+
)
16141624
})
16151625
.collect::<CargoResult<Vec<_>>>()?,
16161626
);
16171627
}
16181628
Ok(patch)
16191629
}
16201630

1631+
/// Transforms a `patch` entry to a [`Dependency`].
16211632
pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
16221633
dep: &manifest::TomlDependency<P>,
16231634
name: &str,
@@ -1627,27 +1638,26 @@ pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
16271638
platform: Option<Platform>,
16281639
root: &Path,
16291640
kind: Option<DepKind>,
1641+
patch_source_url: &Url,
16301642
) -> CargoResult<Dependency> {
1631-
dep_to_dependency(
1632-
dep,
1633-
name,
1634-
&mut ManifestContext {
1635-
deps: &mut Vec::new(),
1636-
source_id,
1637-
gctx,
1638-
warnings,
1639-
platform,
1640-
root,
1641-
},
1642-
kind,
1643-
)
1643+
let manifest_ctx = &mut ManifestContext {
1644+
deps: &mut Vec::new(),
1645+
source_id,
1646+
gctx,
1647+
warnings,
1648+
platform,
1649+
root,
1650+
};
1651+
let patch_source_url = Some((patch_source_url, gctx.cli_unstable().patch_files));
1652+
dep_to_dependency(dep, name, manifest_ctx, kind, patch_source_url)
16441653
}
16451654

16461655
fn dep_to_dependency<P: ResolveToPath + Clone>(
16471656
orig: &manifest::TomlDependency<P>,
16481657
name: &str,
16491658
manifest_ctx: &mut ManifestContext<'_, '_>,
16501659
kind: Option<DepKind>,
1660+
patch_source_url: Option<(&Url, bool)>,
16511661
) -> CargoResult<Dependency> {
16521662
match *orig {
16531663
manifest::TomlDependency::Simple(ref version) => detailed_dep_to_dependency(
@@ -1658,9 +1668,10 @@ fn dep_to_dependency<P: ResolveToPath + Clone>(
16581668
name,
16591669
manifest_ctx,
16601670
kind,
1671+
patch_source_url,
16611672
),
16621673
manifest::TomlDependency::Detailed(ref details) => {
1663-
detailed_dep_to_dependency(details, name, manifest_ctx, kind)
1674+
detailed_dep_to_dependency(details, name, manifest_ctx, kind, patch_source_url)
16641675
}
16651676
}
16661677
}
@@ -1670,6 +1681,7 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
16701681
name_in_toml: &str,
16711682
manifest_ctx: &mut ManifestContext<'_, '_>,
16721683
kind: Option<DepKind>,
1684+
patch_source_url: Option<(&Url, bool)>,
16731685
) -> CargoResult<Dependency> {
16741686
if orig.version.is_none() && orig.path.is_none() && orig.git.is_none() {
16751687
let msg = format!(
@@ -1749,6 +1761,7 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
17491761

17501762
let version = orig.version.as_deref();
17511763
let mut dep = Dependency::parse(pkg_name, version, new_source_id)?;
1764+
17521765
if orig.default_features.is_some() && orig.default_features2.is_some() {
17531766
warn_on_deprecated(
17541767
"default-features",
@@ -1816,6 +1829,11 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
18161829
)
18171830
}
18181831
}
1832+
1833+
if let Some(source_id) = patched_source_id(orig, manifest_ctx, &dep, patch_source_url)? {
1834+
dep.set_source_id(source_id);
1835+
}
1836+
18191837
Ok(dep)
18201838
}
18211839

@@ -1912,6 +1930,88 @@ fn resolve_source_id_from_dependency<P: ResolveToPath + Clone>(
19121930
Ok(new_source_id)
19131931
}
19141932

1933+
// Handle `patches` field for `[patch]` table, if any.
1934+
fn patched_source_id<P: ResolveToPath + Clone>(
1935+
orig: &manifest::TomlDetailedDependency<P>,
1936+
manifest_ctx: &mut ManifestContext<'_, '_>,
1937+
dep: &Dependency,
1938+
patch_source_url: Option<(&Url, bool)>,
1939+
) -> CargoResult<Option<SourceId>> {
1940+
let name_in_toml = dep.name_in_toml().as_str();
1941+
let message = "see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#patch-files about the status of this feature.";
1942+
match (patch_source_url, orig.patches.as_ref()) {
1943+
(_, None) => {
1944+
// not a SourceKind::Patched dep.
1945+
Ok(None)
1946+
}
1947+
(None, Some(_)) => {
1948+
let kind = dep.kind().kind_table();
1949+
manifest_ctx.warnings.push(format!(
1950+
"unused manifest key: {kind}.{name_in_toml}.patches; {message}"
1951+
));
1952+
Ok(None)
1953+
}
1954+
(Some((url, false)), Some(_)) => {
1955+
manifest_ctx.warnings.push(format!(
1956+
"ignoring `patches` on patch for `{name_in_toml}` in `{url}`; {message}"
1957+
));
1958+
Ok(None)
1959+
}
1960+
(Some((url, true)), Some(patches)) => {
1961+
let source_id = dep.source_id();
1962+
if !source_id.is_registry() {
1963+
bail!(
1964+
"patch for `{name_in_toml}` in `{url}` requires a registry source \
1965+
when patching with patch files"
1966+
);
1967+
}
1968+
if &CanonicalUrl::new(url)? != source_id.canonical_url() {
1969+
bail!(
1970+
"patch for `{name_in_toml}` in `{url}` must refer to the same source \
1971+
when patching with patch files"
1972+
)
1973+
}
1974+
let version = match dep.version_req().locked_version() {
1975+
Some(v) => Some(v.to_owned()),
1976+
None if dep.version_req().is_exact() => {
1977+
// Remove the `=` exact operator.
1978+
orig.version
1979+
.as_deref()
1980+
.map(|v| v[1..].trim().parse().ok())
1981+
.flatten()
1982+
}
1983+
None => None,
1984+
};
1985+
let Some(version) = version else {
1986+
bail!(
1987+
"patch for `{name_in_toml}` in `{url}` requires an exact version \
1988+
when patching with patch files"
1989+
);
1990+
};
1991+
let patches: Vec<_> = patches
1992+
.iter()
1993+
.map(|path| {
1994+
let path = path.resolve(manifest_ctx.gctx);
1995+
let path = manifest_ctx.root.join(path);
1996+
// keep paths inside workspace relative to workspace, otherwise absolute.
1997+
path.strip_prefix(manifest_ctx.gctx.cwd())
1998+
.map(Into::into)
1999+
.unwrap_or_else(|_| paths::normalize_path(&path))
2000+
})
2001+
.collect();
2002+
if patches.is_empty() {
2003+
bail!(
2004+
"patch for `{name_in_toml}` in `{url}` requires at least one patch file \
2005+
when patching with patch files"
2006+
);
2007+
}
2008+
let pkg_name = dep.package_name().to_string();
2009+
let patch_info = PatchInfo::new(pkg_name, version.to_string(), patches);
2010+
SourceId::for_patches(source_id, patch_info).map(Some)
2011+
}
2012+
}
2013+
}
2014+
19152015
pub trait ResolveToPath {
19162016
fn resolve(&self, gctx: &GlobalContext) -> PathBuf;
19172017
}

0 commit comments

Comments
 (0)