Implement -Zfix-edition

This adds the implementation for the behavior of `cargo fix
-Zfix-edition`.
This commit is contained in:
Eric Huss 2025-05-24 14:47:54 -07:00
parent 331dcec02d
commit 21629670f4
6 changed files with 234 additions and 18 deletions

View File

@ -91,21 +91,24 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
let allow_dirty = args.flag("allow-dirty"); let allow_dirty = args.flag("allow-dirty");
ops::fix( let mut opts = ops::FixOptions {
gctx, edition: args
&ws, .flag("edition")
&mut ops::FixOptions { .then_some(ops::EditionFixMode::NextRelative),
edition: args idioms: args.flag("edition-idioms"),
.flag("edition") compile_opts: opts,
.then_some(ops::EditionFixMode::NextRelative), allow_dirty,
idioms: args.flag("edition-idioms"), allow_staged: allow_dirty || args.flag("allow-staged"),
compile_opts: opts, allow_no_vcs: args.flag("allow-no-vcs"),
allow_dirty, broken_code: args.flag("broken-code"),
allow_staged: allow_dirty || args.flag("allow-staged"), requested_lockfile_path: lockfile_path,
allow_no_vcs: args.flag("allow-no-vcs"), };
broken_code: args.flag("broken-code"),
requested_lockfile_path: lockfile_path, if let Some(fe) = &gctx.cli_unstable().fix_edition {
}, ops::fix_edition(gctx, &ws, &mut opts, fe)?;
)?; } else {
ops::fix(gctx, &ws, &mut opts)?;
}
Ok(()) Ok(())
} }

View File

@ -0,0 +1,108 @@
//! Support for the permanently unstable `-Zfix-edition` flag.
use super::{EditionFixMode, FixOptions};
use crate::core::features::{Edition, FixEdition};
use crate::core::{Package, Workspace};
use crate::ops;
use crate::util::toml_mut::manifest::LocalManifest;
use crate::{CargoResult, GlobalContext};
use toml_edit::{Formatted, Item, Value};
/// Performs the actions for the `-Zfix-edition` flag.
pub fn fix_edition(
gctx: &GlobalContext,
original_ws: &Workspace<'_>,
opts: &mut FixOptions,
fix_edition: &FixEdition,
) -> CargoResult<()> {
let packages = opts.compile_opts.spec.get_packages(original_ws)?;
let skip_if_not_edition = |edition| -> CargoResult<bool> {
if !packages.iter().all(|p| p.manifest().edition() == edition) {
gctx.shell().status(
"Skipping",
&format!("not all packages are at edition {edition}"),
)?;
Ok(true)
} else {
Ok(false)
}
};
match fix_edition {
FixEdition::Start(edition) => {
// The start point just runs `cargo check` if the edition is the
// starting edition. This is so that crater can set a baseline of
// whether or not the package builds at all. For other editions,
// we skip entirely since they are not of interest since we can't
// migrate them.
if skip_if_not_edition(*edition)? {
return Ok(());
}
ops::compile(&original_ws, &opts.compile_opts)?;
}
FixEdition::End { initial, next } => {
// Skip packages that are not the starting edition, since we can
// only migrate from one edition to the next.
if skip_if_not_edition(*initial)? {
return Ok(());
}
// Do the edition fix.
opts.edition = Some(EditionFixMode::OverrideSpecific(*next));
opts.allow_dirty = true;
opts.allow_no_vcs = true;
opts.allow_staged = true;
ops::fix(gctx, original_ws, opts)?;
// Do `cargo check` with the new edition so that we can verify
// that it also works on the next edition.
replace_edition(&packages, *next)?;
gctx.shell()
.status("Updated", &format!("edition to {next}"))?;
let ws = original_ws.reload(gctx)?;
// Unset these since we just want to do a normal `cargo check`.
*opts
.compile_opts
.build_config
.rustfix_diagnostic_server
.borrow_mut() = None;
opts.compile_opts.build_config.primary_unit_rustc = None;
ops::compile(&ws, &opts.compile_opts)?;
}
}
Ok(())
}
/// Modifies the `edition` value of the given packages to the new edition.
fn replace_edition(packages: &[&Package], to_edition: Edition) -> CargoResult<()> {
for package in packages {
let mut manifest_mut = LocalManifest::try_new(package.manifest_path())?;
let document = &mut manifest_mut.data;
let root = document.as_table_mut();
// Update edition to the new value.
if let Some(package) = root.get_mut("package").and_then(|t| t.as_table_like_mut()) {
package.insert(
"edition",
Item::Value(Value::String(Formatted::new(to_edition.to_string()))),
);
}
// If the edition is unstable, add it to cargo-features.
if !to_edition.is_stable() {
let feature = "unstable-editions";
if let Some(features) = root
.entry("cargo-features")
.or_insert_with(|| Item::Value(Value::Array(toml_edit::Array::new())))
.as_array_mut()
{
if !features
.iter()
.any(|f| f.as_str().map_or(false, |f| f == feature))
{
features.push(feature);
}
}
}
manifest_mut.write()?;
}
Ok(())
}

View File

