commit 680e2c6c409c67ea5006338915c763334b797cb3 Author: su-fang Date: Wed Mar 8 10:22:16 2023 +0800 Import Upstream version 0.4.0 diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..c0d69af --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "6093aa9de6428df2e1c04aa9e969f1af7c34664f" + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22d9b57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +.DS_Store diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c81ff85 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "rustc_version" +version = "0.4.0" +authors = ["Dirkjan Ochtman ", "Marvin Löbel "] +description = "A library for querying the version of a installed rustc compiler" +documentation = "https://docs.rs/rustc_version/" +readme = "README.md" +keywords = ["version", "rustc"] +license = "MIT/Apache-2.0" +repository = "https://github.com/Kimundi/rustc-version-rs" +[dependencies.semver] +version = "1.0" +[dev-dependencies.doc-comment] +version = "0.3" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..464176f --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,17 @@ +[package] +name = "rustc_version" +version = "0.4.0" +authors = ["Dirkjan Ochtman ", "Marvin Löbel "] +license = "MIT/Apache-2.0" +description = "A library for querying the version of a installed rustc compiler" +readme = "README.md" +documentation = "https://docs.rs/rustc_version/" +repository = "https://github.com/Kimundi/rustc-version-rs" +keywords = ["version", "rustc"] +edition = "2018" + +[dependencies] +semver = "1.0" + +[dev-dependencies] +doc-comment = "0.3" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 0000000..16fe87b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 0000000..40b8817 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2016 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e76ef95 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +rustc-version-rs +================ + +[![Documentation](https://docs.rs/rustc_version/badge.svg)](https://docs.rs/rustc_version/) +[![Crates.io](https://img.shields.io/crates/v/rustc_version.svg)](https://crates.io/crates/rustc_version) +[![Build status](https://github.com/Kimundi/rustc-version-rs/workflows/CI/badge.svg)](https://github.com/Kimundi/rustc-version-rs/actions?query=workflow%3ACI) + +A library for querying the version of a `rustc` compiler. + +This can be used by build scripts or other tools dealing with Rust sources +to make decisions based on the version of the compiler. Current MSRV is 1.32.0. + +If this is of interest, also consider looking at these other crates: + +* [autocfg](https://crates.io/crates/autocfg/), which helps with feature detection instead of depending on compiler versions +* [rustversion](https://github.com/dtolnay/rustversion) provides a procedural macro with no other dependencies + +# Getting Started + +[rustc-version-rs is available on crates.io](https://crates.io/crates/rustc_version). +It is recommended to look there for the newest released version, as well as links to the newest builds of the docs. + +At the point of the last update of this README, the latest published version could be used like this: + +Add the following dependency to your Cargo manifest... + +```toml +[build-dependencies] +rustc_version = "0.2" +``` + +... and see the [docs](https://docs.rs/rustc_version) for how to use it. + +# Example + +```rust +// This could be a cargo build script + +use rustc_version::{version, version_meta, Channel, Version}; + +fn main() { + // Assert we haven't travelled back in time + assert!(version().unwrap().major >= 1); + + // Set cfg flags depending on release channel + match version_meta().unwrap().channel { + Channel::Stable => { + println!("cargo:rustc-cfg=RUSTC_IS_STABLE"); + } + Channel::Beta => { + println!("cargo:rustc-cfg=RUSTC_IS_BETA"); + } + Channel::Nightly => { + println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY"); + } + Channel::Dev => { + println!("cargo:rustc-cfg=RUSTC_IS_DEV"); + } + } + + // Check for a minimum version + if version().unwrap() >= Version::parse("1.4.0").unwrap() { + println!("cargo:rustc-cfg=compiler_has_important_bugfix"); + } +} +``` + +## License + +Licensed under either of + + * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any +additional terms or conditions. diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000..38c47a8 --- /dev/null +++ b/deny.toml @@ -0,0 +1,3 @@ +[licenses] +allow-osi-fsf-free = "either" +copyleft = "deny" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..cee1ec8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,417 @@ +// Copyright 2016 rustc-version-rs developers +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![warn(missing_docs)] + +//! Simple library for getting the version information of a `rustc` +//! compiler. +//! +//! This can be used by build scripts or other tools dealing with Rust sources +//! to make decisions based on the version of the compiler. +//! +//! It calls `$RUSTC --version -v` and parses the output, falling +//! back to `rustc` if `$RUSTC` is not set. +//! +//! # Example +//! +//! ```rust +//! // This could be a cargo build script +//! +//! use rustc_version::{version, version_meta, Channel, Version}; +//! +//! // Assert we haven't travelled back in time +//! assert!(version().unwrap().major >= 1); +//! +//! // Set cfg flags depending on release channel +//! match version_meta().unwrap().channel { +//! Channel::Stable => { +//! println!("cargo:rustc-cfg=RUSTC_IS_STABLE"); +//! } +//! Channel::Beta => { +//! println!("cargo:rustc-cfg=RUSTC_IS_BETA"); +//! } +//! Channel::Nightly => { +//! println!("cargo:rustc-cfg=RUSTC_IS_NIGHTLY"); +//! } +//! Channel::Dev => { +//! println!("cargo:rustc-cfg=RUSTC_IS_DEV"); +//! } +//! } +//! +//! // Check for a minimum version +//! if version().unwrap() >= Version::parse("1.4.0").unwrap() { +//! println!("cargo:rustc-cfg=compiler_has_important_bugfix"); +//! } +//! ``` + +#[cfg(test)] +#[macro_use] +extern crate doc_comment; + +#[cfg(test)] +doctest!("../README.md"); + +use std::collections::HashMap; +use std::process::Command; +use std::{env, error, fmt, io, num, str}; +use std::{ffi::OsString, str::FromStr}; + +// Convenience re-export to allow version comparison without needing to add +// semver crate. +pub use semver::Version; + +use Error::*; + +/// Release channel of the compiler. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub enum Channel { + /// Development release channel + Dev, + /// Nightly release channel + Nightly, + /// Beta release channel + Beta, + /// Stable release channel + Stable, +} + +/// LLVM version +/// +/// LLVM's version numbering scheme is not semver compatible until version 4.0 +/// +/// rustc [just prints the major and minor versions], so other parts of the version are not included. +/// +/// [just prints the major and minor versions]: https://github.com/rust-lang/rust/blob/b5c9e2448c9ace53ad5c11585803894651b18b0a/compiler/rustc_codegen_llvm/src/llvm_util.rs#L173-L178 +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct LlvmVersion { + // fields must be ordered major, minor for comparison to be correct + /// Major version + pub major: u64, + /// Minor version + pub minor: u64, + // TODO: expose micro version here +} + +impl fmt::Display for LlvmVersion { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}", self.major, self.minor) + } +} + +impl FromStr for LlvmVersion { + type Err = LlvmVersionParseError; + + fn from_str(s: &str) -> Result { + let mut parts = s + .split('.') + .map(|part| -> Result { + if part == "0" { + Ok(0) + } else if part.starts_with('0') { + Err(LlvmVersionParseError::ComponentMustNotHaveLeadingZeros) + } else if part.starts_with('-') || part.starts_with('+') { + Err(LlvmVersionParseError::ComponentMustNotHaveSign) + } else { + Ok(part.parse()?) + } + }); + + let major = parts.next().unwrap()?; + let mut minor = 0; + + if let Some(part) = parts.next() { + minor = part?; + } else if major < 4 { + // LLVM versions earlier than 4.0 have significant minor versions, so require the minor version in this case. + return Err(LlvmVersionParseError::MinorVersionRequiredBefore4); + } + + if let Some(Err(e)) = parts.next() { + return Err(e); + } + + if parts.next().is_some() { + return Err(LlvmVersionParseError::TooManyComponents); + } + + Ok(Self { major, minor }) + } +} + +/// Rustc version plus metadata like git short hash and build date. +#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct VersionMeta { + /// Version of the compiler + pub semver: Version, + + /// Git short hash of the build of the compiler + pub commit_hash: Option, + + /// Commit date of the compiler + pub commit_date: Option, + + /// Build date of the compiler; this was removed between Rust 1.0.0 and 1.1.0. + pub build_date: Option, + + /// Release channel of the compiler + pub channel: Channel, + + /// Host target triple of the compiler + pub host: String, + + /// Short version string of the compiler + pub short_version_string: String, + + /// Version of LLVM used by the compiler + pub llvm_version: Option, +} + +impl VersionMeta { + /// Returns the version metadata for `cmd`, which should be a `rustc` command. + pub fn for_command(mut cmd: Command) -> Result { + let out = cmd + .arg("-vV") + .output() + .map_err(Error::CouldNotExecuteCommand)?; + + if !out.status.success() { + return Err(Error::CommandError { + stdout: String::from_utf8_lossy(&out.stdout).into(), + stderr: String::from_utf8_lossy(&out.stderr).into(), + }); + } + + version_meta_for(str::from_utf8(&out.stdout)?) + } +} + +/// Returns the `rustc` SemVer version. +pub fn version() -> Result { + Ok(version_meta()?.semver) +} + +/// Returns the `rustc` SemVer version and additional metadata +/// like the git short hash and build date. +pub fn version_meta() -> Result { + let cmd = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); + + VersionMeta::for_command(Command::new(cmd)) +} + +/// Parses a "rustc -vV" output string and returns +/// the SemVer version and additional metadata +/// like the git short hash and build date. +pub fn version_meta_for(verbose_version_string: &str) -> Result { + let mut map = HashMap::new(); + for (i, line) in verbose_version_string.lines().enumerate() { + if i == 0 { + map.insert("short", line); + continue; + } + + let mut parts = line.splitn(2, ": "); + let key = match parts.next() { + Some(key) => key, + None => continue, + }; + + if let Some(value) = parts.next() { + map.insert(key, value); + } + } + + let short_version_string = expect_key("short", &map)?; + let host = expect_key("host", &map)?; + let release = expect_key("release", &map)?; + let semver: Version = release.parse()?; + + let channel = match semver.pre.split('.').next().unwrap() { + "" => Channel::Stable, + "dev" => Channel::Dev, + "beta" => Channel::Beta, + "nightly" => Channel::Nightly, + x => return Err(Error::UnknownPreReleaseTag(x.to_owned())), + }; + + let commit_hash = expect_key_or_unknown("commit-hash", &map)?; + let commit_date = expect_key_or_unknown("commit-date", &map)?; + let build_date = map + .get("build-date") + .filter(|&v| *v != "unknown") + .map(|&v| String::from(v)); + let llvm_version = match map.get("LLVM version") { + Some(&v) => Some(v.parse()?), + None => None, + }; + + Ok(VersionMeta { + semver, + commit_hash, + commit_date, + build_date, + channel, + host, + short_version_string, + llvm_version, + }) +} + +fn expect_key_or_unknown(key: &str, map: &HashMap<&str, &str>) -> Result, Error> { + match map.get(key) { + Some(&v) if v == "unknown" => Ok(None), + Some(&v) => Ok(Some(String::from(v))), + None => Err(Error::UnexpectedVersionFormat), + } +} + +fn expect_key(key: &str, map: &HashMap<&str, &str>) -> Result { + map.get(key) + .map(|&v| String::from(v)) + .ok_or(Error::UnexpectedVersionFormat) +} + +/// LLVM Version Parse Error +#[derive(Debug)] +pub enum LlvmVersionParseError { + /// An error occurred in parsing a version component as an integer + ParseIntError(num::ParseIntError), + /// A version component must not have leading zeros + ComponentMustNotHaveLeadingZeros, + /// A version component has a sign + ComponentMustNotHaveSign, + /// Minor version component must be zero on LLVM versions later than 4.0 + MinorVersionMustBeZeroAfter4, + /// Minor version component is required on LLVM versions earlier than 4.0 + MinorVersionRequiredBefore4, + /// Too many components + TooManyComponents, +} + +impl From for LlvmVersionParseError { + fn from(e: num::ParseIntError) -> Self { + LlvmVersionParseError::ParseIntError(e) + } +} + +impl fmt::Display for LlvmVersionParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + LlvmVersionParseError::ParseIntError(e) => { + write!(f, "error parsing LLVM version component: {}", e) + } + LlvmVersionParseError::ComponentMustNotHaveLeadingZeros => { + write!(f, "a version component must not have leading zeros") + } + LlvmVersionParseError::ComponentMustNotHaveSign => { + write!(f, "a version component must not have a sign") + } + LlvmVersionParseError::MinorVersionMustBeZeroAfter4 => write!( + f, + "LLVM's minor version component must be 0 for versions greater than 4.0" + ), + LlvmVersionParseError::MinorVersionRequiredBefore4 => write!( + f, + "LLVM's minor version component is required for versions less than 4.0" + ), + LlvmVersionParseError::TooManyComponents => write!(f, "too many version components"), + } + } +} + +impl error::Error for LlvmVersionParseError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + LlvmVersionParseError::ParseIntError(e) => Some(e), + LlvmVersionParseError::ComponentMustNotHaveLeadingZeros + | LlvmVersionParseError::ComponentMustNotHaveSign + | LlvmVersionParseError::MinorVersionMustBeZeroAfter4 + | LlvmVersionParseError::MinorVersionRequiredBefore4 + | LlvmVersionParseError::TooManyComponents => None, + } + } +} + +/// The error type for this crate. +#[derive(Debug)] +pub enum Error { + /// An error occurred while trying to find the `rustc` to run. + CouldNotExecuteCommand(io::Error), + /// Error output from the command that was run. + CommandError { + /// stdout output from the command + stdout: String, + /// stderr output from the command + stderr: String, + }, + /// The output of `rustc -vV` was not valid utf-8. + Utf8Error(str::Utf8Error), + /// The output of `rustc -vV` was not in the expected format. + UnexpectedVersionFormat, + /// An error occurred in parsing the semver. + SemVerError(semver::Error), + /// The pre-release tag is unknown. + UnknownPreReleaseTag(String), + /// An error occurred in parsing a `LlvmVersion`. + LlvmVersionError(LlvmVersionParseError), +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + CouldNotExecuteCommand(ref e) => write!(f, "could not execute command: {}", e), + CommandError { + ref stdout, + ref stderr, + } => write!( + f, + "error from command -- stderr:\n\n{}\n\nstderr:\n\n{}", + stderr, stdout, + ), + Utf8Error(_) => write!(f, "invalid UTF-8 output from `rustc -vV`"), + UnexpectedVersionFormat => write!(f, "unexpected `rustc -vV` format"), + SemVerError(ref e) => write!(f, "error parsing version: {}", e), + UnknownPreReleaseTag(ref i) => write!(f, "unknown pre-release tag: {}", i), + LlvmVersionError(ref e) => write!(f, "error parsing LLVM's version: {}", e), + } + } +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + CouldNotExecuteCommand(ref e) => Some(e), + CommandError { .. } => None, + Utf8Error(ref e) => Some(e), + UnexpectedVersionFormat => None, + SemVerError(ref e) => Some(e), + UnknownPreReleaseTag(_) => None, + LlvmVersionError(ref e) => Some(e), + } + } +} + +macro_rules! impl_from { + ($($err_ty:ty => $variant:ident),* $(,)*) => { + $( + impl From<$err_ty> for Error { + fn from(e: $err_ty) -> Error { + Error::$variant(e) + } + } + )* + } +} + +impl_from! { + str::Utf8Error => Utf8Error, + semver::Error => SemVerError, + LlvmVersionParseError => LlvmVersionError, +} + +/// The result type for this crate. +pub type Result = std::result::Result; diff --git a/tests/all.rs b/tests/all.rs new file mode 100644 index 0000000..c3cff70 --- /dev/null +++ b/tests/all.rs @@ -0,0 +1,456 @@ +#![allow(clippy::match_like_matches_macro)] + +use std::process::Command; + +use rustc_version::{ + version, version_meta, version_meta_for, Channel, Error, LlvmVersion, LlvmVersionParseError, + Version, VersionMeta, +}; + +#[test] +fn rustc_error() { + let mut cmd = Command::new("rustc"); + cmd.arg("--FOO"); + let stderr = match VersionMeta::for_command(cmd) { + Err(Error::CommandError { stdout: _, stderr }) => stderr, + _ => panic!("command error expected"), + }; + assert_eq!(stderr, "error: Unrecognized option: \'FOO\'\n\n"); +} + +#[test] +fn smoketest() { + let v = version().unwrap(); + assert!(v.major >= 1); + + let v = version_meta().unwrap(); + assert!(v.semver.major >= 1); + + assert!(version().unwrap() >= Version::parse("1.0.0").unwrap()); +} + +#[test] +fn parse_1_0_0() { + let version = version_meta_for( + "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14) +binary: rustc +commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e +commit-date: 2015-05-13 +build-date: 2015-05-14 +host: x86_64-unknown-linux-gnu +release: 1.0.0", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.0.0").unwrap()); + assert_eq!( + version.commit_hash, + Some("a59de37e99060162a2674e3ff45409ac73595c0e".into()) + ); + assert_eq!(version.commit_date, Some("2015-05-13".into())); + assert_eq!(version.build_date, Some("2015-05-14".into())); + assert_eq!(version.channel, Channel::Stable); + assert_eq!(version.host, "x86_64-unknown-linux-gnu"); + assert_eq!( + version.short_version_string, + "rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)" + ); + assert_eq!(version.llvm_version, None); +} + +#[test] +fn parse_unknown() { + let version = version_meta_for( + "rustc 1.3.0 +binary: rustc +commit-hash: unknown +commit-date: unknown +host: x86_64-unknown-linux-gnu +release: 1.3.0", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.3.0").unwrap()); + assert_eq!(version.commit_hash, None); + assert_eq!(version.commit_date, None); + assert_eq!(version.channel, Channel::Stable); + assert_eq!(version.host, "x86_64-unknown-linux-gnu"); + assert_eq!(version.short_version_string, "rustc 1.3.0"); + assert_eq!(version.llvm_version, None); +} + +#[test] +fn parse_nightly() { + let version = version_meta_for( + "rustc 1.5.0-nightly (65d5c0833 2015-09-29) +binary: rustc +commit-hash: 65d5c083377645a115c4ac23a620d3581b9562b6 +commit-date: 2015-09-29 +host: x86_64-unknown-linux-gnu +release: 1.5.0-nightly", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.5.0-nightly").unwrap()); + assert_eq!( + version.commit_hash, + Some("65d5c083377645a115c4ac23a620d3581b9562b6".into()) + ); + assert_eq!(version.commit_date, Some("2015-09-29".into())); + assert_eq!(version.channel, Channel::Nightly); + assert_eq!(version.host, "x86_64-unknown-linux-gnu"); + assert_eq!( + version.short_version_string, + "rustc 1.5.0-nightly (65d5c0833 2015-09-29)" + ); + assert_eq!(version.llvm_version, None); +} + +#[test] +fn parse_stable() { + let version = version_meta_for( + "rustc 1.3.0 (9a92aaf19 2015-09-15) +binary: rustc +commit-hash: 9a92aaf19a64603b02b4130fe52958cc12488900 +commit-date: 2015-09-15 +host: x86_64-unknown-linux-gnu +release: 1.3.0", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.3.0").unwrap()); + assert_eq!( + version.commit_hash, + Some("9a92aaf19a64603b02b4130fe52958cc12488900".into()) + ); + assert_eq!(version.commit_date, Some("2015-09-15".into())); + assert_eq!(version.channel, Channel::Stable); + assert_eq!(version.host, "x86_64-unknown-linux-gnu"); + assert_eq!( + version.short_version_string, + "rustc 1.3.0 (9a92aaf19 2015-09-15)" + ); + assert_eq!(version.llvm_version, None); +} + +#[test] +fn parse_1_16_0_nightly() { + let version = version_meta_for( + "rustc 1.16.0-nightly (5d994d8b7 2017-01-05) +binary: rustc +commit-hash: 5d994d8b7e482e87467d4a521911477bd8284ce3 +commit-date: 2017-01-05 +host: x86_64-unknown-linux-gnu +release: 1.16.0-nightly +LLVM version: 3.9", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.16.0-nightly").unwrap()); + assert_eq!( + version.commit_hash, + Some("5d994d8b7e482e87467d4a521911477bd8284ce3".into()) + ); + assert_eq!(version.commit_date, Some("2017-01-05".into())); + assert_eq!(version.channel, Channel::Nightly); + assert_eq!(version.host, "x86_64-unknown-linux-gnu"); + assert_eq!( + version.short_version_string, + "rustc 1.16.0-nightly (5d994d8b7 2017-01-05)" + ); + assert_eq!( + version.llvm_version, + Some(LlvmVersion { major: 3, minor: 9 }) + ); +} + +#[test] +fn parse_1_47_0_stable() { + let version = version_meta_for( + "rustc 1.47.0 (18bf6b4f0 2020-10-07) +binary: rustc +commit-hash: 18bf6b4f01a6feaf7259ba7cdae58031af1b7b39 +commit-date: 2020-10-07 +host: powerpc64le-unknown-linux-gnu +release: 1.47.0 +LLVM version: 11.0", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.47.0").unwrap()); + assert_eq!( + version.commit_hash, + Some("18bf6b4f01a6feaf7259ba7cdae58031af1b7b39".into()) + ); + assert_eq!(version.commit_date, Some("2020-10-07".into())); + assert_eq!(version.channel, Channel::Stable); + assert_eq!(version.host, "powerpc64le-unknown-linux-gnu"); + assert_eq!( + version.short_version_string, + "rustc 1.47.0 (18bf6b4f0 2020-10-07)" + ); + assert_eq!( + version.llvm_version, + Some(LlvmVersion { + major: 11, + minor: 0, + }) + ); +} + +#[test] +fn parse_llvm_micro() { + let version = version_meta_for( + "rustc 1.51.0-nightly (4253153db 2021-01-17) +binary: rustc +commit-hash: 4253153db205251f72ea4493687a31e04a2a8ca0 +commit-date: 2021-01-17 +host: x86_64-pc-windows-msvc +release: 1.51.0-nightly +LLVM version: 11.0.1", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.51.0-nightly").unwrap()); + assert_eq!( + version.commit_hash.unwrap(), + "4253153db205251f72ea4493687a31e04a2a8ca0" + ); + assert_eq!(version.commit_date.unwrap(), "2021-01-17"); + assert_eq!(version.host, "x86_64-pc-windows-msvc"); + assert_eq!( + version.short_version_string, + "rustc 1.51.0-nightly (4253153db 2021-01-17)" + ); + assert_eq!( + version.llvm_version, + Some(LlvmVersion { + major: 11, + minor: 0 + }) + ); +} + +#[test] +fn parse_debian_buster() { + let version = version_meta_for( + "rustc 1.41.1 +binary: rustc +commit-hash: unknown +commit-date: unknown +host: powerpc64le-unknown-linux-gnu +release: 1.41.1 +LLVM version: 7.0", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.41.1").unwrap()); + assert_eq!(version.commit_hash, None); + assert_eq!(version.commit_date, None); + assert_eq!(version.channel, Channel::Stable); + assert_eq!(version.host, "powerpc64le-unknown-linux-gnu"); + assert_eq!(version.short_version_string, "rustc 1.41.1"); + assert_eq!( + version.llvm_version, + Some(LlvmVersion { major: 7, minor: 0 }) + ); +} + +#[test] +fn parse_termux() { + let version = version_meta_for( + "rustc 1.46.0 +binary: rustc +commit-hash: unknown +commit-date: unknown +host: aarch64-linux-android +release: 1.46.0 +LLVM version: 10.0", + ) + .unwrap(); + + assert_eq!(version.semver, Version::parse("1.46.0").unwrap()); + assert_eq!(version.commit_hash, None); + assert_eq!(version.commit_date, None); + assert_eq!(version.channel, Channel::Stable); + assert_eq!(version.host, "aarch64-linux-android"); + assert_eq!(version.short_version_string, "rustc 1.46.0"); + assert_eq!( + version.llvm_version, + Some(LlvmVersion { + major: 10, + minor: 0, + }) + ); +} + +#[test] +fn parse_llvm_version_empty() { + let res: Result = "".parse(); + assert!(match res { + Err(LlvmVersionParseError::ParseIntError(_)) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_invalid_char() { + let res: Result = "A".parse(); + assert!(match res { + Err(LlvmVersionParseError::ParseIntError(_)) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_overflow() { + let res: Result = "9999999999999999999999999999999".parse(); + assert!(match res { + Err(LlvmVersionParseError::ParseIntError(_)) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_leading_zero_on_zero() { + let res: Result = "00".parse(); + assert!(match res { + Err(LlvmVersionParseError::ComponentMustNotHaveLeadingZeros) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_leading_zero_on_nonzero() { + let res: Result = "01".parse(); + assert!(match res { + Err(LlvmVersionParseError::ComponentMustNotHaveLeadingZeros) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_4_components() { + let res: Result = "4.0.0.0".parse(); + + assert!(match res { + Err(LlvmVersionParseError::TooManyComponents) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_component_sign_plus() { + let res: Result = "1.+3".parse(); + + assert!(match res { + Err(LlvmVersionParseError::ComponentMustNotHaveSign) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_component_sign_minus() { + let res: Result = "1.-3".parse(); + + assert!(match res { + Err(LlvmVersionParseError::ComponentMustNotHaveSign) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_3() { + let res: Result = "3".parse(); + + assert!(match res { + Err(LlvmVersionParseError::MinorVersionRequiredBefore4) => true, + _ => false, + }); +} + +#[test] +fn parse_llvm_version_5() { + let v: LlvmVersion = "5".parse().unwrap(); + assert_eq!(v, LlvmVersion { major: 5, minor: 0 }); +} + +#[test] +fn parse_llvm_version_5_0() { + let v: LlvmVersion = "5.0".parse().unwrap(); + assert_eq!(v, LlvmVersion { major: 5, minor: 0 }); +} + +#[test] +fn parse_llvm_version_4_0() { + let v: LlvmVersion = "4.0".parse().unwrap(); + assert_eq!(v, LlvmVersion { major: 4, minor: 0 }); +} + +#[test] +fn parse_llvm_version_3_0() { + let v: LlvmVersion = "3.0".parse().unwrap(); + assert_eq!(v, LlvmVersion { major: 3, minor: 0 }); +} + +#[test] +fn parse_llvm_version_3_9() { + let v: LlvmVersion = "3.9".parse().unwrap(); + assert_eq!(v, LlvmVersion { major: 3, minor: 9 }); +} + +#[test] +fn parse_llvm_version_11_0() { + let v: LlvmVersion = "11.0".parse().unwrap(); + assert_eq!( + v, + LlvmVersion { + major: 11, + minor: 0 + } + ); +} + +#[test] +fn parse_llvm_version_11() { + let v: LlvmVersion = "11".parse().unwrap(); + assert_eq!( + v, + LlvmVersion { + major: 11, + minor: 0 + } + ); +} + +#[test] +fn test_llvm_version_comparison() { + // check that field order is correct + assert!(LlvmVersion { major: 3, minor: 9 } < LlvmVersion { major: 4, minor: 0 }); +} + +/* +#[test] +fn version_matches_replacement() { + let f = |s1: &str, s2: &str| { + let a = Version::parse(s1).unwrap(); + let b = Version::parse(s2).unwrap(); + println!("{} <= {} : {}", s1, s2, a <= b); + }; + + println!(); + + f("1.5.0", "1.5.0"); + f("1.5.0-nightly", "1.5.0"); + f("1.5.0", "1.5.0-nightly"); + f("1.5.0-nightly", "1.5.0-nightly"); + + f("1.5.0", "1.6.0"); + f("1.5.0-nightly", "1.6.0"); + f("1.5.0", "1.6.0-nightly"); + f("1.5.0-nightly", "1.6.0-nightly"); + + panic!(); + +} +*/