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