@ -51,6 +51,7 @@ use rustfix::CodeFix;
use semver::Version; use semver::Version;
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
pub use self::fix_edition::fix_edition;
use crate::core::compiler::CompileKind; use crate::core::compiler::CompileKind;
use crate::core::compiler::RustcTargetData; use crate::core::compiler::RustcTargetData;
use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor}; use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor};
@ -66,6 +67,8 @@ use crate::util::GlobalContext;
use crate::util::{existing_vcs_repo, LockServer, LockServerClient}; use crate::util::{existing_vcs_repo, LockServer, LockServerClient};
use crate::{drop_eprint, drop_eprintln}; use crate::{drop_eprint, drop_eprintln};
mod fix_edition;
/// **Internal only.** /// **Internal only.**
/// Indicates Cargo is in fix-proxy-mode if presents. /// Indicates Cargo is in fix-proxy-mode if presents.
/// The value of it is the socket address of the [`LockServer`] being used. /// The value of it is the socket address of the [`LockServer`] being used.

View File

@ -26,7 +26,9 @@ pub use self::cargo_update::upgrade_manifests;
pub use self::cargo_update::write_manifest_upgrades; pub use self::cargo_update::write_manifest_upgrades;
pub use self::cargo_update::UpdateOptions; pub use self::cargo_update::UpdateOptions;
pub use self::common_for_install_and_uninstall::{resolve_root, InstallTracker}; pub use self::common_for_install_and_uninstall::{resolve_root, InstallTracker};
pub use self::fix::{fix, fix_exec_rustc, fix_get_proxy_lock_addr, EditionFixMode, FixOptions}; pub use self::fix::{
fix, fix_edition, fix_exec_rustc, fix_get_proxy_lock_addr, EditionFixMode, FixOptions,
};
pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile}; pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile};
pub use self::registry::info; pub use self::registry::info;
pub use self::registry::modify_owners; pub use self::registry::modify_owners;

View File

@ -2963,3 +2963,103 @@ dep_df_false = { workspace = true, default-features = false }
"#]], "#]],
); );
} }
#[cargo_test]
fn fix_edition_skips_old_editions() {
// Checks that -Zfix-edition will skip things that are not 2024.
let p = project()
.file(
"Cargo.toml",
r#"[workspace]
members = ["e2021", "e2024"]
resolver = "3"
"#,
)
.file(
"e2021/Cargo.toml",
r#"
[package]
name = "e2021"
edition = "2021"
"#,
)
.file("e2021/src/lib.rs", "")
.file(
"e2024/Cargo.toml",
r#"
[package]
name = "e2024"
edition = "2024"
"#,
)
.file("e2024/src/lib.rs", "")
.build();
// Doing the whole workspace should skip since there is a 2021 in the mix.
p.cargo("fix -Zfix-edition=start=2024 -v")
.masquerade_as_nightly_cargo(&["fix-edition"])
.with_stderr_data(str![[r#"
[SKIPPING] not all packages are at edition 2024
"#]])
.run();
// Same with `end`.
p.cargo("fix -Zfix-edition=end=2024,future -v")
.masquerade_as_nightly_cargo(&["fix-edition"])
.with_stderr_data(str![[r#"
[SKIPPING] not all packages are at edition 2024
"#]])
.run();
// Doing an individual package at the correct edition should check it.
p.cargo("fix -Zfix-edition=start=2024 -p e2024")
.masquerade_as_nightly_cargo(&["fix-edition"])
.with_stderr_data(str![[r#"
[CHECKING] e2024 v0.0.0 ([ROOT]/foo/e2024)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
}
#[cargo_test(nightly, reason = "future edition is always unstable")]
fn fix_edition_future() {
// Checks that the -Zfix-edition can work for the future.
let p = project()
.file(
"Cargo.toml",
r#"
[package]
name = "foo"
edition = "2024""#,
)
.file("src/lib.rs", "")
.build();
p.cargo("fix -Zfix-edition=end=2024,future")
.masquerade_as_nightly_cargo(&["fix-edition"])
.with_stderr_data(str![[r#"
[MIGRATING] Cargo.toml from 2024 edition to future
[CHECKING] foo v0.0.0 ([ROOT]/foo)
[MIGRATING] src/lib.rs from 2024 edition to future
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
Updated edition to future
[CHECKING] foo v0.0.0 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
assert_e2e().eq(
p.read_file("Cargo.toml"),
str![[r#"
cargo-features = ["unstable-editions"]
[package]
name = "foo"
edition = "future"
"#]],
);
}

View File

@ -279,7 +279,7 @@ trigger_files = ["src/bin/cargo/commands/fetch.rs", "src/cargo/ops/cargo_fetch.r
trigger_files = [ trigger_files = [
"crates/rustfix/", "crates/rustfix/",
"src/bin/cargo/commands/fix.rs", "src/bin/cargo/commands/fix.rs",
"src/cargo/ops/fix.rs", "src/cargo/ops/fix/",
"src/cargo/util/diagnostic_server.rs", "src/cargo/util/diagnostic_server.rs",
"src/cargo/util/lockserver.rs", "src/cargo/util/lockserver.rs",
] ]