1use std::path::PathBuf;
10
11use crate::ident::{is_ascii_ident, is_crate_name, is_feature_name};
12use crate::output::rerun_if_env_changed;
13
14const ENV: RerunIfEnvChanged<ProcessEnv> = RerunIfEnvChanged::new();
16
17trait Env {
19 fn get(&self, key: &str) -> Option<std::ffi::OsString>;
27
28 fn is_present(&self, key: &str) -> bool;
33}
34
35struct 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
48struct 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#[track_caller]
72pub fn cargo() -> PathBuf {
73 to_path(var_or_panic("CARGO"))
74}
75
76#[track_caller]
82pub fn cargo_manifest_dir() -> PathBuf {
83 to_path(var_or_panic("CARGO_MANIFEST_DIR"))
84}
85
86#[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#[track_caller]
100pub fn cargo_manifest_links() -> Option<String> {
101 ENV.get("CARGO_MANIFEST_LINKS").map(to_string)
102}
103
104#[track_caller]
115pub fn cargo_makeflags() -> Option<String> {
116 ENV.get("CARGO_MAKEFLAGS").map(to_string)
117}
118
119#[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#[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 #[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 #[track_caller]
176 pub fn cargo_cfg_debug_assertions() -> bool {
177 ENV.is_present("CARGO_CFG_DEBUG_ASSERTIONS")
178 }
179
180 #[cfg(any())]
181 #[track_caller]
182 pub fn cargo_cfg_doc() -> bool {
183 ENV.is_present("CARGO_CFG_DOC")
184 }
185
186 #[cfg(any())]
187 #[track_caller]
188 pub fn cargo_cfg_docsrs() -> bool {
189 ENV.is_present("CARGO_CFG_DOCSRS")
190 }
191
192 #[cfg(any())]
193 #[track_caller]
194 pub fn cargo_cfg_doctest() -> bool {
195 ENV.is_present("CARGO_CFG_DOCTEST")
196 }
197
198 #[doc = unstable!(fmt_dbg, 129709)]
200 #[cfg(feature = "unstable")]
201 #[track_caller]
202 pub fn cargo_cfg_fmt_debug() -> String {
203 to_string(var_or_panic("CARGO_CFG_FMT_DEBUG"))
204 }
205
206 #[cfg(any())]
207 #[track_caller]
208 pub fn cargo_cfg_miri() -> bool {
209 ENV.is_present("CARGO_CFG_MIRI")
210 }
211
212 #[doc = unstable!(cfg_overflow_checks, 111466)]
214 #[cfg(feature = "unstable")]
215 #[track_caller]
216 pub fn cargo_cfg_overflow_checks() -> bool {
217 ENV.is_present("CARGO_CFG_OVERFLOW_CHECKS")
218 }
219
220 #[track_caller]
222 pub fn cargo_cfg_panic() -> String {
223 to_string(var_or_panic("CARGO_CFG_PANIC"))
224 }
225
226 #[track_caller]
228 pub fn cargo_cfg_proc_macro() -> bool {
229 ENV.is_present("CARGO_CFG_PROC_MACRO")
230 }
231
232 #[doc = unstable!(cfg_relocation_model, 114929)]
234 #[cfg(feature = "unstable")]
235 #[track_caller]
236 pub fn cargo_cfg_relocation_model() -> String {
237 to_string(var_or_panic("CARGO_CFG_RELOCATION_MODEL"))
238 }
239
240 #[cfg(any())]
241 #[track_caller]
242 pub fn cargo_cfg_rustfmt() -> bool {
243 ENV.is_present("CARGO_CFG_RUSTFMT")
244 }
245
246 #[doc = unstable!(cfg_sanitize, 39699)]
248 #[cfg(feature = "unstable")]
249 #[track_caller]
250 pub fn cargo_cfg_sanitize() -> Option<Vec<String>> {
251 ENV.get("CARGO_CFG_SANITIZE").map(|v| to_strings(v, ','))
252 }
253
254 #[doc = unstable!(cfg_sanitizer_cfi, 89653)]
256 #[cfg(feature = "unstable")]
257 #[track_caller]
258 pub fn cargo_cfg_sanitizer_cfi_generalize_pointers() -> bool {
259 ENV.is_present("CARGO_CFG_SANITIZER_CFI_GENERALIZE_POINTERS")
260 }
261
262 #[doc = unstable!(cfg_sanitizer_cfi, 89653)]
264 #[cfg(feature = "unstable")]
265 #[track_caller]
266 pub fn cargo_cfg_sanitizer_cfi_normalize_integers() -> bool {
267 ENV.is_present("CARGO_CFG_SANITIZER_CFI_NORMALIZE_INTEGERS")
268 }
269
270 #[track_caller]
277 pub fn cargo_cfg_target_abi() -> Option<String> {
278 to_opt(var_or_panic("CARGO_CFG_TARGET_ABI")).map(to_string)
279 }
280
281 #[track_caller]
284 pub fn cargo_cfg_target_arch() -> String {
285 to_string(var_or_panic("CARGO_CFG_TARGET_ARCH"))
286 }
287
288 #[track_caller]
290 pub fn cargo_cfg_target_endian() -> String {
291 to_string(var_or_panic("CARGO_CFG_TARGET_ENDIAN"))
292 }
293
294 #[track_caller]
301 pub fn cargo_cfg_target_env() -> String {
302 to_string(var_or_panic("CARGO_CFG_TARGET_ENV"))
303 }
304
305 #[track_caller]
307 pub fn cargo_target_family() -> Vec<String> {
308 to_strings(var_or_panic(&cargo_cfg_var("target_family")), ',')
309 }
310
311 #[track_caller]
313 pub fn cargo_cfg_target_feature() -> Vec<String> {
314 to_strings(var_or_panic(&cargo_cfg_var("target_feature")), ',')
315 }
316
317 #[track_caller]
319 pub fn cargo_cfg_target_has_atomic() -> Vec<String> {
320 to_strings(var_or_panic(&cargo_cfg_var("target_has_atomic")), ',')
321 }
322
323 #[doc = unstable!(cfg_target_has_atomic_equal_alignment, 93822)]
325 #[cfg(feature = "unstable")]
326 #[track_caller]
327 pub fn cargo_cfg_target_has_atomic_equal_alignment() -> Vec<String> {
328 to_strings(
329 var_or_panic(&cargo_cfg_var("target_has_atomic_equal_alignment")),
330 ',',
331 )
332 }
333
334 #[doc = unstable!(cfg_target_has_atomic_load_store, 94039)]
336 #[cfg(feature = "unstable")]
337 #[track_caller]
338 pub fn cargo_cfg_target_has_atomic_load_store() -> Vec<String> {
339 to_strings(
340 var_or_panic(&cargo_cfg_var("target_has_atomic_load_store")),
341 ',',
342 )
343 }
344
345 #[track_caller]
348 pub fn cargo_cfg_target_os() -> String {
349 to_string(var_or_panic("CARGO_CFG_TARGET_OS"))
350 }
351
352 #[track_caller]
354 pub fn cargo_cfg_target_pointer_width() -> u32 {
355 to_parsed(var_or_panic("CARGO_CFG_TARGET_POINTER_WIDTH"))
356 }
357
358 #[doc = unstable!(cfg_target_thread_local, 29594)]
360 #[cfg(feature = "unstable")]
361 #[track_caller]
362 pub fn cargo_cfg_target_thread_local() -> bool {
363 ENV.is_present("CARGO_CFG_TARGET_THREAD_LOCAL")
364 }
365
366 #[track_caller]
368 pub fn cargo_cfg_target_vendor() -> String {
369 to_string(var_or_panic("CARGO_CFG_TARGET_VENDOR"))
370 }
371
372 #[cfg(any())]
373 #[track_caller]
374 pub fn cargo_cfg_test() -> bool {
375 ENV.is_present("CARGO_CFG_TEST")
376 }
377
378 #[doc = unstable!(cfg_ub_checks, 123499)]
380 #[cfg(feature = "unstable")]
381 #[track_caller]
382 pub fn cargo_cfg_ub_checks() -> bool {
383 ENV.is_present("CARGO_CFG_UB_CHECKS")
384 }
385
386 #[track_caller]
388 pub fn cargo_cfg_unix() -> bool {
389 ENV.is_present("CARGO_CFG_UNIX")
390 }
391
392 #[track_caller]
394 pub fn cargo_cfg_windows() -> bool {
395 ENV.is_present("CARGO_CFG_WINDOWS")
396 }
397}
398
399#[track_caller]
404pub fn out_dir() -> PathBuf {
405 to_path(var_or_panic("OUT_DIR"))
406}
407
408#[track_caller]
413pub fn target() -> String {
414 to_string(var_or_panic("TARGET"))
415}
416
417#[track_caller]
419pub fn host() -> String {
420 to_string(var_or_panic("HOST"))
421}
422
423#[track_caller]
434pub fn num_jobs() -> u32 {
435 to_parsed(var_or_panic("NUM_JOBS"))
436}
437
438#[track_caller]
440pub fn opt_level() -> String {
441 to_string(var_or_panic("OPT_LEVEL"))
442}
443
444#[track_caller]
446pub fn debug() -> String {
447 to_string(var_or_panic("DEBUG"))
448}
449
450#[track_caller]
461pub fn profile() -> String {
462 to_string(var_or_panic("PROFILE"))
463}
464
465#[track_caller]
471pub fn dep_metadata(name: &str, key: &str) -> Option<String> {
472 if !is_crate_name(name) {
473 panic!("invalid dependency name {name:?}")
474 }
475 if !is_ascii_ident(key) {
476 panic!("invalid metadata key {key:?}")
477 }
478
479 let name = name.to_uppercase().replace('-', "_");
480 let key = key.to_uppercase().replace('-', "_");
481 let key = format!("DEP_{name}_{key}");
482 ENV.get(&key).map(to_string)
483}
484
485#[track_caller]
487pub fn rustc() -> PathBuf {
488 to_path(var_or_panic("RUSTC"))
489}
490
491#[track_caller]
493pub fn rustdoc() -> PathBuf {
494 to_path(var_or_panic("RUSTDOC"))
495}
496
497#[track_caller]
501pub fn rustc_wrapper() -> Option<PathBuf> {
502 ENV.get("RUSTC_WRAPPER").map(to_path)
503}
504
505#[track_caller]
510pub fn rustc_workspace_wrapper() -> Option<PathBuf> {
511 ENV.get("RUSTC_WORKSPACE_WRAPPER").map(to_path)
512}
513
514#[track_caller]
518pub fn rustc_linker() -> Option<PathBuf> {
519 ENV.get("RUSTC_LINKER").map(to_path)
520}
521
522#[track_caller]
526pub fn cargo_encoded_rustflags() -> Vec<String> {
527 to_strings(var_or_panic("CARGO_ENCODED_RUSTFLAGS"), '\x1f')
528}
529
530#[track_caller]
532pub fn cargo_pkg_version() -> String {
533 to_string(var_or_panic("CARGO_PKG_VERSION"))
534}
535
536#[track_caller]
538pub fn cargo_pkg_version_major() -> u64 {
539 to_parsed(var_or_panic("CARGO_PKG_VERSION_MAJOR"))
540}
541
542#[track_caller]
544pub fn cargo_pkg_version_minor() -> u64 {
545 to_parsed(var_or_panic("CARGO_PKG_VERSION_MINOR"))
546}
547
548#[track_caller]
550pub fn cargo_pkg_version_patch() -> u64 {
551 to_parsed(var_or_panic("CARGO_PKG_VERSION_PATCH"))
552}
553
554#[track_caller]
556pub fn cargo_pkg_version_pre() -> Option<String> {
557 to_opt(var_or_panic("CARGO_PKG_VERSION_PRE")).map(to_string)
558}
559
560#[track_caller]
562pub fn cargo_pkg_authors() -> Vec<String> {
563 to_strings(var_or_panic("CARGO_PKG_AUTHORS"), ':')
564}
565
566#[track_caller]
568pub fn cargo_pkg_name() -> String {
569 to_string(var_or_panic("CARGO_PKG_NAME"))
570}
571
572#[track_caller]
574pub fn cargo_pkg_description() -> Option<String> {
575 to_opt(var_or_panic("CARGO_PKG_DESCRIPTION")).map(to_string)
576}
577
578#[track_caller]
580pub fn cargo_pkg_homepage() -> Option<String> {
581 to_opt(var_or_panic("CARGO_PKG_HOMEPAGE")).map(to_string)
582}
583
584#[track_caller]
586pub fn cargo_pkg_repository() -> Option<String> {
587 to_opt(var_or_panic("CARGO_PKG_REPOSITORY")).map(to_string)
588}
589
590#[track_caller]
592pub fn cargo_pkg_license() -> Option<String> {
593 to_opt(var_or_panic("CARGO_PKG_LICENSE")).map(to_string)
594}
595
596#[track_caller]
598pub fn cargo_pkg_license_file() -> Option<PathBuf> {
599 to_opt(var_or_panic("CARGO_PKG_LICENSE_FILE")).map(to_path)
600}
601
602#[track_caller]
605pub fn cargo_pkg_rust_version() -> Option<String> {
606 to_opt(var_or_panic("CARGO_PKG_RUST_VERSION")).map(to_string)
607}
608
609#[track_caller]
611pub fn cargo_pkg_readme() -> Option<PathBuf> {
612 to_opt(var_or_panic("CARGO_PKG_README")).map(to_path)
613}
614
615#[track_caller]
616fn var_or_panic(key: &str) -> std::ffi::OsString {
617 ENV.get(key)
618 .unwrap_or_else(|| panic!("cargo environment variable `{key}` is missing"))
619}
620
621fn to_path(value: std::ffi::OsString) -> PathBuf {
622 PathBuf::from(value)
623}
624
625#[track_caller]
626fn to_string(value: std::ffi::OsString) -> String {
627 match value.into_string() {
628 Ok(s) => s,
629 Err(value) => {
630 let err = std::str::from_utf8(value.as_encoded_bytes()).unwrap_err();
631 panic!("{err}")
632 }
633 }
634}
635
636fn to_opt(value: std::ffi::OsString) -> Option<std::ffi::OsString> {
637 (!value.is_empty()).then_some(value)
638}
639
640#[track_caller]
641fn to_strings(value: std::ffi::OsString, sep: char) -> Vec<String> {
642 if value.is_empty() {
643 return Vec::new();
644 }
645 let value = to_string(value);
646 value.split(sep).map(str::to_owned).collect()
647}
648
649#[track_caller]
650fn to_parsed<T>(value: std::ffi::OsString) -> T
651where
652 T: std::str::FromStr,
653 T::Err: std::fmt::Display,
654{
655 let value = to_string(value);
656 match value.parse() {
657 Ok(s) => s,
658 Err(err) => {
659 panic!("{err}")
660 }
661 }
662}