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.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}