Auto merge of #13979 - tweag:issue-12425, r=epage

Add `cargo update --breaking`

Related to #12425.

There are two kinds of manifest mutations here. In `upgrade_manifests` we have to mutate `cargo::core::manifest::Manifest` so that `resolve_ws` does what it needs to do, and outputs a correct lock file. Then, in `write_manifest_upgrades` we mutate `cargo::util::toml_mut::manifest::Manifest`, because that is how we can preserve the existing formatting in the manifest files on disk.

Some of the code here is copied from `cargo-edit`.

# Comparison with `cargo upgrade`

Running on the Cargo source itself gives the following:

```
❯ cargo upgrade --dry-run --incompatible allow --compatible ignore
    Updating 'https://github.com/rust-lang/crates.io-index' index
    Checking benchsuite's dependencies
    Checking capture's dependencies
    Checking cargo's dependencies
name               old req compatible latest  new req note
====               ======= ========== ======  ======= ====
anstream           0.6.13  0.6.14     0.6.14  0.6.13  compatible
anstyle            1.0.6   1.0.7      1.0.7   1.0.6   compatible
anyhow             1.0.82  1.0.86     1.0.86  1.0.82  compatible
itertools          0.12.1  0.12.1     0.13.0  0.13.0
libc               0.2.154 0.2.155    0.2.155 0.2.154 compatible
opener             0.7.0   0.7.1      0.7.1   0.7.0   compatible
openssl            0.10.57 0.10.64    0.10.64 0.10.57 compatible
openssl-sys        =0.9.92 0.9.92     0.9.102 =0.9.92 pinned
pulldown-cmark     0.10.3  0.10.3     0.11.0  0.11.0
security-framework 2.10.0  2.11.0     2.11.0  2.10.0  compatible
semver             1.0.22  1.0.23     1.0.23  1.0.22  compatible
serde              1.0.199 1.0.203    1.0.203 1.0.199 compatible
serde-untagged     0.1.5   0.1.6      0.1.6   0.1.5   compatible
serde_json         1.0.116 1.0.117    1.0.117 1.0.116 compatible
tar                0.4.40  0.4.41     0.4.41  0.4.40  compatible
thiserror          1.0.59  1.0.61     1.0.61  1.0.59  compatible
toml               0.8.12  0.8.14     0.8.14  0.8.12  compatible
toml_edit          0.22.12 0.22.14    0.22.14 0.22.12 compatible
unicode-width      0.1.12  0.1.13     0.1.13  0.1.12  compatible
    Checking cargo-credential's dependencies
    Checking cargo-credential-1password's dependencies
    Checking cargo-credential-libsecret's dependencies
    Checking cargo-credential-macos-keychain's dependencies
    Checking cargo-credential-wincred's dependencies
    Checking cargo-platform's dependencies
    Checking cargo-test-macro's dependencies
    Checking cargo-test-support's dependencies
    Checking cargo-util's dependencies
    Checking cargo-util-schemas's dependencies
    Checking crates-io's dependencies
    Checking home's dependencies
    Checking mdman's dependencies
    Checking resolver-tests's dependencies
    Checking rustfix's dependencies
    Checking semver-check's dependencies
    Checking xtask-build-man's dependencies
    Checking xtask-bump-check's dependencies
    Checking xtask-stale-label's dependencies
note: Re-run with `--pinned` to upgrade pinned version requirements
note: Re-run with `--verbose` to show all dependencies
  local: cargo
  unchanged: annotate-snippets, base64, bytesize, cargo-credential, cargo-credential-libsecret, cargo-credential-macos-keychain, cargo-credential-wincred, cargo-platform, cargo-test-macro, cargo-test-support, cargo-util, cargo-util-schemas, cargo_metadata, clap, color-print, core-foundation, crates-io, criterion, curl, curl-sys, filetime, flate2, git2, git2-curl, gix, glob, handlebars, hex, hmac, home, http-auth, humantime, ignore, im-rc, indexmap, jobserver, lazycell, libgit2-sys, libloading, memchr, miow, os_info, pasetors, pathdiff, percent-encoding, pkg-config, proptest, rand, regex, rusqlite, rustfix, same-file, serde-value, serde_ignored, sha1, sha2, shell-escape, similar, snapbox, supports-hyperlinks, supports-unicode, tempfile, time, tracing, tracing-chrome, tracing-subscriber, unicase, unicode-xid, url, varisat, walkdir, windows-sys
warning: aborting upgrade due to dry run

❯ target/debug/cargo update --breaking --dry-run -Zunstable-options
    Updating crates.io index
   Upgrading itertools ^0.12.1 -> ^0.13.0
   Upgrading pulldown-cmark ^0.10.3 -> ^0.11.0
    Updating crates.io index
     Locking 3 packages to latest compatible versions
    Updating itertools v0.12.1 -> v0.13.0
    Updating pulldown-cmark v0.10.3 -> v0.11.0
    Updating pulldown-cmark-escape v0.10.0 -> v0.11.0
warning: not updating any files due to dry run
```

In both cases we see an upgrade of `itertools` to `^0.13.0` and `pulldown-cmark` to `^0.11.0`.

The diff to the manifest (when it isn't a dry run) is as follows:

```
--- a/Cargo.toml
+++ b/Cargo.toml
`@@` -57,7 +57,7 `@@` humantime = "2.1.0"
 ignore = "0.4.22"
 im-rc = "15.1.0"
 indexmap = "2.2.6"
-itertools = "0.12.1"
+itertools = "0.13.0"
 jobserver = "0.1.31"
 lazycell = "1.3.0"
 libc = "0.2.154"
`@@` -74,7 +74,7 `@@` pathdiff = "0.2.1"
 percent-encoding = "2.3.1"
 pkg-config = "0.3.30"
 proptest = "1.4.0"
-pulldown-cmark = { version = "0.10.3", default-features = false, features = ["html"] }
+pulldown-cmark = { version = "0.11.0", default-features = false, features = ["html"] }
 rand = "0.8.5"
 regex = "1.10.4"
 rusqlite = { version = "0.31.0", features = ["bundled"] }
```

# TODO

- [x] In the case of `--incompatible`, we also need to let `update_lockfile` use `upgrades` in order to only update the incompatible dependencies.
- [x] Testing all the different cases of package sources, version requirements, pinned versions, renamed dependencies, inherited workspace dependencies, multiple versions of the same dependency, selecting a subset `--package`, etc.
- [x] Passing tests.
- [x] Implement suggestions from reviews.
- [x] The preservation of formatting in manifest files should be improved.
- [x] Compare with `cargo upgrade`.
This commit is contained in:
bors 2024-06-07 17:11:18 +00:00
commit fb123c668b
26 changed files with 1127 additions and 74 deletions

View file

@ -33,6 +33,7 @@ fn do_resolve<'gctx>(gctx: &'gctx GlobalContext, ws_root: &Path) -> ResolveInfo<
let force_all_targets = ForceAllTargets::No;
// Do an initial run to download anything necessary so that it does
// not confuse criterion's warmup.
let dry_run = false;
let ws_resolve = cargo::ops::resolve_ws_with_opts(
&ws,
&mut target_data,
@ -41,6 +42,7 @@ fn do_resolve<'gctx>(gctx: &'gctx GlobalContext, ws_root: &Path) -> ResolveInfo<
&specs,
has_dev_units,
force_all_targets,
dry_run,
)
.unwrap();
ResolveInfo {
@ -71,6 +73,7 @@ fn resolve_ws(c: &mut Criterion) {
// iterator once, and we don't want to call `do_resolve` in every
// "step", since that would just be some useless work.
let mut lazy_info = None;
let dry_run = false;
group.bench_function(&ws_name, |b| {
let ResolveInfo {
ws,
@ -91,6 +94,7 @@ fn resolve_ws(c: &mut Criterion) {
specs,
*has_dev_units,
*force_all_targets,
dry_run,
)
.unwrap();
})

View file

@ -176,6 +176,7 @@ static E2E_LITERAL_REDACTIONS: &[(&str, &str)] = &[
("[DIRTY]", " Dirty"),
("[LOCKING]", " Locking"),
("[UPDATING]", " Updating"),
("[UPGRADING]", " Upgrading"),
("[ADDING]", " Adding"),
("[REMOVING]", " Removing"),
("[REMOVED]", " Removed"),

View file

@ -214,11 +214,9 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
};
add(&ws, &options)?;
if !dry_run {
// Reload the workspace since we've changed dependencies
let ws = args.workspace(gctx)?;
resolve_ws(&ws)?;
}
// Reload the workspace since we've changed dependencies
let ws = args.workspace(gctx)?;
resolve_ws(&ws, dry_run)?;
Ok(())
}

View file

@ -121,7 +121,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
ws.gctx()
.shell()
.set_verbosity(cargo::core::Verbosity::Quiet);
let resolve = resolve_ws(&ws);
let resolve = resolve_ws(&ws, dry_run);
ws.gctx().shell().set_verbosity(verbosity);
resolve?.1
};
@ -129,7 +129,7 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
// Attempt to gc unused patches and re-resolve if anything is removed
if gc_unused_patches(&workspace, &resolve)? {
let ws = args.workspace(gctx)?;
resolve_ws(&ws)?;
resolve_ws(&ws, dry_run)?;
}
}
Ok(())

