cc_version/
lib.rs

1use cc::Tool;
2use std::cmp::Ordering;
3use std::fmt::Display;
4use thiserror::Error;
5#[derive(Debug, Eq)]
6pub struct Version {
7    pub major: usize,
8    pub minor: Option<usize>,
9    pub patch: Option<usize>,
10}
11
12impl Display for Version {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        let mut version = format!("{}", self.major);
15        if let Some(x) = self.minor {
16            version.push_str(&format!(".{}", x));
17        }
18        if let Some(x) = self.patch {
19            version.push_str(&format!(".{}", x));
20        }
21
22        write!(f, "{}", version)
23    }
24}
25
26impl PartialEq for Version {
27    fn eq(&self, other: &Self) -> bool {
28        let major = self.major == other.major;
29        let minor = self.minor.unwrap_or(0) == other.minor.unwrap_or(0);
30        let patch = self.patch.unwrap_or(0) == other.patch.unwrap_or(0);
31        major && minor && patch
32    }
33}
34
35impl Ord for Version {
36    fn cmp(&self, other: &Self) -> Ordering {
37        if self.major > other.major {
38            Ordering::Greater
39        } else if self.major < other.major {
40            Ordering::Less
41        } else if self.minor.unwrap_or(0) > other.minor.unwrap_or(0) {
42            Ordering::Greater
43        } else if self.minor.unwrap_or(0) < other.minor.unwrap_or(0) {
44            Ordering::Less
45        } else if self.patch.unwrap_or(0) > other.patch.unwrap_or(0) {
46            Ordering::Greater
47        } else if self.patch.unwrap_or(0) < other.patch.unwrap_or(0) {
48            Ordering::Less
49        } else {
50            Ordering::Equal
51        }
52    }
53}
54
55impl PartialOrd for Version {
56    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
57        Some(self.cmp(other))
58    }
59}
60
61#[derive(Debug, Error)]
62pub enum Error {
63    #[error(transparent)]
64    CommandFailed(#[from] std::io::Error),
65    #[error(transparent)]
66    ParseFailed(#[from] std::num::ParseIntError),
67    #[error("failed to detect compiler")]
68    UnknownCompiler,
69}
70
71impl Version {
72    pub fn parse<T: AsRef<str>>(x: T) -> Result<Self, Error> {
73        let version: Vec<_> = x.as_ref().split('.').collect();
74        let major = version[0].parse().map_err(Error::from)?;
75        let minor = version
76            .get(1)
77            .map(|x| x.parse().map_err(Error::from))
78            .transpose()?;
79        let patch = version
80            .get(2)
81            .map(|x| x.parse().map_err(Error::from))
82            .transpose()?;
83
84        Ok(Version {
85            major,
86            minor,
87            patch,
88        })
89    }
90}
91
92pub fn cc_version(tool: &Tool) -> Result<Version, Error> {
93    if tool.is_like_gnu() || tool.is_like_clang() {
94        let ret = tool
95            .to_command()
96            .args(&["-dumpversion"])
97            .output()
98            .map_err(Error::from)?;
99        let version = String::from_utf8_lossy(&ret.stdout);
100        Ok(Version::parse(version.trim())?)
101    } else if tool.is_like_msvc() {
102        let command = format!("{:?}", tool.to_command());
103        let command: Vec<_> = command.split("\"").collect();
104        let command = command[1].replace("\\\\", "\\");
105        let ret = std::process::Command::new(command)
106            .output()
107            .map_err(Error::from)?;
108        let version = String::from_utf8_lossy(&ret.stderr);
109        let version = get_msvc_version(&version);
110        Ok(Version::parse(version.trim())?)
111    } else {
112        Err(Error::UnknownCompiler)
113    }
114}
115
116fn get_msvc_version(s: &str) -> String {
117    dbg!(s);
118    let start = s.find("Version ").unwrap();
119    let end = s.find(" for ").unwrap();
120    let version = s.get(start + 8..end).unwrap();
121    String::from(version)
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn version_parse() {
130        let version = Version::parse("1.0.0").unwrap();
131        assert_eq!(version.major, 1);
132        assert_eq!(version.minor, Some(0));
133        assert_eq!(version.patch, Some(0));
134
135        let version = Version::parse("1.0").unwrap();
136        assert_eq!(version.major, 1);
137        assert_eq!(version.minor, Some(0));
138        assert_eq!(version.patch, None);
139
140        let version = Version::parse("1").unwrap();
141        assert_eq!(version.major, 1);
142        assert_eq!(version.minor, None);
143        assert_eq!(version.patch, None);
144    }
145
146    #[test]
147    fn version_eq() {
148        let a = Version::parse("1.0.0").unwrap();
149        let b = Version::parse("1.0").unwrap();
150        let c = Version::parse("1").unwrap();
151
152        assert_eq!(a, b);
153        assert_eq!(a, c);
154        assert_eq!(b, c);
155    }
156
157    #[test]
158    fn version_cmp() {
159        let a = Version::parse("1.0.0").unwrap();
160        let b = Version::parse("1.0.0").unwrap();
161
162        assert!(a >= b);
163        assert!(!(a > b));
164        assert!(!(a < b));
165        assert!(a <= b);
166
167        let a = Version::parse("2.0.0").unwrap();
168        let b = Version::parse("1.0.0").unwrap();
169
170        assert!(a >= b);
171        assert!(a > b);
172        assert!(!(a < b));
173        assert!(!(a <= b));
174
175        let a = Version::parse("1.2.0").unwrap();
176        let b = Version::parse("1.0.0").unwrap();
177
178        assert!(a >= b);
179        assert!(a > b);
180        assert!(!(a < b));
181        assert!(!(a <= b));
182
183        let a = Version::parse("1.0.1").unwrap();
184        let b = Version::parse("1.0.0").unwrap();
185
186        assert!(a >= b);
187        assert!(a > b);
188        assert!(!(a < b));
189        assert!(!(a <= b));
190    }
191
192    #[test]
193    fn msvc_version() {
194        let version = get_msvc_version(
195            "Microsoft(R) C/C++ Optimizing Compiler Version 19.16.27027.1 for x64
196Copyright (C) Microsoft Corporation.  All rights reserved.",
197        );
198        assert_eq!(version, "19.16.27027.1");
199
200        let version = Version::parse(version).unwrap();
201        assert_eq!(version.major, 19);
202        assert_eq!(version.minor, Some(16));
203        assert_eq!(version.patch, Some(27027));
204    }
205}