Skip to content

Commit 9617f3b

Browse files
committed
Implement -Zfix-edition
This adds the implementation for the behavior of `cargo fix -Zfix-edition`.
1 parent 61da2b2 commit 9617f3b

File tree

6 files changed

+235
-18
lines changed

6 files changed

+235
-18
lines changed

src/bin/cargo/commands/fix.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,21 +91,24 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
9191

9292
let allow_dirty = args.flag("allow-dirty");
9393

94-
ops::fix(
95-
gctx,
96-
&ws,
97-
&mut ops::FixOptions {
98-
edition: args
99-
.flag("edition")
100-
.then_some(ops::EditionFixMode::NextRelative),
101-
idioms: args.flag("edition-idioms"),
102-
compile_opts: opts,
103-
allow_dirty,
104-
allow_staged: allow_dirty || args.flag("allow-staged"),
105-
allow_no_vcs: args.flag("allow-no-vcs"),
106-
broken_code: args.flag("broken-code"),
107-
requested_lockfile_path: lockfile_path,
108-
},
109-
)?;
94+
let mut opts = ops::FixOptions {
95+
edition: args
96+
.flag("edition")
97+
.then_some(ops::EditionFixMode::NextRelative),
98+
idioms: args.flag("edition-idioms"),
99+
compile_opts: opts,
100+
allow_dirty,
101+
allow_staged: allow_dirty || args.flag("allow-staged"),
102+
allow_no_vcs: args.flag("allow-no-vcs"),
103+
broken_code: args.flag("broken-code"),
104+
requested_lockfile_path: lockfile_path,
105+
};
106+
107+
if let Some(fe) = &gctx.cli_unstable().fix_edition {
108+
ops::fix_edition(gctx, &ws, &mut opts, fe)?;
109+
} else {
110+
ops::fix(gctx, &ws, &mut opts)?;
111+
}
112+
110113
Ok(())
111114
}

