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}