Skip to content

Commit 198ee85

Browse files
authored
Merge pull request djc#4 from kamalmarhubi/specify-command
Allow getting version info for a specified command
2 parents bbff0c6 + 3b4fca5 commit 198ee85

File tree

4 files changed

+145
-49
lines changed

4 files changed

+145
-49
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ repository = "https://github.com/Kimundi/rustc-version-rs"
1212
keywords = ["version", "rustc"]
1313

1414
[dependencies]
15-
semver = "0.2"
15+
semver = "0.2.3"

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ git = "https://github.com/Kimundi/rustc-version-rs"
2626
```
2727
# Example
2828

29+
(This uses the version at `master`, and may differ from the most recently released version.)
30+
2931
```rust
3032
// This could be a cargo build script
3133

@@ -34,10 +36,10 @@ use rustc_version::{version, version_meta, Channel, Version};
3436

3537
fn main() {
3638
// Assert we haven't travelled back in time
37-
assert!(version().major >= 1);
39+
assert!(version().unwrap().major >= 1);
3840

3941
// Set cfg flags depending on release channel
40-
match version_meta().channel {
42+
match version_meta().unwrap().channel {
4143
Channel::Stable => {
4244
println!("cargo:rustc-cfg=RUSTC_IS_STABLE");
4345
}
@@ -53,7 +55,7 @@ fn main() {
5355
}
5456

5557
// Check for a minimum version
56-
if version() >= Version::parse("1.4.0").unwrap() {
58+
if version().unwrap() >= Version::parse("1.4.0").unwrap() {
5759
println!("cargo:rustc-cfg=compiler_has_important_bugfix");
5860
}
5961
}

src/errors.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use std::{self, error, fmt, io, str};
2+
use semver::{self, Identifier};
3+
4+
/// The error type for this crate.
5+
#[derive(Debug)]
6+
pub enum Error {
7+
/// An error ocurrend when executing the `rustc` command.
8+
CouldNotExecuteCommand(io::Error),
9+
/// The output of `rustc -vV` was not valid utf-8.
10+
Utf8Error(str::Utf8Error),
11+
/// The output of `rustc -vV` was not in the expected format.
12+
UnexpectedVersionFormat,
13+
/// An error ocurred in parsing a `VersionReq`.
14+
ReqParseError(semver::ReqParseError),
15+
/// An error ocurred in parsing the semver.
16+
SemVerError(semver::SemVerError),
17+
/// The pre-release tag is unknown.
18+
UnknownPreReleaseTag(Identifier),
19+
}
20+
use Error::*;
21+
22+
impl fmt::Display for Error {
23+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24+
use std::error::Error;
25+
match *self {
26+
CouldNotExecuteCommand(ref e) => write!(f, "{}: {}", self.description(), e),
27+
Utf8Error(_) => write!(f, "{}", self.description()),
28+
UnexpectedVersionFormat => write!(f, "{}", self.description()),
29+
ReqParseError(ref e) => write!(f, "{}: {}", self.description(), e),
30+
SemVerError(ref e) => write!(f, "{}: {}", self.description(), e),
31+
UnknownPreReleaseTag(ref i) => write!(f, "{}: {}", self.description(), i),
32+
}
33+
}
34+
}
35+
36+
impl error::Error for Error {
37+
fn cause(&self) -> Option<&error::Error> {
38+
match *self {
39+
CouldNotExecuteCommand(ref e) => Some(e),
40+
Utf8Error(ref e) => Some(e),
41+
UnexpectedVersionFormat => None,
42+
ReqParseError(ref e) => Some(e),
43+
SemVerError(ref e) => Some(e),
44+
UnknownPreReleaseTag(_) => None,
45+
}
46+
}
47+
48+
fn description(&self) -> &str {
49+
match *self {
50+
CouldNotExecuteCommand(_) => "could not execute command",
51+
Utf8Error(_) => "invalid UTF-8 output from `rustc -vV`",
52+
UnexpectedVersionFormat => "unexpected `rustc -vV` format",
53+
ReqParseError(_) => "error parsing version requirement",
54+
SemVerError(_) => "error parsing version",
55+
UnknownPreReleaseTag(_) => "unknown pre-release tag",
56+
}
57+
}
58+
}
59+
60+
macro_rules! impl_from {
61+
($($err_ty:ty => $variant:ident),* $(,)*) => {
62+
$(
63+
impl From<$err_ty> for Error {
64+
fn from(e: $err_ty) -> Error {
65+
Error::$variant(e)
66+
}
67+
}
68+
)*
69+
}
70+
}
71+
72+
impl_from! {
73+
str::Utf8Error => Utf8Error,
74+
semver::SemVerError => SemVerError,
75+
semver::ReqParseError => ReqParseError,
76+
}
77+
78+
/// The result type for this crate.
79+
pub type Result<T> = std::result::Result<T, Error>;

src/lib.rs

Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@
2424
//!
2525
//! fn main() {
2626
//! // Assert we haven't travelled back in time
27-
//! assert!(version().major >= 1);
27+
//! assert!(version().unwrap().major >= 1);
2828
//!
2929
//! // Set cfg flags depending on release channel
30-
//! match version_meta().channel {
30+
//! match version_meta().unwrap().channel {
3131
//! Channel::Stable => {
3232
//! println!("cargo:rustc-cfg=RUSTC_IS_STABLE");
3333
//! }
@@ -43,7 +43,7 @@
4343
//! }
4444
//!
4545
//! // Check for a minimum version
46-
//! if version() >= Version::parse("1.4.0").unwrap() {
46+
//! if version().unwrap() >= Version::parse("1.4.0").unwrap() {
4747
//! println!("cargo:rustc-cfg=compiler_has_important_bugfix");
4848
//! }
4949
//! }
@@ -52,13 +52,16 @@
5252
extern crate semver;
5353
use semver::Identifier;
5454
use std::process::Command;
55-
use std::env;
55+
use std::{env, str};
5656
use std::ffi::OsString;
5757

5858
// Convenience re-export to allow version comparison without needing to add
5959
// semver crate.
6060
pub use semver::Version;
6161

62+
mod errors;
63+
pub use errors::{Error, Result};
64+
6265
/// Release channel of the compiler.
6366
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
6467
pub enum Channel {
@@ -73,7 +76,7 @@ pub enum Channel {
7376
}
7477

7578
/// Rustc version plus metada like git short hash and build date.
76-
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
79+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
7780
pub struct VersionMeta {
7881
/// Version of the compiler
7982
pub semver: Version,
@@ -97,51 +100,60 @@ pub struct VersionMeta {
97100
pub short_version_string: String,
98101
}
99102

103+
impl VersionMeta {
104+
/// Returns the version metadata for `cmd`, which should be a `rustc` command.
105+
pub fn for_command(cmd: Command) -> Result<VersionMeta> {
106+
let mut cmd = cmd;
107+
108+
let out = match cmd.arg("-vV").output() {
109+
Err(e) => return Err(Error::CouldNotExecuteCommand(e)),
110+
Ok(out) => out,
111+
};
112+
let out = try!(str::from_utf8(&out.stdout));
113+
114+
version_meta_for(out)
115+
}
116+
}
117+
100118
/// Returns the `rustc` SemVer version.
101-
pub fn version() -> Version {
102-
version_meta().semver
119+
pub fn version() -> Result<Version> {
120+
Ok(try!(version_meta()).semver)
103121
}
104122

105123
/// Returns the `rustc` SemVer version and additional metadata
106124
/// like the git short hash and build date.
107-
pub fn version_meta() -> VersionMeta {
125+
pub fn version_meta() -> Result<VersionMeta> {
108126
let cmd = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
109127

110-
let out = Command::new(&cmd)
111-
.arg("-vV")
112-
.output()
113-
.unwrap_or_else(|e| { panic!("failed to execute `RUSTC -vV`: {}", e) });
114-
115-
let out = String::from_utf8(out.stdout)
116-
.ok()
117-
.expect("non utf8 output from RUSTC -vV");
128+
VersionMeta::for_command(Command::new(cmd))
118129

119-
version_meta_for(&out)
120130
}
121131

122132
/// Parses a "rustc -vV" output string and returns
123133
/// the SemVer version and additional metadata
124134
/// like the git short hash and build date.
125-
pub fn version_meta_for(verbose_version_string: &str) -> VersionMeta {
135+
pub fn version_meta_for(verbose_version_string: &str) -> Result<VersionMeta> {
126136
let out: Vec<_> = verbose_version_string.lines().collect();
127137

128-
const ERR_MSG: &'static str = "unexpected -vV format";
129-
130-
assert!(out.len() == 6 || out.len() == 7, ERR_MSG);
138+
if !(out.len() == 6 || out.len() == 7) {
139+
return Err(Error::UnexpectedVersionFormat);
140+
}
131141

132142
let short_version_string = out[0];
133143

134-
fn expect_prefix<'a>(line: &'a str, prefix: &str) -> &'a str {
135-
assert!(line.starts_with(prefix), ERR_MSG);
136-
&line[prefix.len()..]
144+
fn expect_prefix<'a>(line: &'a str, prefix: &str) -> Result<&'a str> {
145+
match line.starts_with(prefix) {
146+
true => Ok(&line[prefix.len()..]),
147+
false => Err(Error::UnexpectedVersionFormat),
148+
}
137149
}
138150

139-
let commit_hash = match expect_prefix(out[2], "commit-hash: ") {
151+
let commit_hash = match try!(expect_prefix(out[2], "commit-hash: ")) {
140152
"unknown" => None,
141153
hash => Some(hash.to_owned()),
142154
};
143155

144-
let commit_date = match expect_prefix(out[3], "commit-date: ") {
156+
let commit_date = match try!(expect_prefix(out[3], "commit-date: ")) {
145157
"unknown" => None,
146158
hash => Some(hash.to_owned()),
147159
};
@@ -150,19 +162,18 @@ pub fn version_meta_for(verbose_version_string: &str) -> VersionMeta {
150162
let mut idx = 4;
151163
let mut build_date = None;
152164
if out[idx].starts_with("build-date") {
153-
build_date = match expect_prefix(out[idx], "build-date: ") {
165+
build_date = match try!(expect_prefix(out[idx], "build-date: ")) {
154166
"unknown" => None,
155167
s => Some(s.to_owned()),
156168
};
157169
idx = idx + 1;
158170
}
159171

172+
let host = try!(expect_prefix(out[idx], "host: "));
173+
idx = idx + 1;
174+
let release = try!(expect_prefix(out[idx], "release: "));
160175

161-
let host = expect_prefix(out[idx], "host: ");
162-
idx = idx +1;
163-
let release = expect_prefix(out[idx], "release: ");
164-
165-
let semver = Version::parse(release).unwrap();
176+
let semver: Version = try!(release.parse());
166177

167178
let channel = if semver.pre.is_empty() {
168179
Channel::Stable
@@ -174,44 +185,48 @@ pub fn version_meta_for(verbose_version_string: &str) -> VersionMeta {
174185
if s == "beta" => Channel::Beta,
175186
Identifier::AlphaNumeric(ref s)
176187
if s == "nightly" => Channel::Nightly,
177-
ref x => panic!("Unknown pre-release tag {}", x),
188+
ref x => return Err(Error::UnknownPreReleaseTag(x.clone())),
178189
}
179190
};
180191

181-
VersionMeta {
192+
Ok(VersionMeta {
182193
semver: semver,
183194
commit_hash: commit_hash,
184195
commit_date: commit_date,
185196
build_date: build_date,
186197
channel: channel,
187198
host: host.into(),
188199
short_version_string: short_version_string.into(),
189-
}
200+
})
190201
}
191202

192203
#[test]
193204
fn smoketest() {
194-
let v = version();
205+
let v = version().unwrap();
195206
assert!(v.major >= 1);
196207

197-
let v = version_meta();
208+
let v = version_meta().unwrap();
198209
assert!(v.semver.major >= 1);
199210

200-
assert!(version() >= Version::parse("1.0.0").unwrap());
211+
assert!(version().unwrap() >= Version::parse("1.0.0").unwrap());
201212
}
202213

203214
#[test]
204-
#[should_panic(expected = "unexpected")]
205-
// Characterization test for behavior on an unexpected key.
206215
fn parse_unexpected() {
207-
version_meta_for(
216+
let res = version_meta_for(
208217
"rustc 1.0.0 (a59de37e9 2015-05-13) (built 2015-05-14)
209218
binary: rustc
210219
commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e
211220
commit-date: 2015-05-13
212221
rust-birthday: 2015-05-14
213222
host: x86_64-unknown-linux-gnu
214223
release: 1.0.0");
224+
225+
assert!(match res {
226+
Err(Error::UnexpectedVersionFormat) => true,
227+
_ => false,
228+
});
229+
215230
}
216231

217232
#[test]
@@ -223,7 +238,7 @@ commit-hash: a59de37e99060162a2674e3ff45409ac73595c0e
223238
commit-date: 2015-05-13
224239
build-date: 2015-05-14
225240
host: x86_64-unknown-linux-gnu
226-
release: 1.0.0");
241+
release: 1.0.0").unwrap();
227242

228243
assert_eq!(version.semver, Version::parse("1.0.0").unwrap());
229244
assert_eq!(version.commit_hash, Some("a59de37e99060162a2674e3ff45409ac73595c0e".into()));
@@ -243,7 +258,7 @@ binary: rustc
243258
commit-hash: unknown
244259
commit-date: unknown
245260
host: x86_64-unknown-linux-gnu
246-
release: 1.3.0");
261+
release: 1.3.0").unwrap();
247262

248263
assert_eq!(version.semver, Version::parse("1.3.0").unwrap());
249264
assert_eq!(version.commit_hash, None);
@@ -261,7 +276,7 @@ binary: rustc
261276
commit-hash: 65d5c083377645a115c4ac23a620d3581b9562b6
262277
commit-date: 2015-09-29
263278
host: x86_64-unknown-linux-gnu
264-
release: 1.5.0-nightly");
279+
release: 1.5.0-nightly").unwrap();
265280

266281
assert_eq!(version.semver, Version::parse("1.5.0-nightly").unwrap());
267282
assert_eq!(version.commit_hash, Some("65d5c083377645a115c4ac23a620d3581b9562b6".into()));
@@ -279,7 +294,7 @@ binary: rustc
279294
commit-hash: 9a92aaf19a64603b02b4130fe52958cc12488900
280295
commit-date: 2015-09-15
281296
host: x86_64-unknown-linux-gnu
282-
release: 1.3.0");
297+
release: 1.3.0").unwrap();
283298

284299
assert_eq!(version.semver, Version::parse("1.3.0").unwrap());
285300
assert_eq!(version.commit_hash, Some("9a92aaf19a64603b02b4130fe52958cc12488900".into()));

0 commit comments

Comments
 (0)