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 #[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 #[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 #[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 #[track_caller]
227 pub fn cargo_cfg_panic() -> String {
228 to_string(var_or_panic("CARGO_CFG_PANIC"))
229 }
230
231 #[track_caller]
233 pub fn cargo_cfg_proc_macro() -> bool {
234 ENV.is_present("CARGO_CFG_PROC_MACRO")
235 }
236
237 #[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 #[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 #[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 #[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 #[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 #[track_caller]
289 pub fn cargo_cfg_target_arch() -> String {
290 to_string(var_or_panic("CARGO_CFG_TARGET_ARCH"))
291 }
292
293 #[track_caller]
295 pub fn cargo_cfg_target_endian() -> String {
296 to_string(var_or_panic("CARGO_CFG_TARGET_ENDIAN"))
297 }
298
299 #[track_caller]
306 pub fn cargo_cfg_target_env() -> String {
307 to_string(var_or_panic("CARGO_CFG_TARGET_ENV"))
308 }
309
310 #[track_caller]
312 pub fn cargo_target_family() -> Vec<String> {
313 to_strings(var_or_panic(&cargo_cfg_var("target_family")), ',')
314 }
315
316 #[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 #[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 #[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 #[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 #[track_caller]
353 pub fn cargo_cfg_target_os() -> String {
354 to_string(var_or_panic("CARGO_CFG_TARGET_OS"))
355 }
356
357 #[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 #[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 #[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 #[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 #[track_caller]
393 pub fn cargo_cfg_unix() -> bool {
394 ENV.is_present("CARGO_CFG_UNIX")
395 }
396
397 #[track_caller]
399 pub fn cargo_cfg_windows() -> bool {
400 ENV.is_present("CARGO_CFG_WINDOWS")
401 }
402}
403
404#[track_caller]
409pub fn out_dir() -> PathBuf {
410 to_path(var_or_panic("OUT_DIR"))
411}
412
413#[track_caller]
418pub fn target() -> String {
419 to_string(var_or_panic("TARGET"))
420}
421
422#[track_caller]
424pub fn host() -> String {
425 to_string(var_or_panic("HOST"))
426}
427
428#[track_caller]
439pub fn num_jobs() -> u32 {
440 to_parsed(var_or_panic("NUM_JOBS"))
441}
442
443#[track_caller]
445pub fn opt_level() -> String {
446 to_string(var_or_panic("OPT_LEVEL"))
447}
448
449#[track_caller]
451pub fn debug() -> String {
452 to_string(var_or_panic("DEBUG"))
453}
454
455#[track_caller]
466pub fn profile() -> String {
467 to_string(var_or_panic("PROFILE"))
468}
469
470#[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#[track_caller]
492pub fn rustc() -> PathBuf {
493 to_path(var_or_panic("RUSTC"))
494}
495
496#[track_caller]
498pub fn rustdoc() -> PathBuf {
499 to_path(var_or_panic("RUSTDOC"))
500}
501
502#[track_caller]
506pub fn rustc_wrapper() -> Option<PathBuf> {
507 ENV.get("RUSTC_WRAPPER").map(to_path)
508}
509
510#[track_caller]
515pub fn rustc_workspace_wrapper() -> Option<PathBuf> {
516 ENV.get("RUSTC_WORKSPACE_WRAPPER").map(to_path)
517}
518
519#[track_caller]
523pub fn rustc_linker() -> Option<PathBuf> {
524 ENV.get("RUSTC_LINKER").map(to_path)
525}
526
527#[track_caller]
531pub fn cargo_encoded_rustflags() -> Vec<String> {
532 to_strings(var_or_panic("CARGO_ENCODED_RUSTFLAGS"), '\x1f')
533}
534
535#[track_caller]
537pub fn cargo_pkg_version() -> String {
538 to_string(var_or_panic("CARGO_PKG_VERSION"))
539}
540
541#[track_caller]
543pub fn cargo_pkg_version_major() -> u64 {
544 to_parsed(var_or_panic("CARGO_PKG_VERSION_MAJOR"))
545}
546
547#[track_caller]
549pub fn cargo_pkg_version_minor() -> u64 {
550 to_parsed(var_or_panic("CARGO_PKG_VERSION_MINOR"))
551}
552
553#[track_caller]
555pub fn cargo_pkg_version_patch() -> u64 {
556 to_parsed(var_or_panic("CARGO_PKG_VERSION_PATCH"))
557}
558
559#[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#[track_caller]
567pub fn cargo_pkg_authors() -> Vec<String> {
568 to_strings(var_or_panic("CARGO_PKG_AUTHORS"), ':')
569}
570
571#[track_caller]
573pub fn cargo_pkg_name() -> String {
574 to_string(var_or_panic("CARGO_PKG_NAME"))
575}
576
577#[track_caller]
579pub fn cargo_pkg_description() -> Option<String> {
580 to_opt(var_or_panic("CARGO_PKG_DESCRIPTION")).map(to_string)
581}
582
583#[track_caller]
585pub fn cargo_pkg_homepage() -> Option<String> {
586 to_opt(var_or_panic("CARGO_PKG_HOMEPAGE")).map(to_string)
587}
588
589#[track_caller]
591pub fn cargo_pkg_repository() -> Option<String> {
592 to_opt(var_or_panic("CARGO_PKG_REPOSITORY")).map(to_string)
593}
594
595#[track_caller]
597pub fn cargo_pkg_license() -> Option<String> {
598 to_opt(var_or_panic("CARGO_PKG_LICENSE")).map(to_string)
599}
600
601#[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#[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#[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}