View file

@ -35,6 +35,13 @@ pub fn cli() -> Command {
.value_name("PRECISE")
.requires("package-group"),
)
.arg(
flag(
"breaking",
"Upgrade [SPEC] to latest breaking versions, unless pinned (unstable)",
)
.short('b'),
)
.arg_silent_suggestion()
.arg(
flag("workspace", "Only update the workspace packages")
@ -59,7 +66,8 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
gctx.cli_unstable().msrv_policy,
)?;
}
let ws = args.workspace(gctx)?;
let mut ws = args.workspace(gctx)?;
if args.is_present_with_zero_values("package") {
print_available_packages(&ws)?;
@ -89,6 +97,24 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
workspace: args.flag("workspace"),
gctx,
};
ops::update_lockfile(&ws, &update_opts)?;
if args.flag("breaking") {
gctx.cli_unstable()
.fail_if_stable_opt("--breaking", 12425)?;
let upgrades = ops::upgrade_manifests(&mut ws, &update_opts.to_update)?;
ops::resolve_ws(&ws, update_opts.dry_run)?;
ops::write_manifest_upgrades(&ws, &upgrades, update_opts.dry_run)?;
if update_opts.dry_run {
update_opts
.gctx
.shell()
.warn("aborting update due to dry run")?;
}
} else {
ops::update_lockfile(&ws, &update_opts)?;
}
Ok(())
}

View file