src/cargo/ops/fix/fix_edition.rs

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//! Support for the permanently unstable `-Zfix-edition` flag.
2+
3+
use super::{EditionFixMode, FixOptions};
4+
use crate::core::features::{Edition, FixEdition};
5+
use crate::core::{Package, Workspace};
6+
use crate::ops;
7+
use crate::util::toml_mut::manifest::LocalManifest;
8+
use crate::{CargoResult, GlobalContext};
9+
use toml_edit::{Formatted, Item, Value};
10+
11+
/// Performs the actions for the `-Zfix-edition` flag.
12+
pub fn fix_edition(
13+
gctx: &GlobalContext,
14+
original_ws: &Workspace<'_>,
15+
opts: &mut FixOptions,
16+
fix_edition: &FixEdition,
17+
) -> CargoResult<()> {
18+
let packages = opts.compile_opts.spec.get_packages(original_ws)?;
19+
let skip_if_not_edition = |edition| -> CargoResult<bool> {
20+
if !packages.iter().all(|p| p.manifest().edition() == edition) {
21+
gctx.shell().status(
22+
"Skipping",
23+
&format!("not all packages are at edition {edition}"),
24+
)?;
25+
Ok(true)
26+
} else {
27+
Ok(false)
28+
}
29+
};
30+
31+
match fix_edition {
32+
FixEdition::Start(edition) => {
33+
// The start point just runs `cargo check` if the edition is the
34+
// starting edition. This is so that crater can set a baseline of
35+
// whether or not the package builds at all. For other editions,
36+
// we skip entirely since they are not of interest since we can't
37+
// migrate them.
38+
if skip_if_not_edition(*edition)? {
39+
return Ok(());
40+
}
41+
ops::compile(&original_ws, &opts.compile_opts)?;
42+
}
43+
FixEdition::End { initial, next } => {
44+
// Skip packages that are not the starting edition, since we can
45+
// only migrate from one edition to the next.
46+
if skip_if_not_edition(*initial)? {
47+
return Ok(());
48+
}
49+
// Do the edition fix.
50+
opts.edition = Some(EditionFixMode::OverrideSpecific(*next));
51+
opts.allow_dirty = true;
52+
opts.allow_no_vcs = true;
53+
opts.allow_staged = true;
54+
ops::fix(gctx, original_ws, opts)?;
55+
// Do `cargo check` with the new edition so that we can verify
56+
// that it also works on the next edition.
57+
replace_edition(&packages, *next)?;
58+
gctx.shell()
59+
.status("Updated", &format!("edition to {next}"))?;
60+
let ws = original_ws.reload(gctx)?;
61+
// Unset these since we just want to do a normal `cargo check`.
62+
*opts
63+
.compile_opts
64+
.build_config
65+
.rustfix_diagnostic_server
66+
.borrow_mut() = None;
67+
opts.compile_opts.build_config.primary_unit_rustc = None;
68+
69+
ops::compile(&ws, &opts.compile_opts)?;
70+
}
71+
}
72+
Ok(())
73+
}
74+
75+
/// Modifies the `edition` value of the given packages to the new edition.
76+
fn replace_edition(packages: &[&Package], to_edition: Edition) -> CargoResult<()> {
77+
for package in packages {
78+
let mut manifest_mut = LocalManifest::try_new(package.manifest_path())?;
79+
let document = &mut manifest_mut.data;
80+
let root = document.as_table_mut();
81+
// Update edition to the new value.
82+
if let Some(package) = root.get_mut("package").and_then(|t| t.as_table_like_mut()) {
83+
package.insert(
84+
"edition",
85+
Item::Value(Value::String(Formatted::new(to_edition.to_string()))),
86+
);
87+
}
88+
// If the edition is unstable, add it to cargo-features.
89+
if !to_edition.is_stable() {
90+
let feature = "unstable-edition";
91+
92+
if let Some(features) = root
93+
.entry("cargo-features")
94+
.or_insert_with(|| Item::Value(Value::Array(toml_edit::Array::new())))
95+
.as_array_mut()
96+
{
97+
if !features
98+
.iter()
99+
.any(|f| f.as_str().map_or(false, |f| f == feature))
100+
{
101+
features.push(feature);
102+
}
103+
}
104+
}
105+
manifest_mut.write()?;
106+
}
107+
Ok(())
108+
}

src/cargo/ops/fix.rs renamed to src/cargo/ops/fix/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ use rustfix::CodeFix;
5151
use semver::Version;
5252
use tracing::{debug, trace, warn};
5353

54+
pub use self::fix_edition::fix_edition;
5455
use crate::core::compiler::CompileKind;
5556
use crate::core::compiler::RustcTargetData;
5657
use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor};
@@ -66,6 +67,8 @@ use crate::util::GlobalContext;
6667
use crate::util::{existing_vcs_repo, LockServer, LockServerClient};
6768
use crate::{drop_eprint, drop_eprintln};
6869

70+
mod fix_edition;
71+
6972
/// **Internal only.**
7073
/// Indicates Cargo is in fix-proxy-mode if presents.
7174
/// The value of it is the socket address of the [`LockServer`] being used.

src/cargo/ops/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ pub use self::cargo_update::upgrade_manifests;
2626
pub use self::cargo_update::write_manifest_upgrades;
2727
pub use self::cargo_update::UpdateOptions;
2828
pub use self::common_for_install_and_uninstall::{resolve_root, InstallTracker};
29-
pub use self::fix::{fix, fix_exec_rustc, fix_get_proxy_lock_addr, EditionFixMode, FixOptions};
29+
pub use self::fix::{
30+
fix, fix_edition, fix_exec_rustc, fix_get_proxy_lock_addr, EditionFixMode, FixOptions,
31+
};
3032
pub use self::lockfile::{load_pkg_lockfile, resolve_to_string, write_pkg_lockfile};
3133
pub use self::registry::info;
3234
pub use self::registry::modify_owners;

