build_rs/
input.rs

1//! Inputs from the build system to the build script.
2//!
3//! This crate does not do any caching or interpreting of the values provided by
4//! Cargo beyond the communication protocol itself. It is up to the build script
5//! to interpret the string values and decide what to do with them.
6//!
7//! Reference: <https://doc.rust-lang.org/stable/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts>
8
9use std::path::PathBuf;
10
11use crate::ident::{is_ascii_ident, is_crate_name, is_feature_name};
12use crate::output::rerun_if_env_changed;
13
14/// [`ProcessEnv`] wrapper that implicit calls [`rerun_if_env_changed`]
15const ENV: RerunIfEnvChanged<ProcessEnv> = RerunIfEnvChanged::new();
16
17/// Abstraction over environment variables
18trait Env {
19    /// Fetches the environment variable `key`, returning `None` if the variable isn’t set or if
20    /// there is another error.
21    ///
22    /// It may return `None` if the environment variable’s name contains the equal sign character
23    /// (`=`) or the NUL character.
24    ///
25    /// Note that this function will not check if the environment variable is valid Unicode.
26    fn get(&self, key: &str) -> Option<std::ffi::OsString>;
27
28    /// Checks the environment variable `key` is present
29    ///
30    /// It may not be considered present if the environment variable’s name contains the equal sign character
31    /// (`=`) or the NUL character.
32    fn is_present(&self, key: &str) -> bool;
33}
34
35/// Fetches environment variables from the current process
36struct ProcessEnv;
37
38impl Env for ProcessEnv {
39    fn get(&self, key: &str) -> Option<std::ffi::OsString> {
40        std::env::var_os(key)
41    }
42
43    fn is_present(&self, key: &str) -> bool {
44        self.get(key).is_some()
45    }
46}
47
48/// [`Env`] wrapper that implicitly calls [`rerun_if_env_changed`]
49struct RerunIfEnvChanged<E: Env>(E);
50
51impl RerunIfEnvChanged<ProcessEnv> {
52    const fn new() -> Self {
53        Self(ProcessEnv)
54    }
55}
56
57impl<E: Env> Env for RerunIfEnvChanged<E> {
58    #[track_caller]
59    fn get(&self, key: &str) -> Option<std::ffi::OsString> {
60        rerun_if_env_changed(key);
61        self.0.get(key)
62    }
63
64    #[track_caller]
65    fn is_present(&self, key: &str) -> bool {
66        self.get(key).is_some()
67    }
68}
69
70/// Path to the `cargo` binary performing the build.
71#[track_caller]
72pub fn cargo() -> PathBuf {
73    to_path(var_or_panic("CARGO"))
74}
75
76/// The directory containing the manifest for the package being built (the package
77/// containing the build script).
78///
79/// Also note that this is the value of the current
80/// working directory of the build script when it starts.
81#[track_caller]
82pub fn cargo_manifest_dir() -> PathBuf {
83    to_path(var_or_panic("CARGO_MANIFEST_DIR"))
84}
85
86/// The path to the manifest of your package.
87#[track_caller]
88pub fn cargo_manifest_path() -> PathBuf {
89    ENV.get("CARGO_MANIFEST_PATH")
90        .map(to_path)
91        .unwrap_or_else(|| {
92            let mut path = cargo_manifest_dir();
93            path.push("Cargo.toml");
94            path
95        })
96}
97
98/// The manifest `links` value.
99#[track_caller]
100pub fn cargo_manifest_links() -> Option<String> {
101    ENV.get("CARGO_MANIFEST_LINKS").map(to_string)
102}
103
104/// Contains parameters needed for Cargo’s [jobserver] implementation to parallelize
105/// subprocesses.
106///
107/// Rustc or cargo invocations from build.rs can already read
108/// `CARGO_MAKEFLAGS`, but GNU Make requires the flags to be specified either
109/// directly as arguments, or through the `MAKEFLAGS` environment variable.
110/// Currently Cargo doesn’t set the `MAKEFLAGS` variable, but it’s free for build
111/// scripts invoking GNU Make to set it to the contents of `CARGO_MAKEFLAGS`.
112///
113/// [jobserver]: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html
114#[track_caller]
115pub fn cargo_makeflags() -> Option<String> {
116    ENV.get("CARGO_MAKEFLAGS").map(to_string)
117}
118
119/// For each activated feature of the package being built, this will be `true`.
120#[track_caller]
121pub fn cargo_feature(name: &str) -> bool {
122    if !is_feature_name(name) {
123        panic!("invalid feature name {name:?}")
124    }
125    let name = name.to_uppercase().replace('-', "_");
126    let key = format!("CARGO_FEATURE_{name}");
127    ENV.is_present(&key)
128}
129
130/// For each [configuration option] of the package being built, this will contain
131/// the value of the configuration.
132///
133/// This includes values built-in to the compiler
134/// (which can be seen with `rustc --print=cfg`) and values set by build scripts
135/// and extra flags passed to rustc (such as those defined in `RUSTFLAGS`).
136///
137/// [configuration option]: https://doc.rust-lang.org/stable/reference/conditional-compilation.html
138#[track_caller]
139pub fn cargo_cfg(cfg: &str) -> Option<Vec<String>> {
140    let var = cargo_cfg_var(cfg);
141    ENV.get(&var).map(|v| to_strings(v, ','))
142}
143
144#[track_caller]
145fn cargo_cfg_var(cfg: &str) -> String {
146    if !is_ascii_ident(cfg) {
147        panic!("invalid configuration option {cfg:?}")
148    }
149    let cfg = cfg.to_uppercase().replace('-', "_");
150    let key = format!("CARGO_CFG_{cfg}");
151    key
152}
153
154pub use self::cfg::*;
155mod cfg {
156    use super::*;
157
158    // those disabled with #[cfg(any())] don't seem meaningfully useful
159    // but we list all cfg that are default known to check-cfg
160
161    /// Each activated feature of the package being built
162    #[doc = requires_msrv!("1.85")]
163    #[track_caller]
164    pub fn cargo_cfg_feature() -> Vec<String> {
165        to_strings(var_or_panic(&cargo_cfg_var("feature")), ',')
166    }
167
168    #[cfg(any())]
169    #[track_caller]
170    pub fn cargo_cfg_clippy() -> bool {
171        ENV.is_present("CARGO_CFG_CLIPPY")
172    }
173
174    /// If we are compiling with debug assertions enabled.
175    ///
176    /// Build scripts are not passed this cfg because
177    /// this cfg is always true and misleading.
178    /// That is because Cargo queries rustc without any profile settings.
179    #[cfg(any())]
180    #[track_caller]
181    pub fn cargo_cfg_debug_assertions() -> bool {
182        ENV.is_present("CARGO_CFG_DEBUG_ASSERTIONS")
183    }
184
185    #[cfg(any())]
186    #[track_caller]
187    pub fn cargo_cfg_doc() -> bool {
188        ENV.is_present("CARGO_CFG_DOC")
189    }
190
191    #[cfg(any())]
192    #[track_caller]
193    pub fn cargo_cfg_docsrs() -> bool {
194        ENV.is_present("CARGO_CFG_DOCSRS")
195    }
196
197    #[cfg(any())]
198    #[track_caller]
199    pub fn cargo_cfg_doctest() -> bool {
200        ENV.is_present("CARGO_CFG_DOCTEST")
201    }
202
203    /// The level of detail provided by derived [`Debug`] implementations.
204    #[doc = unstable!(fmt_dbg, 129709)]
205    #[cfg(feature = "unstable")]
206    #[track_caller]
207    pub fn cargo_cfg_fmt_debug() -> String {
208        to_string(var_or_panic("CARGO_CFG_FMT_DEBUG"))
209    }
210
211    #[cfg(any())]
212    #[track_caller]
213    pub fn cargo_cfg_miri() -> bool {
214        ENV.is_present("CARGO_CFG_MIRI")
215    }
216
217    /// If we are compiling with overflow checks enabled.
218    #[doc = unstable!(cfg_overflow_checks, 111466)]
219    #[cfg(feature = "unstable")]
220    #[track_caller]
221    pub fn cargo_cfg_overflow_checks() -> bool {
222        ENV.is_present("CARGO_CFG_OVERFLOW_CHECKS")
223    }
224
225    /// The [panic strategy](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#panic).
226    #[track_caller]
227    pub fn cargo_cfg_panic() -> String {
228        to_string(var_or_panic("CARGO_CFG_PANIC"))
229    }
230
231    /// If the crate is being compiled as a procedural macro.
232    #[track_caller]
233    pub fn cargo_cfg_proc_macro() -> bool {
234        ENV.is_present("CARGO_CFG_PROC_MACRO")
235    }
236
237    /// The target relocation model.
238    #[doc = unstable!(cfg_relocation_model, 114929)]
239    #[cfg(feature = "unstable")]
240    #[track_caller]
241    pub fn cargo_cfg_relocation_model() -> String {
242        to_string(var_or_panic("CARGO_CFG_RELOCATION_MODEL"))
243    }
244
245    #[cfg(any())]
246    #[track_caller]
247    pub fn cargo_cfg_rustfmt() -> bool {
248        ENV.is_present("CARGO_CFG_RUSTFMT")
249    }
250
251    /// Sanitizers enabled for the crate being compiled.
252    #[doc = unstable!(cfg_sanitize, 39699)]
253    #[cfg(feature = "unstable")]
254    #[track_caller]
255    pub fn cargo_cfg_sanitize() -> Option<Vec<String>> {
256        ENV.get("CARGO_CFG_SANITIZE").map(|v| to_strings(v, ','))
257    }
258
259    /// If CFI sanitization is generalizing pointers.
260    #[doc = unstable!(cfg_sanitizer_cfi, 89653)]
261    #[cfg(feature = "unstable")]
262    #[track_caller]
263    pub fn cargo_cfg_sanitizer_cfi_generalize_pointers() -> bool {
264        ENV.is_present("CARGO_CFG_SANITIZER_CFI_GENERALIZE_POINTERS")
265    }
266
267    /// If CFI sanitization is normalizing integers.
268    #[doc = unstable!(cfg_sanitizer_cfi, 89653)]
269    #[cfg(feature = "unstable")]
270    #[track_caller]
271    pub fn cargo_cfg_sanitizer_cfi_normalize_integers() -> bool {
272        ENV.is_present("CARGO_CFG_SANITIZER_CFI_NORMALIZE_INTEGERS")
273    }
274
275    /// Disambiguation of the [target ABI](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_abi)
276    /// when the [target env](cargo_cfg_target_env) isn't sufficient.
277    ///
278    /// For historical reasons, this value is only defined as `Some` when
279    /// actually needed for disambiguation. Thus, for example, on many GNU platforms,
280    /// this value will be `None`.
281    #[track_caller]
282    pub fn cargo_cfg_target_abi() -> Option<String> {
283        to_opt(var_or_panic("CARGO_CFG_TARGET_ABI")).map(to_string)
284    }
285
286    /// The CPU [target architecture](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_arch).
287    /// This is similar to the first element of the platform's target triple, but not identical.
288    #[track_caller]
289    pub fn cargo_cfg_target_arch() -> String {
290        to_string(var_or_panic("CARGO_CFG_TARGET_ARCH"))
291    }
292
293    /// The CPU [target endianness](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_endian).
294    #[track_caller]
295    pub fn cargo_cfg_target_endian() -> String {
296        to_string(var_or_panic("CARGO_CFG_TARGET_ENDIAN"))
297    }
298
299    /// The [target environment](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_env) ABI.
300    /// This value is similar to the fourth element of the platform's target triple.
301    ///
302    /// For historical reasons, this value is only defined as not the empty-string when
303    /// actually needed for disambiguation. Thus, for example, on many GNU platforms,
304    /// this value will be empty.
305    #[track_caller]
306    pub fn cargo_cfg_target_env() -> String {
307        to_string(var_or_panic("CARGO_CFG_TARGET_ENV"))
308    }
309
310    /// The [target family](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_family).
311    #[track_caller]
312    pub fn cargo_target_family() -> Vec<String> {
313        to_strings(var_or_panic(&cargo_cfg_var("target_family")), ',')
314    }
315
316    /// List of CPU [target features](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_feature) enabled.
317    #[track_caller]
318    pub fn cargo_cfg_target_feature() -> Vec<String> {
319        to_strings(var_or_panic(&cargo_cfg_var("target_feature")), ',')
320    }
321
322    /// List of CPU [supported atomic widths](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_has_atomic).
323    #[track_caller]
324    pub fn cargo_cfg_target_has_atomic() -> Vec<String> {
325        to_strings(var_or_panic(&cargo_cfg_var("target_has_atomic")), ',')
326    }
327
328    /// List of atomic widths that have equal alignment requirements.
329    #[doc = unstable!(cfg_target_has_atomic_equal_alignment, 93822)]
330    #[cfg(feature = "unstable")]
331    #[track_caller]
332    pub fn cargo_cfg_target_has_atomic_equal_alignment() -> Vec<String> {
333        to_strings(
334            var_or_panic(&cargo_cfg_var("target_has_atomic_equal_alignment")),
335            ',',
336        )
337    }
338
339    /// List of atomic widths that have atomic load and store operations.
340    #[doc = unstable!(cfg_target_has_atomic_load_store, 94039)]
341    #[cfg(feature = "unstable")]
342    #[track_caller]
343    pub fn cargo_cfg_target_has_atomic_load_store() -> Vec<String> {
344        to_strings(
345            var_or_panic(&cargo_cfg_var("target_has_atomic_load_store")),
346            ',',
347        )
348    }
349
350    /// The [target operating system](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_os).
351    /// This value is similar to the second and third element of the platform's target triple.
352    #[track_caller]
353    pub fn cargo_cfg_target_os() -> String {
354        to_string(var_or_panic("CARGO_CFG_TARGET_OS"))
355    }
356
357    /// The CPU [pointer width](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_pointer_width).
358    #[track_caller]
359    pub fn cargo_cfg_target_pointer_width() -> u32 {
360        to_parsed(var_or_panic("CARGO_CFG_TARGET_POINTER_WIDTH"))
361    }
362
363    /// If the target supports thread-local storage.
364    #[doc = unstable!(cfg_target_thread_local, 29594)]
365    #[cfg(feature = "unstable")]
366    #[track_caller]
367    pub fn cargo_cfg_target_thread_local() -> bool {
368        ENV.is_present("CARGO_CFG_TARGET_THREAD_LOCAL")
369    }
370
371    /// The [target vendor](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#target_vendor).
372    #[track_caller]
373    pub fn cargo_cfg_target_vendor() -> String {
374        to_string(var_or_panic("CARGO_CFG_TARGET_VENDOR"))
375    }
376
377    #[cfg(any())]
378    #[track_caller]
379    pub fn cargo_cfg_test() -> bool {
380        ENV.is_present("CARGO_CFG_TEST")
381    }
382
383    /// If we are compiling with UB checks enabled.
384    #[doc = unstable!(cfg_ub_checks, 123499)]
385    #[cfg(feature = "unstable")]
386    #[track_caller]
387    pub fn cargo_cfg_ub_checks() -> bool {
388        ENV.is_present("CARGO_CFG_UB_CHECKS")
389    }
390
391    /// Set on [unix-like platforms](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#unix-and-windows).
392    #[track_caller]
393    pub fn cargo_cfg_unix() -> bool {
394        ENV.is_present("CARGO_CFG_UNIX")
395    }
396
397    /// Set on [windows-like platforms](https://doc.rust-lang.org/stable/reference/conditional-compilation.html#unix-and-windows).
398    #[track_caller]
399    pub fn cargo_cfg_windows() -> bool {
400        ENV.is_present("CARGO_CFG_WINDOWS")
401    }
402}
403
404/// The folder in which all output and intermediate artifacts should be placed.
405///
406/// This folder is inside the build directory for the package being built, and
407/// it is unique for the package in question.
408#[track_caller]
409pub fn out_dir() -> PathBuf {
410    to_path(var_or_panic("OUT_DIR"))
411}
412
413/// The [target triple] that is being compiled for. Native code should be compiled
414///  for this triple.
415///
416/// [target triple]: https://doc.rust-lang.org/stable/cargo/appendix/glossary.html#target
417#[track_caller]
418pub fn target() -> String {
419    to_string(var_or_panic("TARGET"))
420}
421
422/// The host triple of the Rust compiler.
423#[track_caller]
424pub fn host() -> String {
425    to_string(var_or_panic("HOST"))
426}
427
428/// The parallelism specified as the top-level parallelism.
429///
430/// This can be useful to
431/// pass a `-j` parameter to a system like `make`. Note that care should be taken
432/// when interpreting this value. For historical purposes this is still provided
433/// but Cargo, for example, does not need to run `make -j`, and instead can set the
434/// `MAKEFLAGS` env var to the content of `CARGO_MAKEFLAGS` to activate the use of
435/// Cargo’s GNU Make compatible [jobserver] for sub-make invocations.
436///
437/// [jobserver]: https://www.gnu.org/software/make/manual/html_node/Job-Slots.html
438#[track_caller]
439pub fn num_jobs() -> u32 {
440    to_parsed(var_or_panic("NUM_JOBS"))
441}
442
443/// The [level of optimization](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#opt-level).
444#[track_caller]
445pub fn opt_level() -> String {
446    to_string(var_or_panic("OPT_LEVEL"))
447}
448
449/// The amount of [debug information](https://doc.rust-lang.org/stable/cargo/reference/profiles.html#debug) included.
450#[track_caller]
451pub fn debug() -> String {
452    to_string(var_or_panic("DEBUG"))
453}
454
455/// `release` for release builds, `debug` for other builds.
456///
457/// This is determined based
458/// on if the [profile] inherits from the [`dev`] or [`release`] profile. Using this
459/// function is not recommended. Using other functions like [`opt_level`] provides
460/// a more correct view of the actual settings being used.
461///
462/// [profile]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html
463/// [`dev`]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html#dev
464/// [`release`]: https://doc.rust-lang.org/stable/cargo/reference/profiles.html#release
465#[track_caller]
466pub fn profile() -> String {
467    to_string(var_or_panic("PROFILE"))
468}
469
470/// [Metadata] set by dependencies. For more information, see build script
471/// documentation about [the `links` manifest key][links].
472///
473/// [metadata]: crate::output::metadata
474/// [links]: https://doc.rust-lang.org/stable/cargo/reference/build-scripts.html#the-links-manifest-key
475#[track_caller]
476pub fn dep_metadata(name: &str, key: &str) -> Option<String> {
477    if !is_crate_name(name) {
478        panic!("invalid dependency name {name:?}")
479    }
480    if !is_ascii_ident(key) {
481        panic!("invalid metadata key {key:?}")
482    }
483
484    let name = name.to_uppercase().replace('-', "_");
485    let key = key.to_uppercase().replace('-', "_");
486    let key = format!("DEP_{name}_{key}");
487    ENV.get(&key).map(to_string)
488}
489
490/// The compiler that Cargo has resolved to use.
491#[track_caller]
492pub fn rustc() -> PathBuf {
493    to_path(var_or_panic("RUSTC"))
494}
495
496/// The documentation generator that Cargo has resolved to use.
497#[track_caller]
498pub fn rustdoc() -> PathBuf {
499    to_path(var_or_panic("RUSTDOC"))
500}
501
502/// The rustc wrapper, if any, that Cargo is using. See [`build.rustc-wrapper`].
503///
504/// [`build.rustc-wrapper`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustc-wrapper
505#[track_caller]
506pub fn rustc_wrapper() -> Option<PathBuf> {
507    ENV.get("RUSTC_WRAPPER").map(to_path)
508}
509
510/// The rustc wrapper, if any, that Cargo is using for workspace members. See
511/// [`build.rustc-workspace-wrapper`].
512///
513/// [`build.rustc-workspace-wrapper`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustc-workspace-wrapper
514#[track_caller]
515pub fn rustc_workspace_wrapper() -> Option<PathBuf> {
516    ENV.get("RUSTC_WORKSPACE_WRAPPER").map(to_path)
517}
518
519/// The linker that Cargo has resolved to use for the current target, if specified.
520///
521/// [`target.*.linker`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#targettriplelinker
522#[track_caller]
523pub fn rustc_linker() -> Option<PathBuf> {
524    ENV.get("RUSTC_LINKER").map(to_path)
525}
526
527/// Extra flags that Cargo invokes rustc with. See [`build.rustflags`].
528///
529/// [`build.rustflags`]: https://doc.rust-lang.org/stable/cargo/reference/config.html#buildrustflags
530#[track_caller]
531pub fn cargo_encoded_rustflags() -> Vec<String> {
532    to_strings(var_or_panic("CARGO_ENCODED_RUSTFLAGS"), '\x1f')
533}
534
535/// The full version of your package.
536#[track_caller]
537pub fn cargo_pkg_version() -> String {
538    to_string(var_or_panic("CARGO_PKG_VERSION"))
539}
540
541/// The major version of your package.
542#[track_caller]
543pub fn cargo_pkg_version_major() -> u64 {
544    to_parsed(var_or_panic("CARGO_PKG_VERSION_MAJOR"))
545}
546
547/// The minor version of your package.
548#[track_caller]
549pub fn cargo_pkg_version_minor() -> u64 {
550    to_parsed(var_or_panic("CARGO_PKG_VERSION_MINOR"))
551}
552
553/// The patch version of your package.
554#[track_caller]
555pub fn cargo_pkg_version_patch() -> u64 {
556    to_parsed(var_or_panic("CARGO_PKG_VERSION_PATCH"))
557}
558
559/// The pre-release version of your package.
560#[track_caller]
561pub fn cargo_pkg_version_pre() -> Option<String> {
562    to_opt(var_or_panic("CARGO_PKG_VERSION_PRE")).map(to_string)
563}
564
565/// The authors from the manifest of your package.
566#[track_caller]
567pub fn cargo_pkg_authors() -> Vec<String> {
568    to_strings(var_or_panic("CARGO_PKG_AUTHORS"), ':')
569}
570
571/// The name of your package.
572#[track_caller]
573pub fn cargo_pkg_name() -> String {
574    to_string(var_or_panic("CARGO_PKG_NAME"))
575}
576
577/// The description from the manifest of your package.
578#[track_caller]
579pub fn cargo_pkg_description() -> Option<String> {
580    to_opt(var_or_panic("CARGO_PKG_DESCRIPTION")).map(to_string)
581}
582
583/// The home page from the manifest of your package.
584#[track_caller]
585pub fn cargo_pkg_homepage() -> Option<String> {
586    to_opt(var_or_panic("CARGO_PKG_HOMEPAGE")).map(to_string)
587}
588
589/// The repository from the manifest of your package.
590#[track_caller]
591pub fn cargo_pkg_repository() -> Option<String> {
592    to_opt(var_or_panic("CARGO_PKG_REPOSITORY")).map(to_string)
593}
594
595/// The license from the manifest of your package.
596#[track_caller]
597pub fn cargo_pkg_license() -> Option<String> {
598    to_opt(var_or_panic("CARGO_PKG_LICENSE")).map(to_string)
599}
600
601/// The license file from the manifest of your package.
602#[track_caller]
603pub fn cargo_pkg_license_file() -> Option<PathBuf> {
604    to_opt(var_or_panic("CARGO_PKG_LICENSE_FILE")).map(to_path)
605}
606
607/// The Rust version from the manifest of your package. Note that this is the
608/// minimum Rust version supported by the package, not the current Rust version.
609#[track_caller]
610pub fn cargo_pkg_rust_version() -> Option<String> {
611    to_opt(var_or_panic("CARGO_PKG_RUST_VERSION")).map(to_string)
612}
613
614/// Path to the README file of your package.
615#[track_caller]
616pub fn cargo_pkg_readme() -> Option<PathBuf> {
617    to_opt(var_or_panic("CARGO_PKG_README")).map(to_path)
618}
619
620#[track_caller]
621fn var_or_panic(key: &str) -> std::ffi::OsString {
622    ENV.get(key)
623        .unwrap_or_else(|| panic!("cargo environment variable `{key}` is missing"))
624}
625
626fn to_path(value: std::ffi::OsString) -> PathBuf {
627    PathBuf::from(value)
628}
629
630#[track_caller]
631fn to_string(value: std::ffi::OsString) -> String {
632    match value.into_string() {
633        Ok(s) => s,
634        Err(value) => {
635            let err = std::str::from_utf8(value.as_encoded_bytes()).unwrap_err();
636            panic!("{err}")
637        }
638    }
639}
640
641fn to_opt(value: std::ffi::OsString) -> Option<std::ffi::OsString> {
642    (!value.is_empty()).then_some(value)
643}
644
645#[track_caller]
646fn to_strings(value: std::ffi::OsString, sep: char) -> Vec<String> {
647    if value.is_empty() {
648        return Vec::new();
649    }
650    let value = to_string(value);
651    value.split(sep).map(str::to_owned).collect()
652}
653
654#[track_caller]
655fn to_parsed<T>(value: std::ffi::OsString) -> T
656where
657    T: std::str::FromStr,
658    T::Err: std::fmt::Display,
659{
660    let value = to_string(value);
661    match value.parse() {
662        Ok(s) => s,
663        Err(err) => {
664            panic!("{err}")
665        }
666    }
667}