cargo/ops/fix/
fix_edition.rs

1//! Support for the permanently unstable `-Zfix-edition` flag.
2
3use super::{EditionFixMode, FixOptions};
4use crate::core::features::{Edition, FixEdition};
5use crate::core::{Package, Workspace};
6use crate::ops;
7use crate::util::toml_mut::manifest::LocalManifest;
8use crate::{CargoResult, GlobalContext};
9use toml_edit::{Formatted, Item, Value};
10
11/// Performs the actions for the `-Zfix-edition` flag.
12pub 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.
76fn 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-editions";
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}