1 | use crate::util::ArrayDisplay; |
2 | use crate::{fmt_option_str, write_str_variable, write_variable}; |
3 | use std::{borrow, collections, env, ffi, fmt, fs, io, process}; |
4 | |
5 | pub struct EnvironmentMap(collections::HashMap<String, String>); |
6 | |
7 | fn get_version_from_cmd(executable: &ffi::OsStr) -> io::Result<String> { |
8 | let output: Output = process::Command::new(program:executable).arg("-V" ).output()?; |
9 | let mut v: String = String::from_utf8(vec:output.stdout).unwrap(); |
10 | v.pop(); // remove newline |
11 | Ok(v) |
12 | } |
13 | |
14 | impl EnvironmentMap { |
15 | pub fn new() -> Self { |
16 | let mut envmap = collections::HashMap::new(); |
17 | for (k, v) in env::vars_os() { |
18 | let k = k.into_string(); |
19 | let v = v.into_string(); |
20 | if let (Ok(k), Ok(v)) = (k, v) { |
21 | envmap.insert(k, v); |
22 | } |
23 | } |
24 | Self(envmap) |
25 | } |
26 | |
27 | pub fn write_ci(&self, mut w: &fs::File) -> io::Result<()> { |
28 | use io::Write; |
29 | |
30 | write_variable!( |
31 | w, |
32 | "CI_PLATFORM" , |
33 | "Option<&str>" , |
34 | fmt_option_str(self.detect_ci()), |
35 | "The Continuous Integration platform detected during compilation." |
36 | ); |
37 | Ok(()) |
38 | } |
39 | |
40 | pub fn write_env(&self, mut w: &fs::File) -> io::Result<()> { |
41 | use io::Write; |
42 | macro_rules! write_env_str { |
43 | ($(($name:ident, $env_name:expr,$doc:expr)),*) => {$( |
44 | write_str_variable!( |
45 | w, |
46 | stringify!($name), |
47 | self.0.get($env_name) |
48 | .expect(stringify!(Missing expected environment variable $env_name)), |
49 | $doc |
50 | ); |
51 | )*} |
52 | } |
53 | |
54 | write_env_str!( |
55 | (PKG_VERSION, "CARGO_PKG_VERSION" , "The full version." ), |
56 | ( |
57 | PKG_VERSION_MAJOR, |
58 | "CARGO_PKG_VERSION_MAJOR" , |
59 | "The major version." |
60 | ), |
61 | ( |
62 | PKG_VERSION_MINOR, |
63 | "CARGO_PKG_VERSION_MINOR" , |
64 | "The minor version." |
65 | ), |
66 | ( |
67 | PKG_VERSION_PATCH, |
68 | "CARGO_PKG_VERSION_PATCH" , |
69 | "The patch version." |
70 | ), |
71 | ( |
72 | PKG_VERSION_PRE, |
73 | "CARGO_PKG_VERSION_PRE" , |
74 | "The pre-release version." |
75 | ), |
76 | ( |
77 | PKG_AUTHORS, |
78 | "CARGO_PKG_AUTHORS" , |
79 | "A colon-separated list of authors." |
80 | ), |
81 | (PKG_NAME, "CARGO_PKG_NAME" , "The name of the package." ), |
82 | (PKG_DESCRIPTION, "CARGO_PKG_DESCRIPTION" , "The description." ), |
83 | (PKG_HOMEPAGE, "CARGO_PKG_HOMEPAGE" , "The homepage." ), |
84 | (PKG_LICENSE, "CARGO_PKG_LICENSE" , "The license." ), |
85 | ( |
86 | PKG_REPOSITORY, |
87 | "CARGO_PKG_REPOSITORY" , |
88 | "The source repository as advertised in Cargo.toml." |
89 | ), |
90 | ( |
91 | TARGET, |
92 | "TARGET" , |
93 | "The target triple that was being compiled for." |
94 | ), |
95 | (HOST, "HOST" , "The host triple of the rust compiler." ), |
96 | ( |
97 | PROFILE, |
98 | "PROFILE" , |
99 | "`release` for release builds, `debug` for other builds." |
100 | ), |
101 | (RUSTC, "RUSTC" , "The compiler that cargo resolved to use." ), |
102 | ( |
103 | RUSTDOC, |
104 | "RUSTDOC" , |
105 | "The documentation generator that cargo resolved to use." |
106 | ) |
107 | ); |
108 | write_str_variable!( |
109 | w, |
110 | "OPT_LEVEL" , |
111 | env::var("OPT_LEVEL" ).unwrap(), |
112 | "Value of OPT_LEVEL for the profile used during compilation." |
113 | ); |
114 | write_variable!( |
115 | w, |
116 | "NUM_JOBS" , |
117 | "u32" , |
118 | if env::var(crate::SOURCE_DATE_EPOCH).is_ok() { |
119 | borrow::Cow::Borrowed("1" ) |
120 | } else { |
121 | borrow::Cow::Owned(env::var("NUM_JOBS" ).unwrap()) |
122 | }, |
123 | "The parallelism that was specified during compilation." |
124 | ); |
125 | write_variable!( |
126 | w, |
127 | "DEBUG" , |
128 | "bool" , |
129 | env::var("DEBUG" ).unwrap() == "true" , |
130 | "Value of DEBUG for the profile used during compilation." |
131 | ); |
132 | Ok(()) |
133 | } |
134 | |
135 | pub fn write_features(&self, mut w: &fs::File) -> io::Result<()> { |
136 | use io::Write; |
137 | |
138 | let mut features = Vec::new(); |
139 | for name in self.0.keys() { |
140 | if let Some(feat) = name.strip_prefix("CARGO_FEATURE_" ) { |
141 | features.push(feat.to_owned()); |
142 | } |
143 | } |
144 | features.sort_unstable(); |
145 | |
146 | write_variable!( |
147 | w, |
148 | "FEATURES" , |
149 | format_args!("[&str; {}]" , features.len()), |
150 | ArrayDisplay(&features, |t, f| write!(f, " \"{}\"" , t.escape_default())), |
151 | "The features that were enabled during compilation." |
152 | ); |
153 | let features_str = features.join(", " ); |
154 | write_str_variable!( |
155 | w, |
156 | "FEATURES_STR" , |
157 | features_str, |
158 | "The features as a comma-separated string." |
159 | ); |
160 | |
161 | let mut lowercase_features = features |
162 | .iter() |
163 | .map(|name| name.to_lowercase()) |
164 | .collect::<Vec<_>>(); |
165 | lowercase_features.sort_unstable(); |
166 | |
167 | write_variable!( |
168 | w, |
169 | "FEATURES_LOWERCASE" , |
170 | format_args!("[&str; {}]" , lowercase_features.len()), |
171 | ArrayDisplay(&lowercase_features, |val, fmt| write!( |
172 | fmt, |
173 | " \"{}\"" , |
174 | val.escape_default() |
175 | )), |
176 | "The features as above, as lowercase strings." |
177 | ); |
178 | let lowercase_features_str = lowercase_features.join(", " ); |
179 | write_str_variable!( |
180 | w, |
181 | "FEATURES_LOWERCASE_STR" , |
182 | lowercase_features_str, |
183 | "The feature-string as above, from lowercase strings." |
184 | ); |
185 | |
186 | Ok(()) |
187 | } |
188 | |
189 | pub fn write_cfg(&self, mut w: &fs::File) -> io::Result<()> { |
190 | use io::Write; |
191 | |
192 | write_str_variable!( |
193 | w, |
194 | "CFG_TARGET_ARCH" , |
195 | self.0["CARGO_CFG_TARGET_ARCH" ], |
196 | "The target architecture, given by `CARGO_CFG_TARGET_ARCH`." |
197 | ); |
198 | |
199 | write_str_variable!( |
200 | w, |
201 | "CFG_ENDIAN" , |
202 | self.0["CARGO_CFG_TARGET_ENDIAN" ], |
203 | "The endianness, given by `CARGO_CFG_TARGET_ENDIAN`." |
204 | ); |
205 | |
206 | write_str_variable!( |
207 | w, |
208 | "CFG_ENV" , |
209 | self.0["CARGO_CFG_TARGET_ENV" ], |
210 | "The toolchain-environment, given by `CARGO_CFG_TARGET_ENV`." |
211 | ); |
212 | |
213 | write_str_variable!( |
214 | w, |
215 | "CFG_FAMILY" , |
216 | self.0 |
217 | .get("CARGO_CFG_TARGET_FAMILY" ) |
218 | .map(|s| s.as_str()) |
219 | .unwrap_or_default(), |
220 | "The OS-family, given by `CARGO_CFG_TARGET_FAMILY`." |
221 | ); |
222 | |
223 | write_str_variable!( |
224 | w, |
225 | "CFG_OS" , |
226 | self.0["CARGO_CFG_TARGET_OS" ], |
227 | "The operating system, given by `CARGO_CFG_TARGET_OS`." |
228 | ); |
229 | |
230 | write_str_variable!( |
231 | w, |
232 | "CFG_POINTER_WIDTH" , |
233 | self.0["CARGO_CFG_TARGET_POINTER_WIDTH" ], |
234 | "The pointer width, given by `CARGO_CFG_TARGET_POINTER_WIDTH`." |
235 | ); |
236 | |
237 | Ok(()) |
238 | } |
239 | |
240 | pub fn write_compiler_version(&self, mut w: &fs::File) -> io::Result<()> { |
241 | use std::io::Write; |
242 | |
243 | let rustc = &self.0["RUSTC" ]; |
244 | let rustdoc = &self.0["RUSTDOC" ]; |
245 | |
246 | let rustc_version = get_version_from_cmd(rustc.as_ref())?; |
247 | let rustdoc_version = get_version_from_cmd(rustdoc.as_ref()).unwrap_or_default(); |
248 | |
249 | write_str_variable!( |
250 | w, |
251 | "RUSTC_VERSION" , |
252 | rustc_version, |
253 | format_args!("The output of ` {rustc} -V`" ) |
254 | ); |
255 | |
256 | write_str_variable!( |
257 | w, |
258 | "RUSTDOC_VERSION" , |
259 | rustdoc_version, |
260 | format_args!( |
261 | "The output of ` {rustdoc} -V`; empty string if ` {rustdoc} -V` failed to execute" |
262 | ) |
263 | ); |
264 | Ok(()) |
265 | } |
266 | |
267 | pub fn detect_ci(&self) -> Option<CIPlatform> { |
268 | macro_rules! detect { |
269 | ($(($k:expr, $v:expr, $i:ident)),*) => {$( |
270 | if self.0.get($k).map_or(false, |v| v == $v) { |
271 | return Some(CIPlatform::$i); |
272 | } |
273 | )*}; |
274 | ($(($k:expr, $i:ident)),*) => {$( |
275 | if self.0.contains_key($k) { |
276 | return Some(CIPlatform::$i); |
277 | } |
278 | )*}; |
279 | ($($k:expr),*) => {$( |
280 | if self.0.contains_key($k) { |
281 | return Some(CIPlatform::Generic); |
282 | } |
283 | )*}; |
284 | } |
285 | // Variable names collected by watson/ci-info |
286 | detect!( |
287 | ("TRAVIS" , Travis), |
288 | ("CIRCLECI" , Circle), |
289 | ("GITLAB_CI" , GitLab), |
290 | ("APPVEYOR" , AppVeyor), |
291 | ("DRONE" , Drone), |
292 | ("MAGNUM" , Magnum), |
293 | ("SEMAPHORE" , Semaphore), |
294 | ("JENKINS_URL" , Jenkins), |
295 | ("bamboo_planKey" , Bamboo), |
296 | ("TF_BUILD" , TFS), |
297 | ("TEAMCITY_VERSION" , TeamCity), |
298 | ("BUILDKITE" , Buildkite), |
299 | ("HUDSON_URL" , Hudson), |
300 | ("GO_PIPELINE_LABEL" , GoCD), |
301 | ("BITBUCKET_COMMIT" , BitBucket), |
302 | ("GITHUB_ACTIONS" , GitHubActions) |
303 | ); |
304 | |
305 | if self.0.contains_key("TASK_ID" ) && self.0.contains_key("RUN_ID" ) { |
306 | return Some(CIPlatform::TaskCluster); |
307 | } |
308 | |
309 | detect!(("CI_NAME" , "codeship" , Codeship)); |
310 | |
311 | detect!( |
312 | "CI" , // Could be Travis, Circle, GitLab, AppVeyor or CodeShip |
313 | "CONTINUOUS_INTEGRATION" , // Probably Travis |
314 | "BUILD_NUMBER" // Jenkins, TeamCity |
315 | ); |
316 | None |
317 | } |
318 | } |
319 | |
320 | /// Various Continuous Integration platforms whose presence can be detected. |
321 | pub enum CIPlatform { |
322 | /// <https://travis-ci.org> |
323 | Travis, |
324 | /// <https://circleci.com> |
325 | Circle, |
326 | /// <https://about.gitlab.com/gitlab-ci> |
327 | GitLab, |
328 | /// <https://www.appveyor.com> |
329 | AppVeyor, |
330 | /// <https://codeship.com> |
331 | Codeship, |
332 | /// <https://github.com/drone/drone> |
333 | Drone, |
334 | /// <https://magnum-ci.com> |
335 | Magnum, |
336 | /// <https://semaphoreci.com> |
337 | Semaphore, |
338 | /// <https://jenkins.io> |
339 | Jenkins, |
340 | /// <https://www.atlassian.com/software/bamboo> |
341 | Bamboo, |
342 | /// <https://www.visualstudio.com/de/tfs> |
343 | TFS, |
344 | /// <https://www.jetbrains.com/teamcity> |
345 | TeamCity, |
346 | /// <https://buildkite.com> |
347 | Buildkite, |
348 | /// <http://hudson-ci.org> |
349 | Hudson, |
350 | /// <https://github.com/taskcluster> |
351 | TaskCluster, |
352 | /// <https://www.gocd.io> |
353 | GoCD, |
354 | /// <https://bitbucket.org> |
355 | BitBucket, |
356 | /// <https://github.com/features/actions> |
357 | GitHubActions, |
358 | /// Unspecific |
359 | Generic, |
360 | } |
361 | |
362 | impl fmt::Display for CIPlatform { |
363 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
364 | f.write_str(match *self { |
365 | CIPlatform::Travis => "Travis CI" , |
366 | CIPlatform::Circle => "CircleCI" , |
367 | CIPlatform::GitLab => "GitLab" , |
368 | CIPlatform::AppVeyor => "AppVeyor" , |
369 | CIPlatform::Codeship => "CodeShip" , |
370 | CIPlatform::Drone => "Drone" , |
371 | CIPlatform::Magnum => "Magnum" , |
372 | CIPlatform::Semaphore => "Semaphore" , |
373 | CIPlatform::Jenkins => "Jenkins" , |
374 | CIPlatform::Bamboo => "Bamboo" , |
375 | CIPlatform::TFS => "Team Foundation Server" , |
376 | CIPlatform::TeamCity => "TeamCity" , |
377 | CIPlatform::Buildkite => "Buildkite" , |
378 | CIPlatform::Hudson => "Hudson" , |
379 | CIPlatform::TaskCluster => "TaskCluster" , |
380 | CIPlatform::GoCD => "GoCD" , |
381 | CIPlatform::BitBucket => "BitBucket" , |
382 | CIPlatform::GitHubActions => "GitHub Actions" , |
383 | CIPlatform::Generic => "Generic CI" , |
384 | }) |
385 | } |
386 | } |
387 | |