Skip to content

Commit 9ecdfe0

Browse files
committed
install-upgrade: Fix bugs, more comments, review updates.
1 parent e023a66 commit 9ecdfe0

File tree

4 files changed

+164
-42
lines changed

4 files changed

+164
-42
lines changed

src/cargo/ops/cargo_install.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,9 @@ fn install_one(
241241
}
242242
};
243243

244+
// For bare `cargo install` (no `--bin` or `--example`), check if there is
245+
// *something* to install. Explicit `--bin` or `--example` flags will be
246+
// checked at the start of `compile_ws`.
244247
if !opts.filter.is_specific() && !pkg.targets().iter().any(|t| t.is_bin()) {
245248
bail!("specified package `{}` has no binaries", pkg);
246249
}
@@ -394,8 +397,7 @@ fn install_one(
394397
try_install()
395398
};
396399

397-
if !no_track {
398-
let mut tracker = tracker.unwrap();
400+
if let Some(mut tracker) = tracker {
399401
tracker.mark_installed(
400402
pkg,
401403
&successful_bins,

src/cargo/ops/common_for_install_and_uninstall.rs

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ use crate::util::{FileLock, Filesystem, Freshness};
2525
///
2626
/// This maintains a filesystem lock, preventing other instances of Cargo from
2727
/// modifying at the same time. Drop the value to unlock.
28+
///
29+
/// If/when v2 is stabilized, it is intended that v1 is retained for a while
30+
/// during a longish transition period, and then v1 can be removed.
2831
pub struct InstallTracker {
2932
v1: CrateListingV1,
3033
v2: CrateListingV2,
@@ -33,6 +36,10 @@ pub struct InstallTracker {
3336
unstable_upgrade: bool,
3437
}
3538

39+
/// Tracking information for the set of installed packages.
40+
///
41+
/// This v2 format is unstable and requires the `-Z unstable-upgrade` option
42+
/// to enable.
3643
#[derive(Default, Deserialize, Serialize)]
3744
struct CrateListingV2 {
3845
installs: BTreeMap<PackageId, InstallInfo>,
@@ -41,6 +48,14 @@ struct CrateListingV2 {
4148
other: BTreeMap<String, serde_json::Value>,
4249
}
4350

51+
/// Tracking information for the installation of a single package.
52+
///
53+
/// This tracks the settings that were used when the package was installed.
54+
/// Future attempts to install the same package will check these settings to
55+
/// determine if it needs to be rebuilt/reinstalled. If nothing has changed,
56+
/// then Cargo will inform the user that it is "up to date".
57+
///
58+
/// This is only used for the (unstable) v2 format.
4459
#[derive(Debug, Deserialize, Serialize)]
4560
struct InstallInfo {
4661
/// Version requested via `--version`.
@@ -68,6 +83,7 @@ struct InstallInfo {
6883
other: BTreeMap<String, serde_json::Value>,
6984
}
7085

86+
/// Tracking information for the set of installed packages.
7187
#[derive(Default, Deserialize, Serialize)]
7288
pub struct CrateListingV1 {
7389
v1: BTreeMap<PackageId, BTreeSet<String>>,
@@ -102,23 +118,20 @@ impl InstallTracker {
102118
})?;
103119

104120
let v2 = (|| -> CargoResult<_> {
105-
if unstable_upgrade {
106-
let mut contents = String::new();
107-
v2_lock
108-
.as_ref()
109-
.unwrap()
110-
.file()
111-
.read_to_string(&mut contents)?;
112-
let mut v2 = if contents.is_empty() {
113-
CrateListingV2::default()
114-
} else {
115-
serde_json::from_str(&contents)
116-
.chain_err(|| format_err!("invalid JSON found for metadata"))?
117-
};
118-
v2.sync_v1(&v1)?;
119-
Ok(v2)
120-
} else {
121-
Ok(CrateListingV2::default())
121+
match &v2_lock {
122+
Some(lock) => {
123+
let mut contents = String::new();
124+
lock.file().read_to_string(&mut contents)?;
125+
let mut v2 = if contents.is_empty() {
126+
CrateListingV2::default()
127+
} else {
128+
serde_json::from_str(&contents)
129+
.chain_err(|| format_err!("invalid JSON found for metadata"))?
130+
};
131+
v2.sync_v1(&v1)?;
132+
Ok(v2)
133+
}
134+
None => Ok(CrateListingV2::default()),
122135
}
123136
})()
124137
.chain_err(|| {
@@ -142,8 +155,14 @@ impl InstallTracker {
142155
///
143156
/// Returns a tuple `(freshness, map)`. `freshness` indicates if the
144157
/// package should be built (`Dirty`) or if it is already up-to-date
145-
/// (`Fresh`). The map maps binary names to the PackageId that installed
146-
/// it (which is None if not known).
158+
/// (`Fresh`) and should be skipped. The map maps binary names to the
159+
/// PackageId that installed it (which is None if not known).
160+
///
161+
/// If there are no duplicates, then it will be considered `Dirty` (i.e.,
162+
/// it is OK to build/install).
163+
///
164+
/// `force=true` will always be considered `Dirty` (i.e., it will always
165+
/// be rebuilt/reinstalled).
147166
///
148167
/// Returns an error if there is a duplicate and `--force` is not used.
149168
pub fn check_upgrade(
@@ -156,12 +175,17 @@ impl InstallTracker {
156175
_rustc: &str,
157176
) -> CargoResult<(Freshness, BTreeMap<String, Option<PackageId>>)> {
158177
let exes = exe_names(pkg, &opts.filter);
178+
// Check if any tracked exe's are already installed.
159179
let duplicates = self.find_duplicates(dst, &exes);
160180
if force || duplicates.is_empty() {
161181
return Ok((Freshness::Dirty, duplicates));
162182
}
163-
// If any duplicates are not tracked, then --force is required.
164-
// If any duplicates are from a package with a different name, --force is required.
183+
// Check if all duplicates come from packages of the same name. If
184+
// there are duplicates from other packages, then --force will be
185+
// required.
186+
//
187+
// There may be multiple matching duplicates if different versions of
188+
// the same package installed different binaries.
165189
let matching_duplicates: Vec<PackageId> = duplicates
166190
.values()
167191
.filter_map(|v| match v {
@@ -170,9 +194,13 @@ impl InstallTracker {
170194
})
171195
.collect();
172196

197+
// If both sets are the same length, that means all duplicates come
198+
// from packages with the same name.
173199
if self.unstable_upgrade && matching_duplicates.len() == duplicates.len() {
200+
// Determine if it is dirty or fresh.
174201
let source_id = pkg.package_id().source_id();
175202
if source_id.is_path() {
203+
// `cargo install --path ...` is always rebuilt.
176204
return Ok((Freshness::Dirty, duplicates));
177205
}
178206
if matching_duplicates.iter().all(|dupe_pkg_id| {
@@ -182,6 +210,8 @@ impl InstallTracker {
182210
.get(dupe_pkg_id)
183211
.expect("dupes must be in sync");
184212
let precise_equal = if source_id.is_git() {
213+
// Git sources must have the exact same hash to be
214+
// considered "fresh".
185215
dupe_pkg_id.source_id().precise() == source_id.precise()
186216
} else {
187217
true
@@ -190,13 +220,7 @@ impl InstallTracker {
190220
dupe_pkg_id.version() == pkg.version()
191221
&& dupe_pkg_id.source_id() == source_id
192222
&& precise_equal
193-
&& info.features == feature_set(&opts.features)
194-
&& info.all_features == opts.all_features
195-
&& info.no_default_features == opts.no_default_features
196-
&& info.profile == profile_name(opts.build_config.release)
197-
&& (info.target.is_none()
198-
|| info.target.as_ref().map(|t| t.as_ref()) == Some(target))
199-
&& info.bins == exes
223+
&& info.is_up_to_date(opts, target, &exes)
200224
}) {
201225
Ok((Freshness::Fresh, duplicates))
202226
} else {
@@ -218,6 +242,11 @@ impl InstallTracker {
218242
}
219243
}
220244

245+
/// Check if any executables are already installed.
246+
///
247+
/// Returns a map of duplicates, the key is the executable name and the
248+
/// value is the PackageId that is already installed. The PackageId is
249+
/// None if it is an untracked executable.
221250
fn find_duplicates(
222251
&self,
223252
dst: &Path,
@@ -312,7 +341,7 @@ impl CrateListingV1 {
312341
other_bins.remove(bin);
313342
}
314343
}
315-
// Remove empty metadata lines. If only BTreeMap had `retain`.
344+
// Remove entries where `bins` is empty.
316345
let to_remove = self
317346
.v1
318347
.iter()
@@ -322,11 +351,10 @@ impl CrateListingV1 {
322351
self.v1.remove(p);
323352
}
324353
// Add these bins.
325-
let mut bins = bins.clone();
326354
self.v1
327355
.entry(pkg.package_id())
328-
.and_modify(|set| set.append(&mut bins))
329-
.or_insert(bins);
356+
.or_insert_with(BTreeSet::new)
357+
.append(&mut bins.clone());
330358
}
331359

332360
fn remove(&mut self, pkg_id: PackageId, bins: &BTreeSet<String>) {
@@ -354,6 +382,11 @@ impl CrateListingV1 {
354382
}
355383

356384
impl CrateListingV2 {
385+
/// Incorporate any changes from v1 into self.
386+
/// This handles the initial upgrade to v2, *and* handles the case
387+
/// where v2 is in use, and a v1 update is made, then v2 is used again.
388+
/// i.e., `cargo +new install foo ; cargo +old install bar ; cargo +new install bar`
389+
/// For now, v1 is the source of truth, so its values are trusted over v2.
357390
fn sync_v1(&mut self, v1: &CrateListingV1) -> CargoResult<()> {
358391
// Make the `bins` entries the same.
359392
for (pkg_id, bins) in &v1.v1 {
@@ -397,7 +430,7 @@ impl CrateListingV2 {
397430
info.bins.remove(bin);
398431
}
399432
}
400-
// Remove empty metadata lines. If only BTreeMap had `retain`.
433+
// Remove entries where `bins` is empty.
401434
let to_remove = self
402435
.installs
403436
.iter()
@@ -408,9 +441,7 @@ impl CrateListingV2 {
408441
}
409442
// Add these bins.
410443
if let Some(info) = self.installs.get_mut(&pkg.package_id()) {
411-
for bin in bins {
412-
info.bins.remove(bin);
413-
}
444+
info.bins.append(&mut bins.clone());
414445
info.version_req = version_req;
415446
info.features = feature_set(&opts.features);
416447
info.all_features = opts.all_features;
@@ -454,7 +485,8 @@ impl CrateListingV2 {
454485
let mut file = lock.file();
455486
file.seek(SeekFrom::Start(0))?;
456487
file.set_len(0)?;
457-
serde_json::to_writer(file, self)?;
488+
let data = serde_json::to_string(self)?;
489+
file.write_all(data.as_bytes())?;
458490
Ok(())
459491
}
460492
}
@@ -473,6 +505,23 @@ impl InstallInfo {
473505
other: BTreeMap::new(),
474506
}
475507
}
508+
509+
/// Determine if this installation is "up to date", or if it needs to be reinstalled.
510+
///
511+
/// This does not do Package/Source/Version checking.
512+
fn is_up_to_date(
513+
&self,
514+
opts: &CompileOptions<'_>,
515+
target: &str,
516+
exes: &BTreeSet<String>,
517+
) -> bool {
518+
self.features == feature_set(&opts.features)
519+
&& self.all_features == opts.all_features
520+
&& self.no_default_features == opts.no_default_features
521+
&& self.profile == profile_name(opts.build_config.release)
522+
&& (self.target.is_none() || self.target.as_ref().map(|t| t.as_ref()) == Some(target))
523+
&& &self.bins == exes
524+
}
476525
}
477526

478527
/// Determines the root directory where installation is done.

src/doc/src/reference/unstable.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,9 @@ If any of these values change, then Cargo will reinstall the package.
258258
Installation will still fail if a different package installs a binary of the
259259
same name. `--force` may be used to unconditionally reinstall the package.
260260

261+
Installing with `--path` will always build and install, unless there are
262+
conflicting binaries from another package.
263+
261264
Additionally, a new flag `--no-track` is available to prevent `cargo install`
262265
from writing tracking information in `$CARGO_HOME` about which packages are
263266
installed.

0 commit comments

Comments
 (0)