cargo_msrv/compatibility/
rustup_toolchain_check.rs1use crate::compatibility::IsCompatible;
2use crate::context::EnvironmentContext;
3use crate::error::{IoError, IoErrorSource, LockfileHandlerError};
4use crate::external_command::cargo_command::CargoCommand;
5use crate::external_command::rustup_command::RustupCommand;
6use crate::lockfile::LockfileHandler;
7use crate::outcome::Incompatible;
8use crate::reporter::event::{CheckMethod, CheckResult, CheckToolchain, Method};
9use crate::rust::Toolchain;
10use crate::rust::setup_toolchain::{SetupRustupToolchain, SetupToolchain};
11use crate::{CargoMSRVError, Compatibility, Reporter, TResult};
12use camino::{Utf8Path, Utf8PathBuf};
13use std::fmt;
14use std::fmt::Formatter;
15
16pub struct RustupToolchainCheck<'reporter, 'env, R: Reporter> {
17 reporter: &'reporter R,
18 settings: Settings<'env>,
19}
20
21impl<'reporter, 'env, R: Reporter> RustupToolchainCheck<'reporter, 'env, R> {
22 pub fn new(
23 reporter: &'reporter R,
24 ignore_lockfile: bool,
25 no_check_feedback: bool,
26 skip_unavailable_toolchains: bool,
27 environment: &'env EnvironmentContext,
28 run_command: RunCommand,
29 ) -> Self {
30 Self {
31 reporter,
32 settings: Settings {
33 ignore_lockfile,
34 no_check_feedback,
35 skip_unavailable_toolchains,
36 environment,
37 check_cmd: run_command,
38 },
39 }
40 }
41}
42
43impl<R: Reporter> IsCompatible for RustupToolchainCheck<'_, '_, R> {
44 fn is_compatible(&self, toolchain: &Toolchain) -> TResult<Compatibility> {
45 let settings = &self.settings;
46
47 self.reporter
48 .run_scoped_event(CheckToolchain::new(toolchain.to_owned()), || {
49 info!(ignore_lockfile_enabled = settings.ignore_lockfile());
50
51 let ignore_lockfile = settings.ignore_lockfile();
53 let handle_wrap = create_lockfile_handle(ignore_lockfile, settings.environment)?
54 .map(|handle| handle.move_lockfile())
55 .transpose()?;
56
57 match setup_toolchain(self.reporter, toolchain) {
60 Ok(()) => Ok(()),
61 Err(err) if settings.skip_unavailable_toolchains() => {
62 return Ok(Compatibility::Incompatible(Incompatible {
63 toolchain_spec: toolchain.clone(),
64 error_message: err.to_string(),
65 }));
66 }
67 Err(err) => Err(err),
68 }?;
69
70 if handle_wrap.is_some() {
71 remove_lockfile(&settings.lockfile_path())?;
72 }
73
74 let crate_root = settings.crate_root_path();
75 let cmd = &self.settings.check_cmd;
76
77 let outcome = run_check_command_via_rustup(
78 self.reporter,
79 toolchain,
80 crate_root,
81 cmd.components(),
82 )?;
83
84 report_outcome(self.reporter, &outcome, settings.no_check_feedback())?;
86
87 if let Some(handle) = handle_wrap {
89 drop(handle);
90 }
91
92 Ok(outcome)
93 })
94 }
95}
96
97impl<R: Reporter> fmt::Debug for RustupToolchainCheck<'_, '_, R> {
98 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
99 f.write_fmt(format_args!("{:?}", self.settings))
100 }
101}
102
103fn setup_toolchain(reporter: &impl Reporter, toolchain: &Toolchain) -> TResult<()> {
104 let downloader = SetupRustupToolchain::new(reporter);
105 downloader.download(toolchain)?;
106
107 Ok(())
108}
109
110fn run_check_command_via_rustup(
111 reporter: &impl Reporter,
112 toolchain: &Toolchain,
113 dir: &Utf8Path,
114 check: &[String],
115) -> TResult<Compatibility> {
116 let version = format!("{}", toolchain.version());
117 let mut cmd = vec![version.as_str()];
118 cmd.extend(check.iter().map(|s| s.as_str()));
119
120 reporter.report_event(CheckMethod::new(
121 toolchain.to_owned(),
122 Method::rustup_run(&cmd, dir),
123 ))?;
124
125 let rustup_output = RustupCommand::new()
126 .with_args(cmd.iter())
127 .with_dir(dir)
128 .with_stderr()
129 .run()
130 .map_err(|_| CargoMSRVError::UnableToRunCheck {
131 command: cmd[1..].join(" "),
132 cwd: dir.to_path_buf(),
133 })?;
134
135 let status = rustup_output.exit_status();
136
137 if status.success() {
138 Ok(Compatibility::new_success(toolchain.to_owned()))
139 } else {
140 let stderr = rustup_output.stderr();
141 let command = cmd.join(" ");
142
143 info!(
144 ?toolchain,
145 stderr,
146 cmd = command.as_str(),
147 "try_building run failed"
148 );
149
150 Ok(Compatibility::new_failure(
151 toolchain.to_owned(),
152 stderr.to_string(),
153 ))
154 }
155}
156
157fn report_outcome(
158 reporter: &impl Reporter,
159 outcome: &Compatibility,
160 no_error_report: bool,
161) -> TResult<()> {
162 match outcome {
163 Compatibility::Compatible(outcome) => {
164 reporter.report_event(CheckResult::compatible(outcome.toolchain_spec.to_owned()))?
166 }
167 Compatibility::Incompatible(outcome) if no_error_report => {
168 reporter.report_event(CheckResult::incompatible(
170 outcome.toolchain_spec.to_owned(),
171 None,
172 ))?
173 }
174 Compatibility::Incompatible(outcome) => {
175 reporter.report_event(CheckResult::incompatible(
177 outcome.toolchain_spec.to_owned(),
178 Some(outcome.error_message.clone()),
179 ))?
180 }
181 };
182
183 Ok(())
184}
185
186fn create_lockfile_handle(
189 ignore_lockfile: bool,
190 env: &EnvironmentContext,
191) -> Result<Option<LockfileHandler>, LockfileHandlerError> {
192 ignore_lockfile
193 .then(|| env.lock())
194 .filter(|lockfile| lockfile.is_file())
195 .map(LockfileHandler::try_new)
196 .transpose()
197}
198
199fn remove_lockfile(lock_file: &Utf8Path) -> TResult<()> {
200 if lock_file.is_file() {
201 std::fs::remove_file(lock_file).map_err(|error| IoError {
202 error,
203 source: IoErrorSource::RemoveFile(lock_file.to_path_buf()),
204 })?;
205 }
206
207 Ok(())
208}
209
210#[derive(Debug)]
211struct Settings<'env> {
212 ignore_lockfile: bool,
213 no_check_feedback: bool,
214 skip_unavailable_toolchains: bool,
215
216 environment: &'env EnvironmentContext,
217 check_cmd: RunCommand,
218}
219
220impl Settings<'_> {
221 pub fn ignore_lockfile(&self) -> bool {
222 self.ignore_lockfile
223 }
224
225 pub fn no_check_feedback(&self) -> bool {
226 self.no_check_feedback
227 }
228
229 pub fn skip_unavailable_toolchains(&self) -> bool {
230 self.skip_unavailable_toolchains
231 }
232
233 pub fn crate_root_path(&self) -> &Utf8Path {
234 self.environment.root()
235 }
236
237 pub fn lockfile_path(&self) -> Utf8PathBuf {
238 self.environment.lock()
239 }
240}
241
242#[derive(Debug)]
243pub struct RunCommand {
244 command: Vec<String>,
245}
246
247impl RunCommand {
248 pub fn from_cargo_command(cargo_command: CargoCommand) -> Self {
249 Self {
250 command: cargo_command.into_args(),
251 }
252 }
253
254 pub fn custom(command: Vec<String>) -> Self {
255 Self { command }
256 }
257
258 pub fn components(&self) -> &[String] {
259 self.command.as_ref()
260 }
261}