Skip to content

Commit 6f06fe9

Browse files
committed
Auto merge of #13747 - epage:deprecated, r=weihanglo
fix(toml): Error on `[project]` in Edition 2024 ### What does this PR try to resolve? `[package]` was added in 86b2a2a in the pre-1.0 days but `[project]` was never removed nor did we warn on its use until recently in #11135. So likely we can't remove it completely but we can remove it in Edition 2024+. This includes `cargo fix` support which is hard coded directly into the `cargo fix` command. This is part of - #13629 - rust-lang/rust#123754 While we haven't signed off on everything in #13629, I figured this (and a couple other changes) are not very controversial. ### How should we test and review this PR? Per commit. Tests are added to show the behavior changes, including in `cargo fix`. ### Additional information
2 parents b9d913e + cbd9def commit 6f06fe9

File tree

6 files changed

+268
-56
lines changed

6 files changed

+268
-56
lines changed

src/bin/cargo/commands/fix.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::command_prelude::*;
22

3+
use cargo::core::Workspace;
34
use cargo::ops;
45

56
pub fn cli() -> Command {
@@ -60,7 +61,6 @@ pub fn cli() -> Command {
6061
}
6162

6263
pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
63-
let ws = args.workspace(gctx)?;
6464
// This is a legacy behavior that causes `cargo fix` to pass `--test`.
6565
let test = matches!(
6666
args.get_one::<String>("profile").map(String::as_str),
@@ -70,6 +70,9 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
7070

7171
// Unlike other commands default `cargo fix` to all targets to fix as much
7272
// code as we can.
73+
let root_manifest = args.root_manifest(gctx)?;
74+
let mut ws = Workspace::new(&root_manifest, gctx)?;
75+
ws.set_honor_rust_version(args.honor_rust_version());
7376
let mut opts = args.compile_options(gctx, mode, Some(&ws), ProfileChecking::LegacyTestOnly)?;
7477

7578
if !opts.filter.is_specific() {
@@ -78,7 +81,9 @@ pub fn exec(gctx: &mut GlobalContext, args: &ArgMatches) -> CliResult {
7881
}
7982

8083
ops::fix(
84+
gctx,
8185
&ws,
86+
&root_manifest,
8287
&mut ops::FixOptions {
8388
edition: args.flag("edition"),
8489
idioms: args.flag("edition-idioms"),

src/cargo/core/workspace.rs

+4
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,10 @@ impl<'gctx> Workspace<'gctx> {
611611
self.honor_rust_version = honor_rust_version;
612612
}
613613

614+
pub fn honor_rust_version(&self) -> Option<bool> {
615+
self.honor_rust_version
616+
}
617+
614618
pub fn resolve_honors_rust_version(&self) -> bool {
615619
self.gctx().cli_unstable().msrv_policy && self.honor_rust_version.unwrap_or(true)
616620
}

src/cargo/ops/fix.rs

+78-6
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ use tracing::{debug, trace, warn};
5353
use crate::core::compiler::RustcTargetData;
5454
use crate::core::resolver::features::{DiffMap, FeatureOpts, FeatureResolver, FeaturesFor};
5555
use crate::core::resolver::{HasDevUnits, Resolve, ResolveBehavior};
56-
use crate::core::{Edition, MaybePackage, PackageId, Workspace};
56+
use crate::core::PackageIdSpecQuery as _;
57+
use crate::core::{Edition, MaybePackage, Package, PackageId, Workspace};
5758
use crate::ops::resolve::WorkspaceResolve;
5859
use crate::ops::{self, CompileOptions};
5960
use crate::util::diagnostic_server::{Message, RustfixDiagnosticServer};
@@ -87,11 +88,26 @@ pub struct FixOptions {
8788
pub broken_code: bool,
8889
}
8990

90-
pub fn fix(ws: &Workspace<'_>, opts: &mut FixOptions) -> CargoResult<()> {
91-
check_version_control(ws.gctx(), opts)?;
91+
pub fn fix(
92+
gctx: &GlobalContext,
93+
original_ws: &Workspace<'_>,
94+
root_manifest: &Path,
95+
opts: &mut FixOptions,
96+
) -> CargoResult<()> {
97+
check_version_control(gctx, opts)?;
98+
9299
if opts.edition {
93-
check_resolver_change(ws, opts)?;
100+
let specs = opts.compile_opts.spec.to_package_id_specs(&original_ws)?;
101+
let members: Vec<&Package> = original_ws
102+
.members()
103+
.filter(|m| specs.iter().any(|spec| spec.matches(m.package_id())))
104+
.collect();
105+
migrate_manifests(original_ws, &members)?;
106+
107+
check_resolver_change(&original_ws, opts)?;
94108
}
109+
let mut ws = Workspace::new(&root_manifest, gctx)?;
110+
ws.set_honor_rust_version(original_ws.honor_rust_version());
95111

96112
// Spin up our lock server, which our subprocesses will use to synchronize fixes.
97113
let lock_server = LockServer::new()?;
@@ -128,7 +144,7 @@ pub fn fix(ws: &Workspace<'_>, opts: &mut FixOptions) -> CargoResult<()> {
128144
server.configure(&mut wrapper);
129145
}
130146

131-
let rustc = ws.gctx().load_global_rustc(Some(ws))?;
147+
let rustc = ws.gctx().load_global_rustc(Some(&ws))?;
132148
wrapper.arg(&rustc.path);
133149
// This is calling rustc in cargo fix-proxy-mode, so it also need to retry.
134150
// The argfile handling are located at `FixArgs::from_args`.
@@ -138,7 +154,7 @@ pub fn fix(ws: &Workspace<'_>, opts: &mut FixOptions) -> CargoResult<()> {
138154
// repeating build until there are no more changes to be applied
139155
opts.compile_opts.build_config.primary_unit_rustc = Some(wrapper);
140156

141-
ops::compile(ws, &opts.compile_opts)?;
157+
ops::compile(&ws, &opts.compile_opts)?;
142158
Ok(())
143159
}
144160

@@ -215,6 +231,62 @@ fn check_version_control(gctx: &GlobalContext, opts: &FixOptions) -> CargoResult
215231
);
216232
}
217233

234+
fn migrate_manifests(ws: &Workspace<'_>, pkgs: &[&Package]) -> CargoResult<()> {
235+
for pkg in pkgs {
236+
let existing_edition = pkg.manifest().edition();
237+
let prepare_for_edition = existing_edition.saturating_next();
238+
if existing_edition == prepare_for_edition
239+
|| (!prepare_for_edition.is_stable() && !ws.gctx().nightly_features_allowed)
240+
{
241+
continue;
242+
}
243+
let file = pkg.manifest_path();
244+
let file = file.strip_prefix(ws.root()).unwrap_or(file);
245+
let file = file.display();
246+
ws.gctx().shell().status(
247+
"Migrating",
248+
format!("{file} from {existing_edition} edition to {prepare_for_edition}"),
249+
)?;
250+
251+
if Edition::Edition2024 <= prepare_for_edition {
252+
let mut document = pkg.manifest().document().clone().into_mut();
253+
let mut fixes = 0;
254+
255+
let root = document.as_table_mut();
256+
if rename_table(root, "project", "package") {
257+
fixes += 1;
258+
}
259+
260+
if 0 < fixes {
261+
let verb = if fixes == 1 { "fix" } else { "fixes" };
262+
let msg = format!("{file} ({fixes} {verb})");
263+
ws.gctx().shell().status("Fixed", msg)?;
264+
265+
let s = document.to_string();
266+
let new_contents_bytes = s.as_bytes();
267+
cargo_util::paths::write_atomic(pkg.manifest_path(), new_contents_bytes)?;
268+
}
269+
}
270+
}
271+
272+
Ok(())
273+
}
274+
275+
fn rename_table(parent: &mut dyn toml_edit::TableLike, old: &str, new: &str) -> bool {
276+
let Some(old_key) = parent.key(old).cloned() else {
277+
return false;
278+
};
279+
280+
let project = parent.remove(old).expect("returned early");
281+
if !parent.contains_key(new) {
282+
parent.insert(new, project);
283+
let mut new_key = parent.key_mut(new).expect("just inserted");
284+
*new_key.dotted_decor_mut() = old_key.dotted_decor().clone();
285+
*new_key.leaf_decor_mut() = old_key.leaf_decor().clone();
286+
}
287+
true
288+
}
289+
218290
fn check_resolver_change(ws: &Workspace<'_>, opts: &FixOptions) -> CargoResult<()> {
219291
let root = ws.root_maybe();
220292
match root {

src/cargo/util/toml/mod.rs

+13-20
Original file line numberDiff line numberDiff line change
@@ -935,26 +935,9 @@ fn to_real_manifest(
935935
);
936936
};
937937

938-
let original_package = match (&original_toml.package, &original_toml.project) {
939-
(Some(_), Some(project)) => {
940-
warnings.push(format!(
941-
"manifest at `{}` contains both `project` and `package`, \
942-
this could become a hard error in the future",
943-
package_root.display()
944-
));
945-
project.clone()
946-
}
947-
(Some(package), None) => package.clone(),
948-
(None, Some(project)) => {
949-
warnings.push(format!(
950-
"manifest at `{}` contains `[project]` instead of `[package]`, \
951-
this could become a hard error in the future",
952-
package_root.display()
953-
));
954-
project.clone()
955-
}
956-
(None, None) => bail!("no `package` section found"),
957-
};
938+
let original_package = original_toml
939+
.package()
940+
.ok_or_else(|| anyhow::format_err!("no `package` section found"))?;
958941

959942
let package_name = &original_package.name;
960943
if package_name.contains(':') {
@@ -1044,6 +1027,16 @@ fn to_real_manifest(
10441027
)));
10451028
}
10461029

1030+
if original_toml.project.is_some() {
1031+
if Edition::Edition2024 <= edition {
1032+
anyhow::bail!(
1033+
"`[project]` is not supported as of the 2024 Edition, please use `[package]`"
1034+
);
1035+
} else {
1036+
warnings.push(format!("`[project]` is deprecated in favor of `[package]`"));
1037+
}
1038+
}
1039+
10471040
if resolved_package.metabuild.is_some() {
10481041
features.require(Feature::metabuild())?;
10491042
}

tests/testsuite/check.rs

+58-27
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,63 @@ fn rustc_workspace_wrapper_excludes_published_deps() {
979979
.run();
980980
}
981981

982+
#[cargo_test]
983+
fn warn_manifest_with_project() {
984+
let p = project()
985+
.file(
986+
"Cargo.toml",
987+
r#"
988+
[project]
989+
name = "foo"
990+
version = "0.0.1"
991+
edition = "2015"
992+
"#,
993+
)
994+
.file("src/main.rs", "fn main() {}")
995+
.build();
996+
997+
p.cargo("check")
998+
.with_stderr(
999+
"\
1000+
[WARNING] `[project]` is deprecated in favor of `[package]`
1001+
[CHECKING] foo v0.0.1 ([CWD])
1002+
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]
1003+
",
1004+
)
1005+
.run();
1006+
}
1007+
1008+
#[cargo_test(nightly, reason = "edition2024")]
1009+
fn error_manifest_with_project_on_2024() {
1010+
let p = project()
1011+
.file(
1012+
"Cargo.toml",
1013+
r#"
1014+
cargo-features = ["edition2024"]
1015+
1016+
[project]
1017+
name = "foo"
1018+
version = "0.0.1"
1019+
edition = "2024"
1020+
"#,
1021+
)
1022+
.file("src/main.rs", "fn main() {}")
1023+
.build();
1024+
1025+
p.cargo("check")
1026+
.masquerade_as_nightly_cargo(&["edition2024"])
1027+
.with_status(101)
1028+
.with_stderr(
1029+
"\
1030+
[ERROR] failed to parse manifest at `[CWD]/Cargo.toml`
1031+
1032+
Caused by:
1033+
`[project]` is not supported as of the 2024 Edition, please use `[package]`
1034+
",
1035+
)
1036+
.run();
1037+
}
1038+
9821039
#[cargo_test]
9831040
fn warn_manifest_package_and_project() {
9841041
let p = project()
@@ -1002,7 +1059,7 @@ fn warn_manifest_package_and_project() {
10021059
p.cargo("check")
10031060
.with_stderr(
10041061
"\
1005-
[WARNING] manifest at `[CWD]` contains both `project` and `package`, this could become a hard error in the future
1062+
[WARNING] `[project]` is deprecated in favor of `[package]`
10061063
[CHECKING] foo v0.0.1 ([CWD])
10071064
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]
10081065
",
@@ -1065,32 +1122,6 @@ fn git_manifest_package_and_project() {
10651122
.run();
10661123
}
10671124

1068-
#[cargo_test]
1069-
fn warn_manifest_with_project() {
1070-
let p = project()
1071-
.file(
1072-
"Cargo.toml",
1073-
r#"
1074-
[project]
1075-
name = "foo"
1076-
version = "0.0.1"
1077-
edition = "2015"
1078-
"#,
1079-
)
1080-
.file("src/main.rs", "fn main() {}")
1081-
.build();
1082-
1083-
p.cargo("check")
1084-
.with_stderr(
1085-
"\
1086-
[WARNING] manifest at `[CWD]` contains `[project]` instead of `[package]`, this could become a hard error in the future
1087-
[CHECKING] foo v0.0.1 ([CWD])
1088-
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [..]
1089-
",
1090-
)
1091-
.run();
1092-
}
1093-
10941125
#[cargo_test]
10951126
fn git_manifest_with_project() {
10961127
let p = project();

0 commit comments

Comments
 (0)