1 //! High-level overview of how `fix` works:
3 //! The main goal is to run `cargo check` to get rustc to emit JSON
4 //! diagnostics with suggested fixes that can be applied to the files on the
5 //! filesystem, and validate that those changes didn't break anything.
7 //! Cargo begins by launching a `LockServer` thread in the background to
8 //! listen for network connections to coordinate locking when multiple targets
9 //! are built simultaneously. It ensures each package has only one fix running
12 //! The `RustfixDiagnosticServer` is launched in a background thread (in
13 //! `JobQueue`) to listen for network connections to coordinate displaying
14 //! messages to the user on the console (so that multiple processes don't try
15 //! to print at the same time).
17 //! Cargo begins a normal `cargo check` operation with itself set as a proxy
18 //! for rustc by setting `primary_unit_rustc` in the build config. When
19 //! cargo launches rustc to check a crate, it is actually launching itself.
20 //! The `FIX_ENV` environment variable is set so that cargo knows it is in
23 //! Each proxied cargo-as-rustc detects it is in fix-proxy-mode (via `FIX_ENV`
24 //! environment variable in `main`) and does the following:
26 //! - Acquire a lock from the `LockServer` from the master cargo process.
27 //! - Launches the real rustc (`rustfix_and_fix`), looking at the JSON output
28 //! for suggested fixes.
29 //! - Uses the `rustfix` crate to apply the suggestions to the files on the
31 //! - If rustfix fails to apply any suggestions (for example, they are
32 //! overlapping), but at least some suggestions succeeded, it will try the
33 //! previous two steps up to 4 times as long as some suggestions succeed.
34 //! - Assuming there's at least one suggestion applied, and the suggestions
35 //! applied cleanly, rustc is run again to verify the suggestions didn't
36 //! break anything. The change will be backed out if it fails (unless
37 //! `--broken-code` is used).
38 //! - If there are any warnings or errors, rustc will be run one last time to
39 //! show them to the user.
41 use std
::collections
::{BTreeSet, HashMap, HashSet}
;
43 use std
::ffi
::OsString
;
44 use std
::path
::{Path, PathBuf}
;
45 use std
::process
::{self, Command, ExitStatus}
;
48 use anyhow
::{bail, Context, Error}
;
49 use cargo_util
::{paths, ProcessBuilder}
;
50 use log
::{debug, trace, warn}
;
51 use rustfix
::diagnostics
::Diagnostic
;
52 use rustfix
::{self, CodeFix}
;
54 use crate::core
::compiler
::RustcTargetData
;
55 use crate::core
::resolver
::features
::{FeatureOpts, FeatureResolver}
;
56 use crate::core
::resolver
::{HasDevUnits, ResolveBehavior}
;
57 use crate::core
::{Edition, MaybePackage, Workspace}
;
58 use crate::ops
::{self, CompileOptions}
;
59 use crate::util
::diagnostic_server
::{Message, RustfixDiagnosticServer}
;
60 use crate::util
::errors
::CargoResult
;
61 use crate::util
::Config
;
62 use crate::util
::{existing_vcs_repo, LockServer, LockServerClient}
;
63 use crate::{drop_eprint, drop_eprintln}
;
65 const FIX_ENV
: &str = "__CARGO_FIX_PLZ";
66 const BROKEN_CODE_ENV
: &str = "__CARGO_FIX_BROKEN_CODE";
67 const EDITION_ENV
: &str = "__CARGO_FIX_EDITION";
68 const IDIOMS_ENV
: &str = "__CARGO_FIX_IDIOMS";
70 pub struct FixOptions
{
73 pub compile_opts
: CompileOptions
,
74 pub allow_dirty
: bool
,
75 pub allow_no_vcs
: bool
,
76 pub allow_staged
: bool
,
77 pub broken_code
: bool
,
80 pub fn fix(ws
: &Workspace
<'_
>, opts
: &mut FixOptions
) -> CargoResult
<()> {
81 check_version_control(ws
.config(), opts
)?
;
83 check_resolver_change(ws
, opts
)?
;
86 // Spin up our lock server, which our subprocesses will use to synchronize fixes.
87 let lock_server
= LockServer
::new()?
;
88 let mut wrapper
= ProcessBuilder
::new(env
::current_exe()?
);
89 wrapper
.env(FIX_ENV
, lock_server
.addr().to_string());
90 let _started
= lock_server
.start()?
;
92 opts
.compile_opts
.build_config
.force_rebuild
= true;
95 wrapper
.env(BROKEN_CODE_ENV
, "1");
99 wrapper
.env(EDITION_ENV
, "1");
102 wrapper
.env(IDIOMS_ENV
, "1");
108 .rustfix_diagnostic_server
109 .borrow_mut() = Some(RustfixDiagnosticServer
::new()?
);
111 if let Some(server
) = opts
114 .rustfix_diagnostic_server
118 server
.configure(&mut wrapper
);
121 let rustc
= ws
.config().load_global_rustc(Some(ws
))?
;
122 wrapper
.arg(&rustc
.path
);
124 // primary crates are compiled using a cargo subprocess to do extra work of applying fixes and
125 // repeating build until there are no more changes to be applied
126 opts
.compile_opts
.build_config
.primary_unit_rustc
= Some(wrapper
);
128 ops
::compile(ws
, &opts
.compile_opts
)?
;
132 fn check_version_control(config
: &Config
, opts
: &FixOptions
) -> CargoResult
<()> {
133 if opts
.allow_no_vcs
{
136 if !existing_vcs_repo(config
.cwd(), config
.cwd()) {
138 "no VCS found for this package and `cargo fix` can potentially \
139 perform destructive changes; if you'd like to suppress this \
140 error pass `--allow-no-vcs`"
144 if opts
.allow_dirty
&& opts
.allow_staged
{
148 let mut dirty_files
= Vec
::new();
149 let mut staged_files
= Vec
::new();
150 if let Ok(repo
) = git2
::Repository
::discover(config
.cwd()) {
151 let mut repo_opts
= git2
::StatusOptions
::new();
152 repo_opts
.include_ignored(false);
153 for status
in repo
.statuses(Some(&mut repo_opts
))?
.iter() {
154 if let Some(path
) = status
.path() {
155 match status
.status() {
156 git2
::Status
::CURRENT
=> (),
157 git2
::Status
::INDEX_NEW
158 | git2
::Status
::INDEX_MODIFIED
159 | git2
::Status
::INDEX_DELETED
160 | git2
::Status
::INDEX_RENAMED
161 | git2
::Status
::INDEX_TYPECHANGE
=> {
162 if !opts
.allow_staged
{
163 staged_files
.push(path
.to_string())
167 if !opts
.allow_dirty
{
168 dirty_files
.push(path
.to_string())
176 if dirty_files
.is_empty() && staged_files
.is_empty() {
180 let mut files_list
= String
::new();
181 for file
in dirty_files
{
182 files_list
.push_str(" * ");
183 files_list
.push_str(&file
);
184 files_list
.push_str(" (dirty)\n");
186 for file
in staged_files
{
187 files_list
.push_str(" * ");
188 files_list
.push_str(&file
);
189 files_list
.push_str(" (staged)\n");
193 "the working directory of this package has uncommitted changes, and \
194 `cargo fix` can potentially perform destructive changes; if you'd \
195 like to suppress this error pass `--allow-dirty`, `--allow-staged`, \
196 or commit the changes to these files:\n\
204 fn check_resolver_change(ws
: &Workspace
<'_
>, opts
: &FixOptions
) -> CargoResult
<()> {
205 let root
= ws
.root_maybe();
207 MaybePackage
::Package(root_pkg
) => {
208 if root_pkg
.manifest().resolve_behavior().is_some() {
209 // If explicitly specified by the user, no need to check.
212 // Only trigger if updating the root package from 2018.
213 let pkgs
= opts
.compile_opts
.spec
.get_packages(ws
)?
;
214 if !pkgs
.iter().any(|&pkg
| pkg
== root_pkg
) {
215 // The root is not being migrated.
218 if root_pkg
.manifest().edition() != Edition
::Edition2018
{
219 // V1 to V2 only happens on 2018 to 2021.
223 MaybePackage
::Virtual(_vm
) => {
224 // Virtual workspaces don't have a global edition to set (yet).
228 // 2018 without `resolver` set must be V1
229 assert_eq
!(ws
.resolve_behavior(), ResolveBehavior
::V1
);
230 let specs
= opts
.compile_opts
.spec
.to_package_id_specs(ws
)?
;
231 let target_data
= RustcTargetData
::new(ws
, &opts
.compile_opts
.build_config
.requested_kinds
)?
;
232 // HasDevUnits::No because that may uncover more differences.
233 // This is not the same as what `cargo fix` is doing, since it is doing
234 // `--all-targets` which includes dev dependencies.
235 let ws_resolve
= ops
::resolve_ws_with_opts(
238 &opts
.compile_opts
.build_config
.requested_kinds
,
239 &opts
.compile_opts
.cli_features
,
242 crate::core
::resolver
::features
::ForceAllTargets
::No
,
245 let feature_opts
= FeatureOpts
::new_behavior(ResolveBehavior
::V2
, HasDevUnits
::No
);
246 let v2_features
= FeatureResolver
::resolve(
249 &ws_resolve
.targeted_resolve
,
251 &opts
.compile_opts
.cli_features
,
253 &opts
.compile_opts
.build_config
.requested_kinds
,
257 let differences
= v2_features
.compare_legacy(&ws_resolve
.resolved_features
);
258 if differences
.features
.is_empty() && differences
.optional_deps
.is_empty() {
259 // Nothing is different, nothing to report.
262 let config
= ws
.config();
264 "Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo.",
268 "This may cause dependencies to resolve with a different set of features."
272 "More information about the resolver changes may be found \
273 at https://doc.rust-lang.org/cargo/reference/features.html#feature-resolver-version-2"
277 "The following differences were detected with the current configuration:\n"
279 let report
= |changes
: crate::core
::resolver
::features
::DiffMap
, what
| {
280 for ((pkg_id
, for_host
), removed
) in changes
{
281 drop_eprint
!(config
, " {}", pkg_id
);
283 drop_eprint
!(config
, " (as build dependency)");
285 if !removed
.is_empty() {
286 let joined
: Vec
<_
> = removed
.iter().map(|s
| s
.as_str()).collect();
287 drop_eprint
!(config
, " removed {} `{}`", what
, joined
.join(","));
289 drop_eprint
!(config
, "\n");
292 report(differences
.features
, "features");
293 report(differences
.optional_deps
, "optional dependency");
294 drop_eprint
!(config
, "\n");
298 /// Entry point for `cargo` running as a proxy for `rustc`.
300 /// This is called every time `cargo` is run to check if it is in proxy mode.
302 /// Returns `false` if `fix` is not being run (not in proxy mode). Returns
303 /// `true` if in `fix` proxy mode, and the fix was complete without any
304 /// warnings or errors. If there are warnings or errors, this does not return,
305 /// and the process exits with the corresponding `rustc` exit code.
306 pub fn fix_maybe_exec_rustc(config
: &Config
) -> CargoResult
<bool
> {
307 let lock_addr
= match env
::var(FIX_ENV
) {
309 Err(_
) => return Ok(false),
312 let args
= FixArgs
::get()?
;
313 trace
!("cargo-fix as rustc got file {:?}", args
.file
);
315 let workspace_rustc
= std
::env
::var("RUSTC_WORKSPACE_WRAPPER")
318 let rustc
= ProcessBuilder
::new(&args
.rustc
).wrapped(workspace_rustc
.as_ref());
320 trace
!("start rustfixing {:?}", args
.file
);
321 let fixes
= rustfix_crate(&lock_addr
, &rustc
, &args
.file
, &args
, config
)?
;
323 // Ok now we have our final goal of testing out the changes that we applied.
324 // If these changes went awry and actually started to cause the crate to
325 // *stop* compiling then we want to back them out and continue to print
326 // warnings to the user.
328 // If we didn't actually make any changes then we can immediately execute the
329 // new rustc, and otherwise we capture the output to hide it in the scenario
330 // that we have to back it all out.
331 if !fixes
.files
.is_empty() {
332 let mut cmd
= rustc
.build_command();
333 args
.apply(&mut cmd
);
334 cmd
.arg("--error-format=json");
335 let output
= cmd
.output().context("failed to spawn rustc")?
;
337 if output
.status
.success() {
338 for (path
, file
) in fixes
.files
.iter() {
341 fixes
: file
.fixes_applied
,
347 // If we succeeded then we'll want to commit to the changes we made, if
348 // any. If stderr is empty then there's no need for the final exec at
349 // the end, we just bail out here.
350 if output
.status
.success() && output
.stderr
.is_empty() {
354 // Otherwise, if our rustc just failed, then that means that we broke the
355 // user's code with our changes. Back out everything and fall through
356 // below to recompile again.
357 if !output
.status
.success() {
358 if env
::var_os(BROKEN_CODE_ENV
).is_none() {
359 for (path
, file
) in fixes
.files
.iter() {
360 paths
::write(path
, &file
.original_code
)?
;
363 log_failed_fix(&output
.stderr
)?
;
367 // This final fall-through handles multiple cases;
368 // - If the fix failed, show the original warnings and suggestions.
369 // - If `--broken-code`, show the error messages.
370 // - If the fix succeeded, show any remaining warnings.
371 let mut cmd
= rustc
.build_command();
372 args
.apply(&mut cmd
);
373 for arg
in args
.format_args
{
374 // Add any json/error format arguments that Cargo wants. This allows
375 // things like colored output to work correctly.
378 exit_with(cmd
.status().context("failed to spawn rustc")?
);
383 files
: HashMap
<String
, FixedFile
>,
387 errors_applying_fixes
: Vec
<String
>,
389 original_code
: String
,
392 /// Attempts to apply fixes to a single crate.
394 /// This runs `rustc` (possibly multiple times) to gather suggestions from the
395 /// compiler and applies them to the files on disk.
398 rustc
: &ProcessBuilder
,
402 ) -> Result
<FixedCrate
, Error
> {
403 args
.check_edition_and_send_status(config
)?
;
405 // First up, we want to make sure that each crate is only checked by one
406 // process at a time. If two invocations concurrently check a crate then
407 // it's likely to corrupt it.
409 // We currently do this by assigning the name on our lock to the manifest
411 let dir
= env
::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is missing?");
412 let _lock
= LockServerClient
::lock(&lock_addr
.parse()?
, dir
)?
;
414 // Next up, this is a bit suspicious, but we *iteratively* execute rustc and
415 // collect suggestions to feed to rustfix. Once we hit our limit of times to
416 // execute rustc or we appear to be reaching a fixed point we stop running
419 // This is currently done to handle code like:
423 // where there are two fixes to happen here: `crate::foo::<crate::Bar>()`.
424 // The spans for these two suggestions are overlapping and its difficult in
425 // the compiler to **not** have overlapping spans here. As a result, a naive
426 // implementation would feed the two compiler suggestions for the above fix
427 // into `rustfix`, but one would be rejected because it overlaps with the
430 // In this case though, both suggestions are valid and can be automatically
431 // applied! To handle this case we execute rustc multiple times, collecting
432 // fixes each time we do so. Along the way we discard any suggestions that
433 // failed to apply, assuming that they can be fixed the next time we run
436 // Naturally, we want a few protections in place here though to avoid looping
437 // forever or otherwise losing data. To that end we have a few termination
440 // * Do this whole process a fixed number of times. In theory we probably
441 // need an infinite number of times to apply fixes, but we're not gonna
442 // sit around waiting for that.
443 // * If it looks like a fix genuinely can't be applied we need to bail out.
444 // Detect this when a fix fails to get applied *and* no suggestions
445 // successfully applied to the same file. In that case looks like we
446 // definitely can't make progress, so bail out.
447 let mut fixes
= FixedCrate
::default();
448 let mut last_fix_counts
= HashMap
::new();
449 let iterations
= env
::var("CARGO_FIX_MAX_RETRIES")
451 .and_then(|n
| n
.parse().ok())
453 for _
in 0..iterations
{
454 last_fix_counts
.clear();
455 for (path
, file
) in fixes
.files
.iter_mut() {
456 last_fix_counts
.insert(path
.clone(), file
.fixes_applied
);
457 // We'll generate new errors below.
458 file
.errors_applying_fixes
.clear();
460 rustfix_and_fix(&mut fixes
, rustc
, filename
, args
)?
;
461 let mut progress_yet_to_be_made
= false;
462 for (path
, file
) in fixes
.files
.iter_mut() {
463 if file
.errors_applying_fixes
.is_empty() {
466 // If anything was successfully fixed *and* there's at least one
467 // error, then assume the error was spurious and we'll try again on
468 // the next iteration.
469 if file
.fixes_applied
!= *last_fix_counts
.get(path
).unwrap_or(&0) {
470 progress_yet_to_be_made
= true;
473 if !progress_yet_to_be_made
{
478 // Any errors still remaining at this point need to be reported as probably
479 // bugs in Cargo and/or rustfix.
480 for (path
, file
) in fixes
.files
.iter_mut() {
481 for error
in file
.errors_applying_fixes
.drain(..) {
482 Message
::ReplaceFailed
{
493 /// Executes `rustc` to apply one round of suggestions to the crate in question.
495 /// This will fill in the `fixes` map with original code, suggestions applied,
496 /// and any errors encountered while fixing files.
498 fixes
: &mut FixedCrate
,
499 rustc
: &ProcessBuilder
,
502 ) -> Result
<(), Error
> {
503 // If not empty, filter by these lints.
504 // TODO: implement a way to specify this.
505 let only
= HashSet
::new();
507 let mut cmd
= rustc
.build_command();
508 cmd
.arg("--error-format=json");
509 args
.apply(&mut cmd
);
510 let output
= cmd
.output().with_context(|| {
512 "failed to execute `{}`",
513 rustc
.get_program().to_string_lossy()
517 // If rustc didn't succeed for whatever reasons then we're very likely to be
518 // looking at otherwise broken code. Let's not make things accidentally
519 // worse by applying fixes where a bug could cause *more* broken code.
520 // Instead, punt upwards which will reexec rustc over the original code,
521 // displaying pretty versions of the diagnostics we just read out.
522 if !output
.status
.success() && env
::var_os(BROKEN_CODE_ENV
).is_none() {
524 "rustfixing `{:?}` failed, rustc exited with {:?}",
531 let fix_mode
= env
::var_os("__CARGO_FIX_YOLO")
532 .map(|_
| rustfix
::Filter
::Everything
)
533 .unwrap_or(rustfix
::Filter
::MachineApplicableOnly
);
535 // Sift through the output of the compiler to look for JSON messages.
536 // indicating fixes that we can apply.
537 let stderr
= str::from_utf8(&output
.stderr
).context("failed to parse rustc stderr as UTF-8")?
;
539 let suggestions
= stderr
541 .filter(|x
| !x
.is_empty())
542 .inspect(|y
| trace
!("line: {}", y
))
543 // Parse each line of stderr, ignoring errors, as they may not all be JSON.
544 .filter_map(|line
| serde_json
::from_str
::<Diagnostic
>(line
).ok())
545 // From each diagnostic, try to extract suggestions from rustc.
546 .filter_map(|diag
| rustfix
::collect_suggestions(&diag
, &only
, fix_mode
));
548 // Collect suggestions by file so we can apply them one at a time later.
549 let mut file_map
= HashMap
::new();
550 let mut num_suggestion
= 0;
551 for suggestion
in suggestions
{
552 trace
!("suggestion");
553 // Make sure we've got a file associated with this suggestion and all
554 // snippets point to the same file. Right now it's not clear what
555 // we would do with multiple files.
556 let file_names
= suggestion
559 .flat_map(|s
| s
.replacements
.iter())
560 .map(|r
| &r
.snippet
.file_name
);
562 let file_name
= if let Some(file_name
) = file_names
.clone().next() {
565 trace
!("rejecting as it has no solutions {:?}", suggestion
);
569 if !file_names
.clone().all(|f
| f
== &file_name
) {
570 trace
!("rejecting as it changes multiple files: {:?}", suggestion
);
576 .or_insert_with(Vec
::new
)
582 "collected {} suggestions for `{}`",
587 for (file
, suggestions
) in file_map
{
588 // Attempt to read the source code for this file. If this fails then
589 // that'd be pretty surprising, so log a message and otherwise keep
591 let code
= match paths
::read(file
.as_ref()) {
594 warn
!("failed to read `{}`: {}", file
, e
);
598 let num_suggestions
= suggestions
.len();
599 debug
!("applying {} fixes to {}", num_suggestions
, file
);
601 // If this file doesn't already exist then we just read the original
602 // code, so save it. If the file already exists then the original code
603 // doesn't need to be updated as we've just read an interim state with
604 // some fixes but perhaps not all.
605 let fixed_file
= fixes
608 .or_insert_with(|| FixedFile
{
609 errors_applying_fixes
: Vec
::new(),
611 original_code
: code
.clone(),
613 let mut fixed
= CodeFix
::new(&code
);
615 // As mentioned above in `rustfix_crate`, we don't immediately warn
616 // about suggestions that fail to apply here, and instead we save them
617 // off for later processing.
618 for suggestion
in suggestions
.iter().rev() {
619 match fixed
.apply(suggestion
) {
620 Ok(()) => fixed_file
.fixes_applied
+= 1,
621 Err(e
) => fixed_file
.errors_applying_fixes
.push(e
.to_string()),
624 let new_code
= fixed
.finish()?
;
625 paths
::write(&file
, new_code
)?
;
631 fn exit_with(status
: ExitStatus
) -> ! {
635 use std
::os
::unix
::prelude
::*;
636 if let Some(signal
) = status
.signal() {
638 std
::io
::stderr().lock(),
639 "child failed with signal `{}`",
645 process
::exit(status
.code().unwrap_or(3));
648 fn log_failed_fix(stderr
: &[u8]) -> Result
<(), Error
> {
649 let stderr
= str::from_utf8(stderr
).context("failed to parse rustc stderr as utf-8")?
;
651 let diagnostics
= stderr
653 .filter(|x
| !x
.is_empty())
654 .filter_map(|line
| serde_json
::from_str
::<Diagnostic
>(line
).ok());
655 let mut files
= BTreeSet
::new();
656 let mut errors
= Vec
::new();
657 for diagnostic
in diagnostics
{
658 errors
.push(diagnostic
.rendered
.unwrap_or(diagnostic
.message
));
659 for span
in diagnostic
.spans
.into_iter() {
660 files
.insert(span
.file_name
);
663 let mut krate
= None
;
664 let mut prev_dash_dash_krate_name
= false;
665 for arg
in env
::args() {
666 if prev_dash_dash_krate_name
{
667 krate
= Some(arg
.clone());
670 if arg
== "--crate-name" {
671 prev_dash_dash_krate_name
= true;
673 prev_dash_dash_krate_name
= false;
677 let files
= files
.into_iter().collect();
688 /// Various command-line options and settings used when `cargo` is running as
689 /// a proxy for `rustc` during the fix operation.
691 /// This is the `.rs` file that is being fixed.
693 /// If `--edition` is used to migrate to the next edition, this is the
694 /// edition we are migrating towards.
695 prepare_for_edition
: Option
<Edition
>,
696 /// `true` if `--edition-idioms` is enabled.
698 /// The current edition.
700 /// `None` if on 2015.
701 enabled_edition
: Option
<Edition
>,
702 /// Other command-line arguments not reflected by other fields in
704 other
: Vec
<OsString
>,
705 /// Path to the `rustc` executable.
707 /// Console output flags (`--error-format`, `--json`, etc.).
709 /// The normal fix procedure always uses `--json`, so it overrides what
710 /// Cargo normally passes when applying fixes. When displaying warnings or
711 /// errors, it will use these flags.
712 format_args
: Vec
<String
>,
716 fn get() -> Result
<FixArgs
, Error
> {
717 let rustc
= env
::args_os()
720 .ok_or_else(|| anyhow
::anyhow
!("expected rustc as first argument"))?
;
722 let mut enabled_edition
= None
;
723 let mut other
= Vec
::new();
724 let mut format_args
= Vec
::new();
726 for arg
in env
::args_os().skip(2) {
727 let path
= PathBuf
::from(arg
);
728 if path
.extension().and_then(|s
| s
.to_str()) == Some("rs") && path
.exists() {
732 if let Some(s
) = path
.to_str() {
733 if let Some(edition
) = s
.strip_prefix("--edition=") {
734 enabled_edition
= Some(edition
.parse()?
);
737 if s
.starts_with("--error-format=") || s
.starts_with("--json=") {
738 // Cargo may add error-format in some cases, but `cargo
739 // fix` wants to add its own.
740 format_args
.push(s
.to_string());
744 other
.push(path
.into());
746 let file
= file
.ok_or_else(|| anyhow
::anyhow
!("could not find .rs file in rustc args"))?
;
747 let idioms
= env
::var(IDIOMS_ENV
).is_ok();
749 let prepare_for_edition
= env
::var(EDITION_ENV
).ok().map(|_
| {
751 .unwrap_or(Edition
::Edition2015
)
766 fn apply(&self, cmd
: &mut Command
) {
768 cmd
.args(&self.other
).arg("--cap-lints=warn");
769 if let Some(edition
) = self.enabled_edition
{
770 cmd
.arg("--edition").arg(edition
.to_string());
771 if self.idioms
&& edition
.supports_idiom_lint() {
772 cmd
.arg(format
!("-Wrust-{}-idioms", edition
));
776 if let Some(edition
) = self.prepare_for_edition
{
777 if edition
.supports_compat_lint() {
778 cmd
.arg("-W").arg(format
!("rust-{}-compatibility", edition
));
783 /// Validates the edition, and sends a message indicating what is being
785 fn check_edition_and_send_status(&self, config
: &Config
) -> CargoResult
<()> {
786 let to_edition
= match self.prepare_for_edition
{
789 return Message
::Fixing
{
790 file
: self.file
.display().to_string(),
795 // Unfortunately determining which cargo targets are being built
796 // isn't easy, and each target can be a different edition. The
797 // cargo-as-rustc fix wrapper doesn't know anything about the
798 // workspace, so it can't check for the `cargo-features` unstable
799 // opt-in. As a compromise, this just restricts to the nightly
802 // Unfortunately this results in a pretty poor error message when
803 // multiple jobs run in parallel (the error appears multiple
804 // times). Hopefully this doesn't happen often in practice.
805 if !to_edition
.is_stable() && !config
.nightly_features_allowed
{
807 "cannot migrate {} to edition {to_edition}\n\
808 Edition {to_edition} is unstable and not allowed in this release, \
809 consider trying the nightly release channel.",
811 to_edition
= to_edition
814 let from_edition
= self.enabled_edition
.unwrap_or(Edition
::Edition2015
);
815 if from_edition
== to_edition
{
816 Message
::EditionAlreadyEnabled
{
817 file
: self.file
.display().to_string(),
823 file
: self.file
.display().to_string(),