| #![forbid(unsafe_code)] |
| #![warn(rust_2018_idioms, single_use_lifetimes)] |
| |
| use std::{ |
| env, fs, |
| path::{Path, PathBuf}, |
| process::Command, |
| str, |
| }; |
| |
| // The rustc-cfg strings below are *not* public API. Please let us know by |
| // opening a GitHub issue if your build environment requires some way to enable |
| // these cfgs other than by executing our build script. |
| fn main() { |
| let rustc = env::var_os("RUSTC").map_or_else(|| "rustc".into(), PathBuf::from); |
| let version = match Version::from_rustc(&rustc) { |
| Ok(version) => version.print(), |
| Err(e) => { |
| println!( |
| "cargo:warning={}: unable to determine rustc version: {}", |
| env!("CARGO_PKG_NAME"), |
| e |
| ); |
| return; |
| } |
| }; |
| |
| let out_dir = env::var_os("OUT_DIR").map(PathBuf::from).expect("OUT_DIR not set"); |
| let out_file = out_dir.join("version.rs"); |
| fs::write(out_file, version).expect("failed to write version.rs"); |
| |
| // Mark as build script has been run successfully. |
| println!("cargo:rustc-cfg=const_fn_has_build_script"); |
| } |
| |
| struct Version { |
| minor: u32, |
| nightly: bool, |
| } |
| |
| impl Version { |
| // Based on https://github.com/cuviper/autocfg/blob/1.0.1/src/version.rs#L25-L59 |
| // |
| // TODO: use autocfg if https://github.com/cuviper/autocfg/issues/28 merged |
| // or https://github.com/taiki-e/const_fn/issues/27 rejected. |
| fn from_rustc(rustc: &Path) -> Result<Self, String> { |
| let output = |
| Command::new(rustc).args(&["--version", "--verbose"]).output().map_err(|e| { |
| format!("could not execute `{} --version --verbose`: {}", rustc.display(), e) |
| })?; |
| if !output.status.success() { |
| return Err(format!( |
| "process didn't exit successfully: `{} --version --verbose`", |
| rustc.display() |
| )); |
| } |
| let output = str::from_utf8(&output.stdout).map_err(|e| { |
| format!("failed to parse output of `{} --version --verbose`: {}", rustc.display(), e) |
| })?; |
| |
| // Find the release line in the verbose version output. |
| let release = output |
| .lines() |
| .find(|line| line.starts_with("release: ")) |
| .map(|line| &line["release: ".len()..]) |
| .ok_or_else(|| { |
| format!( |
| "could not find rustc release from output of `{} --version --verbose`: {}", |
| rustc.display(), |
| output |
| ) |
| })?; |
| |
| // Split the version and channel info. |
| let mut version_channel = release.split('-'); |
| let version = version_channel.next().unwrap(); |
| let channel = version_channel.next(); |
| |
| let minor = (|| { |
| // Split the version into semver components. |
| let mut digits = version.splitn(3, '.'); |
| let major = digits.next()?; |
| if major != "1" { |
| return None; |
| } |
| let minor = digits.next()?.parse().ok()?; |
| let _patch = digits.next()?; |
| Some(minor) |
| })() |
| .ok_or_else(|| { |
| format!("unexpected output from `{} --version --verbose`: {}", rustc.display(), output) |
| })?; |
| |
| let nightly = channel.map_or(false, |c| c == "dev" || c == "nightly"); |
| Ok(Self { minor, nightly }) |
| } |
| |
| fn print(&self) -> String { |
| format!("Version {{ minor: {}, nightly: {} }}\n", self.minor, self.nightly) |
| } |
| } |