build_data/lib.rs
1//! build-data
2//! ==========
3//! [](https://crates.io/crates/build-data)
4//! [](https://gitlab.com/leonhard-llc/ops/-/raw/main/build-data/LICENSE)
5//! [](https://github.com/rust-secure-code/safety-dance/)
6//! [](https://gitlab.com/leonhard-llc/ops/-/pipelines)
7//!
8//! Include build data in your program.
9//!
10//! # Features
11//! - Saves build-time data:
12//! - Git commit, branch, and dirtiness
13//! - Source modification date & time
14//! - Rustc version
15//! - Rust channel (stable, nightly, or beta)
16//! - Does all of its work in your
17//! [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html).
18//! - Sets environment variables.
19//! Use [`env!`](https://doc.rust-lang.org/core/macro.env.html) to use them
20//! in your program.
21//! - No macros
22//! - No runtime dependencies
23//! - Light build dependencies
24//! - `forbid(unsafe_code)`
25//! - 100% test coverage
26//!
27//! # Alternatives
28//! - Environment variables that cargo sets for crates:
29//! - `CARGO_PKG_NAME`
30//! - `CARGO_PKG_VERSION`
31//! - `CARGO_BIN_NAME`
32//! - [others](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates)
33//! - [`vergen`](https://crates.io/crates/vergen)
34//! - Mature & very popular
35//! - Good API, needs only `env!` to retrieve values
36//! - Excellent test coverage
37//! - Heavy build dependencies
38//! - [`build-info`](https://crates.io/crates/build-info)
39//! - Mature
40//! - Confusing API
41//! - Uses procedural macros
42//!
43//! # Example
44//!
45//! ```toml
46//! // Cargo.toml
47//! [dependencies]
48//!
49//! [build-dependencies]
50//! build-data = "0"
51//! ```
52//!
53//! Add a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html)
54//! file next to your `Cargo.toml`.
55//! Call [`build_data::set_*`](https://docs.rs/build-data/) functions to
56//! set variables.
57//! ```
58//! // build.rs
59//!
60//! fn main() {
61//! # }
62//! # fn f() {
63//! build_data::set_GIT_BRANCH();
64//! build_data::set_GIT_COMMIT();
65//! build_data::set_GIT_DIRTY();
66//! build_data::set_SOURCE_TIMESTAMP();
67//! build_data::no_debug_rebuilds();
68//! }
69//! ```
70//!
71//! Use [`env!`](https://doc.rust-lang.org/core/macro.env.html) to access the
72//! variables in your program:
73//! ```
74//! // src/bin/main.rs
75//! # macro_rules! env (
76//! # ($arg:expr) => { "" };
77//! # );
78//! fn main() {
79//! // Built from branch=release
80//! // commit=a5547bfb1edb9712588f0f85d3e2c8ba618ac51f
81//! // dirty=false
82//! // source_timestamp=2021-04-14T06:25:59+00:00
83//! println!("Built from branch={} commit={} dirty={} source_timestamp={}",
84//! env!("GIT_BRANCH"),
85//! env!("GIT_COMMIT"),
86//! env!("GIT_DIRTY"),
87//! env!("SOURCE_TIMESTAMP"),
88//! );
89//! }
90//! ```
91//!
92//! # Cargo Geiger Safety Report
93//! # Changelog
94//! - v0.2.2 - Fix `get_source_time` when git config has `log.showsignature=true`.
95//! - v0.2.1
96//! - Add `set_TARGET_PLATFORM`. Thanks [tison](https://gitlab.com/tisonkun)!
97//! - Use u64 for timestamps in helper functions.
98//! - v0.1.5 - Update a dependency. Thanks [dignifiedquire](https://gitlab.com/dignifiedquire)!
99//! - v0.1.4 - Update a dependency.
100//! - v0.1.3 - Update docs.
101//! - v0.1.2 - Rewrote based on
102//! [feedback](https://www.reddit.com/r/rust/comments/mqnbvw/)
103//! from r/rust.
104//! - v0.1.1 - Update docs.
105//! - v0.1.0 - Initial version
106//!
107//! # To Do
108#![forbid(unsafe_code)]
109#![allow(non_snake_case)]
110
111// https://doc.rust-lang.org/cargo/reference/build-scripts.html
112// https://doc.rust-lang.org/cargo/reference/build-script-examples.html
113
114use std::ffi::OsStr;
115use std::sync::atomic::{AtomicU64, Ordering};
116
117/// Converts a byte slice into a string using
118/// [`core::ascii::escape_default`](https://doc.rust-lang.org/core/ascii/fn.escape_default.html)
119/// to escape each byte.
120///
121/// # Example
122/// ```
123/// use build_data::escape_ascii;
124/// assert_eq!("abc", escape_ascii(b"abc"));
125/// assert_eq!("abc\\n", escape_ascii(b"abc\n"));
126/// assert_eq!(
127/// "Euro sign: \\xe2\\x82\\xac",
128/// escape_ascii("Euro sign: \u{20AC}".as_bytes())
129/// );
130/// assert_eq!("\\x01\\x02\\x03", escape_ascii(&[1, 2, 3]));
131/// ```
132#[allow(clippy::missing_panics_doc)]
133#[must_use]
134pub fn escape_ascii(input: impl AsRef<[u8]>) -> String {
135 let mut result = String::new();
136 for byte in input.as_ref() {
137 for ascii_byte in core::ascii::escape_default(*byte) {
138 result.push_str(core::str::from_utf8(&[ascii_byte]).unwrap());
139 }
140 }
141 result
142}
143
144/// Executes `cmd` with `args` as parameters, waits for it to exit, and
145/// returns its stdout, trimmed, and escaped with
146/// [`escape_ascii`](#method.escape_ascii).
147///
148/// # Errors
149/// Returns a descriptive error string if it fails to execute the command
150/// or if the command exits with a non-zero status.
151///
152/// # Panics
153/// Panics if the process writes non-UTF bytes to stdout.
154pub fn exec(cmd: impl AsRef<OsStr>, args: &[&str]) -> Result<String, String> {
155 let output = std::process::Command::new(cmd.as_ref())
156 .args(args)
157 .output()
158 .map_err(|e| {
159 format!(
160 "error executing '{} {}': {e}",
161 cmd.as_ref().to_string_lossy(),
162 args.join(" "),
163 )
164 })?;
165 if !output.status.success() {
166 return Err(format!(
167 "command '{} {}' failed: exit={} stdout='{}' stderr='{}'",
168 cmd.as_ref().to_string_lossy(),
169 args.join(" "),
170 output
171 .status
172 .code()
173 .map_or_else(|| String::from("signal"), |c| c.to_string()),
174 escape_ascii(output.stdout),
175 escape_ascii(output.stderr)
176 ));
177 }
178 let stdout = std::str::from_utf8(&output.stdout).map_err(|_| {
179 format!(
180 "command '{} {}' wrote non-utf8 bytes to stdout",
181 cmd.as_ref().to_string_lossy(),
182 args.join(" ")
183 )
184 })?;
185 Ok(escape_ascii(stdout.trim()))
186}
187
188/// Formats the epoch timestamp as a UTC date like `"2021-05-04Z"`.
189#[allow(clippy::missing_panics_doc)]
190#[must_use]
191pub fn format_date(epoch: u64) -> String {
192 let epoch_i64 = i64::try_from(epoch).unwrap();
193 chrono::TimeZone::timestamp_opt(&chrono::Utc, epoch_i64, 0)
194 .unwrap()
195 .format("%Y-%m-%dZ")
196 .to_string()
197}
198
199/// Formats the epoch timestamp as a UTC time like `"13:02:59Z"`.
200#[allow(clippy::missing_panics_doc)]
201#[must_use]
202pub fn format_time(epoch: u64) -> String {
203 let epoch_i64 = i64::try_from(epoch).unwrap();
204 chrono::TimeZone::timestamp_opt(&chrono::Utc, epoch_i64, 0)
205 .unwrap()
206 .format("%H:%M:%SZ")
207 .to_string()
208}
209
210/// Formats the epoch timestamp as a UTC timestamp like `"20201-05-04T13:02:59Z"`.
211#[allow(clippy::missing_panics_doc)]
212#[must_use]
213pub fn format_timestamp(epoch: u64) -> String {
214 let epoch_i64 = i64::try_from(epoch).unwrap();
215 chrono::TimeZone::timestamp_opt(&chrono::Utc, epoch_i64, 0)
216 .unwrap()
217 .to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
218}
219
220/// Gets the current time as an epoch timestamp, caching it so future calls return the same time.
221#[allow(clippy::missing_panics_doc)]
222#[must_use]
223pub fn now() -> u64 {
224 static CACHED_VALUE: AtomicU64 = AtomicU64::new(0);
225 let value = CACHED_VALUE.load(Ordering::Acquire);
226 if value != 0 {
227 return value;
228 }
229 let value = std::time::SystemTime::now()
230 .duration_since(std::time::UNIX_EPOCH)
231 .unwrap()
232 .as_secs();
233 CACHED_VALUE.store(value, Ordering::Release);
234 value
235}
236
237/// Gets the environment variable named `name` if it is set.
238///
239/// Returns `None` if the variable is unset, is empty, or contains only whitespace.
240///
241/// Trims whitespace from the start and end of the value before returning it.
242///
243/// # Errors
244/// Returns an error if the environment variable value is not valid utf-8.
245pub fn get_env(name: &str) -> Result<Option<String>, String> {
246 let value = match std::env::var(name) {
247 Ok(value) => value,
248 Err(std::env::VarError::NotPresent) => return Ok(None),
249 Err(std::env::VarError::NotUnicode(_)) => {
250 return Err(format!("env var '{name}' contains non-utf8 bytes"))
251 }
252 };
253 let trimmed = value.trim();
254 if trimmed.is_empty() {
255 return Ok(None);
256 }
257 Ok(Some(trimmed.to_string()))
258}
259
260/// Gets the latest git commit of the source code directory.
261///
262/// Example: `"a5547bfb1edb9712588f0f85d3e2c8ba618ac51f"`
263///
264/// # Errors
265/// Returns an error if it fails to execute the `git` command.
266pub fn get_git_commit() -> Result<String, String> {
267 exec("git", &["rev-parse", "HEAD"])
268}
269
270/// Gets the latest git commit of the source code directory.
271/// Returns the truncated hash.
272///
273/// Example: `"a5547bf"`
274///
275/// # Errors
276/// Returns an error if it fails to execute the `git` command.
277pub fn get_git_commit_short() -> Result<String, String> {
278 let long = get_git_commit()?;
279 if long.len() < 7 {
280 return Err(format!("got malformed commit hash from git: '{long}'"));
281 }
282 let short = &long[0..7];
283 Ok(short.to_string())
284}
285
286/// Gets the current branch of the source code directory.
287///
288/// Example: `"release"`
289///
290/// # Errors
291/// Returns an error if it fails to execute the `git` command.
292pub fn get_git_branch() -> Result<String, String> {
293 exec("git", &["rev-parse", "--abbrev-ref=loose", "HEAD"])
294}
295
296/// Returns `true` if the source directory contains uncommitted changes.
297///
298/// # Errors
299/// Returns an error if it fails to execute the `git` command.
300pub fn get_git_dirty() -> Result<bool, String> {
301 Ok(!exec("git", &["status", "-s"])?.is_empty())
302}
303
304/// Gets the name of the current machine.
305///
306/// Cargo doesn't pass the `HOSTNAME` env var to build scripts.
307/// Uses the `hostname` command.
308///
309/// # Errors
310/// Returns an error if it fails to execute the `hostname` command.
311pub fn get_hostname() -> Result<String, String> {
312 exec("hostname", &[])
313}
314
315/// Gets the version of the Rust compiler used to build the build script.
316///
317/// Example: `"rustc 1.53.0-nightly (07e0e2ec2 2021-03-24)"`
318///
319/// # Errors
320/// Returns an error if it fails to execute the `rustc` command.
321pub fn get_rustc_version() -> Result<String, String> {
322 // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
323 let rustc_var = std::env::var_os("RUSTC")
324 .filter(|s| !s.is_empty())
325 .ok_or_else(|| String::from("RUSTC env var is not set"))?;
326 exec(rustc_var, &["--version"])
327}
328
329#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
330pub enum RustChannel {
331 Stable,
332 Beta,
333 Nightly,
334}
335impl core::fmt::Display for RustChannel {
336 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
337 match self {
338 RustChannel::Stable => write!(f, "stable"),
339 RustChannel::Beta => write!(f, "beta"),
340 RustChannel::Nightly => write!(f, "nightly"),
341 }
342 }
343}
344
345/// Parses the output of `rustc --version`.
346///
347/// # Errors
348/// Returns an error if it fails to execute `rustc`,
349/// the process exits with non-zero status,
350/// or it fails to parse the output.
351///
352/// # Panics
353/// Panics if the process writes non-UTF bytes to stdout.
354#[allow(clippy::if_not_else, clippy::range_plus_one, clippy::assign_op_pattern)]
355pub fn parse_rustc_version(version: impl AsRef<str>) -> Result<(String, RustChannel), String> {
356 let matcher: safe_regex::Matcher3<_> =
357 safe_regex::regex!(br"(?:rustc )?([0-9]+\.[0-9]+\.[0-9]+)(?:(-beta)|(-nightly))?(?: .*)?");
358 let (semver_bytes, beta, nightly) = matcher
359 .match_slices(version.as_ref().trim().as_bytes())
360 .ok_or_else(|| format!("failed parsing rustc version: '{}'", version.as_ref()))?;
361 let semver = String::from_utf8(semver_bytes.to_vec()).unwrap();
362 let channel = if !beta.is_empty() {
363 RustChannel::Beta
364 } else if !nightly.is_empty() {
365 RustChannel::Nightly
366 } else {
367 RustChannel::Stable
368 };
369 Ok((semver, channel))
370}
371
372/// Gets the dotted-numeric version from the rustc version string.
373///
374/// Example: `"1.53.0"`
375///
376/// # Errors
377/// Returns an error if it fails to parse `version`.
378#[allow(clippy::missing_panics_doc)]
379pub fn parse_rustc_semver(version: impl AsRef<str>) -> Result<String, String> {
380 let (semver, _channel) = parse_rustc_version(version)?;
381 Ok(semver)
382}
383
384/// Gets the channel from the rustc version string.
385///
386/// # Errors
387/// Returns an error if it fails to parse `version`.
388#[allow(clippy::missing_panics_doc)]
389pub fn parse_rustc_channel(version: impl AsRef<str>) -> Result<RustChannel, String> {
390 let (_semver, channel) = parse_rustc_version(version)?;
391 Ok(channel)
392}
393
394/// Gets the modification time of the source code.
395///
396/// Reads the
397/// [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/docs/source-date-epoch/)
398/// env var if set. Otherwise, runs `git` to get the value.
399///
400/// # Errors
401/// Returns an error when:
402/// - `SOURCE_DATE_EPOCH` is non-empty and cannot be parsed as an `i64`.
403/// - it failed to execute `git`
404/// - `git` exited with non-zero status
405/// - `git` wrote stdout data that cannot be parsed as an `i64`.
406///
407/// # Panics
408/// Panics if `git` writes non-UTF bytes to stdout.
409pub fn get_source_time() -> Result<u64, String> {
410 static CACHED_VALUE: AtomicU64 = AtomicU64::new(0);
411 if let Some(value) = get_env("SOURCE_DATE_EPOCH").unwrap() {
412 return value
413 .parse()
414 .map_err(|_| format!("failed parsing env var as i64: SOURCE_DATE_EPOCH='{value}'",));
415 }
416 let value = CACHED_VALUE.load(Ordering::Acquire);
417 if value != 0 {
418 return Ok(value);
419 }
420 let stdout = exec("git", &["log", "--no-show-signature", "-1", "--pretty=%ct"])?;
421 let value: u64 = stdout.parse().map_err(|_| {
422 format!("failed parsing output of 'git log -1 --pretty=%ct' as i64: {stdout}",)
423 })?;
424 CACHED_VALUE.store(value, Ordering::Release);
425 Ok(value)
426}
427
428/// Gets the Rust target platform string. See [`set_TARGET_PLATFORM`].
429///
430/// # Errors
431/// Returns `Err` when the `TARGET` env var is not set or is not valid UTF-8.
432pub fn get_target_platform() -> Result<String, String> {
433 std::env::var("TARGET").map_err(|e| format!("Error getting TARGET env var: {e}"))
434}
435
436/// Tells cargo not to rebuild `build.rs` during debug builds when other files
437/// change.
438///
439/// This speeds up development builds.
440#[allow(clippy::missing_panics_doc)]
441pub fn no_debug_rebuilds() {
442 // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
443 // "PROFILE — release for release builds, debug for other builds."
444 if &get_env("PROFILE")
445 .unwrap()
446 .expect("PROFILE env var not set")
447 == "debug"
448 {
449 // https://doc.rust-lang.org/cargo/reference/build-scripts.html#change-detection
450 // "The rerun-if-env-changed instruction tells Cargo to re-run the
451 // build script if the value of an environment variable of the
452 // given name has changed."
453 println!("cargo:rerun-if-env-changed=PROFILE");
454 }
455}
456
457/// Sets the `SOURCE_DATE` env variable.
458///
459/// Call this from `build.rs`.
460/// Use `env!` in your `main.rs` to use the variable.
461///
462/// Example value: `"2021-04-14Z"`
463///
464/// # Panics
465/// Panics if `SOURCE_DATE_EPOCH` env var is set to a non-integer value.
466/// Panics if it fails to get the timestamp from `git`.
467pub fn set_SOURCE_DATE() {
468 let value = format_date(get_source_time().unwrap());
469 println!("cargo:rustc-env=SOURCE_DATE={value}");
470}
471
472/// Sets the `SOURCE_TIME` env variable.
473///
474/// Call this from `build.rs`.
475/// Use `env!` in your `main.rs` to use the variable.
476///
477/// Example value: `"03:25:07Z"`
478///
479/// # Panics
480/// Panics if `SOURCE_DATE_EPOCH` env var is set to a non-integer value.
481/// Panics if it fails to get the timestamp from `git`.
482pub fn set_SOURCE_TIME() {
483 let value = format_time(get_source_time().unwrap());
484 println!("cargo:rustc-env=SOURCE_TIME={value}");
485}
486
487/// Sets the `SOURCE_TIMESTAMP` env variable.
488///
489/// Call this from `build.rs`.
490/// Use `env!` in your `main.rs` to use the variable.
491///
492/// Example value: `"2021-04-14T03:25:07Z"`
493///
494/// # Panics
495/// Panics if `SOURCE_DATE_EPOCH` env var is set to a non-integer value.
496/// Panics if it fails to get the timestamp from `git`.
497pub fn set_SOURCE_TIMESTAMP() {
498 let value = format_timestamp(get_source_time().unwrap());
499 println!("cargo:rustc-env=SOURCE_TIMESTAMP={value}");
500}
501
502/// Sets the `SOURCE_EPOCH_TIME` env variable.
503///
504/// Call this from `build.rs`.
505/// Use `env!` in your `main.rs` to use the variable.
506///
507/// Example value: `"1618370707"`
508///
509/// # Panics
510/// Panics if `SOURCE_DATE_EPOCH` env var is set to a non-integer value.
511/// Panics if it fails to get the timestamp from `git`.
512pub fn set_SOURCE_EPOCH_TIME() {
513 let value = get_source_time().unwrap();
514 println!("cargo:rustc-env=SOURCE_EPOCH_TIME={value}");
515}
516
517/// Sets the `BUILD_DATE` env variable with the current date, in UTC.
518///
519/// Call this from `build.rs`.
520/// Use `env!` in your `main.rs` to use the variable.
521///
522/// Calling this will make your build
523/// [non-reproducible](https://reproducible-builds.org/docs/timestamps/).
524///
525/// Example value: `"2021-04-14Z"`
526pub fn set_BUILD_DATE() {
527 let value = format_date(now());
528 println!("cargo:rustc-env=BUILD_DATE={value}");
529}
530
531/// Sets the `BUILD_TIME` env variable, with the current time, in UTC.
532///
533/// Call this from `build.rs`.
534/// Use `env!` in your `main.rs` to use the variable.
535///
536/// Calling this will make your build
537/// [non-reproducible](https://reproducible-builds.org/docs/timestamps/).
538///
539/// Example value: `"03:25:07Z"`
540pub fn set_BUILD_TIME() {
541 let value = format_time(now());
542 println!("cargo:rustc-env=BUILD_TIME={value}");
543}
544
545/// Sets the `BUILD_TIMESTAMP` env variable, with the current date & time, in UTC.
546///
547/// Call this from `build.rs`.
548/// Use `env!` in your `main.rs` to use the variable.
549///
550/// Calling this will make your build
551/// [non-reproducible](https://reproducible-builds.org/docs/timestamps/).
552///
553/// Example value: `"2021-04-14T03:25:07Z"`
554pub fn set_BUILD_TIMESTAMP() {
555 let value = format_timestamp(now());
556 println!("cargo:rustc-env=BUILD_TIMESTAMP={value}");
557}
558
559/// Sets the `BUILD_EPOCH_TIME` env variable, with the current time.
560///
561/// Call this from `build.rs`.
562/// Use `env!` in your `main.rs` to use the variable.
563///
564/// Calling this will make your build
565/// [non-reproducible](https://reproducible-builds.org/docs/timestamps/).
566///
567/// Example value: `"1618370707"`
568pub fn set_BUILD_EPOCH_TIME() {
569 let value = now();
570 println!("cargo:rustc-env=BUILD_EPOCH_TIME={value}");
571}
572
573/// Sets the `BUILD_HOSTNAME` env variable, with the hostname of the machine executing the build.
574///
575/// Call this from `build.rs`.
576/// Use `env!` in your `main.rs` to use the variable.
577///
578/// Calling this will make your build
579/// [non-reproducible](https://reproducible-builds.org/docs/timestamps/).
580///
581/// Example value: `"builder2"`
582///
583/// Executes the `hostname` command.
584///
585/// # Panics
586/// Panics if it fails to get the timestamp from `hostname`.
587pub fn set_BUILD_HOSTNAME() {
588 let value = get_hostname().unwrap();
589 println!("cargo:rustc-env=BUILD_HOSTNAME={value}");
590}
591
592/// Sets the `GIT_BRANCH` env variable.
593///
594/// Call this from `build.rs`.
595/// Use `env!` in your `main.rs` to use the variable.
596///
597/// Executes the `git` command.
598///
599/// Example value: `"release"`
600///
601/// # Panics
602/// Panics if it fails to get the value from `git`.
603pub fn set_GIT_BRANCH() {
604 let value = get_git_branch().unwrap();
605 println!("cargo:rustc-env=GIT_BRANCH={value}");
606}
607
608/// Sets the `GIT_COMMIT` env variable.
609///
610/// Call this from `build.rs`.
611/// Use `env!` in your `main.rs` to use the variable.
612///
613/// Executes the `git` command.
614///
615/// Example value: `"a5547bfb1edb9712588f0f85d3e2c8ba618ac51f"`
616///
617/// # Panics
618/// Panics if it fails to get the value from `git`.
619pub fn set_GIT_COMMIT() {
620 let value = get_git_commit().unwrap();
621 println!("cargo:rustc-env=GIT_COMMIT={value}");
622}
623
624/// Sets the `GIT_COMMIT_SHORT` env variable.
625///
626/// Call this from `build.rs`.
627/// Use `env!` in your `main.rs` to use the variable.
628///
629/// Executes the `git` command.
630///
631/// Example value: `"a5547bf"`
632///
633/// # Panics
634/// Panics if it fails to get the value from `git`.
635pub fn set_GIT_COMMIT_SHORT() {
636 let value = get_git_commit_short().unwrap();
637 println!("cargo:rustc-env=GIT_COMMIT_SHORT={value}");
638}
639
640/// Sets the `GIT_DIRTY` env variable.
641///
642/// Call this from `build.rs`.
643/// Use `env!` in your `main.rs` to use the variable.
644///
645/// Executes the `git` command.
646///
647/// Sets the variable to `"true"` if the git repository contains uncommitted
648/// changes. Otherwise, sets it to `"false"`.
649///
650/// # Panics
651/// Panics if it fails to get the value from `git`.
652pub fn set_GIT_DIRTY() {
653 let value = get_git_dirty().unwrap();
654 println!("cargo:rustc-env=GIT_DIRTY={value}");
655}
656
657/// Sets the `RUSTC_VERSION` env variable to the output of `rustc --version`.
658///
659/// Call this from `build.rs`.
660/// Use `env!` in your `main.rs` to use the variable.
661///
662/// Example value: `"rustc 1.53.0-nightly (07e0e2ec2 2021-03-24)"`
663///
664/// Executes the `rustc` command.
665///
666/// # Panics
667/// Panics if it fails to get the value from `rustc`.
668pub fn set_RUSTC_VERSION() {
669 let value = get_rustc_version().unwrap();
670 println!("cargo:rustc-env=RUSTC_VERSION={value}");
671}
672
673/// Sets the `RUSTC_VERSION_SEMVER` to the dotted version number of the `rustc`
674/// used by the current build.
675///
676/// Call this from `build.rs`.
677/// Use `env!` in your `main.rs` to use the variable.
678///
679/// Example value: `"1.53.0"`
680///
681/// Executes the `rustc` command.
682///
683/// # Panics
684/// Panics if it fails to get the value from `rustc`.
685pub fn set_RUSTC_VERSION_SEMVER() {
686 let value = parse_rustc_semver(get_rustc_version().unwrap()).unwrap();
687 println!("cargo:rustc-env=RUSTC_VERSION_SEMVER={value}");
688}
689
690/// Sets the `RUST_CHANNEL` env variable to Rust channel used by the current build.
691///
692/// Call this from `build.rs`.
693/// Use `env!` in your `main.rs` to use the variable.
694///
695/// Possible values:
696/// - `"stable"`
697/// - `"beta"`
698/// - `"nightly"`
699///
700/// Executes the `rustc` command.
701///
702/// # Panics
703/// Panics if it fails to get the value from `rustc`.
704pub fn set_RUST_CHANNEL() {
705 let value = parse_rustc_channel(get_rustc_version().unwrap()).unwrap();
706 println!("cargo:rustc-env=RUST_CHANNEL={value}");
707}
708
709/// Sets the `TARGET_PLATFORM` env variable to the Rust target triple.
710/// See "Target Triple" in
711/// [The Cargo Book - Glossary](https://doc.rust-lang.org/cargo/appendix/glossary.html#target).
712///
713/// Examples:
714/// - `x86_64-unknown-linux-gnu` (Linux on Intel)
715/// - `x86_64-apple-darwin` (macOS on Intel)
716/// - `aarch64-unknown-linux-gnu` (Linux on ARM)
717/// - `aarch64-apple-darwin` (macOS on Apple Silicon)
718/// - See <https://doc.rust-lang.org/rustc/platform-support.html#tier-1-with-host-tools>
719///
720/// # Panics
721/// Panics when the `TARGET` env var is not set or is not valid UTF-8.
722pub fn set_TARGET_PLATFORM() {
723 let value = get_target_platform().unwrap();
724 println!("cargo:rustc-env=TARGET_PLATFORM={value}");
725}