Skip to content

Commit 4dcbca1

Browse files
committed
Auto merge of #13926 - tweag:overlay, r=epage
Add local registry overlays This PR adds (private to cargo internals) support for local registry overlays, in which you can locally pretend to add packages to remote registries; the local packages will have the same source ids as the remote registry that you're overlaying. There are two ways to set up these overlays: programmatically using `GlobalContext::local_overlays` and through the `__CARGO_TEST_PACKAGE_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS` environment variable. You can't set up these overlays with `.cargo/config`. The motivation for this is [packaging workspaces](#10948). When we're packing a workspace, we'd like to be able to pretend (for lockfile generation and verification) that some workspace packages are already published even though they aren't.
2 parents b134eff + ba9dd1e commit 4dcbca1

File tree

14 files changed

+572
-21
lines changed

14 files changed

+572
-21
lines changed

crates/cargo-test-support/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,17 @@ impl Execs {
877877
self
878878
}
879879

880+
pub fn overlay_registry(&mut self, url: &Url, path: &str) -> &mut Self {
881+
if let Some(ref mut p) = self.process_builder {
882+
let env_value = format!("{}={}", url, path);
883+
p.env(
884+
"__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS",
885+
env_value,
886+
);
887+
}
888+
self
889+
}
890+
880891
pub fn enable_split_debuginfo_packed(&mut self) -> &mut Self {
881892
self.env("CARGO_PROFILE_DEV_SPLIT_DEBUGINFO", "packed")
882893
.env("CARGO_PROFILE_TEST_SPLIT_DEBUGINFO", "packed")

crates/cargo-test-support/src/registry.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1649,7 +1649,12 @@ impl Package {
16491649
/// Returns the path to the compressed package file.
16501650
pub fn archive_dst(&self) -> PathBuf {
16511651
if self.local {
1652-
registry_path().join(format!("{}-{}.crate", self.name, self.vers))
1652+
let path = if self.alternative {
1653+
alt_registry_path()
1654+
} else {
1655+
registry_path()
1656+
};
1657+
path.join(format!("{}-{}.crate", self.name, self.vers))
16531658
} else if self.alternative {
16541659
alt_dl_path()
16551660
.join(&self.name)

crates/xtask-bump-check/src/xtask.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use std::fs;
1616
use std::task;
1717

1818
use cargo::core::dependency::Dependency;
19-
use cargo::core::registry::PackageRegistry;
2019
use cargo::core::Package;
2120
use cargo::core::Registry;
2221
use cargo::core::SourceId;
@@ -137,7 +136,7 @@ fn bump_check(args: &clap::ArgMatches, gctx: &cargo::util::GlobalContext) -> Car
137136

138137
let mut needs_bump = Vec::new();
139138

140-
check_crates_io(gctx, &changed_members, &mut needs_bump)?;
139+
check_crates_io(&ws, &changed_members, &mut needs_bump)?;
141140

142141
if let Some(referenced_commit) = referenced_commit.as_ref() {
143142
status(&format!("compare against `{}`", referenced_commit.id()))?;
@@ -385,12 +384,13 @@ fn symmetric_diff<'a>(
385384
///
386385
/// Assumption: We always release a version larger than all existing versions.
387386
fn check_crates_io<'a>(
388-
gctx: &GlobalContext,
387+
ws: &Workspace<'a>,
389388
changed_members: &HashMap<&'a str, &'a Package>,
390389
needs_bump: &mut Vec<&'a Package>,
391390
) -> CargoResult<()> {
391+
let gctx = ws.gctx();
392392
let source_id = SourceId::crates_io(gctx)?;
393-
let mut registry = PackageRegistry::new(gctx)?;
393+
let mut registry = ws.package_registry()?;
394394
let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
395395
registry.lock_patches();
396396
gctx.shell().status(

src/cargo/core/registry.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,10 @@ pub struct LockedPatchDependency {
194194
}
195195

196196
impl<'gctx> PackageRegistry<'gctx> {
197-
pub fn new(gctx: &'gctx GlobalContext) -> CargoResult<PackageRegistry<'gctx>> {
198-
let source_config = SourceConfigMap::new(gctx)?;
197+
pub fn new_with_source_config(
198+
gctx: &'gctx GlobalContext,
199+
source_config: SourceConfigMap<'gctx>,
200+
) -> CargoResult<PackageRegistry<'gctx>> {
199201
Ok(PackageRegistry {
200202
gctx,
201203
sources: SourceMap::new(),

src/cargo/core/workspace.rs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::core::{
2020
};
2121
use crate::core::{EitherManifest, Package, SourceId, VirtualManifest};
2222
use crate::ops;
23-
use crate::sources::{PathSource, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
23+
use crate::sources::{PathSource, SourceConfigMap, CRATES_IO_INDEX, CRATES_IO_REGISTRY};
2424
use crate::util::edit_distance;
2525
use crate::util::errors::{CargoResult, ManifestError};
2626
use crate::util::interning::InternedString;
@@ -109,6 +109,9 @@ pub struct Workspace<'gctx> {
109109

110110
/// Workspace-level custom metadata
111111
custom_metadata: Option<toml::Value>,
112+
113+
/// Local overlay configuration. See [`crate::sources::overlay`].
114+
local_overlays: HashMap<SourceId, PathBuf>,
112115
}
113116

114117
// Separate structure for tracking loaded packages (to avoid loading anything
@@ -237,6 +240,7 @@ impl<'gctx> Workspace<'gctx> {
237240
resolve_behavior: ResolveBehavior::V1,
238241
resolve_honors_rust_version: false,
239242
custom_metadata: None,
243+
local_overlays: HashMap::new(),
240244
}
241245
}
242246

@@ -1674,6 +1678,44 @@ impl<'gctx> Workspace<'gctx> {
16741678
// Cargo to panic, see issue #10545.
16751679
self.is_member(&unit.pkg) && !(unit.target.for_host() || unit.pkg.proc_macro())
16761680
}
1681+
1682+
/// Adds a local package registry overlaying a `SourceId`.
1683+
///
1684+
/// See [`crate::sources::overlay::DependencyConfusionThreatOverlaySource`] for why you shouldn't use this.
1685+
pub fn add_local_overlay(&mut self, id: SourceId, registry_path: PathBuf) {
1686+
self.local_overlays.insert(id, registry_path);
1687+
}
1688+
1689+
/// Builds a package registry that reflects this workspace configuration.
1690+
pub fn package_registry(&self) -> CargoResult<PackageRegistry<'gctx>> {
1691+
let source_config =
1692+
SourceConfigMap::new_with_overlays(self.gctx(), self.local_overlays()?)?;
1693+
PackageRegistry::new_with_source_config(self.gctx(), source_config)
1694+
}
1695+
1696+
/// Returns all the configured local overlays, including the ones from our secret environment variable.
1697+
fn local_overlays(&self) -> CargoResult<impl Iterator<Item = (SourceId, SourceId)>> {
1698+
let mut ret = self
1699+
.local_overlays
1700+
.iter()
1701+
.map(|(id, path)| Ok((*id, SourceId::for_local_registry(path)?)))
1702+
.collect::<CargoResult<Vec<_>>>()?;
1703+
1704+
if let Ok(overlay) = self
1705+
.gctx
1706+
.get_env("__CARGO_TEST_DEPENDENCY_CONFUSION_VULNERABILITY_DO_NOT_USE_THIS")
1707+
{
1708+
let (url, path) = overlay.split_once('=').ok_or(anyhow::anyhow!(
1709+
"invalid overlay format. I won't tell you why; you shouldn't be using it anyway"
1710+
))?;
1711+
ret.push((
1712+
SourceId::from_url(url)?,
1713+
SourceId::for_local_registry(path.as_ref())?,
1714+
));
1715+
}
1716+
1717+
Ok(ret.into_iter())
1718+
}
16771719
}
16781720

16791721
impl<'gctx> Packages<'gctx> {

src/cargo/ops/cargo_add/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ pub fn add(workspace: &Workspace<'_>, options: &AddOptions<'_>) -> CargoResult<(
7878
);
7979
}
8080

81-
let mut registry = PackageRegistry::new(options.gctx)?;
81+
let mut registry = workspace.package_registry()?;
8282

8383
let deps = {
8484
let _lock = options

src/cargo/ops/cargo_package.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::task::Poll;
99
use crate::core::compiler::{BuildConfig, CompileMode, DefaultExecutor, Executor};
1010
use crate::core::manifest::Target;
1111
use crate::core::resolver::CliFeatures;
12-
use crate::core::{registry::PackageRegistry, resolver::HasDevUnits};
12+
use crate::core::resolver::HasDevUnits;
1313
use crate::core::{Feature, PackageIdSpecQuery, Shell, Verbosity, Workspace};
1414
use crate::core::{Package, PackageId, PackageSet, Resolve, SourceId};
1515
use crate::sources::PathSource;
@@ -472,7 +472,7 @@ fn build_lock(ws: &Workspace<'_>, publish_pkg: &Package) -> CargoResult<String>
472472
let orig_resolve = ops::load_pkg_lockfile(ws)?;
473473

474474
let tmp_ws = Workspace::ephemeral(publish_pkg.clone(), ws.gctx(), None, true)?;
475-
let mut tmp_reg = PackageRegistry::new(ws.gctx())?;
475+
let mut tmp_reg = ws.package_registry()?;
476476
let mut new_resolve = ops::resolve_with_previous(
477477
&mut tmp_reg,
478478
&tmp_ws,

src/cargo/ops/cargo_update.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ pub struct UpdateOptions<'a> {
3232
}
3333

3434
pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> {
35-
let mut registry = PackageRegistry::new(ws.gctx())?;
35+
let mut registry = ws.package_registry()?;
3636
let previous_resolve = None;
3737
let mut resolve = ops::resolve_with_previous(
3838
&mut registry,
@@ -73,7 +73,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
7373
// Precise option specified, so calculate a previous_resolve required
7474
// by precise package update later.
7575
Some(_) => {
76-
let mut registry = PackageRegistry::new(opts.gctx)?;
76+
let mut registry = ws.package_registry()?;
7777
ops::resolve_with_previous(
7878
&mut registry,
7979
ws,
@@ -88,7 +88,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes
8888
}
8989
}
9090
};
91-
let mut registry = PackageRegistry::new(opts.gctx)?;
91+
let mut registry = ws.package_registry()?;
9292
let mut to_avoid = HashSet::new();
9393

9494
if opts.to_update.is_empty() {
@@ -226,7 +226,7 @@ pub fn upgrade_manifests(
226226
// that we're synchronized against other Cargos.
227227
let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
228228

229-
let mut registry = PackageRegistry::new(gctx)?;
229+
let mut registry = ws.package_registry()?;
230230
registry.lock_patches();
231231

232232
for member in ws.members_mut().sorted() {

src/cargo/ops/resolve.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ version. This may also occur with an optional dependency that is not enabled.";
116116
/// This is a simple interface used by commands like `clean`, `fetch`, and
117117
/// `package`, which don't specify any options or features.
118118
pub fn resolve_ws<'a>(ws: &Workspace<'a>, dry_run: bool) -> CargoResult<(PackageSet<'a>, Resolve)> {
119-
let mut registry = PackageRegistry::new(ws.gctx())?;
119+
let mut registry = ws.package_registry()?;
120120
let resolve = resolve_with_registry(ws, &mut registry, dry_run)?;
121121
let packages = get_resolved_packages(&resolve, registry)?;
122122
Ok((packages, resolve))
@@ -142,7 +142,7 @@ pub fn resolve_ws_with_opts<'gctx>(
142142
force_all_targets: ForceAllTargets,
143143
dry_run: bool,
144144
) -> CargoResult<WorkspaceResolve<'gctx>> {
145-
let mut registry = PackageRegistry::new(ws.gctx())?;
145+
let mut registry = ws.package_registry()?;
146146
let (resolve, resolved_with_overrides) = if ws.ignore_lock() {
147147
let add_patches = true;
148148
let resolve = None;

src/cargo/sources/config.rs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//! sources to one another via the `replace-with` key in `.cargo/config`.
66
77
use crate::core::{GitReference, PackageId, SourceId};
8+
use crate::sources::overlay::DependencyConfusionThreatOverlaySource;
89
use crate::sources::source::Source;
910
use crate::sources::{ReplacedSource, CRATES_IO_REGISTRY};
1011
use crate::util::context::{self, ConfigRelativePath, OptValue};
@@ -24,6 +25,8 @@ pub struct SourceConfigMap<'gctx> {
2425
cfgs: HashMap<String, SourceConfig>,
2526
/// Mapping of [`SourceId`] to the source name.
2627
id2name: HashMap<SourceId, String>,
28+
/// Mapping of sources to local registries that will be overlaid on them.
29+
overlays: HashMap<SourceId, SourceId>,
2730
gctx: &'gctx GlobalContext,
2831
}
2932

@@ -81,6 +84,18 @@ impl<'gctx> SourceConfigMap<'gctx> {
8184
base.add_config(key, value)?;
8285
}
8386
}
87+
88+
Ok(base)
89+
}
90+
91+
/// Like [`SourceConfigMap::new`] but includes sources from source
92+
/// replacement configurations.
93+
pub fn new_with_overlays(
94+
gctx: &'gctx GlobalContext,
95+
overlays: impl IntoIterator<Item = (SourceId, SourceId)>,
96+
) -> CargoResult<SourceConfigMap<'gctx>> {
97+
let mut base = SourceConfigMap::new(gctx)?;
98+
base.overlays = overlays.into_iter().collect();
8499
Ok(base)
85100
}
86101

@@ -90,6 +105,7 @@ impl<'gctx> SourceConfigMap<'gctx> {
90105
let mut base = SourceConfigMap {
91106
cfgs: HashMap::new(),
92107
id2name: HashMap::new(),
108+
overlays: HashMap::new(),
93109
gctx,
94110
};
95111
base.add(
@@ -136,7 +152,7 @@ impl<'gctx> SourceConfigMap<'gctx> {
136152
debug!("loading: {}", id);
137153

138154
let Some(mut name) = self.id2name.get(&id) else {
139-
return id.load(self.gctx, yanked_whitelist);
155+
return self.load_overlaid(id, yanked_whitelist);
140156
};
141157
let mut cfg_loc = "";
142158
let orig_name = name;
@@ -161,7 +177,7 @@ impl<'gctx> SourceConfigMap<'gctx> {
161177
name = s;
162178
cfg_loc = c;
163179
}
164-
None if id == cfg.id => return id.load(self.gctx, yanked_whitelist),
180+
None if id == cfg.id => return self.load_overlaid(id, yanked_whitelist),
165181
None => {
166182
break cfg.id.with_precise_from(id);
167183
}
@@ -178,8 +194,8 @@ impl<'gctx> SourceConfigMap<'gctx> {
178194
}
179195
};
180196

181-
let new_src = new_id.load(
182-
self.gctx,
197+
let new_src = self.load_overlaid(
198+
new_id,
183199
&yanked_whitelist
184200
.iter()
185201
.map(|p| p.map_source(id, new_id))
@@ -215,6 +231,23 @@ restore the source replacement configuration to continue the build
215231
Ok(Box::new(ReplacedSource::new(id, new_id, new_src)))
216232
}
217233

234+
/// Gets the [`Source`] for a given [`SourceId`] without performing any source replacement.
235+
fn load_overlaid(
236+
&self,
237+
id: SourceId,
238+
yanked_whitelist: &HashSet<PackageId>,
239+
) -> CargoResult<Box<dyn Source + 'gctx>> {
240+
let src = id.load(self.gctx, yanked_whitelist)?;
241+
if let Some(overlay_id) = self.overlays.get(&id) {
242+
let overlay = overlay_id.load(self.gctx(), yanked_whitelist)?;
243+
Ok(Box::new(DependencyConfusionThreatOverlaySource::new(
244+
overlay, src,
245+
)))
246+
} else {
247+
Ok(src)
248+
}
249+
}
250+
218251
/// Adds a source config with an associated name.
219252
fn add(&mut self, name: &str, cfg: SourceConfig) -> CargoResult<()> {
220253
if let Some(old_name) = self.id2name.insert(cfg.id, name.to_string()) {

src/cargo/sources/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub use self::replaced::ReplacedSource;
3939
pub mod config;
4040
pub mod directory;
4141
pub mod git;
42+
pub mod overlay;
4243
pub mod path;
4344
pub mod registry;
4445
pub mod replaced;

0 commit comments

Comments
 (0)