rustversion_detect/
lib.rs

1//! This crate provides a simple API for detecting the rustc
2//! compiler version.
3//!
4//! It is only intended for use at build time,
5//! because it requires executing the `rustc` compiler.
6//!
7//! The implementation is forked from the [`rustversion` crate], but with proc-macro code removed.
8//!
9//! [`rustversion` crate]: https://github.com/dtolnay/rustversion
10//!
11//! # Dependency
12//! Add the following to your build script:
13//! ```toml
14//! [build-dependencies]
15//! rustversion-detect = "0.1"
16//! ```
17#![deny(missing_docs)]
18use std::error::Error;
19use std::fmt::{self, Display};
20
21#[macro_use]
22mod macros;
23mod build;
24pub mod date;
25pub mod version;
26
27pub use crate::date::Date;
28pub use crate::version::{Channel, RustVersion, StableVersionSpec};
29
30/// Detect the current version by executing `rustc`.
31///
32/// This should only be called at build time (usually a build script),
33/// since the rust compiler is likely unavailable at runtime.
34/// It will execute whatever command is present in the `RUSTC` environment variable,
35/// so should not be run in an untrusted environment.
36///
37/// Once the version is successfully detected,
38/// it will be cached for future runs.
39///
40/// # Errors
41/// Returns an error if unable to execute the result compiler
42/// or unable to parse the result.
43pub fn detect_version() -> Result<crate::RustVersion, VersionDetectionError> {
44    {
45        let lock = state::state_mutex()
46            .read()
47            .unwrap_or_else(std::sync::PoisonError::into_inner);
48        match &*lock {
49            Some(cached) => return Ok(*cached),
50            _ => {
51                // fallthrough to detection
52            }
53        }
54    }
55    match build::determine_version() {
56        Ok(success) => {
57            {
58                let mut lock = state::state_mutex()
59                    .write()
60                    .unwrap_or_else(std::sync::PoisonError::into_inner);
61                *lock = Some(success);
62            }
63            Ok(success)
64        }
65        Err(failure) => Err(failure),
66    }
67}
68
69/// Indicates failure to detect the compiler's rust version.
70#[derive(Debug)]
71pub struct VersionDetectionError {
72    desc: String,
73    cause: Option<std::io::Error>,
74}
75impl VersionDetectionError {
76    pub(crate) fn new(desc: String) -> Self {
77        VersionDetectionError { desc, cause: None }
78    }
79
80    pub(crate) fn with_cause(desc: String, cause: std::io::Error) -> Self {
81        VersionDetectionError {
82            desc,
83            cause: Some(cause),
84        }
85    }
86}
87impl Display for VersionDetectionError {
88    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
89        write!(f, "{}", self.desc)?;
90        if let Some(ref cause) = self.cause {
91            write!(f, ": {}", cause)?;
92        }
93        Ok(())
94    }
95}
96impl Error for VersionDetectionError {
97    fn source(&self) -> Option<&(dyn Error + 'static)> {
98        self.cause.as_ref().map(|x| x as _)
99    }
100}
101
102/// Caches the detected rust version.
103#[allow(unused_imports)]
104mod state {
105    use std::sync::{Once, RwLock};
106
107    #[allow(deprecated)] // Only available since 1.32
108    static CACHED_STATE_INIT: Once = std::sync::ONCE_INIT;
109    static mut CACHED_STATE: Option<RwLock<Option<crate::RustVersion>>> = None;
110
111    pub fn state_mutex() -> &'static RwLock<Option<crate::RustVersion>> {
112        CACHED_STATE_INIT.call_once(|| {
113            // SAFETY: Will only be called once
114            unsafe {
115                CACHED_STATE = Some(RwLock::new(None));
116            }
117        });
118        // SAFETY: After completion of `Once::call_once`,
119        // the mutex is fully initialized and not `None`
120        unsafe {
121            match CACHED_STATE {
122                Some(ref mutex) => mutex,
123                None => std::hint::unreachable_unchecked(),
124            }
125        }
126    }
127}