build_data/
lib.rs

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