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