1#[cfg(feature = "cargo_metadata")]
10use self::cargo_builder::SetDependencies;
11use self::cargo_builder::{SetDebug, SetFeatures, SetOptLevel, SetTargetTriple};
12#[cfg(feature = "cargo_metadata")]
13use anyhow::anyhow;
14use anyhow::{Error, Result};
15use bon::Builder;
16#[cfg(feature = "cargo_metadata")]
17use cargo_metadata::{DepKindInfo, DependencyKind, MetadataCommand, Package, PackageId};
18#[cfg(feature = "cargo_metadata")]
19use regex::Regex;
20use std::env;
21#[cfg(feature = "cargo_metadata")]
22use vergen_lib::constants::CARGO_DEPENDENCIES;
23use vergen_lib::{
24 AddEntries, CargoRerunIfChanged, CargoRustcEnvMap, CargoWarning, DefaultConfig, VergenKey,
25 add_default_map_entry, add_map_entry,
26 constants::{CARGO_DEBUG, CARGO_FEATURES, CARGO_OPT_LEVEL, CARGO_TARGET_TRIPLE},
27};
28
29#[derive(Builder, Clone, Copy, Debug, PartialEq)]
115#[allow(clippy::struct_excessive_bools)]
116pub struct Cargo {
117 #[builder(field)]
121 all: bool,
122 #[builder(default = all)]
129 debug: bool,
130 #[builder(default = all)]
137 features: bool,
138 #[builder(default = all)]
145 opt_level: bool,
146 #[builder(default = all)]
153 target_triple: bool,
154 #[cfg(feature = "cargo_metadata")]
161 #[builder(default = all)]
162 dependencies: bool,
163 #[cfg(feature = "cargo_metadata")]
170 #[builder(into)]
171 name_filter: Option<&'static str>,
172 #[cfg(feature = "cargo_metadata")]
179 #[builder(into)]
180 dep_kind_filter: Option<DependencyKind>,
181}
182
183impl<S: cargo_builder::State> CargoBuilder<S> {
184 fn all(mut self) -> Self {
189 self.all = true;
190 self
191 }
192}
193
194impl Cargo {
195 #[must_use]
197 pub fn all_cargo() -> Self {
198 Self::builder().all().build()
199 }
200
201 #[cfg(not(feature = "cargo_metadata"))]
203 pub fn all_cargo_builder() -> CargoBuilder<SetTargetTriple<SetOptLevel<SetFeatures<SetDebug>>>>
204 {
205 Self::builder()
206 .debug(true)
207 .features(true)
208 .opt_level(true)
209 .target_triple(true)
210 }
211
212 #[cfg(feature = "cargo_metadata")]
214 pub fn all_cargo_builder()
215 -> CargoBuilder<SetDependencies<SetTargetTriple<SetOptLevel<SetFeatures<SetDebug>>>>> {
216 Self::builder()
217 .debug(true)
218 .features(true)
219 .opt_level(true)
220 .target_triple(true)
221 .dependencies(true)
222 }
223
224 #[cfg(not(feature = "cargo_metadata"))]
225 fn any(self) -> bool {
226 self.debug || self.features || self.opt_level || self.target_triple
227 }
228
229 #[cfg(feature = "cargo_metadata")]
230 fn any(self) -> bool {
231 self.debug || self.features || self.opt_level || self.target_triple || self.dependencies
232 }
233
234 fn is_cargo_feature(var: (String, String)) -> Option<String> {
235 let (k, _) = var;
236 if k.starts_with("CARGO_FEATURE_") {
237 Some(k.replace("CARGO_FEATURE_", "").to_lowercase())
238 } else {
239 None
240 }
241 }
242
243 #[cfg(feature = "cargo_metadata")]
244 fn get_dependencies(
245 name_filter: Option<&'static str>,
246 dep_kind_filter: Option<DependencyKind>,
247 ) -> Result<String> {
248 let metadata = MetadataCommand::new().exec()?;
249 let resolved_crates = metadata.resolve.ok_or_else(|| anyhow!("No resolve"))?;
250 let root_id = resolved_crates.root.ok_or_else(|| anyhow!("No root id"))?;
251 let root = resolved_crates
252 .nodes
253 .into_iter()
254 .find(|node| node.id == root_id)
255 .ok_or_else(|| anyhow!("No root node"))?;
256 let package_ids: Vec<(PackageId, Vec<DepKindInfo>)> = root
257 .deps
258 .into_iter()
259 .map(|node_dep| (node_dep.pkg, node_dep.dep_kinds))
260 .collect();
261
262 let packages: Vec<(&Package, &Vec<DepKindInfo>)> = package_ids
263 .iter()
264 .filter_map(|(package_id, dep_kinds)| {
265 metadata
266 .packages
267 .iter()
268 .find(|&package| package.id == *package_id)
269 .map(|package| (package, dep_kinds))
270 })
271 .collect();
272
273 let regex_opt = if let Some(name_regex) = name_filter {
274 Regex::new(name_regex).ok()
275 } else {
276 None
277 };
278 let results: Vec<String> = packages
279 .iter()
280 .filter_map(|(package, dep_kind_info)| {
281 if let Some(regex) = ®ex_opt {
282 if regex.is_match(&package.name) {
283 Some((package, dep_kind_info))
284 } else {
285 None
286 }
287 } else {
288 Some((package, dep_kind_info))
289 }
290 })
291 .filter_map(|(package, dep_kind_info)| {
292 if let Some(dep_kind_filter) = dep_kind_filter {
293 let kinds: Vec<DependencyKind> = dep_kind_info
294 .iter()
295 .map(|dep_kind_info| dep_kind_info.kind)
296 .collect();
297 if kinds.contains(&dep_kind_filter) {
298 Some(package)
299 } else {
300 None
301 }
302 } else {
303 Some(package)
304 }
305 })
306 .map(|package| format!("{} {}", package.name, package.version))
307 .collect();
308 Ok(results.join(","))
309 }
310
311 #[cfg(feature = "cargo_metadata")]
318 pub fn set_name_filter(&mut self, val: Option<&'static str>) -> &mut Self {
319 self.name_filter = val;
320 self
321 }
322 #[cfg(feature = "cargo_metadata")]
329 pub fn set_dep_kind_filter(&mut self, val: Option<DependencyKind>) -> &mut Self {
330 self.dep_kind_filter = val;
331 self
332 }
333
334 #[cfg(not(feature = "cargo_metadata"))]
335 #[allow(
336 clippy::unnecessary_wraps,
337 clippy::unused_self,
338 clippy::trivially_copy_pass_by_ref
339 )]
340 fn add_dependencies(&self, _cargo_rustc_env: &mut CargoRustcEnvMap) -> Result<()> {
341 Ok(())
342 }
343
344 #[cfg(feature = "cargo_metadata")]
345 fn add_dependencies(&self, cargo_rustc_env: &mut CargoRustcEnvMap) -> Result<()> {
346 if self.dependencies {
347 if let Ok(value) = env::var(CARGO_DEPENDENCIES) {
348 add_map_entry(VergenKey::CargoDependencies, value, cargo_rustc_env);
349 } else {
350 let value = Self::get_dependencies(self.name_filter, self.dep_kind_filter)?;
351 if !value.is_empty() {
352 add_map_entry(VergenKey::CargoDependencies, value, cargo_rustc_env);
353 }
354 }
355 }
356 Ok(())
357 }
358
359 #[cfg(not(feature = "cargo_metadata"))]
360 #[allow(clippy::unused_self, clippy::trivially_copy_pass_by_ref)]
361 fn add_default_dependencies(
362 &self,
363 _config: &DefaultConfig,
364 _cargo_rustc_env_map: &mut CargoRustcEnvMap,
365 _cargo_warning: &mut CargoWarning,
366 ) {
367 }
368
369 #[cfg(feature = "cargo_metadata")]
370 fn add_default_dependencies(
371 &self,
372 config: &DefaultConfig,
373 cargo_rustc_env_map: &mut CargoRustcEnvMap,
374 cargo_warning: &mut CargoWarning,
375 ) {
376 if self.dependencies {
377 add_default_map_entry(
378 *config.idempotent(),
379 VergenKey::CargoDependencies,
380 cargo_rustc_env_map,
381 cargo_warning,
382 );
383 }
384 }
385}
386
387impl AddEntries for Cargo {
388 fn add_map_entries(
389 &self,
390 _idempotent: bool,
391 cargo_rustc_env: &mut CargoRustcEnvMap,
392 _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
393 _cargo_warning: &mut CargoWarning,
394 ) -> Result<()> {
395 if self.any() {
396 if self.debug {
397 if let Ok(value) = env::var(CARGO_DEBUG) {
398 add_map_entry(VergenKey::CargoDebug, value, cargo_rustc_env);
399 } else {
400 add_map_entry(VergenKey::CargoDebug, env::var("DEBUG")?, cargo_rustc_env);
401 }
402 }
403
404 if self.features {
405 if let Ok(value) = env::var(CARGO_FEATURES) {
406 add_map_entry(VergenKey::CargoFeatures, value, cargo_rustc_env);
407 } else {
408 let features: Vec<String> =
409 env::vars().filter_map(Self::is_cargo_feature).collect();
410 let feature_str = features.as_slice().join(",");
411 add_map_entry(VergenKey::CargoFeatures, feature_str, cargo_rustc_env);
412 }
413 }
414
415 if self.opt_level {
416 if let Ok(value) = env::var(CARGO_OPT_LEVEL) {
417 add_map_entry(VergenKey::CargoOptLevel, value, cargo_rustc_env);
418 } else {
419 add_map_entry(
420 VergenKey::CargoOptLevel,
421 env::var("OPT_LEVEL")?,
422 cargo_rustc_env,
423 );
424 }
425 }
426
427 if self.target_triple {
428 if let Ok(value) = env::var(CARGO_TARGET_TRIPLE) {
429 add_map_entry(VergenKey::CargoTargetTriple, value, cargo_rustc_env);
430 } else {
431 add_map_entry(
432 VergenKey::CargoTargetTriple,
433 env::var("TARGET")?,
434 cargo_rustc_env,
435 );
436 }
437 }
438
439 self.add_dependencies(cargo_rustc_env)?;
440 }
441 Ok(())
442 }
443
444 fn add_default_entries(
445 &self,
446 config: &DefaultConfig,
447 cargo_rustc_env_map: &mut CargoRustcEnvMap,
448 _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
449 cargo_warning: &mut CargoWarning,
450 ) -> Result<()> {
451 if *config.fail_on_error() {
452 let error = Error::msg(format!("{:?}", config.error()));
453 Err(error)
454 } else {
455 if self.debug {
456 add_default_map_entry(
457 *config.idempotent(),
458 VergenKey::CargoDebug,
459 cargo_rustc_env_map,
460 cargo_warning,
461 );
462 }
463 if self.features {
464 add_default_map_entry(
465 *config.idempotent(),
466 VergenKey::CargoFeatures,
467 cargo_rustc_env_map,
468 cargo_warning,
469 );
470 }
471 if self.opt_level {
472 add_default_map_entry(
473 *config.idempotent(),
474 VergenKey::CargoOptLevel,
475 cargo_rustc_env_map,
476 cargo_warning,
477 );
478 }
479 if self.target_triple {
480 add_default_map_entry(
481 *config.idempotent(),
482 VergenKey::CargoTargetTriple,
483 cargo_rustc_env_map,
484 cargo_warning,
485 );
486 }
487 self.add_default_dependencies(config, cargo_rustc_env_map, cargo_warning);
488 Ok(())
489 }
490 }
491}
492
493#[cfg(test)]
494mod test {
495 use super::Cargo;
496 use crate::Emitter;
497 use anyhow::Result;
498 use serial_test::serial;
499 use std::io::Write;
500 use test_util::{with_cargo_vars, with_cargo_vars_ext};
501 use vergen_lib::count_idempotent;
502
503 #[test]
504 #[serial]
505 #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
506 fn cargo_clone_works() -> Result<()> {
507 let cargo = Cargo::all_cargo();
508 let another = cargo.clone();
509 assert_eq!(another, cargo);
510 Ok(())
511 }
512
513 #[test]
514 #[serial]
515 fn cargo_debug_works() -> Result<()> {
516 let cargo = Cargo::all_cargo();
517 let mut buf = vec![];
518 write!(buf, "{cargo:?}")?;
519 assert!(!buf.is_empty());
520 Ok(())
521 }
522
523 #[test]
524 #[serial]
525 fn cargo_default() -> Result<()> {
526 let cargo = Cargo::builder().build();
527 let emitter = Emitter::default().add_instructions(&cargo)?.test_emit();
528 assert_eq!(0, emitter.cargo_rustc_env_map().len());
529 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
530 assert_eq!(0, emitter.cargo_warning().len());
531 Ok(())
532 }
533
534 #[test]
535 #[serial]
536 fn all_idempotent() {
537 let result = with_cargo_vars(|| {
538 let cargo = Cargo::all_cargo();
539 let config = Emitter::default()
540 .idempotent()
541 .add_instructions(&cargo)?
542 .test_emit();
543 #[cfg(feature = "cargo_metadata")]
544 assert_eq!(5, config.cargo_rustc_env_map().len());
545 #[cfg(not(feature = "cargo_metadata"))]
546 assert_eq!(4, config.cargo_rustc_env_map().len());
547 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
548 assert_eq!(0, config.cargo_warning().len());
549 Ok(())
550 });
551 assert!(result.is_ok());
552 }
553
554 #[test]
555 #[serial]
556 fn all() {
557 let result = with_cargo_vars(|| {
558 let cargo = Cargo::all_cargo();
559 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
560 #[cfg(feature = "cargo_metadata")]
561 assert_eq!(5, config.cargo_rustc_env_map().len());
562 #[cfg(not(feature = "cargo_metadata"))]
563 assert_eq!(4, config.cargo_rustc_env_map().len());
564 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
565 assert_eq!(0, config.cargo_warning().len());
566 Ok(())
567 });
568 assert!(result.is_ok());
569 }
570
571 #[test]
572 #[serial]
573 fn debug() {
574 let result = with_cargo_vars(|| {
575 let cargo = Cargo::builder().debug(true).build();
576 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
577 assert_eq!(1, config.cargo_rustc_env_map().len());
578 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
579 assert_eq!(0, config.cargo_warning().len());
580 Ok(())
581 });
582 assert!(result.is_ok());
583 }
584
585 #[test]
586 #[serial]
587 fn features() {
588 let result = with_cargo_vars(|| {
589 let cargo = Cargo::builder().features(true).build();
590 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
591 assert_eq!(1, config.cargo_rustc_env_map().len());
592 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
593 assert_eq!(0, config.cargo_warning().len());
594 Ok(())
595 });
596 assert!(result.is_ok());
597 }
598
599 #[test]
600 #[serial]
601 fn opt_level() {
602 let result = with_cargo_vars(|| {
603 let cargo = Cargo::builder().opt_level(true).build();
604 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
605 assert_eq!(1, config.cargo_rustc_env_map().len());
606 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
607 assert_eq!(0, config.cargo_warning().len());
608 Ok(())
609 });
610 assert!(result.is_ok());
611 }
612
613 #[test]
614 #[serial]
615 fn target_triple() {
616 let result = with_cargo_vars(|| {
617 let cargo = Cargo::builder().target_triple(true).build();
618 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
619 assert_eq!(1, config.cargo_rustc_env_map().len());
620 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
621 assert_eq!(0, config.cargo_warning().len());
622 Ok(())
623 });
624 assert!(result.is_ok());
625 }
626
627 #[test]
628 #[serial]
629 #[cfg(feature = "cargo_metadata")]
630 fn dependencies() {
631 let result = with_cargo_vars(|| {
632 let cargo = Cargo::builder()
633 .dependencies(true)
634 .name_filter("anyhow")
635 .build();
636 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
637 assert_eq!(1, config.cargo_rustc_env_map().len());
638 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
639 assert_eq!(0, config.cargo_warning().len());
640 Ok(())
641 });
642 assert!(result.is_ok());
643 }
644
645 #[test]
646 #[serial]
647 #[cfg(feature = "cargo_metadata")]
648 fn dependencies_bad_name_filter() {
649 let result = with_cargo_vars(|| {
650 let cargo = Cargo::builder().dependencies(true).name_filter("(").build();
651 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
652 assert_eq!(1, config.cargo_rustc_env_map().len());
653 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
654 assert_eq!(0, config.cargo_warning().len());
655 Ok(())
656 });
657 assert!(result.is_ok());
658 }
659
660 #[test]
661 #[serial]
662 fn bad_env_fails() -> Result<()> {
663 let cargo = Cargo::all_cargo();
664 assert!(
665 Emitter::default()
666 .fail_on_error()
667 .add_instructions(&cargo)
668 .is_err()
669 );
670 Ok(())
671 }
672
673 #[test]
674 #[serial]
675 fn bad_env_emits_warnings() -> Result<()> {
676 let cargo = Cargo::all_cargo();
677 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
678 assert_eq!(0, config.cargo_rustc_env_map().len());
679 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
680 #[cfg(feature = "cargo_metadata")]
681 assert_eq!(5, config.cargo_warning().len());
682 #[cfg(not(feature = "cargo_metadata"))]
683 assert_eq!(4, config.cargo_warning().len());
684 Ok(())
685 }
686
687 #[test]
688 #[serial]
689 fn bad_env_emits_idempotent() -> Result<()> {
690 let cargo = Cargo::all_cargo();
691 let config = Emitter::default()
692 .idempotent()
693 .add_instructions(&cargo)?
694 .test_emit();
695 #[cfg(feature = "cargo_metadata")]
696 assert_eq!(5, config.cargo_rustc_env_map().len());
697 #[cfg(not(feature = "cargo_metadata"))]
698 assert_eq!(4, config.cargo_rustc_env_map().len());
699 #[cfg(feature = "cargo_metadata")]
700 assert_eq!(5, count_idempotent(config.cargo_rustc_env_map()));
701 #[cfg(not(feature = "cargo_metadata"))]
702 assert_eq!(4, count_idempotent(config.cargo_rustc_env_map()));
703 #[cfg(feature = "cargo_metadata")]
704 assert_eq!(5, config.cargo_warning().len());
705 #[cfg(not(feature = "cargo_metadata"))]
706 assert_eq!(4, config.cargo_warning().len());
707 Ok(())
708 }
709
710 #[test]
711 #[serial]
712 fn cargo_debug_override_works() {
713 let result = with_cargo_vars_ext(
714 &[("VERGEN_CARGO_DEBUG", Some("this is a bad date"))],
715 || {
716 let mut stdout_buf = vec![];
717 let cargo = Cargo::all_cargo();
718 assert!(
719 Emitter::default()
720 .add_instructions(&cargo)?
721 .emit_to(&mut stdout_buf)
722 .is_ok()
723 );
724 let output = String::from_utf8_lossy(&stdout_buf);
725 assert!(output.contains("cargo:rustc-env=VERGEN_CARGO_DEBUG=this is a bad date"));
726 Ok(())
727 },
728 );
729 assert!(result.is_ok());
730 }
731
732 #[test]
733 #[serial]
734 fn cargo_features_override_works() {
735 let result = with_cargo_vars_ext(
736 &[("VERGEN_CARGO_FEATURES", Some("this is a bad date"))],
737 || {
738 let mut stdout_buf = vec![];
739 let cargo = Cargo::all_cargo();
740 assert!(
741 Emitter::default()
742 .add_instructions(&cargo)?
743 .emit_to(&mut stdout_buf)
744 .is_ok()
745 );
746 let output = String::from_utf8_lossy(&stdout_buf);
747 assert!(
748 output.contains("cargo:rustc-env=VERGEN_CARGO_FEATURES=this is a bad date")
749 );
750 Ok(())
751 },
752 );
753 assert!(result.is_ok());
754 }
755
756 #[test]
757 #[serial]
758 fn cargo_opt_level_override_works() {
759 let result = with_cargo_vars_ext(
760 &[("VERGEN_CARGO_OPT_LEVEL", Some("this is a bad date"))],
761 || {
762 let mut stdout_buf = vec![];
763 let cargo = Cargo::all_cargo();
764 assert!(
765 Emitter::default()
766 .add_instructions(&cargo)?
767 .emit_to(&mut stdout_buf)
768 .is_ok()
769 );
770 let output = String::from_utf8_lossy(&stdout_buf);
771 assert!(
772 output.contains("cargo:rustc-env=VERGEN_CARGO_OPT_LEVEL=this is a bad date")
773 );
774 Ok(())
775 },
776 );
777 assert!(result.is_ok());
778 }
779
780 #[test]
781 #[serial]
782 fn cargo_target_triple_override_works() {
783 let result = with_cargo_vars_ext(
784 &[("VERGEN_CARGO_TARGET_TRIPLE", Some("this is a bad date"))],
785 || {
786 let mut stdout_buf = vec![];
787 let cargo = Cargo::all_cargo();
788 assert!(
789 Emitter::default()
790 .add_instructions(&cargo)?
791 .emit_to(&mut stdout_buf)
792 .is_ok()
793 );
794 let output = String::from_utf8_lossy(&stdout_buf);
795 assert!(
796 output
797 .contains("cargo:rustc-env=VERGEN_CARGO_TARGET_TRIPLE=this is a bad date")
798 );
799 Ok(())
800 },
801 );
802 assert!(result.is_ok());
803 }
804
805 #[test]
806 #[serial]
807 #[cfg(feature = "cargo_metadata")]
808 fn cargo_dependencies_override_works() {
809 let result = with_cargo_vars_ext(
810 &[("VERGEN_CARGO_DEPENDENCIES", Some("this is a bad date"))],
811 || {
812 let mut stdout_buf = vec![];
813 let cargo = Cargo::all_cargo();
814 assert!(
815 Emitter::default()
816 .add_instructions(&cargo)?
817 .emit_to(&mut stdout_buf)
818 .is_ok()
819 );
820 let output = String::from_utf8_lossy(&stdout_buf);
821 assert!(
822 output.contains("cargo:rustc-env=VERGEN_CARGO_DEPENDENCIES=this is a bad date")
823 );
824 Ok(())
825 },
826 );
827 assert!(result.is_ok());
828 }
829}