@@ -50,12 +50,16 @@ use log::{debug, trace, warn};
5050use rustfix:: diagnostics:: Diagnostic ;
5151use rustfix:: { self , CodeFix } ;
5252
53- use crate :: core:: { Edition , Workspace } ;
53+ use crate :: core:: compiler:: RustcTargetData ;
54+ use crate :: core:: resolver:: features:: { FeatureOpts , FeatureResolver , RequestedFeatures } ;
55+ use crate :: core:: resolver:: { HasDevUnits , ResolveBehavior , ResolveOpts } ;
56+ use crate :: core:: { Edition , MaybePackage , Workspace } ;
5457use crate :: ops:: { self , CompileOptions } ;
5558use crate :: util:: diagnostic_server:: { Message , RustfixDiagnosticServer } ;
5659use crate :: util:: errors:: CargoResult ;
5760use crate :: util:: { self , paths, Config , ProcessBuilder } ;
5861use crate :: util:: { existing_vcs_repo, LockServer , LockServerClient } ;
62+ use crate :: { drop_eprint, drop_eprintln} ;
5963
6064const FIX_ENV : & str = "__CARGO_FIX_PLZ" ;
6165const BROKEN_CODE_ENV : & str = "__CARGO_FIX_BROKEN_CODE" ;
@@ -74,6 +78,9 @@ pub struct FixOptions {
7478
7579pub fn fix ( ws : & Workspace < ' _ > , opts : & mut FixOptions ) -> CargoResult < ( ) > {
7680 check_version_control ( ws. config ( ) , opts) ?;
81+ if opts. edition {
82+ check_resolver_change ( ws, opts) ?;
83+ }
7784
7885 // Spin up our lock server, which our subprocesses will use to synchronize fixes.
7986 let lock_server = LockServer :: new ( ) ?;
@@ -193,6 +200,114 @@ fn check_version_control(config: &Config, opts: &FixOptions) -> CargoResult<()>
193200 ) ;
194201}
195202
203+ fn check_resolver_change ( ws : & Workspace < ' _ > , opts : & FixOptions ) -> CargoResult < ( ) > {
204+ let root = ws. root_maybe ( ) ;
205+ match root {
206+ MaybePackage :: Package ( root_pkg) => {
207+ if root_pkg. manifest ( ) . resolve_behavior ( ) . is_some ( ) {
208+ // If explicitly specified by the user, no need to check.
209+ return Ok ( ( ) ) ;
210+ }
211+ // Only trigger if updating the root package from 2018.
212+ let pkgs = opts. compile_opts . spec . get_packages ( ws) ?;
213+ if !pkgs. iter ( ) . any ( |& pkg| pkg == root_pkg) {
214+ // The root is not being migrated.
215+ return Ok ( ( ) ) ;
216+ }
217+ if root_pkg. manifest ( ) . edition ( ) != Edition :: Edition2018 {
218+ // V1 to V2 only happens on 2018 to 2021.
219+ return Ok ( ( ) ) ;
220+ }
221+ }
222+ MaybePackage :: Virtual ( _vm) => {
223+ // Virtual workspaces don't have a global edition to set (yet).
224+ return Ok ( ( ) ) ;
225+ }
226+ }
227+ if !opts. compile_opts . filter . is_all_targets ( ) {
228+ // When migrating specific targets, we can't know if the user intends
229+ // to set the global edition at this time or not. Conservatively
230+ // assume the user will figure things out.
231+ return Ok ( ( ) ) ;
232+ }
233+ // 2018 without `resolver` set must be V1
234+ assert_eq ! ( ws. resolve_behavior( ) , ResolveBehavior :: V1 ) ;
235+ let specs = opts. compile_opts . spec . to_package_id_specs ( ws) ?;
236+ let resolve_opts = ResolveOpts :: new (
237+ /*dev_deps*/ true ,
238+ RequestedFeatures :: from_command_line (
239+ & opts. compile_opts . features ,
240+ opts. compile_opts . all_features ,
241+ !opts. compile_opts . no_default_features ,
242+ ) ,
243+ ) ;
244+ let target_data = RustcTargetData :: new ( ws, & opts. compile_opts . build_config . requested_kinds ) ?;
245+ // HasDevUnits::No because that may uncover more differences.
246+ // This is not the same as what `cargo fix` is doing, since it is doing
247+ // `--all-targets` which includes dev dependencies.
248+ let ws_resolve = ops:: resolve_ws_with_opts (
249+ ws,
250+ & target_data,
251+ & opts. compile_opts . build_config . requested_kinds ,
252+ & resolve_opts,
253+ & specs,
254+ HasDevUnits :: No ,
255+ crate :: core:: resolver:: features:: ForceAllTargets :: No ,
256+ ) ?;
257+
258+ let feature_opts = FeatureOpts :: new_behavior ( ResolveBehavior :: V2 , HasDevUnits :: No ) ;
259+ let v2_features = FeatureResolver :: resolve (
260+ ws,
261+ & target_data,
262+ & ws_resolve. targeted_resolve ,
263+ & ws_resolve. pkg_set ,
264+ & resolve_opts. features ,
265+ & specs,
266+ & opts. compile_opts . build_config . requested_kinds ,
267+ feature_opts,
268+ ) ?;
269+
270+ let differences = v2_features. compare_legacy ( & ws_resolve. resolved_features ) ;
271+ if differences. features . is_empty ( ) && differences. optional_deps . is_empty ( ) {
272+ // Nothing is different, nothing to report.
273+ return Ok ( ( ) ) ;
274+ }
275+ let config = ws. config ( ) ;
276+ config. shell ( ) . note (
277+ "Switching to Edition 2021 will enable the use of the version 2 feature resolver in Cargo." ,
278+ ) ?;
279+ drop_eprintln ! (
280+ config,
281+ "This may cause dependencies to resolve with a different set of features."
282+ ) ;
283+ drop_eprintln ! (
284+ config,
285+ "More information about the resolver changes may be found \
286+ at https://doc.rust-lang.org/cargo/reference/features.html#feature-resolver-version-2"
287+ ) ;
288+ drop_eprintln ! (
289+ config,
290+ "The following differences were detected with the current configuration:\n "
291+ ) ;
292+ let report = |changes : crate :: core:: resolver:: features:: DiffMap , what| {
293+ for ( ( pkg_id, for_host) , removed) in changes {
294+ drop_eprint ! ( config, " {}" , pkg_id) ;
295+ if for_host {
296+ drop_eprint ! ( config, " (as build dependency)" ) ;
297+ }
298+ if !removed. is_empty ( ) {
299+ let joined: Vec < _ > = removed. iter ( ) . map ( |s| s. as_str ( ) ) . collect ( ) ;
300+ drop_eprint ! ( config, " removed {} `{}`" , what, joined. join( "," ) ) ;
301+ }
302+ drop_eprint ! ( config, "\n " ) ;
303+ }
304+ } ;
305+ report ( differences. features , "features" ) ;
306+ report ( differences. optional_deps , "optional dependency" ) ;
307+ drop_eprint ! ( config, "\n " ) ;
308+ Ok ( ( ) )
309+ }
310+
196311/// Entry point for `cargo` running as a proxy for `rustc`.
197312///
198313/// This is called every time `cargo` is run to check if it is in proxy mode.
0 commit comments