Skip to content

Commit 29f8d03

Browse files
authored
fix(vendor): dont remove non-cached source (#15260)
### What does this PR try to resolve? Fixes #15244 With this fix, `cargo vendor` will not delete original sources, if you want to vendor things from one directory sources to the other #### Background cargo-vendor has a workaround that to mitigate #5956: it removes all cached sources in order to trigger a re-unpack. It was meant for dealing with registry sources only, but accidentally applied to directory source kind. While directory source kind was invented for vendoring, and vendoring from one vendored directory to the other seems unusual, Cargo IMO should not delete any real sources. It does not mean that registry sources are okay to delete, In long term, we should explore a way that unpacks `.crate` files directly, without any removal. See #12509 (comment) ### How should we test and review this PR? The added test should suffice. Also, although this is for fixing #15244, `cargo vendor` still doesn't support vendor from and to the same location. Unless we figure out an `rsync`-like solutin to update vendor sources, it is not going to support in short term. (And I also doubt the real world use case of it) ### Additional information
2 parents abe461c + 9e18e48 commit 29f8d03

File tree

3 files changed

+92
-6
lines changed

3 files changed

+92
-6
lines changed

src/bin/cargo/commands/vendor.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
6060
// to respect any of the `source` configuration in Cargo itself. That's
6161
// intended for other consumers of Cargo, but we want to go straight to the
6262
// source, e.g. crates.io, to fetch crates.
63-
if !args.flag("respect-source-config") {
63+
let respect_source_config = args.flag("respect-source-config");
64+
if !respect_source_config {
6465
gctx.values_mut()?.remove("source");
6566
}
6667

@@ -80,6 +81,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
8081
.unwrap_or_default()
8182
.cloned()
8283
.collect(),
84+
respect_source_config,
8385
},
8486
)?;
8587
Ok(())

src/cargo/ops/vendor.rs

+47-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::core::shell::Verbosity;
2+
use crate::core::SourceId;
23
use crate::core::{GitReference, Package, Workspace};
34
use crate::ops;
45
use crate::sources::path::PathSource;
56
use crate::sources::PathEntry;
7+
use crate::sources::SourceConfigMap;
68
use crate::sources::CRATES_IO_REGISTRY;
79
use crate::util::cache_lock::CacheLockMode;
810
use crate::util::{try_canonicalize, CargoResult, GlobalContext};
@@ -21,6 +23,7 @@ pub struct VendorOptions<'a> {
2123
pub versioned_dirs: bool,
2224
pub destination: &'a Path,
2325
pub extra: Vec<PathBuf>,
26+
pub respect_source_config: bool,
2427
}
2528

2629
pub fn vendor(ws: &Workspace<'_>, opts: &VendorOptions<'_>) -> CargoResult<()> {
@@ -76,6 +79,32 @@ enum VendorSource {
7679
},
7780
}
7881

82+
/// Cache for mapping replaced sources to replacements.
83+
struct SourceReplacementCache<'gctx> {
84+
map: SourceConfigMap<'gctx>,
85+
cache: HashMap<SourceId, SourceId>,
86+
}
87+
88+
impl SourceReplacementCache<'_> {
89+
fn new(gctx: &GlobalContext) -> CargoResult<SourceReplacementCache<'_>> {
90+
Ok(SourceReplacementCache {
91+
map: SourceConfigMap::new(gctx)?,
92+
cache: Default::default(),
93+
})
94+
}
95+
96+
fn get(&mut self, id: SourceId) -> CargoResult<SourceId> {
97+
use std::collections::hash_map::Entry;
98+
match self.cache.entry(id) {
99+
Entry::Occupied(e) => Ok(e.get().clone()),
100+
Entry::Vacant(e) => {
101+
let replaced = self.map.load(id, &HashSet::new())?.replaced_source_id();
102+
Ok(e.insert(replaced).clone())
103+
}
104+
}
105+
}
106+
}
107+
79108
fn sync(
80109
gctx: &GlobalContext,
81110
workspaces: &[&Workspace<'_>],
@@ -101,6 +130,8 @@ fn sync(
101130
}
102131
}
103132

133+
let mut source_replacement_cache = SourceReplacementCache::new(gctx)?;
134+
104135
// First up attempt to work around rust-lang/cargo#5956. Apparently build
105136
// artifacts sprout up in Cargo's global cache for whatever reason, although
106137
// it's unsure what tool is causing these issues at this time. For now we
@@ -121,20 +152,31 @@ fn sync(
121152
.context("failed to download packages")?;
122153

123154
for pkg in resolve.iter() {
155+
let sid = if opts.respect_source_config {
156+
source_replacement_cache.get(pkg.source_id())?
157+
} else {
158+
pkg.source_id()
159+
};
160+
124161
// Don't delete actual source code!
125-
if pkg.source_id().is_path() {
126-
if let Ok(path) = pkg.source_id().url().to_file_path() {
162+
if sid.is_path() {
163+
if let Ok(path) = sid.url().to_file_path() {
127164
if let Ok(path) = try_canonicalize(path) {
128165
to_remove.remove(&path);
129166
}
130167
}
131168
continue;
132169
}
133-
if pkg.source_id().is_git() {
170+
if sid.is_git() {
134171
continue;
135172
}
136-
if let Ok(pkg) = packages.get_one(pkg) {
137-
drop(fs::remove_dir_all(pkg.root()));
173+
174+
// Only delete sources that are safe to delete, i.e. they are caches.
175+
if sid.is_registry() {
176+
if let Ok(pkg) = packages.get_one(pkg) {
177+
drop(fs::remove_dir_all(pkg.root()));
178+
}
179+
continue;
138180
}
139181
}
140182
}

tests/testsuite/vendor.rs

+42
Original file line numberDiff line numberDiff line change
@@ -1939,3 +1939,45 @@ fn vendor_crate_with_ws_inherit() {
19391939
"#]])
19401940
.run();
19411941
}
1942+
1943+
#[cargo_test]
1944+
fn dont_delete_non_registry_sources_with_respect_source_config() {
1945+
let p = project()
1946+
.file(
1947+
"Cargo.toml",
1948+
r#"
1949+
[package]
1950+
name = "foo"
1951+
version = "0.1.0"
1952+
1953+
[dependencies]
1954+
log = "0.3.5"
1955+
"#,
1956+
)
1957+
.file("src/lib.rs", "")
1958+
.build();
1959+
1960+
Package::new("log", "0.3.5").publish();
1961+
1962+
p.cargo("vendor --respect-source-config").run();
1963+
let lock = p.read_file("vendor/log/Cargo.toml");
1964+
assert!(lock.contains("version = \"0.3.5\""));
1965+
1966+
add_crates_io_vendor_config(&p);
1967+
p.cargo("vendor --respect-source-config new-vendor-dir")
1968+
.with_stderr_data(str![[r#"
1969+
Vendoring log v0.3.5 ([ROOT]/foo/vendor/log) to new-vendor-dir/log
1970+
To use vendored sources, add this to your .cargo/config.toml for this project:
1971+
1972+
1973+
"#]])
1974+
.with_stdout_data(str![[r#"
1975+
[source.crates-io]
1976+
replace-with = "vendored-sources"
1977+
1978+
[source.vendored-sources]
1979+
directory = "new-vendor-dir"
1980+
1981+
"#]])
1982+
.run();
1983+
}

0 commit comments

Comments
 (0)