@ -149,6 +149,7 @@ pub fn resolve_std<'gctx>(
let cli_features = CliFeatures::from_command_line(
&features, /*all_features*/ false, /*uses_default_features*/ false,
)?;
let dry_run = false;
let resolve = ops::resolve_ws_with_opts(
&std_ws,
target_data,
@ -157,6 +158,7 @@ pub fn resolve_std<'gctx>(
&specs,
HasDevUnits::No,
crate::core::resolver::features::ForceAllTargets::No,
dry_run,
)?;
Ok((
resolve.pkg_set,

View file

@ -103,15 +103,25 @@ impl Summary {
Rc::make_mut(&mut self.inner).checksum = Some(cksum);
}
pub fn map_dependencies<F>(mut self, f: F) -> Summary
pub fn map_dependencies<F>(self, mut f: F) -> Summary
where
F: FnMut(Dependency) -> Dependency,
{
self.try_map_dependencies(|dep| Ok(f(dep))).unwrap()
}
pub fn try_map_dependencies<F>(mut self, f: F) -> CargoResult<Summary>
where
F: FnMut(Dependency) -> CargoResult<Dependency>,
{
{
let slot = &mut Rc::make_mut(&mut self.inner).dependencies;
*slot = mem::take(slot).into_iter().map(f).collect();
*slot = mem::take(slot)
.into_iter()
.map(f)
.collect::<CargoResult<_>>()?;
}
self
Ok(self)
}
pub fn map_source(self, to_replace: SourceId, replace_with: SourceId) -> Summary {

View file

@ -77,7 +77,14 @@ pub fn clean(ws: &Workspace<'_>, opts: &CleanOptions<'_>) -> CargoResult<()> {
if opts.spec.is_empty() {
clean_ctx.remove_paths(&[target_dir.into_path_unlocked()])?;
} else {
clean_specs(&mut clean_ctx, &ws, &profiles, &opts.targets, &opts.spec)?;
clean_specs(
&mut clean_ctx,
&ws,
&profiles,
&opts.targets,
&opts.spec,
opts.dry_run,
)?;
}
}
@ -91,11 +98,12 @@ fn clean_specs(
profiles: &Profiles,
targets: &[String],
spec: &[String],
dry_run: bool,
) -> CargoResult<()> {
// Clean specific packages.
let requested_kinds = CompileKind::from_requested_targets(clean_ctx.gctx, targets)?;
let target_data = RustcTargetData::new(ws, &requested_kinds)?;
let (pkg_set, resolve) = ops::resolve_ws(ws)?;
let (pkg_set, resolve) = ops::resolve_ws(ws, dry_run)?;
let prof_dir_name = profiles.get_dir_name();
let host_layout = Layout::new(ws, None, &prof_dir_name)?;
// Convert requested kinds to a Vec of layouts.

View file

@ -264,6 +264,7 @@ pub fn create_bcx<'a, 'gctx>(
HasDevUnits::No
}
};
let dry_run = false;
let resolve = ops::resolve_ws_with_opts(
ws,
&mut target_data,
@ -272,6 +273,7 @@ pub fn create_bcx<'a, 'gctx>(
&specs,
has_dev_units,
crate::core::resolver::features::ForceAllTargets::No,
dry_run,
)?;
let WorkspaceResolve {
mut pkg_set,

View file

@ -19,7 +19,8 @@ pub fn fetch<'a>(
options: &FetchOptions<'a>,
) -> CargoResult<(Resolve, PackageSet<'a>)> {
ws.emit_warnings()?;
let (mut packages, resolve) = ops::resolve_ws(ws)?;
let dry_run = false;
let (mut packages, resolve) = ops::resolve_ws(ws, dry_run)?;
let jobs = Some(JobsConfig::Integer(1));
let keep_going = false;

View file

@ -561,7 +561,8 @@ impl<'gctx> InstallablePackage<'gctx> {
// It would be best if `source` could be passed in here to avoid a
// duplicate "Updating", but since `source` is taken by value, then it
// wouldn't be available for `compile_ws`.
let (pkg_set, resolve) = ops::resolve_ws(&self.ws)?;
let dry_run = false;
let (pkg_set, resolve) = ops::resolve_ws(&self.ws, dry_run)?;
ops::check_yanked(
self.ws.gctx(),
&pkg_set,

View file

@ -142,6 +142,7 @@ fn build_resolve_graph(
// Note that even with --filter-platform we end up downloading host dependencies as well,
// as that is the behavior of download_accessible.
let dry_run = false;
let ws_resolve = ops::resolve_ws_with_opts(
ws,
&mut target_data,
@ -150,6 +151,7 @@ fn build_resolve_graph(
&specs,
HasDevUnits::Yes,
force_all,
dry_run,
)?;
let package_map: BTreeMap<PackageId, Package> = ws_resolve

View file

@ -190,7 +190,8 @@ pub fn package(ws: &Workspace<'_>, opts: &PackageOpts<'_>) -> CargoResult<Option
if ws.root().join("Cargo.lock").exists() {
// Make sure the Cargo.lock is up-to-date and valid.
let _ = ops::resolve_ws(ws)?;
let dry_run = false;
let _ = ops::resolve_ws(ws, dry_run)?;
// If Cargo.lock does not exist, it will be generated by `build_lock`
// below, and will be validated during the verification step.
}

View file

@ -1,3 +1,4 @@
use crate::core::dependency::Dependency;
use crate::core::registry::PackageRegistry;
use crate::core::resolver::features::{CliFeatures, HasDevUnits};
use crate::core::shell::Verbosity;
@ -8,11 +9,18 @@ use crate::ops;
use crate::sources::source::QueryKind;
use crate::util::cache_lock::CacheLockMode;
use crate::util::context::GlobalContext;
use crate::util::style;
use crate::util::CargoResult;
use crate::util::toml_mut::dependency::{MaybeWorkspace, Source};
use crate::util::toml_mut::manifest::LocalManifest;
use crate::util::toml_mut::upgrade::upgrade_requirement;
use crate::util::{style, OptVersionReq};
use crate::util::{CargoResult, VersionExt};
use itertools::Itertools;
use semver::{Op, Version, VersionReq};
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use tracing::debug;
use std::collections::{BTreeMap, HashMap, HashSet};
use tracing::{debug, trace};
pub type UpgradeMap = HashMap<(String, SourceId), Version>;
pub struct UpdateOptions<'a> {
pub gctx: &'a GlobalContext,
@ -206,6 +214,251 @@ pub fn print_lockfile_changes(
print_lockfile_generation(ws, resolve, registry)
}
}
pub fn upgrade_manifests(
ws: &mut Workspace<'_>,
to_update: &Vec<String>,
) -> CargoResult<UpgradeMap> {
let gctx = ws.gctx();
let mut upgrades = HashMap::new();
let mut upgrade_messages = HashSet::new();
// Updates often require a lot of modifications to the registry, so ensure
// that we're synchronized against other Cargos.
let _lock = gctx.acquire_package_cache_lock(CacheLockMode::DownloadExclusive)?;
let mut registry = PackageRegistry::new(gctx)?;
registry.lock_patches();
for member in ws.members_mut().sorted() {
debug!("upgrading manifest for `{}`", member.name());
*member.manifest_mut().summary_mut() = member
.manifest()
.summary()
.clone()
.try_map_dependencies(|d| {
upgrade_dependency(
&gctx,
to_update,
&mut registry,
&mut upgrades,
&mut upgrade_messages,
d,
)
})?;
}
Ok(upgrades)
}
fn upgrade_dependency(
gctx: &GlobalContext,
to_update: &Vec<String>,
registry: &mut PackageRegistry<'_>,
upgrades: &mut UpgradeMap,
upgrade_messages: &mut HashSet<String>,
dependency: Dependency,
) -> CargoResult<Dependency> {
let name = dependency.package_name();
let renamed_to = dependency.name_in_toml();
if name != renamed_to {
trace!(
"skipping dependency renamed from `{}` to `{}`",
name,
renamed_to
);
return Ok(dependency);
}
if !to_update.is_empty() && !to_update.contains(&name.to_string()) {
trace!("skipping dependency `{}` not selected for upgrading", name);
return Ok(dependency);
}
if !dependency.source_id().is_registry() {
trace!("skipping non-registry dependency: {}", name);
return Ok(dependency);
}
let version_req = dependency.version_req();
let OptVersionReq::Req(current) = version_req else {
trace!(
"skipping dependency `{}` without a simple version requirement: {}",
name,
version_req
);
return Ok(dependency);
};
let [comparator] = &current.comparators[..] else {
trace!(
"skipping dependency `{}` with multiple version comparators: {:?}",
name,
&current.comparators
);
return Ok(dependency);
};
if comparator.op != Op::Caret {
trace!("skipping non-caret dependency `{}`: {}", name, comparator);
return Ok(dependency);
}
let query =
crate::core::dependency::Dependency::parse(name, None, dependency.source_id().clone())?;
let possibilities = {
loop {
match registry.query_vec(&query, QueryKind::Exact) {
std::task::Poll::Ready(res) => {
break res?;
}
std::task::Poll::Pending => registry.block_until_ready()?,
}
}
};
let latest = if !possibilities.is_empty() {
possibilities
.iter()
.map(|s| s.as_summary())
.map(|s| s.version())
.filter(|v| !v.is_prerelease())
.max()
} else {
None
};
let Some(latest) = latest else {
trace!(
"skipping dependency `{}` without any published versions",
name
);
return Ok(dependency);
};
if current.matches(&latest) {
trace!(
"skipping dependency `{}` without a breaking update available",
name
);
return Ok(dependency);
}
let Some(new_req_string) = upgrade_requirement(&current.to_string(), latest)? else {
trace!(
"skipping dependency `{}` because the version requirement didn't change",
name
);
return Ok(dependency);
};
let upgrade_message = format!("{} {} -> {}", name, current, new_req_string);
trace!(upgrade_message);
if upgrade_messages.insert(upgrade_message.clone()) {
gctx.shell()
.status_with_color("Upgrading", &upgrade_message, &style::GOOD)?;
}
upgrades.insert((name.to_string(), dependency.source_id()), latest.clone());
let req = OptVersionReq::Req(VersionReq::parse(&latest.to_string())?);
let mut dep = dependency.clone();
dep.set_version_req(req);
Ok(dep)
}
/// Update manifests with upgraded versions, and write to disk. Based on cargo-edit.
/// Returns true if any file has changed.
pub fn write_manifest_upgrades(
ws: &Workspace<'_>,
upgrades: &UpgradeMap,
dry_run: bool,
) -> CargoResult<bool> {
if upgrades.is_empty() {
return Ok(false);
}
let mut any_file_has_changed = false;
let manifest_paths = std::iter::once(ws.root_manifest())
.chain(ws.members().map(|member| member.manifest_path()))
.collect::<Vec<_>>();
for manifest_path in manifest_paths {
trace!(
"updating TOML manifest at `{:?}` with upgraded dependencies",
manifest_path
);
let crate_root = manifest_path
.parent()
.expect("manifest path is absolute")
.to_owned();
let mut local_manifest = LocalManifest::try_new(&manifest_path)?;
let mut manifest_has_changed = false;
for dep_table in local_manifest.get_dependency_tables_mut() {
for (mut dep_key, dep_item) in dep_table.iter_mut() {
let dep_key_str = dep_key.get();
let dependency = crate::util::toml_mut::dependency::Dependency::from_toml(
&manifest_path,
dep_key_str,
dep_item,
)?;
let Some(current) = dependency.version() else {
trace!("skipping dependency without a version: {}", dependency.name);
continue;
};
let (MaybeWorkspace::Other(source_id), Some(Source::Registry(source))) =
(dependency.source_id(ws.gctx())?, dependency.source())
else {
trace!("skipping non-registry dependency: {}", dependency.name);
continue;
};
let Some(latest) = upgrades.get(&(dependency.name.to_owned(), source_id)) else {
trace!(
"skipping dependency without an upgrade: {}",
dependency.name
);
continue;
};
let Some(new_req_string) = upgrade_requirement(current, latest)? else {
trace!(
"skipping dependency `{}` because the version requirement didn't change",
dependency.name
);
continue;
};
let mut dep = dependency.clone();
let mut source = source.clone();
source.version = new_req_string;
dep.source = Some(Source::Registry(source));
trace!("upgrading dependency {}", dependency.name);
dep.update_toml(&crate_root, &mut dep_key, dep_item);
manifest_has_changed = true;
any_file_has_changed = true;
}
}
if manifest_has_changed && !dry_run {
debug!("writing upgraded manifest to {}", manifest_path.display());
local_manifest.write()?;
}
}
Ok(any_file_has_changed)
}
fn print_lockfile_generation(
ws: &Workspace<'_>,

View file

@ -506,6 +506,7 @@ fn check_resolver_change<'gctx>(
assert_eq!(ws.resolve_behavior(), ResolveBehavior::V1);
let specs = opts.compile_opts.spec.to_package_id_specs(ws)?;
let mut resolve_differences = |has_dev_units| -> CargoResult<(WorkspaceResolve<'_>, DiffMap)> {
let dry_run = false;
let ws_resolve = ops::resolve_ws_with_opts(
ws,
target_data,
@ -514,6 +515,7 @@ fn check_resolver_change<'gctx>(
&specs,
has_dev_units,
crate::core::resolver::features::ForceAllTargets::No,
dry_run,
)?;
let feature_opts = FeatureOpts::new_behavior(ResolveBehavior::V2, has_dev_units);

View file

@ -7,10 +7,6 @@ pub use self::cargo_compile::{
pub use self::cargo_compile::{CompileFilter, FilterRule, LibRule, Packages};
pub use self::cargo_doc::{doc, DocOptions, OutputFormat};
pub use self::cargo_fetch::{fetch, FetchOptions};
pub use self::cargo_generate_lockfile::generate_lockfile;
pub use self::cargo_generate_lockfile::print_lockfile_changes;
pub use self::cargo_generate_lockfile::update_lockfile;
pub use self::cargo_generate_lockfile::UpdateOptions;
pub use self::cargo_install::{install, install_list};
pub use self::cargo_new::{init, new, NewOptions, NewProjectKind, VersionControl};
pub use self::cargo_output_metadata::{output_metadata, ExportInfo, OutputMetadataOptions};
@ -20,6 +16,12 @@ pub use self::cargo_read_manifest::{read_package, read_packages};
pub use self::cargo_run::run;
pub use self::cargo_test::{run_benches, run_tests, TestOptions};
pub use self::cargo_uninstall::uninstall;
pub use self::cargo_update::generate_lockfile;
pub use self::cargo_update::print_lockfile_changes;
pub use self::cargo_update::update_lockfile;
pub use self::cargo_update::upgrade_manifests;
pub use self::cargo_update::write_manifest_upgrades;
pub use self::cargo_update::UpdateOptions;
pub use self::fix::{fix, fix_exec_rustc, fix_get_proxy_lock_addr, FixOptions};
pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile};
pub use self::registry::modify_owners;
@ -44,7 +46,6 @@ pub(crate) mod cargo_compile;
pub mod cargo_config;
mod cargo_doc;
mod cargo_fetch;
mod cargo_generate_lockfile;
mod cargo_install;
mod cargo_new;
mod cargo_output_metadata;
@ -55,6 +56,7 @@ pub mod cargo_remove;
mod cargo_run;
mod cargo_test;
mod cargo_uninstall;
mod cargo_update;
mod common_for_install_and_uninstall;
mod fix;
pub(crate) mod lockfile;

View file

@ -115,9 +115,9 @@ version. This may also occur with an optional dependency that is not enabled.";
///
/// This is a simple interface used by commands like `clean`, `fetch`, and
/// `package`, which don't specify any options or features.
pub fn resolve_ws<'a>(ws: &Workspace<'a>) -> CargoResult<(PackageSet<'a>, Resolve)> {
pub fn resolve_ws<'a>(ws: &Workspace<'a>, dry_run: bool) -> CargoResult<(PackageSet<'a>, Resolve)> {
let mut registry = PackageRegistry::new(ws.gctx())?;
let resolve = resolve_with_registry(ws, &mut registry)?;
let resolve = resolve_with_registry(ws, &mut registry, dry_run)?;
let packages = get_resolved_packages(&resolve, registry)?;
Ok((packages, resolve))
}
@ -140,6 +140,7 @@ pub fn resolve_ws_with_opts<'gctx>(
specs: &[PackageIdSpec],
has_dev_units: HasDevUnits,
force_all_targets: ForceAllTargets,
dry_run: bool,
) -> CargoResult<WorkspaceResolve<'gctx>> {
let mut registry = PackageRegistry::new(ws.gctx())?;
let (resolve, resolved_with_overrides) = if ws.ignore_lock() {
@ -160,7 +161,7 @@ pub fn resolve_ws_with_opts<'gctx>(
} else if ws.require_optional_deps() {
// First, resolve the root_package's *listed* dependencies, as well as
// downloading and updating all remotes and such.
let resolve = resolve_with_registry(ws, &mut registry)?;
let resolve = resolve_with_registry(ws, &mut registry, dry_run)?;
// No need to add patches again, `resolve_with_registry` has done it.
let add_patches = false;
@ -269,6 +270,7 @@ pub fn resolve_ws_with_opts<'gctx>(
fn resolve_with_registry<'gctx>(
ws: &Workspace<'gctx>,
registry: &mut PackageRegistry<'gctx>,
dry_run: bool,
) -> CargoResult<Resolve> {
let prev = ops::load_pkg_lockfile(ws)?;
let mut resolve = resolve_with_previous(
@ -283,7 +285,11 @@ fn resolve_with_registry<'gctx>(
)?;
let print = if !ws.is_ephemeral() && ws.require_optional_deps() {
ops::write_pkg_lockfile(ws, &mut resolve)?
if !dry_run {
ops::write_pkg_lockfile(ws, &mut resolve)?
} else {
true
}
} else {
// This mostly represents
// - `cargo install --locked` and the only change is the package is no longer local but

View file

@ -131,6 +131,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
} else {
ForceAllTargets::No
};
let dry_run = false;
let ws_resolve = ops::resolve_ws_with_opts(
ws,
&mut target_data,
@ -139,6 +140,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<()
&specs,
has_dev,
force_all,
dry_run,
)?;
let package_map: HashMap<PackageId, &Package> = ws_resolve

View file

@ -80,6 +80,7 @@ fn sync(
workspaces: &[&Workspace<'_>],
opts: &VendorOptions<'_>,
) -> CargoResult<VendorConfig> {
let dry_run = false;
let canonical_destination = try_canonicalize(opts.destination);
let canonical_destination = canonical_destination.as_deref().unwrap_or(opts.destination);
let dest_dir_already_exists = canonical_destination.exists();
@ -112,7 +113,7 @@ fn sync(
// crate to work with.
for ws in workspaces {
let (packages, resolve) =
ops::resolve_ws(ws).with_context(|| "failed to load pkg lockfile")?;
ops::resolve_ws(ws, dry_run).with_context(|| "failed to load pkg lockfile")?;
packages
.get_many(resolve.iter())
@ -144,7 +145,7 @@ fn sync(
// tables about them.
for ws in workspaces {
let (packages, resolve) =
ops::resolve_ws(ws).with_context(|| "failed to load pkg lockfile")?;
ops::resolve_ws(ws, dry_run).with_context(|| "failed to load pkg lockfile")?;
packages
.get_many(resolve.iter())

View file

@ -276,23 +276,6 @@ impl LocalManifest {
/// Write changes back to the file.
pub fn write(&self) -> CargoResult<()> {
if !self.manifest.data.contains_key("package")
&& !self.manifest.data.contains_key("project")
{
if self.manifest.data.contains_key("workspace") {
anyhow::bail!(
"found virtual manifest at {}, but this command requires running against an \
actual package in this workspace.",
self.path.display()
);
} else {
anyhow::bail!(
"missing expected `package` or `project` fields in {}",
self.path.display()
);
}
}
let s = self.manifest.data.to_string();
let new_contents_bytes = s.as_bytes();
@ -397,6 +380,55 @@ impl LocalManifest {
Ok(())
}
/// Allow mutating depedencies, wherever they live.
/// Copied from cargo-edit.
pub fn get_dependency_tables_mut(
&mut self,
) -> impl Iterator<Item = &mut dyn toml_edit::TableLike> + '_ {
let root = self.data.as_table_mut();
root.iter_mut().flat_map(|(k, v)| {
if DepTable::KINDS
.iter()
.any(|dt| dt.kind.kind_table() == k.get())
{
v.as_table_like_mut().into_iter().collect::<Vec<_>>()
} else if k == "workspace" {
v.as_table_like_mut()
.unwrap()
.iter_mut()
.filter_map(|(k, v)| {
if k.get() == "dependencies" {
v.as_table_like_mut()
} else {
None
}
})
.collect::<Vec<_>>()
} else if k == "target" {
v.as_table_like_mut()
.unwrap()
.iter_mut()
.flat_map(|(_, v)| {
v.as_table_like_mut().into_iter().flat_map(|v| {
v.iter_mut().filter_map(|(k, v)| {
if DepTable::KINDS
.iter()
.any(|dt| dt.kind.kind_table() == k.get())
{
v.as_table_like_mut()
} else {
None
}
})
})
})
.collect::<Vec<_>>()
} else {
Vec::new()
}
})
}
/// Remove references to `dep_key` if its no longer present.
pub fn gc_dep(&mut self, dep_key: &str) {
let explicit_dep_activation = self.is_explicit_dep_activation(dep_key);

View file

@ -11,6 +11,7 @@
pub mod dependency;
pub mod manifest;
pub mod upgrade;
// Based on Iterator::is_sorted from nightly std; remove in favor of that when stabilized.
pub fn is_sorted(mut it: impl Iterator<Item = impl PartialOrd>) -> bool {

View file

@ -0,0 +1,219 @@
use std::fmt::Display;
use crate::CargoResult;
/// Upgrade an existing requirement to a new version.
/// Copied from cargo-edit.
pub(crate) fn upgrade_requirement(
req: &str,
version: &semver::Version,
) -> CargoResult<Option<String>> {
let req_text = req.to_string();
let raw_req = semver::VersionReq::parse(&req_text)
.expect("semver to generate valid version requirements");
if raw_req.comparators.is_empty() {
// Empty matches everything, no-change.
Ok(None)
} else {
let comparators: CargoResult<Vec<_>> = raw_req
.comparators
.into_iter()
.map(|p| set_comparator(p, version))
.collect();
let comparators = comparators?;
let new_req = semver::VersionReq { comparators };
let mut new_req_text = new_req.to_string();
if new_req_text.starts_with('^') && !req.starts_with('^') {
new_req_text.remove(0);
}
// Validate contract
#[cfg(debug_assertions)]
{
assert!(
new_req.matches(version),
"New req {} is invalid, because {} does not match {}",
new_req_text,
new_req,
version
)
}
if new_req_text == req_text {
Ok(None)
} else {
Ok(Some(new_req_text))
}
}
}
fn set_comparator(
mut pred: semver::Comparator,
version: &semver::Version,
) -> CargoResult<semver::Comparator> {
match pred.op {
semver::Op::Wildcard => {
pred.major = version.major;
if pred.minor.is_some() {
pred.minor = Some(version.minor);
}
if pred.patch.is_some() {
pred.patch = Some(version.patch);
}
Ok(pred)
}
semver::Op::Exact => Ok(assign_partial_req(version, pred)),
semver::Op::Greater | semver::Op::GreaterEq | semver::Op::Less | semver::Op::LessEq => {
let user_pred = pred.to_string();
Err(unsupported_version_req(user_pred))
}
semver::Op::Tilde => Ok(assign_partial_req(version, pred)),
semver::Op::Caret => Ok(assign_partial_req(version, pred)),
_ => {
let user_pred = pred.to_string();
Err(unsupported_version_req(user_pred))
}
}
}
fn assign_partial_req(
version: &semver::Version,
mut pred: semver::Comparator,
) -> semver::Comparator {
pred.major = version.major;
if pred.minor.is_some() {
pred.minor = Some(version.minor);
}
if pred.patch.is_some() {
pred.patch = Some(version.patch);
}
pred.pre = version.pre.clone();
pred
}
fn unsupported_version_req(req: impl Display) -> anyhow::Error {
anyhow::format_err!("Support for modifying {} is currently unsupported", req)
}
#[cfg(test)]
mod test {
use super::*;
mod upgrade_requirement {
use super::*;
#[track_caller]
fn assert_req_bump<'a, O: Into<Option<&'a str>>>(version: &str, req: &str, expected: O) {
let version = semver::Version::parse(version).unwrap();
let actual = upgrade_requirement(req, &version).unwrap();
let expected = expected.into();
assert_eq!(actual.as_deref(), expected);
}
#[test]
fn wildcard_major() {
assert_req_bump("1.0.0", "*", None);
}
#[test]
fn wildcard_minor() {
assert_req_bump("1.0.0", "1.*", None);
assert_req_bump("1.1.0", "1.*", None);
assert_req_bump("2.0.0", "1.*", "2.*");
}
#[test]
fn wildcard_patch() {
assert_req_bump("1.0.0", "1.0.*", None);
assert_req_bump("1.1.0", "1.0.*", "1.1.*");
assert_req_bump("1.1.1", "1.0.*", "1.1.*");
assert_req_bump("2.0.0", "1.0.*", "2.0.*");
}
#[test]
fn caret_major() {
assert_req_bump("1.0.0", "1", None);
assert_req_bump("1.0.0", "^1", None);
assert_req_bump("1.1.0", "1", None);
assert_req_bump("1.1.0", "^1", None);
assert_req_bump("2.0.0", "1", "2");
assert_req_bump("2.0.0", "^1", "^2");
}
#[test]
fn caret_minor() {
assert_req_bump("1.0.0", "1.0", None);
assert_req_bump("1.0.0", "^1.0", None);
assert_req_bump("1.1.0", "1.0", "1.1");
assert_req_bump("1.1.0", "^1.0", "^1.1");
assert_req_bump("1.1.1", "1.0", "1.1");
assert_req_bump("1.1.1", "^1.0", "^1.1");
assert_req_bump("2.0.0", "1.0", "2.0");
assert_req_bump("2.0.0", "^1.0", "^2.0");
}
#[test]
fn caret_patch() {
assert_req_bump("1.0.0", "1.0.0", None);
assert_req_bump("1.0.0", "^1.0.0", None);
assert_req_bump("1.1.0", "1.0.0", "1.1.0");
assert_req_bump("1.1.0", "^1.0.0", "^1.1.0");
assert_req_bump("1.1.1", "1.0.0", "1.1.1");
assert_req_bump("1.1.1", "^1.0.0", "^1.1.1");
assert_req_bump("2.0.0", "1.0.0", "2.0.0");
assert_req_bump("2.0.0", "^1.0.0", "^2.0.0");
}
#[test]
fn tilde_major() {
assert_req_bump("1.0.0", "~1", None);
assert_req_bump("1.1.0", "~1", None);
assert_req_bump("2.0.0", "~1", "~2");
}
#[test]
fn tilde_minor() {
assert_req_bump("1.0.0", "~1.0", None);
assert_req_bump("1.1.0", "~1.0", "~1.1");
assert_req_bump("1.1.1", "~1.0", "~1.1");
assert_req_bump("2.0.0", "~1.0", "~2.0");
}
#[test]
fn tilde_patch() {
assert_req_bump("1.0.0", "~1.0.0", None);
assert_req_bump("1.1.0", "~1.0.0", "~1.1.0");
assert_req_bump("1.1.1", "~1.0.0", "~1.1.1");
assert_req_bump("2.0.0", "~1.0.0", "~2.0.0");
}
#[test]
fn equal_major() {
assert_req_bump("1.0.0", "=1", None);
assert_req_bump("1.1.0", "=1", None);
assert_req_bump("2.0.0", "=1", "=2");
}
#[test]
fn equal_minor() {
assert_req_bump("1.0.0", "=1.0", None);
assert_req_bump("1.1.0", "=1.0", "=1.1");
assert_req_bump("1.1.1", "=1.0", "=1.1");
assert_req_bump("2.0.0", "=1.0", "=2.0");
}
#[test]
fn equal_patch() {
assert_req_bump("1.0.0", "=1.0.0", None);
assert_req_bump("1.1.0", "=1.0.0", "=1.1.0");
assert_req_bump("1.1.1", "=1.0.0", "=1.1.1");
assert_req_bump("2.0.0", "=1.0.0", "=2.0.0");
}
}
}

View file

@ -73,6 +73,7 @@ For the latest nightly, see the [nightly version] of this page.
* [public-dependency](#public-dependency) --- Allows dependencies to be classified as either public or private.
* [msrv-policy](#msrv-policy) --- MSRV-aware resolver and version selection
* [precise-pre-release](#precise-pre-release) --- Allows pre-release versions to be selected with `update --precise`
* [update-breaking](#update-breaking) --- Allows upgrading to breaking versions with `update --breaking`
* Output behavior
* [artifact-dir](#artifact-dir) --- Adds a directory where artifacts are copied to.
* [Different binary name](#different-binary-name) --- Assign a name to the built binary that is separate from the crate name.
@ -378,6 +379,25 @@ It's possible to update `my-dependency` to a pre-release with `update -Zunstable
This is because `0.1.2-pre.0` is considered compatible with `0.1.1`.
It would not be possible to upgrade to `0.2.0-pre.0` from `0.1.1` in the same way.
## update-breaking
* Tracking Issue: [#12425](https://github.com/rust-lang/cargo/issues/12425)
This feature allows upgrading dependencies to breaking versions with
`update --breaking`.
This is essentially migrating `cargo upgrade` from `cargo-edit` into Cargo itself,
and involves making changes to the `Cargo.toml` manifests, not just the lock file.
When doing a breaking update, Cargo will keep all non-breaking dependencies
unchanged. It will also not change any dependencies that use a different version
operator than the default caret. Also, it will not upgrade any renamed package
dependencies. Example:
```sh
cargo +nightly update --breaking -Z unstable-options
```
## build-std
* Tracking Repository: <https://github.com/rust-lang/wg-cargo-std-aware>

View file

@ -1,4 +1,4 @@
<svg width="852px" height="524px" xmlns="http://www.w3.org/2000/svg">
<svg width="852px" height="542px" xmlns="http://www.w3.org/2000/svg">
<style>
.fg { fill: #AAAAAA }
.bg { background: #000000 }
@ -35,45 +35,47 @@
</tspan>
<tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-cyan bold">--precise</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PRECISE&gt;</tspan><tspan> Update [SPEC] to exactly PRECISE</tspan>
</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan> Use verbose output (-vv very verbose/build.rs output)</tspan>
<tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-cyan bold">-b</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--breaking</tspan><tspan> Upgrade [SPEC] to latest breaking versions, unless pinned (unstable)</tspan>
</tspan>
<tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan> Do not print cargo log messages</tspan>
<tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-cyan bold">-v</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--verbose</tspan><tspan class="fg-cyan">...</tspan><tspan> Use verbose output (-vv very verbose/build.rs output)</tspan>
</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan> Coloring: auto, always, never</tspan>
<tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-cyan bold">-q</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--quiet</tspan><tspan> Do not print cargo log messages</tspan>
</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan> Override a configuration value</tspan>
<tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-cyan bold">--color</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;WHEN&gt;</tspan><tspan> Coloring: auto, always, never</tspan>
</tspan>
<tspan x="10px" y="244px"><tspan> </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details</tspan>
<tspan x="10px" y="244px"><tspan> </tspan><tspan class="fg-cyan bold">--config</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;KEY=VALUE&gt;</tspan><tspan> Override a configuration value</tspan>
</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan> Print help</tspan>
<tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-cyan bold">-Z</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;FLAG&gt;</tspan><tspan> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details</tspan>
</tspan>
<tspan x="10px" y="280px">
<tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-cyan bold">-h</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--help</tspan><tspan> Print help</tspan>
</tspan>
<tspan x="10px" y="298px"><tspan class="fg-green bold">Package Selection:</tspan>
<tspan x="10px" y="298px">
</tspan>
<tspan x="10px" y="316px"><tspan> </tspan><tspan class="fg-cyan bold">-w</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--workspace</tspan><tspan> Only update the workspace packages</tspan>
<tspan x="10px" y="316px"><tspan class="fg-green bold">Package Selection:</tspan>
</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-cyan">[SPEC]...</tspan><tspan> Package to update</tspan>
<tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-cyan bold">-w</tspan><tspan>, </tspan><tspan class="fg-cyan bold">--workspace</tspan><tspan> Only update the workspace packages</tspan>
</tspan>
<tspan x="10px" y="352px">
<tspan x="10px" y="352px"><tspan> </tspan><tspan class="fg-cyan">[SPEC]...</tspan><tspan> Package to update</tspan>
</tspan>
<tspan x="10px" y="370px"><tspan class="fg-green bold">Manifest Options:</tspan>
<tspan x="10px" y="370px">
</tspan>
<tspan x="10px" y="388px"><tspan> </tspan><tspan class="fg-cyan bold">--manifest-path</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PATH&gt;</tspan><tspan> Path to Cargo.toml</tspan>
<tspan x="10px" y="388px"><tspan class="fg-green bold">Manifest Options:</tspan>
</tspan>
<tspan x="10px" y="406px"><tspan> </tspan><tspan class="fg-cyan bold">--ignore-rust-version</tspan><tspan> Ignore `rust-version` specification in packages (unstable)</tspan>
<tspan x="10px" y="406px"><tspan> </tspan><tspan class="fg-cyan bold">--manifest-path</tspan><tspan class="fg-cyan"> </tspan><tspan class="fg-cyan">&lt;PATH&gt;</tspan><tspan> Path to Cargo.toml</tspan>
</tspan>
<tspan x="10px" y="424px"><tspan> </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan> Assert that `Cargo.lock` will remain unchanged</tspan>
<tspan x="10px" y="424px"><tspan> </tspan><tspan class="fg-cyan bold">--ignore-rust-version</tspan><tspan> Ignore `rust-version` specification in packages (unstable)</tspan>
</tspan>
<tspan x="10px" y="442px"><tspan> </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan> Run without accessing the network</tspan>
<tspan x="10px" y="442px"><tspan> </tspan><tspan class="fg-cyan bold">--locked</tspan><tspan> Assert that `Cargo.lock` will remain unchanged</tspan>
</tspan>
<tspan x="10px" y="460px"><tspan> </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan> Equivalent to specifying both --locked and --offline</tspan>
<tspan x="10px" y="460px"><tspan> </tspan><tspan class="fg-cyan bold">--offline</tspan><tspan> Run without accessing the network</tspan>
</tspan>
<tspan x="10px" y="478px">
<tspan x="10px" y="478px"><tspan> </tspan><tspan class="fg-cyan bold">--frozen</tspan><tspan> Equivalent to specifying both --locked and --offline</tspan>
</tspan>
<tspan x="10px" y="496px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help update</tspan><tspan class="bold">` for more detailed information.</tspan>
<tspan x="10px" y="496px">
</tspan>
<tspan x="10px" y="514px">
<tspan x="10px" y="514px"><tspan>Run `</tspan><tspan class="fg-cyan bold">cargo help update</tspan><tspan class="bold">` for more detailed information.</tspan>
</tspan>
<tspan x="10px" y="532px">
</tspan>
</text>

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View file

@ -1,7 +1,9 @@
//! Tests for the `cargo update` command.
use cargo_test_support::registry::Package;
use cargo_test_support::{basic_lib_manifest, basic_manifest, git, project};
use cargo_test_support::compare::assert_e2e;
use cargo_test_support::registry::{self};
use cargo_test_support::registry::{Dependency, Package};
use cargo_test_support::{basic_lib_manifest, basic_manifest, git, project, str};
#[cargo_test]
fn minor_update_two_places() {
@ -1637,3 +1639,458 @@ fn update_with_missing_feature() {
)
.run();
}
#[cargo_test]
fn update_breaking_unstable() {
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
"#,
)
.file("src/lib.rs", "")
.build();
p.cargo("update --breaking")
.masquerade_as_nightly_cargo(&["update-breaking"])
.with_status(101)
.with_stderr(
"\
[ERROR] the `--breaking` flag is unstable, pass `-Z unstable-options` to enable it
See https://github.com/rust-lang/cargo/issues/12425 for more information about the `--breaking` flag.
",
)
.run();
}
#[cargo_test]
fn update_breaking_dry_run() {
Package::new("incompatible", "1.0.0").publish();
Package::new("ws", "1.0.0").publish();
let root_manifest = r#"
# Check if formatting is preserved. Nothing here should change, due to dry-run.
[workspace]
members = ["foo"]
[workspace.dependencies]
ws = "1.0" # Preserve formatting
"#;
let crate_manifest = r#"
# Check if formatting is preserved. Nothing here should change, due to dry-run.
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
incompatible = "1.0" # Preserve formatting
ws.workspace = true # Preserve formatting
"#;
let p = project()
.file("Cargo.toml", root_manifest)
.file("foo/Cargo.toml", crate_manifest)
.file("foo/src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
let lock_file = p.read_file("Cargo.lock");
Package::new("incompatible", "1.0.1").publish();
Package::new("ws", "1.0.1").publish();
Package::new("incompatible", "2.0.0").publish();
Package::new("ws", "2.0.0").publish();
p.cargo("update -Zunstable-options --dry-run --breaking")
.masquerade_as_nightly_cargo(&["update-breaking"])
.with_stderr(
"\
[UPDATING] `dummy-registry` index
[UPGRADING] incompatible ^1.0 -> ^2.0
[UPGRADING] ws ^1.0 -> ^2.0
[LOCKING] 2 packages to latest compatible versions
[UPDATING] incompatible v1.0.0 -> v2.0.0
[UPDATING] ws v1.0.0 -> v2.0.0
[WARNING] aborting update due to dry run
",
)
.run();
let root_manifest_after = p.read_file("Cargo.toml");
assert_e2e().eq(&root_manifest_after, root_manifest);
let crate_manifest_after = p.read_file("foo/Cargo.toml");
assert_e2e().eq(&crate_manifest_after, crate_manifest);
let lock_file_after = p.read_file("Cargo.lock");
assert_e2e().eq(&lock_file_after, lock_file);
}
#[cargo_test]
fn update_breaking() {
registry::alt_init();
Package::new("compatible", "1.0.0").publish();
Package::new("incompatible", "1.0.0").publish();
Package::new("pinned", "1.0.0").publish();
Package::new("less-than", "1.0.0").publish();
Package::new("renamed-from", "1.0.0").publish();
Package::new("pre-release", "1.0.0").publish();
Package::new("yanked", "1.0.0").publish();
Package::new("ws", "1.0.0").publish();
Package::new("shared", "1.0.0").publish();
Package::new("multiple-versions", "1.0.0").publish();
Package::new("multiple-versions", "2.0.0").publish();
Package::new("alternative-1", "1.0.0")
.alternative(true)
.publish();
Package::new("alternative-2", "1.0.0")
.alternative(true)
.publish();
Package::new("bar", "1.0.0").alternative(true).publish();
Package::new("multiple-registries", "1.0.0").publish();
Package::new("multiple-registries", "2.0.0")
.alternative(true)
.publish();
Package::new("multiple-source-types", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
# Check if formatting is preserved
[workspace]
members = ["foo", "bar"]
[workspace.dependencies]
ws = "1.0" # This line gets partially rewritten
"#,
)
.file(
"foo/Cargo.toml",
r#"
# Check if formatting is preserved
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
compatible = "1.0" # Comment
incompatible = "1.0" # Comment
pinned = "=1.0" # Comment
less-than = "<99.0" # Comment
renamed-to = { package = "renamed-from", version = "1.0" } # Comment
pre-release = "1.0" # Comment
yanked = "1.0" # Comment
ws.workspace = true # Comment
shared = "1.0" # Comment
multiple-versions = "1.0" # Comment
alternative-1 = { registry = "alternative", version = "1.0" } # Comment
multiple-registries = "1.0" # Comment
bar = { path = "../bar", registry = "alternative", version = "1.0.0" } # Comment
multiple-source-types = { path = "../multiple-source-types", version = "1.0.0" } # Comment
[dependencies.alternative-2] # Comment
version = "1.0" # Comment
registry = "alternative" # Comment
"#,
)
.file("foo/src/lib.rs", "")
.file(
"bar/Cargo.toml",
r#"
[package]
name = "bar"
version = "1.0.0"
edition = "2015"
authors = []
[dependencies]
shared = "1.0"
multiple-versions = "2.0"
multiple-registries = { registry = "alternative", version = "2.0" } # Comment
multiple-source-types = "1.0" # Comment
"#,
)
.file("bar/src/lib.rs", "")
.file(
"multiple-source-types/Cargo.toml",
r#"
[package]
name = "multiple-source-types"
version = "1.0.0"
edition = "2015"
authors = []
"#,
)
.file("multiple-source-types/src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
Package::new("compatible", "1.0.1").publish();
Package::new("incompatible", "1.0.1").publish();
Package::new("pinned", "1.0.1").publish();
Package::new("less-than", "1.0.1").publish();
Package::new("renamed-from", "1.0.1").publish();
Package::new("ws", "1.0.1").publish();
Package::new("multiple-versions", "1.0.1").publish();
Package::new("multiple-versions", "2.0.1").publish();
Package::new("alternative-1", "1.0.1")
.alternative(true)
.publish();
Package::new("alternative-2", "1.0.1")
.alternative(true)
.publish();
Package::new("incompatible", "2.0.0").publish();
Package::new("pinned", "2.0.0").publish();
Package::new("less-than", "2.0.0").publish();
Package::new("renamed-from", "2.0.0").publish();
Package::new("pre-release", "2.0.0-alpha").publish();
Package::new("yanked", "2.0.0").yanked(true).publish();
Package::new("ws", "2.0.0").publish();
Package::new("shared", "2.0.0").publish();
Package::new("multiple-versions", "3.0.0").publish();
Package::new("alternative-1", "2.0.0")
.alternative(true)
.publish();
Package::new("alternative-2", "2.0.0")
.alternative(true)
.publish();
Package::new("bar", "2.0.0").alternative(true).publish();
Package::new("multiple-registries", "2.0.0").publish();
Package::new("multiple-registries", "3.0.0")
.alternative(true)
.publish();
Package::new("multiple-source-types", "2.0.0").publish();
p.cargo("update -Zunstable-options --breaking")
.masquerade_as_nightly_cargo(&["update-breaking"])
.with_stderr(
"\
[UPDATING] `alternative` index
[UPGRADING] multiple-registries ^2.0 -> ^3.0
[UPDATING] `dummy-registry` index
[UPGRADING] multiple-source-types ^1.0 -> ^2.0
[UPGRADING] multiple-versions ^2.0 -> ^3.0
[UPGRADING] shared ^1.0 -> ^2.0
[UPGRADING] alternative-1 ^1.0 -> ^2.0
[UPGRADING] alternative-2 ^1.0 -> ^2.0
[UPGRADING] incompatible ^1.0 -> ^2.0
[UPGRADING] multiple-registries ^1.0 -> ^2.0
[UPGRADING] multiple-versions ^1.0 -> ^3.0
[UPGRADING] ws ^1.0 -> ^2.0
[LOCKING] 9 packages to latest compatible versions
[UPDATING] alternative-1 v1.0.0 (registry `alternative`) -> v2.0.0
[UPDATING] alternative-2 v1.0.0 (registry `alternative`) -> v2.0.0
[UPDATING] incompatible v1.0.0 -> v2.0.0
[UPDATING] multiple-registries v2.0.0 (registry `alternative`) -> v3.0.0
[UPDATING] multiple-registries v1.0.0 -> v2.0.0
[UPDATING] multiple-source-types v1.0.0 -> v2.0.0
[ADDING] multiple-versions v3.0.0
[UPDATING] shared v1.0.0 -> v2.0.0
[UPDATING] ws v1.0.0 -> v2.0.0
",
)
.run();
let root_manifest = p.read_file("Cargo.toml");
assert_e2e().eq(
&root_manifest,
str![[r#"
# Check if formatting is preserved
[workspace]
members = ["foo", "bar"]
[workspace.dependencies]
ws = "2.0" # This line gets partially rewritten
"#]],
);
let foo_manifest = p.read_file("foo/Cargo.toml");
assert_e2e().eq(
&foo_manifest,
str![[r#"
# Check if formatting is preserved
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
compatible = "1.0" # Comment
incompatible = "2.0" # Comment
pinned = "=1.0" # Comment
less-than = "<99.0" # Comment
renamed-to = { package = "renamed-from", version = "1.0" } # Comment
pre-release = "1.0" # Comment
yanked = "1.0" # Comment
ws.workspace = true # Comment
shared = "2.0" # Comment
multiple-versions = "3.0" # Comment
alternative-1 = { registry = "alternative", version = "2.0" } # Comment
multiple-registries = "2.0" # Comment
bar = { path = "../bar", registry = "alternative", version = "1.0.0" } # Comment
multiple-source-types = { path = "../multiple-source-types", version = "1.0.0" } # Comment
[dependencies.alternative-2] # Comment
version = "2.0" # Comment
registry = "alternative" # Comment
"#]],
);
let bar_manifest = p.read_file("bar/Cargo.toml");
assert_e2e().eq(
&bar_manifest,
str![[r#"
[package]
name = "bar"
version = "1.0.0"
edition = "2015"
authors = []
[dependencies]
shared = "2.0"
multiple-versions = "3.0"
multiple-registries = { registry = "alternative", version = "3.0" } # Comment
multiple-source-types = "2.0" # Comment
"#]],
);
p.cargo("update")
.with_stderr(
"\
[UPDATING] `alternative` index
[UPDATING] `dummy-registry` index
[LOCKING] 4 packages to latest compatible versions
[UPDATING] compatible v1.0.0 -> v1.0.1
[UPDATING] less-than v1.0.0 -> v2.0.0
[UPDATING] pinned v1.0.0 -> v1.0.1 (latest: v2.0.0)
[UPDATING] renamed-from v1.0.0 -> v1.0.1 (latest: v2.0.0)
",
)
.run();
}
#[cargo_test]
fn update_breaking_specific_packages() {
Package::new("just-foo", "1.0.0")
.add_dep(Dependency::new("transitive-compatible", "1.0.0").build())
.add_dep(Dependency::new("transitive-incompatible", "1.0.0").build())
.publish();
Package::new("just-bar", "1.0.0").publish();
Package::new("shared", "1.0.0").publish();
Package::new("ws", "1.0.0").publish();
Package::new("transitive-compatible", "1.0.0").publish();
Package::new("transitive-incompatible", "1.0.0").publish();
let p = project()
.file(
"Cargo.toml",
r#"
[workspace]
members = ["foo", "bar"]
[workspace.dependencies]
ws = "1.0"
"#,
)
.file(
"foo/Cargo.toml",
r#"
[package]
name = "foo"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
just-foo = "1.0"
shared = "1.0"
ws.workspace = true
"#,
)
.file("foo/src/lib.rs", "")
.file(
"bar/Cargo.toml",
r#"
[package]
name = "bar"
version = "0.0.1"
edition = "2015"
authors = []
[dependencies]
just-bar = "1.0"
shared = "1.0"
ws.workspace = true
"#,
)
.file("bar/src/lib.rs", "")
.build();
p.cargo("generate-lockfile").run();
Package::new("just-foo", "1.0.1")
.add_dep(Dependency::new("transitive-compatible", "1.0.0").build())
.add_dep(Dependency::new("transitive-incompatible", "1.0.0").build())
.publish();
Package::new("just-bar", "1.0.1").publish();
Package::new("shared", "1.0.1").publish();
Package::new("ws", "1.0.1").publish();
Package::new("transitive-compatible", "1.0.1").publish();
Package::new("transitive-incompatible", "1.0.1").publish();
Package::new("just-foo", "2.0.0")
// Upgrading just-foo implies accepting an update of transitive-compatible.
.add_dep(Dependency::new("transitive-compatible", "1.0.1").build())
// Upgrading just-foo implies accepting a major update of transitive-incompatible.
.add_dep(Dependency::new("transitive-incompatible", "2.0.0").build())
.publish();
Package::new("just-bar", "2.0.0").publish();
Package::new("shared", "2.0.0").publish();
Package::new("ws", "2.0.0").publish();
Package::new("transitive-incompatible", "2.0.0").publish();
p.cargo("update -Zunstable-options --breaking just-foo shared ws")
.masquerade_as_nightly_cargo(&["update-breaking"])
.with_stderr(
"\
[UPDATING] `[..]` index
[UPGRADING] shared ^1.0 -> ^2.0
[UPGRADING] ws ^1.0 -> ^2.0
[UPGRADING] just-foo ^1.0 -> ^2.0
[LOCKING] 5 packages to latest compatible versions
[UPDATING] just-foo v1.0.0 -> v2.0.0
[UPDATING] shared v1.0.0 -> v2.0.0
[UPDATING] transitive-compatible v1.0.0 -> v1.0.1
[UPDATING] transitive-incompatible v1.0.0 -> v2.0.0
[UPDATING] ws v1.0.0 -> v2.0.0
",
)
.run();
}

View file

@ -265,7 +265,7 @@ trigger_files = [
]
[autolabel."Command-generate-lockfile"]
trigger_files = ["src/bin/cargo/commands/generate_lockfile.rs", "src/cargo/ops/cargo_generate_lockfile.rs"]
trigger_files = ["src/bin/cargo/commands/generate_lockfile.rs"]
[autolabel."Command-git-checkout"]
trigger_files = ["src/bin/cargo/commands/git_checkout.rs"]
@ -334,7 +334,7 @@ trigger_files = ["src/bin/cargo/commands/tree.rs", "src/cargo/ops/tree/"]
trigger_files = ["src/bin/cargo/commands/uninstall.rs", "src/cargo/ops/cargo_uninstall.rs"]
[autolabel."Command-update"]
trigger_files = ["src/bin/cargo/commands/update.rs"]
trigger_files = ["src/bin/cargo/commands/update.rs", "src/cargo/ops/cargo_update.rs"]
[autolabel."Command-vendor"]
trigger_files = ["src/bin/cargo/commands/vendor.rs", "src/cargo/ops/vendor.rs"]