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