1use crate::util::ArrayDisplay;
2use crate::{fmt_option_str, write_str_variable, write_variable};
3use std::{borrow, collections, env, ffi, fmt, fs, io, process};
4
5pub struct EnvironmentMap(collections::HashMap<String, String>);
6
7fn 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
14impl 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.
321pub 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
362impl 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