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