Skip to main content

cargo_msrv/sub_command/
verify.rs

1use camino::Utf8PathBuf;
2use cargo_metadata::MetadataCommand;
3use std::convert::TryFrom;
4
5use rust_releases::{Release, ReleaseIndex};
6
7use crate::compatibility::IsCompatible;
8use crate::context::{EnvironmentContext, VerifyContext};
9use crate::error::{CargoMSRVError, TResult};
10use crate::manifest::CargoManifest;
11use crate::manifest::bare_version::BareVersion;
12use crate::outcome::Compatibility;
13use crate::reporter::Reporter;
14use crate::reporter::event::VerifyResult;
15use crate::rust::Toolchain;
16use crate::sub_command::SubCommand;
17
18/// Verifier which determines whether a given Rust version is deemed compatible or not.
19pub struct Verify<'index, C: IsCompatible> {
20    release_index: &'index ReleaseIndex,
21    runner: C,
22}
23
24impl<'index, C: IsCompatible> Verify<'index, C> {
25    /// Instantiate the verifier using a release index and a runner.
26    ///
27    /// The runner is used to determine whether a given Rust version will be deemed compatible or not.
28    pub fn new(release_index: &'index ReleaseIndex, runner: C) -> Self {
29        Self {
30            release_index,
31            runner,
32        }
33    }
34}
35
36impl<C: IsCompatible> SubCommand for Verify<'_, C> {
37    type Context = VerifyContext;
38    type Output = ();
39
40    /// Run the verifier against a Rust version which is obtained from the config.
41    fn run(&self, ctx: &Self::Context, reporter: &impl Reporter) -> TResult<Self::Output> {
42        // todo!
43        let rust_version = ctx.rust_version.clone();
44
45        verify_msrv(
46            reporter,
47            ctx,
48            self.release_index,
49            rust_version,
50            &self.runner,
51        )?;
52
53        Ok(())
54    }
55}
56
57/// Verify whether a Cargo project is compatible with a `rustup run` command,
58/// for the (given or specified) `rust_version`.
59fn verify_msrv(
60    reporter: &impl Reporter,
61    ctx: &VerifyContext,
62    release_index: &ReleaseIndex,
63    rust_version: RustVersion,
64    runner: &impl IsCompatible,
65) -> TResult<()> {
66    let bare_version = rust_version.version();
67    let version =
68        bare_version.try_to_semver(release_index.releases().iter().map(Release::version))?;
69
70    let target = ctx.toolchain.target;
71    let components = ctx.toolchain.components;
72    let toolchain = Toolchain::new(version.clone(), target, components);
73
74    match runner.is_compatible(&toolchain)? {
75        Compatibility::Compatible(_) => success(reporter, toolchain),
76        Compatibility::Incompatible(f) => {
77            failure(reporter, toolchain, rust_version, Some(f.error_message))
78        }
79    }
80}
81
82// Report the successful verification to the user
83fn success(reporter: &impl Reporter, toolchain: Toolchain) -> TResult<()> {
84    reporter.report_event(VerifyResult::compatible(toolchain))?;
85    Ok(())
86}
87
88// Report the failed verification to the user, and return a VerifyFailed error
89fn failure(
90    reporter: &impl Reporter,
91    toolchain: Toolchain,
92    rust_version: RustVersion,
93    error: Option<String>,
94) -> TResult<()> {
95    reporter.report_event(VerifyResult::incompatible(toolchain, error))?;
96
97    Err(CargoMSRVError::SubCommandVerify(Error::VerifyFailed(
98        VerifyFailed::from(rust_version),
99    )))
100}
101
102/// Error which can be returned if the verifier deemed the tested Rust version incompatible.
103#[derive(Debug, thiserror::Error)]
104pub enum Error {
105    #[error(
106        "Crate source was found to be incompatible with Rust version '{}' specified {}", .0.rust_version, .0.source
107    )]
108    VerifyFailed(VerifyFailed),
109}
110
111/// Data structure which contains information about which version failed to verify, and where
112/// we obtained this version from.
113///
114/// It is combination of the Rust version which was tested for compatibility and the source which was
115/// used to find this tested Rust version.
116#[derive(Debug)]
117pub struct VerifyFailed {
118    rust_version: BareVersion,
119    source: RustVersionSource,
120}
121
122impl From<RustVersion> for VerifyFailed {
123    fn from(value: RustVersion) -> Self {
124        VerifyFailed {
125            rust_version: value.rust_version,
126            source: value.source,
127        }
128    }
129}
130
131/// A combination of a bare (two- or three component) Rust version and the source which was used to
132/// locate this version.
133#[derive(Clone, Debug)]
134pub struct RustVersion {
135    rust_version: BareVersion,
136    source: RustVersionSource,
137}
138
139impl RustVersion {
140    pub fn from_arg(rust_version: BareVersion) -> Self {
141        Self {
142            rust_version,
143            source: RustVersionSource::Arg,
144        }
145    }
146
147    pub fn try_from_environment(env: &EnvironmentContext) -> TResult<Self> {
148        let manifest_path = env.manifest();
149
150        let metadata = MetadataCommand::new()
151            .manifest_path(&manifest_path)
152            .exec()?;
153        CargoManifest::try_from(metadata)?
154            .minimum_rust_version()
155            .ok_or_else(|| CargoMSRVError::NoMSRVKeyInCargoToml(manifest_path.clone()))
156            .map(|v| RustVersion {
157                rust_version: v.clone(),
158                source: RustVersionSource::Manifest(manifest_path.clone()),
159            })
160    }
161
162    /// Get the bare (two- or three component) version specifying the Rust version.
163    pub fn version(&self) -> &BareVersion {
164        &self.rust_version
165    }
166
167    /// Get the version and discard all else.
168    pub fn into_version(self) -> BareVersion {
169        self.rust_version
170    }
171}
172
173/// Source used to obtain a Rust version for the verifier.
174#[derive(Clone, Debug, thiserror::Error)]
175enum RustVersionSource {
176    #[error("as --rust-version argument")]
177    Arg,
178
179    #[error("as MSRV in the Cargo manifest located at '{0}'")]
180    Manifest(Utf8PathBuf),
181}