tests/testsuite/fix.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2963,3 +2963,104 @@ dep_df_false = { workspace = true, default-features = false }
29632963
"#]],
29642964
);
29652965
}
2966+
2967+
#[cargo_test]
2968+
fn fix_edition_skips_old_editions() {
2969+
// Checks that -Zfix-edition will skip things that are not 2024.
2970+
let p = project()
2971+
.file(
2972+
"Cargo.toml",
2973+
r#"[workspace]
2974+
members = ["e2021", "e2024"]
2975+
resolver = "3"
2976+
"#,
2977+
)
2978+
.file(
2979+
"e2021/Cargo.toml",
2980+
r#"
2981+
[package]
2982+
name = "e2021"
2983+
edition = "2021"
2984+
"#,
2985+
)
2986+
.file("e2021/src/lib.rs", "")
2987+
.file(
2988+
"e2024/Cargo.toml",
2989+
r#"
2990+
[package]
2991+
name = "e2024"
2992+
edition = "2024"
2993+
"#,
2994+
)
2995+
.file("e2024/src/lib.rs", "")
2996+
.build();
2997+
2998+
// Doing the whole workspace should skip since there is a 2021 in the mix.
2999+
p.cargo("fix -Zfix-edition=start=2024 -v")
3000+
.masquerade_as_nightly_cargo(&["fix-edition"])
3001+
.with_stderr_data(str![[r#"
3002+
[SKIPPING] not all packages are at edition 2024
3003+
3004+
"#]])
3005+
.run();
3006+
3007+
// Same with `end`.
3008+
p.cargo("fix -Zfix-edition=end=2024,future -v")
3009+
.masquerade_as_nightly_cargo(&["fix-edition"])
3010+
.with_stderr_data(str![[r#"
3011+
[SKIPPING] not all packages are at edition 2024
3012+
3013+
"#]])
3014+
.run();
3015+
3016+
// Doing an individual package at the correct edition should check it.
3017+
p.cargo("fix -Zfix-edition=start=2024 -p e2024")
3018+
.masquerade_as_nightly_cargo(&["fix-edition"])
3019+
.with_stderr_data(str![[r#"
3020+
[CHECKING] e2024 v0.0.0 ([ROOT]/foo/e2024)
3021+
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
3022+
3023+
"#]])
3024+
.run();
3025+
}
3026+
3027+
#[cargo_test]
3028+
fn fix_edition_future() {
3029+
// Checks that the -Zfix-edition can work for the future.
3030+
let p = project()
3031+
.file(
3032+
"Cargo.toml",
3033+
r#"
3034+
[package]
3035+
name = "foo"
3036+
edition = "2024"
3037+
"#,
3038+
)
3039+
.file("src/lib.rs", "")
3040+
.build();
3041+
3042+
p.cargo("fix -Zfix-edition=end=2024,future")
3043+
.masquerade_as_nightly_cargo(&["fix-edition"])
3044+
.with_stderr_data(str![[r#"
3045+
[MIGRATING] Cargo.toml from 2024 edition to future
3046+
[CHECKING] foo v0.0.0 ([ROOT]/foo)
3047+
[MIGRATING] src/lib.rs from 2024 edition to future
3048+
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
3049+
Updated edition to future
3050+
[CHECKING] foo v0.0.0 ([ROOT]/foo)
3051+
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
3052+
3053+
"#]])
3054+
.run();
3055+
assert_e2e().eq(
3056+
p.read_file("Cargo.toml"),
3057+
str![[r#"
3058+
cargo-features = ["unstable-edition"]
3059+
3060+
[package]
3061+
name = "foo"
3062+
edition = "future"
3063+
3064+
"#]],
3065+
);
3066+
}

triagebot.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ trigger_files = ["src/bin/cargo/commands/fetch.rs", "src/cargo/ops/cargo_fetch.r
279279
trigger_files = [
280280
"crates/rustfix/",
281281
"src/bin/cargo/commands/fix.rs",
282-
"src/cargo/ops/fix.rs",
282+
"src/cargo/ops/fix/",
283283
"src/cargo/util/diagnostic_server.rs",
284284
"src/cargo/util/lockserver.rs",
285285
]

0 commit comments

Comments
 (0)