1use anyhow::{Error, Result};
10use bon::Builder;
11use rustc_version::{Channel, VersionMeta, version_meta};
12use std::env;
13use vergen_lib::{
14 AddEntries, CargoRerunIfChanged, CargoRustcEnvMap, CargoWarning, DefaultConfig, VergenKey,
15 add_default_map_entry, add_map_entry,
16 constants::{
17 RUSTC_CHANNEL_NAME, RUSTC_COMMIT_DATE, RUSTC_COMMIT_HASH, RUSTC_HOST_TRIPLE_NAME,
18 RUSTC_LLVM_VERSION, RUSTC_SEMVER_NAME,
19 },
20};
21
22#[derive(Builder, Clone, Copy, Debug, PartialEq)]
88#[allow(clippy::struct_excessive_bools)]
89pub struct Rustc {
90 #[builder(field)]
94 all: bool,
95 #[cfg(test)]
96 #[builder(field)]
97 str_to_test: Option<&'static str>,
98 #[builder(default = all)]
100 channel: bool,
101 #[builder(default = all)]
103 commit_date: bool,
104 #[builder(default = all)]
106 commit_hash: bool,
107 #[builder(default = all)]
109 host_triple: bool,
110 #[builder(default = all)]
112 llvm_version: bool,
113 #[builder(default = all)]
115 semver: bool,
116}
117
118impl<S: rustc_builder::State> RustcBuilder<S> {
119 fn all(mut self) -> Self {
124 self.all = true;
125 self
126 }
127}
128
129impl Rustc {
130 #[must_use]
132 pub fn all_rustc() -> Self {
133 Self::builder().all().build()
134 }
135
136 fn any(self) -> bool {
137 self.channel
138 || self.commit_date
139 || self.commit_hash
140 || self.host_triple
141 || self.llvm_version
142 || self.semver
143 }
144
145 #[cfg(not(test))]
146 fn add_rustc_map_entries(
147 self,
148 cargo_rustc_env: &mut CargoRustcEnvMap,
149 cargo_warning: &mut CargoWarning,
150 ) -> Result<()> {
151 self.add_rustc_to_map(version_meta(), cargo_rustc_env, cargo_warning)
152 }
153
154 #[cfg(test)]
155 fn add_rustc_map_entries(
156 self,
157 cargo_rustc_env: &mut CargoRustcEnvMap,
158 cargo_warning: &mut CargoWarning,
159 ) -> Result<()> {
160 use rustc_version::version_meta_for;
161
162 let vm = if let Some(rustc_str) = self.str_to_test {
163 version_meta_for(rustc_str)
164 } else {
165 version_meta()
166 };
167 self.add_rustc_to_map(vm, cargo_rustc_env, cargo_warning)
168 }
169
170 #[allow(clippy::too_many_lines)]
171 fn add_rustc_to_map(
172 self,
173 rustc_res: std::result::Result<VersionMeta, rustc_version::Error>,
174 cargo_rustc_env: &mut CargoRustcEnvMap,
175 cargo_warning: &mut CargoWarning,
176 ) -> Result<()> {
177 let rustc = rustc_res?;
178
179 if self.channel {
180 if let Ok(_value) = env::var(RUSTC_CHANNEL_NAME) {
181 add_default_map_entry(
182 false,
183 VergenKey::RustcChannel,
184 cargo_rustc_env,
185 cargo_warning,
186 );
187 } else {
188 let channel = match rustc.channel {
189 Channel::Dev => "dev",
190 Channel::Nightly => "nightly",
191 Channel::Beta => "beta",
192 Channel::Stable => "stable",
193 };
194 add_map_entry(VergenKey::RustcChannel, channel, cargo_rustc_env);
195 }
196 }
197
198 if self.commit_date {
199 if let Ok(_value) = env::var(RUSTC_COMMIT_DATE) {
200 add_default_map_entry(
201 false,
202 VergenKey::RustcCommitDate,
203 cargo_rustc_env,
204 cargo_warning,
205 );
206 } else if let Some(commit_date) = rustc.commit_date {
207 add_map_entry(VergenKey::RustcCommitDate, commit_date, cargo_rustc_env);
208 } else {
209 add_default_map_entry(
210 false,
211 VergenKey::RustcCommitDate,
212 cargo_rustc_env,
213 cargo_warning,
214 );
215 }
216 }
217
218 if self.commit_hash {
219 if let Ok(_value) = env::var(RUSTC_COMMIT_HASH) {
220 add_default_map_entry(
221 false,
222 VergenKey::RustcCommitHash,
223 cargo_rustc_env,
224 cargo_warning,
225 );
226 } else if let Some(commit_hash) = rustc.commit_hash {
227 add_map_entry(VergenKey::RustcCommitHash, commit_hash, cargo_rustc_env);
228 } else {
229 add_default_map_entry(
230 false,
231 VergenKey::RustcCommitHash,
232 cargo_rustc_env,
233 cargo_warning,
234 );
235 }
236 }
237
238 if self.host_triple {
239 if let Ok(_value) = env::var(RUSTC_HOST_TRIPLE_NAME) {
240 add_default_map_entry(
241 false,
242 VergenKey::RustcHostTriple,
243 cargo_rustc_env,
244 cargo_warning,
245 );
246 } else {
247 add_map_entry(VergenKey::RustcHostTriple, rustc.host, cargo_rustc_env);
248 }
249 }
250
251 if self.llvm_version {
252 if let Ok(_value) = env::var(RUSTC_LLVM_VERSION) {
253 add_default_map_entry(
254 false,
255 VergenKey::RustcLlvmVersion,
256 cargo_rustc_env,
257 cargo_warning,
258 );
259 } else if let Some(llvm_version) = rustc.llvm_version {
260 add_map_entry(
261 VergenKey::RustcLlvmVersion,
262 format!("{llvm_version}"),
263 cargo_rustc_env,
264 );
265 } else {
266 add_default_map_entry(
267 false,
268 VergenKey::RustcLlvmVersion,
269 cargo_rustc_env,
270 cargo_warning,
271 );
272 }
273 }
274
275 if self.semver {
276 if let Ok(_value) = env::var(RUSTC_SEMVER_NAME) {
277 add_default_map_entry(
278 false,
279 VergenKey::RustcSemver,
280 cargo_rustc_env,
281 cargo_warning,
282 );
283 } else {
284 add_map_entry(
285 VergenKey::RustcSemver,
286 format!("{}", rustc.semver),
287 cargo_rustc_env,
288 );
289 }
290 }
291
292 Ok(())
293 }
294
295 #[cfg(test)]
296 fn with_rustc_str(&mut self, rustc_str: &'static str) -> &mut Self {
297 self.str_to_test = Some(rustc_str);
298 self
299 }
300}
301
302impl AddEntries for Rustc {
303 fn add_map_entries(
304 &self,
305 _idempotent: bool,
306 cargo_rustc_env: &mut CargoRustcEnvMap,
307 _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
308 cargo_warning: &mut CargoWarning,
309 ) -> Result<()> {
310 if self.any() {
311 self.add_rustc_map_entries(cargo_rustc_env, cargo_warning)
312 } else {
313 Ok(())
314 }
315 }
316
317 fn add_default_entries(
318 &self,
319 config: &DefaultConfig,
320 cargo_rustc_env_map: &mut CargoRustcEnvMap,
321 _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
322 cargo_warning: &mut CargoWarning,
323 ) -> Result<()> {
324 if *config.fail_on_error() {
325 let error = Error::msg(format!("{:?}", config.error()));
326 Err(error)
327 } else {
328 if self.channel {
329 add_default_map_entry(
330 false,
331 VergenKey::RustcChannel,
332 cargo_rustc_env_map,
333 cargo_warning,
334 );
335 }
336 if self.commit_date {
337 add_default_map_entry(
338 false,
339 VergenKey::RustcCommitDate,
340 cargo_rustc_env_map,
341 cargo_warning,
342 );
343 }
344 if self.commit_hash {
345 add_default_map_entry(
346 false,
347 VergenKey::RustcCommitHash,
348 cargo_rustc_env_map,
349 cargo_warning,
350 );
351 }
352 if self.host_triple {
353 add_default_map_entry(
354 false,
355 VergenKey::RustcHostTriple,
356 cargo_rustc_env_map,
357 cargo_warning,
358 );
359 }
360 if self.llvm_version {
361 add_default_map_entry(
362 false,
363 VergenKey::RustcLlvmVersion,
364 cargo_rustc_env_map,
365 cargo_warning,
366 );
367 }
368 if self.semver {
369 add_default_map_entry(
370 false,
371 VergenKey::RustcSemver,
372 cargo_rustc_env_map,
373 cargo_warning,
374 );
375 }
376
377 Ok(())
378 }
379 }
380}
381
382#[cfg(test)]
383mod test {
384 use super::Rustc;
385 use crate::Emitter;
386 use anyhow::Result;
387 use serial_test::serial;
388 use std::io::Write;
389 use temp_env::with_var;
390 use vergen_lib::count_idempotent;
391
392 #[test]
393 #[serial]
394 #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
395 fn rustc_clone_works() {
396 let rustc = Rustc::all_rustc();
397 let another = rustc.clone();
398 assert_eq!(another, rustc);
399 }
400
401 #[test]
402 #[serial]
403 fn rustc_debug_works() -> Result<()> {
404 let rustc = Rustc::all_rustc();
405 let mut buf = vec![];
406 write!(buf, "{rustc:?}")?;
407 assert!(!buf.is_empty());
408 Ok(())
409 }
410
411 #[test]
412 #[serial]
413 fn rustc_default() -> Result<()> {
414 let rustc = Rustc::builder().build();
415 let emitter = Emitter::default().add_instructions(&rustc)?.test_emit();
416 assert_eq!(0, emitter.cargo_rustc_env_map().len());
417 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
418 assert_eq!(0, emitter.cargo_warning().len());
419 Ok(())
420 }
421
422 #[test]
423 #[serial]
424 fn rustc_all_idempotent() -> Result<()> {
425 let rustc = Rustc::all_rustc();
426 let config = Emitter::default()
427 .idempotent()
428 .add_instructions(&rustc)?
429 .test_emit();
430 assert_eq!(6, config.cargo_rustc_env_map().len());
431 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
432 assert_eq!(0, config.cargo_warning().len());
433 Ok(())
434 }
435
436 #[test]
437 #[serial]
438 fn rustc_all() -> Result<()> {
439 let rustc = Rustc::all_rustc();
440 let config = Emitter::default().add_instructions(&rustc)?.test_emit();
441 assert_eq!(6, config.cargo_rustc_env_map().len());
442 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
443 assert_eq!(0, config.cargo_warning().len());
444 Ok(())
445 }
446
447 #[test]
448 #[serial]
449 fn rustc_commit_date() -> Result<()> {
450 let rustc = Rustc::builder().commit_date(true).build();
451 let config = Emitter::default().add_instructions(&rustc)?.test_emit();
452 assert_eq!(1, config.cargo_rustc_env_map().len());
453 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
454 assert_eq!(0, config.cargo_warning().len());
455 Ok(())
456 }
457
458 #[test]
459 #[serial]
460 fn rustc_commit_hash() -> Result<()> {
461 let rustc = Rustc::builder().commit_hash(true).build();
462 let config = Emitter::default().add_instructions(&rustc)?.test_emit();
463 assert_eq!(1, config.cargo_rustc_env_map().len());
464 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
465 assert_eq!(0, config.cargo_warning().len());
466 Ok(())
467 }
468
469 #[test]
470 #[serial]
471 fn rustc_host_triple() -> Result<()> {
472 let rustc = Rustc::builder().host_triple(true).build();
473 let config = Emitter::default().add_instructions(&rustc)?.test_emit();
474 assert_eq!(1, config.cargo_rustc_env_map().len());
475 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
476 assert_eq!(0, config.cargo_warning().len());
477 Ok(())
478 }
479
480 #[test]
481 #[serial]
482 fn rustc_llvm_version() -> Result<()> {
483 let rustc = Rustc::builder().llvm_version(true).build();
484 let config = Emitter::default().add_instructions(&rustc)?.test_emit();
485 assert_eq!(1, config.cargo_rustc_env_map().len());
486 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
487 assert_eq!(0, config.cargo_warning().len());
488 Ok(())
489 }
490
491 #[test]
492 #[serial]
493 fn rustc_semver() -> Result<()> {
494 let rustc = Rustc::builder().semver(true).build();
495 let config = Emitter::default().add_instructions(&rustc)?.test_emit();
496 assert_eq!(1, config.cargo_rustc_env_map().len());
497 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
498 assert_eq!(0, config.cargo_warning().len());
499 Ok(())
500 }
501
502 const NO_LLVM: &str = r"rustc 1.68.0-nightly (270c94e48 2022-12-28)
503binary: rustc
504commit-hash: 270c94e484e19764a2832ef918c95224eb3f17c7
505commit-date: 2022-12-28
506host: x86_64-unknown-linux-gnu
507release: 1.68.0-nightly
508 ";
509
510 #[test]
511 #[serial]
512 fn no_llvm_in_rustc() -> Result<()> {
513 let mut rustc = Rustc::all_rustc();
514 let _ = rustc.with_rustc_str(NO_LLVM);
515 let emitter = Emitter::default()
516 .fail_on_error()
517 .add_instructions(&rustc)?
518 .test_emit();
519 assert_eq!(5, emitter.cargo_rustc_env_map().len());
520 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
521 assert_eq!(1, emitter.cargo_warning().len());
522 Ok(())
523 }
524
525 const DEV_BUILD: &str = r"rustc 1.68.0-nightly (270c94e48 2022-12-28)
526binary: rustc
527commit-hash: 270c94e484e19764a2832ef918c95224eb3f17c7
528commit-date: 2022-12-28
529host: x86_64-unknown-linux-gnu
530release: 1.68.0-dev
531LLVM version: 15.0.6
532 ";
533
534 #[test]
535 #[serial]
536 fn rustc_dev_build() -> Result<()> {
537 let mut rustc = Rustc::all_rustc();
538 let _ = rustc.with_rustc_str(DEV_BUILD);
539 let emitter = Emitter::default()
540 .fail_on_error()
541 .add_instructions(&rustc)?
542 .test_emit();
543 assert_eq!(6, emitter.cargo_rustc_env_map().len());
544 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
545 assert_eq!(0, emitter.cargo_warning().len());
546 Ok(())
547 }
548
549 const UNKNOWN_BITS: &str = r"rustc 1.68.0-nightly (270c94e48 2022-12-28)
550binary: rustc
551commit-hash: unknown
552commit-date: unknown
553host: x86_64-unknown-linux-gnu
554release: 1.68.0-dev
555LLVM version: 15.0.6
556 ";
557
558 #[test]
559 #[serial]
560 fn rustc_unknown_bits() -> Result<()> {
561 let mut rustc = Rustc::all_rustc();
562 let _ = rustc.with_rustc_str(UNKNOWN_BITS);
563 let emitter = Emitter::default()
564 .fail_on_error()
565 .add_instructions(&rustc)?
566 .test_emit();
567 assert_eq!(4, emitter.cargo_rustc_env_map().len());
568 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
569 assert_eq!(2, emitter.cargo_warning().len());
570 Ok(())
571 }
572
573 #[test]
574 #[serial]
575 fn rustc_fails_on_bad_input() -> Result<()> {
576 let mut rustc = Rustc::all_rustc();
577 let _ = rustc.with_rustc_str("a_bad_rustcvv_string");
578 assert!(
579 Emitter::default()
580 .fail_on_error()
581 .add_instructions(&rustc)
582 .is_err()
583 );
584 Ok(())
585 }
586
587 #[test]
588 #[serial]
589 fn rustc_warnings_on_bad_input() -> Result<()> {
590 let mut rustc = Rustc::all_rustc();
591 let _ = rustc.with_rustc_str("a_bad_rustcvv_string");
592 let emitter = Emitter::default().add_instructions(&rustc)?.test_emit();
593 assert_eq!(0, emitter.cargo_rustc_env_map().len());
594 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
595 assert_eq!(6, emitter.cargo_warning().len());
596 Ok(())
597 }
598
599 #[test]
600 #[serial]
601 fn rustc_channel_override_works() {
602 with_var("VERGEN_RUSTC_CHANNEL", Some("this is a bad date"), || {
603 let result = || -> Result<()> {
604 let mut stdout_buf = vec![];
605 let rustc = Rustc::all_rustc();
606 assert!(
607 Emitter::default()
608 .add_instructions(&rustc)?
609 .emit_to(&mut stdout_buf)
610 .is_ok()
611 );
612 let output = String::from_utf8_lossy(&stdout_buf);
613 assert!(output.contains("cargo:rustc-env=VERGEN_RUSTC_CHANNEL=this is a bad date"));
614 Ok(())
615 }();
616 assert!(result.is_ok());
617 });
618 }
619
620 #[test]
621 #[serial]
622 fn rustc_commit_date_override_works() {
623 with_var(
624 "VERGEN_RUSTC_COMMIT_DATE",
625 Some("this is a bad date"),
626 || {
627 let result =
628 || -> Result<()> {
629 let mut stdout_buf = vec![];
630 let rustc = Rustc::all_rustc();
631 assert!(
632 Emitter::default()
633 .add_instructions(&rustc)?
634 .emit_to(&mut stdout_buf)
635 .is_ok()
636 );
637 let output = String::from_utf8_lossy(&stdout_buf);
638 assert!(output.contains(
639 "cargo:rustc-env=VERGEN_RUSTC_COMMIT_DATE=this is a bad date"
640 ));
641 Ok(())
642 }();
643 assert!(result.is_ok());
644 },
645 );
646 }
647
648 #[test]
649 #[serial]
650 fn rustc_commit_hash_override_works() {
651 with_var(
652 "VERGEN_RUSTC_COMMIT_HASH",
653 Some("this is a bad date"),
654 || {
655 let result =
656 || -> Result<()> {
657 let mut stdout_buf = vec![];
658 let rustc = Rustc::all_rustc();
659 assert!(
660 Emitter::default()
661 .add_instructions(&rustc)?
662 .emit_to(&mut stdout_buf)
663 .is_ok()
664 );
665 let output = String::from_utf8_lossy(&stdout_buf);
666 assert!(output.contains(
667 "cargo:rustc-env=VERGEN_RUSTC_COMMIT_HASH=this is a bad date"
668 ));
669 Ok(())
670 }();
671 assert!(result.is_ok());
672 },
673 );
674 }
675
676 #[test]
677 #[serial]
678 fn rustc_host_triple_override_works() {
679 with_var(
680 "VERGEN_RUSTC_HOST_TRIPLE",
681 Some("this is a bad date"),
682 || {
683 let result =
684 || -> Result<()> {
685 let mut stdout_buf = vec![];
686 let rustc = Rustc::all_rustc();
687 assert!(
688 Emitter::default()
689 .add_instructions(&rustc)?
690 .emit_to(&mut stdout_buf)
691 .is_ok()
692 );
693 let output = String::from_utf8_lossy(&stdout_buf);
694 assert!(output.contains(
695 "cargo:rustc-env=VERGEN_RUSTC_HOST_TRIPLE=this is a bad date"
696 ));
697 Ok(())
698 }();
699 assert!(result.is_ok());
700 },
701 );
702 }
703
704 #[test]
705 #[serial]
706 fn rustc_llvm_version_override_works() {
707 with_var(
708 "VERGEN_RUSTC_LLVM_VERSION",
709 Some("this is a bad date"),
710 || {
711 let result =
712 || -> Result<()> {
713 let mut stdout_buf = vec![];
714 let rustc = Rustc::all_rustc();
715 assert!(
716 Emitter::default()
717 .add_instructions(&rustc)?
718 .emit_to(&mut stdout_buf)
719 .is_ok()
720 );
721 let output = String::from_utf8_lossy(&stdout_buf);
722 assert!(output.contains(
723 "cargo:rustc-env=VERGEN_RUSTC_LLVM_VERSION=this is a bad date"
724 ));
725 Ok(())
726 }();
727 assert!(result.is_ok());
728 },
729 );
730 }
731
732 #[test]
733 #[serial]
734 fn rustc_semver_override_works() {
735 with_var("VERGEN_RUSTC_SEMVER", Some("this is a bad date"), || {
736 let result = || -> Result<()> {
737 let mut stdout_buf = vec![];
738 let rustc = Rustc::all_rustc();
739 assert!(
740 Emitter::default()
741 .add_instructions(&rustc)?
742 .emit_to(&mut stdout_buf)
743 .is_ok()
744 );
745 let output = String::from_utf8_lossy(&stdout_buf);
746 assert!(output.contains("cargo:rustc-env=VERGEN_RUSTC_SEMVER=this is a bad date"));
747 Ok(())
748 }();
749 assert!(result.is_ok());
750 });
751 }
752}