Skip to content

Commit c1ca923

Browse files
committed
feat: parse dep.patches to construct patched source
1 parent 5ac7b60 commit c1ca923

File tree

2 files changed

+121
-21
lines changed

2 files changed

+121
-21
lines changed

src/cargo/core/workspace.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ impl<'gctx> Workspace<'gctx> {
484484
})?,
485485
};
486486
patch.insert(
487-
url,
487+
url.clone(),
488488
deps.iter()
489489
.map(|(name, dep)| {
490490
crate::util::toml::to_dependency(
@@ -498,6 +498,7 @@ impl<'gctx> Workspace<'gctx> {
498498
// any relative paths are resolved before they'd be joined with root.
499499
Path::new("unused-relative-path"),
500500
/* kind */ None,
501+
&url,
501502
)
502503
})
503504
.collect::<CargoResult<Vec<_>>>()?,

src/cargo/util/toml/mod.rs

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

3335
mod embedded;
@@ -1326,7 +1328,7 @@ fn to_real_manifest(
13261328
)?;
13271329
}
13281330
let replace = replace(&resolved_toml, &mut manifest_ctx)?;
1329-
let patch = patch(&resolved_toml, &mut manifest_ctx)?;
1331+
let patch = patch(&resolved_toml, &mut manifest_ctx, &features)?;
13301332

13311333
{
13321334
let mut names_sources = BTreeMap::new();
@@ -1585,7 +1587,7 @@ fn to_virtual_manifest(
15851587
};
15861588
(
15871589
replace(&original_toml, &mut manifest_ctx)?,
1588-
patch(&original_toml, &mut manifest_ctx)?,
1590+
patch(&original_toml, &mut manifest_ctx, &features)?,
15891591
)
15901592
};
15911593
if let Some(profiles) = &original_toml.profile {
@@ -1664,7 +1666,7 @@ fn gather_dependencies(
16641666

16651667
for (n, v) in dependencies.iter() {
16661668
let resolved = v.resolved().expect("previously resolved");
1667-
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind)?;
1669+
let dep = dep_to_dependency(&resolved, n, manifest_ctx, kind, None)?;
16681670
manifest_ctx.deps.push(dep);
16691671
}
16701672
Ok(())
@@ -1698,7 +1700,7 @@ fn replace(
16981700
);
16991701
}
17001702

1701-
let mut dep = dep_to_dependency(replacement, spec.name(), manifest_ctx, None)?;
1703+
let mut dep = dep_to_dependency(replacement, spec.name(), manifest_ctx, None, None)?;
17021704
let version = spec.version().ok_or_else(|| {
17031705
anyhow!(
17041706
"replacements must specify a version \
@@ -1721,7 +1723,9 @@ fn replace(
17211723
fn patch(
17221724
me: &manifest::TomlManifest,
17231725
manifest_ctx: &mut ManifestContext<'_, '_>,
1726+
features: &Features,
17241727
) -> CargoResult<HashMap<Url, Vec<Dependency>>> {
1728+
let patch_files_enabled = features.require(Feature::patch_files()).is_ok();
17251729
let mut patch = HashMap::new();
17261730
for (toml_url, deps) in me.patch.iter().flatten() {
17271731
let url = match &toml_url[..] {
@@ -1738,7 +1742,7 @@ fn patch(
17381742
})?,
17391743
};
17401744
patch.insert(
1741-
url,
1745+
url.clone(),
17421746
deps.iter()
17431747
.map(|(name, dep)| {
17441748
unused_dep_keys(
@@ -1747,14 +1751,21 @@ fn patch(
17471751
dep.unused_keys(),
17481752
&mut manifest_ctx.warnings,
17491753
);
1750-
dep_to_dependency(dep, name, manifest_ctx, None)
1754+
dep_to_dependency(
1755+
dep,
1756+
name,
1757+
manifest_ctx,
1758+
None,
1759+
Some((&url, patch_files_enabled)),
1760+
)
17511761
})
17521762
.collect::<CargoResult<Vec<_>>>()?,
17531763
);
17541764
}
17551765
Ok(patch)
17561766
}
17571767

1768+
/// Transforms a `patch` entry to a [`Dependency`].
17581769
pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
17591770
dep: &manifest::TomlDependency<P>,
17601771
name: &str,
@@ -1764,27 +1775,26 @@ pub(crate) fn to_dependency<P: ResolveToPath + Clone>(
17641775
platform: Option<Platform>,
17651776
root: &Path,
17661777
kind: Option<DepKind>,
1778+
patch_source_url: &Url,
17671779
) -> CargoResult<Dependency> {
1768-
dep_to_dependency(
1769-
dep,
1770-
name,
1771-
&mut ManifestContext {
1772-
deps: &mut Vec::new(),
1773-
source_id,
1774-
gctx,
1775-
warnings,
1776-
platform,
1777-
root,
1778-
},
1779-
kind,
1780-
)
1780+
let manifest_ctx = &mut ManifestContext {
1781+
deps: &mut Vec::new(),
1782+
source_id,
1783+
gctx,
1784+
warnings,
1785+
platform,
1786+
root,
1787+
};
1788+
let patch_source_url = Some((patch_source_url, gctx.cli_unstable().patch_files));
1789+
dep_to_dependency(dep, name, manifest_ctx, kind, patch_source_url)
17811790
}
17821791

17831792
fn dep_to_dependency<P: ResolveToPath + Clone>(
17841793
orig: &manifest::TomlDependency<P>,
17851794
name: &str,
17861795
manifest_ctx: &mut ManifestContext<'_, '_>,
17871796
kind: Option<DepKind>,
1797+
patch_source_url: Option<(&Url, bool)>,
17881798
) -> CargoResult<Dependency> {
17891799
match *orig {
17901800
manifest::TomlDependency::Simple(ref version) => detailed_dep_to_dependency(
@@ -1795,9 +1805,10 @@ fn dep_to_dependency<P: ResolveToPath + Clone>(
17951805
name,
17961806
manifest_ctx,
17971807
kind,
1808+
patch_source_url,
17981809
),
17991810
manifest::TomlDependency::Detailed(ref details) => {
1800-
detailed_dep_to_dependency(details, name, manifest_ctx, kind)
1811+
detailed_dep_to_dependency(details, name, manifest_ctx, kind, patch_source_url)
18011812
}
18021813
}
18031814
}
@@ -1807,6 +1818,7 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
18071818
name_in_toml: &str,
18081819
manifest_ctx: &mut ManifestContext<'_, '_>,
18091820
kind: Option<DepKind>,
1821+
patch_source_url: Option<(&Url, bool)>,
18101822
) -> CargoResult<Dependency> {
18111823
if orig.version.is_none() && orig.path.is_none() && orig.git.is_none() {
18121824
anyhow::bail!(
@@ -1942,6 +1954,11 @@ fn detailed_dep_to_dependency<P: ResolveToPath + Clone>(
19421954
)
19431955
}
19441956
}
1957+
1958+
if let Some(source_id) = patched_source_id(orig, manifest_ctx, &dep, patch_source_url)? {
1959+
dep.set_source_id(source_id);
1960+
}
1961+
19451962
Ok(dep)
19461963
}
19471964

@@ -2030,6 +2047,88 @@ fn to_dependency_source_id<P: ResolveToPath + Clone>(
20302047
}
20312048
}
20322049

2050+
// Handle `patches` field for `[patch]` table, if any.
2051+
fn patched_source_id<P: ResolveToPath + Clone>(
2052+
orig: &manifest::TomlDetailedDependency<P>,
2053+
manifest_ctx: &mut ManifestContext<'_, '_>,
2054+
dep: &Dependency,
2055+
patch_source_url: Option<(&Url, bool)>,
2056+
) -> CargoResult<Option<SourceId>> {
2057+
let name_in_toml = dep.name_in_toml().as_str();
2058+
let message = "see https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#patch-files about the status of this feature.";
2059+
match (patch_source_url, orig.patches.as_ref()) {
2060+
(_, None) => {
2061+
// not a SourceKind::Patched dep.
2062+
Ok(None)
2063+
}
2064+
(None, Some(_)) => {
2065+
let kind = dep.kind().kind_table();
2066+
manifest_ctx.warnings.push(format!(
2067+
"unused manifest key: {kind}.{name_in_toml}.patches; {message}"
2068+
));
2069+
Ok(None)
2070+
}
2071+
(Some((url, false)), Some(_)) => {
2072+
manifest_ctx.warnings.push(format!(
2073+
"ignoring `patches` on patch for `{name_in_toml}` in `{url}`; {message}"
2074+
));
2075+
Ok(None)
2076+
}
2077+
(Some((url, true)), Some(patches)) => {
2078+
let source_id = dep.source_id();
2079+
if !source_id.is_registry() {
2080+
bail!(
2081+
"patch for `{name_in_toml}` in `{url}` requires a registry source \
2082+
when patching with patch files"
2083+
);
2084+
}
2085+
if &CanonicalUrl::new(url)? != source_id.canonical_url() {
2086+
bail!(
2087+
"patch for `{name_in_toml}` in `{url}` must refer to the same source \
2088+
when patching with patch files"
2089+
)
2090+
}
2091+
let version = match dep.version_req().locked_version() {
2092+
Some(v) => Some(v.to_owned()),
2093+
None if dep.version_req().is_exact() => {
2094+
// Remove the `=` exact operator.
2095+
orig.version
2096+
.as_deref()
2097+
.map(|v| v[1..].trim().parse().ok())
2098+
.flatten()
2099+
}
2100+
None => None,
2101+
};
2102+
let Some(version) = version else {
2103+
bail!(
2104+
"patch for `{name_in_toml}` in `{url}` requires an exact version \
2105+
when patching with patch files"
2106+
);
2107+
};
2108+
let patches: Vec<_> = patches
2109+
.iter()
2110+
.map(|path| {
2111+
let path = path.resolve(manifest_ctx.gctx);
2112+
let path = manifest_ctx.root.join(path);
2113+
// keep paths inside workspace relative to workspace, otherwise absolute.
2114+
path.strip_prefix(manifest_ctx.gctx.cwd())
2115+
.map(Into::into)
2116+
.unwrap_or_else(|_| paths::normalize_path(&path))
2117+
})
2118+
.collect();
2119+
if patches.is_empty() {
2120+
bail!(
2121+
"patch for `{name_in_toml}` in `{url}` requires at least one patch file \
2122+
when patching with patch files"
2123+
);
2124+
}
2125+
let pkg_name = dep.package_name().to_string();
2126+
let patch_info = PatchInfo::new(pkg_name, version.to_string(), patches);
2127+
SourceId::for_patches(source_id, patch_info).map(Some)
2128+
}
2129+
}
2130+
}
2131+
20332132
pub trait ResolveToPath {
20342133
fn resolve(&self, gctx: &GlobalContext) -> PathBuf;
20352134
}

0 commit comments

Comments
 (0)