1use anyhow::{anyhow, Error, Result};
10use cargo_metadata::{DepKindInfo, DependencyKind, MetadataCommand, Package, PackageId};
11use derive_builder::Builder as DeriveBuilder;
12use regex::Regex;
13use std::env;
14use vergen_lib::{
15 add_default_map_entry, add_map_entry,
16 constants::{
17 CARGO_DEBUG, CARGO_DEPENDENCIES, CARGO_FEATURES, CARGO_OPT_LEVEL, CARGO_TARGET_TRIPLE,
18 },
19 AddEntries, CargoRerunIfChanged, CargoRustcEnvMap, CargoWarning, DefaultConfig, VergenKey,
20};
21
22#[derive(Clone, Copy, Debug, DeriveBuilder, PartialEq)]
108#[allow(clippy::struct_excessive_bools)]
109pub struct Cargo {
110 #[builder(default = "false")]
117 debug: bool,
118 #[builder(default = "false")]
125 features: bool,
126 #[builder(default = "false")]
133 opt_level: bool,
134 #[builder(default = "false")]
141 target_triple: bool,
142 #[builder(default = "false")]
149 dependencies: bool,
150 #[builder(default = "None", setter(into))]
157 name_filter: Option<&'static str>,
158 #[builder(default = "None", setter(into))]
165 dep_kind_filter: Option<DependencyKind>,
166}
167
168impl CargoBuilder {
169 pub fn all_cargo() -> Result<Cargo> {
175 Self::default()
176 .debug(true)
177 .features(true)
178 .opt_level(true)
179 .target_triple(true)
180 .dependencies(true)
181 .build()
182 .map_err(Into::into)
183 }
184}
185
186impl Cargo {
187 fn any(self) -> bool {
188 self.debug || self.features || self.opt_level || self.target_triple || self.dependencies
189 }
190
191 fn is_cargo_feature(var: (String, String)) -> Option<String> {
192 let (k, _) = var;
193 if k.starts_with("CARGO_FEATURE_") {
194 Some(k.replace("CARGO_FEATURE_", "").to_lowercase())
195 } else {
196 None
197 }
198 }
199
200 fn get_dependencies(
201 name_filter: Option<&'static str>,
202 dep_kind_filter: Option<DependencyKind>,
203 ) -> Result<String> {
204 let metadata = MetadataCommand::new().exec()?;
205 let resolved_crates = metadata.resolve.ok_or_else(|| anyhow!("No resolve"))?;
206 let root_id = resolved_crates.root.ok_or_else(|| anyhow!("No root id"))?;
207 let root = resolved_crates
208 .nodes
209 .into_iter()
210 .find(|node| node.id == root_id)
211 .ok_or_else(|| anyhow!("No root node"))?;
212 let package_ids: Vec<(PackageId, Vec<DepKindInfo>)> = root
213 .deps
214 .into_iter()
215 .map(|node_dep| (node_dep.pkg, node_dep.dep_kinds))
216 .collect();
217
218 let packages: Vec<(&Package, &Vec<DepKindInfo>)> = package_ids
219 .iter()
220 .filter_map(|(package_id, dep_kinds)| {
221 metadata
222 .packages
223 .iter()
224 .find(|&package| package.id == *package_id)
225 .map(|package| (package, dep_kinds))
226 })
227 .collect();
228
229 let regex_opt = if let Some(name_regex) = name_filter {
230 Regex::new(name_regex).ok()
231 } else {
232 None
233 };
234 let results: Vec<String> = packages
235 .iter()
236 .filter_map(|(package, dep_kind_info)| {
237 if let Some(regex) = ®ex_opt {
238 if regex.is_match(&package.name) {
239 Some((package, dep_kind_info))
240 } else {
241 None
242 }
243 } else {
244 Some((package, dep_kind_info))
245 }
246 })
247 .filter_map(|(package, dep_kind_info)| {
248 if let Some(dep_kind_filter) = dep_kind_filter {
249 let kinds: Vec<DependencyKind> = dep_kind_info
250 .iter()
251 .map(|dep_kind_info| dep_kind_info.kind)
252 .collect();
253 if kinds.contains(&dep_kind_filter) {
254 Some(package)
255 } else {
256 None
257 }
258 } else {
259 Some(package)
260 }
261 })
262 .map(|package| format!("{} {}", package.name, package.version))
263 .collect();
264 Ok(results.join(","))
265 }
266
267 pub fn set_name_filter(&mut self, val: Option<&'static str>) -> &mut Self {
274 self.name_filter = val;
275 self
276 }
277 pub fn set_dep_kind_filter(&mut self, val: Option<DependencyKind>) -> &mut Self {
284 self.dep_kind_filter = val;
285 self
286 }
287}
288
289impl AddEntries for Cargo {
290 fn add_map_entries(
291 &self,
292 _idempotent: bool,
293 cargo_rustc_env: &mut CargoRustcEnvMap,
294 _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
295 _cargo_warning: &mut CargoWarning,
296 ) -> Result<()> {
297 if self.any() {
298 if self.debug {
299 if let Ok(value) = env::var(CARGO_DEBUG) {
300 add_map_entry(VergenKey::CargoDebug, value, cargo_rustc_env);
301 } else {
302 add_map_entry(VergenKey::CargoDebug, env::var("DEBUG")?, cargo_rustc_env);
303 }
304 }
305
306 if self.features {
307 if let Ok(value) = env::var(CARGO_FEATURES) {
308 add_map_entry(VergenKey::CargoFeatures, value, cargo_rustc_env);
309 } else {
310 let features: Vec<String> =
311 env::vars().filter_map(Self::is_cargo_feature).collect();
312 let feature_str = features.as_slice().join(",");
313 add_map_entry(VergenKey::CargoFeatures, feature_str, cargo_rustc_env);
314 }
315 }
316
317 if self.opt_level {
318 if let Ok(value) = env::var(CARGO_OPT_LEVEL) {
319 add_map_entry(VergenKey::CargoOptLevel, value, cargo_rustc_env);
320 } else {
321 add_map_entry(
322 VergenKey::CargoOptLevel,
323 env::var("OPT_LEVEL")?,
324 cargo_rustc_env,
325 );
326 }
327 }
328
329 if self.target_triple {
330 if let Ok(value) = env::var(CARGO_TARGET_TRIPLE) {
331 add_map_entry(VergenKey::CargoTargetTriple, value, cargo_rustc_env);
332 } else {
333 add_map_entry(
334 VergenKey::CargoTargetTriple,
335 env::var("TARGET")?,
336 cargo_rustc_env,
337 );
338 }
339 }
340
341 if self.dependencies {
342 if let Ok(value) = env::var(CARGO_DEPENDENCIES) {
343 add_map_entry(VergenKey::CargoDependencies, value, cargo_rustc_env);
344 } else {
345 let value = Self::get_dependencies(self.name_filter, self.dep_kind_filter)?;
346 if !value.is_empty() {
347 add_map_entry(VergenKey::CargoDependencies, value, cargo_rustc_env);
348 }
349 }
350 }
351 }
352 Ok(())
353 }
354
355 fn add_default_entries(
356 &self,
357 config: &DefaultConfig,
358 cargo_rustc_env_map: &mut CargoRustcEnvMap,
359 _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
360 cargo_warning: &mut CargoWarning,
361 ) -> Result<()> {
362 if *config.fail_on_error() {
363 let error = Error::msg(format!("{:?}", config.error()));
364 Err(error)
365 } else {
366 if self.debug {
367 add_default_map_entry(VergenKey::CargoDebug, cargo_rustc_env_map, cargo_warning);
368 }
369 if self.features {
370 add_default_map_entry(VergenKey::CargoFeatures, cargo_rustc_env_map, cargo_warning);
371 }
372 if self.opt_level {
373 add_default_map_entry(VergenKey::CargoOptLevel, cargo_rustc_env_map, cargo_warning);
374 }
375 if self.target_triple {
376 add_default_map_entry(
377 VergenKey::CargoTargetTriple,
378 cargo_rustc_env_map,
379 cargo_warning,
380 );
381 }
382 if self.dependencies {
383 add_default_map_entry(
384 VergenKey::CargoDependencies,
385 cargo_rustc_env_map,
386 cargo_warning,
387 );
388 }
389 Ok(())
390 }
391 }
392}
393
394#[cfg(test)]
395mod test {
396 use super::CargoBuilder;
397 use crate::Emitter;
398 use anyhow::Result;
399 use serial_test::serial;
400 use std::io::Write;
401 use test_util::{with_cargo_vars, with_cargo_vars_ext};
402 use vergen_lib::count_idempotent;
403
404 #[test]
405 #[serial]
406 #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
407 fn cargo_clone_works() -> Result<()> {
408 let cargo = CargoBuilder::all_cargo()?;
409 let another = cargo.clone();
410 assert_eq!(another, cargo);
411 Ok(())
412 }
413
414 #[test]
415 #[serial]
416 fn cargo_debug_works() -> Result<()> {
417 let cargo = CargoBuilder::all_cargo()?;
418 let mut buf = vec![];
419 write!(buf, "{cargo:?}")?;
420 assert!(!buf.is_empty());
421 Ok(())
422 }
423
424 #[test]
425 #[serial]
426 fn cargo_default() -> Result<()> {
427 let cargo = CargoBuilder::default().build()?;
428 let emitter = Emitter::default().add_instructions(&cargo)?.test_emit();
429 assert_eq!(0, emitter.cargo_rustc_env_map().len());
430 assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
431 assert_eq!(0, emitter.cargo_warning().len());
432 Ok(())
433 }
434
435 #[test]
436 #[serial]
437 fn all_idempotent() {
438 let result = with_cargo_vars(|| {
439 let cargo = CargoBuilder::all_cargo()?;
440 let config = Emitter::default()
441 .idempotent()
442 .add_instructions(&cargo)?
443 .test_emit();
444 assert_eq!(5, config.cargo_rustc_env_map().len());
445 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
446 assert_eq!(0, config.cargo_warning().len());
447 Ok(())
448 });
449 assert!(result.is_ok());
450 }
451
452 #[test]
453 #[serial]
454 fn all() {
455 let result = with_cargo_vars(|| {
456 let cargo = CargoBuilder::all_cargo()?;
457 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
458 assert_eq!(5, config.cargo_rustc_env_map().len());
459 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
460 assert_eq!(0, config.cargo_warning().len());
461 Ok(())
462 });
463 assert!(result.is_ok());
464 }
465
466 #[test]
467 #[serial]
468 fn debug() {
469 let result = with_cargo_vars(|| {
470 let cargo = CargoBuilder::default().debug(true).build()?;
471 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
472 assert_eq!(1, config.cargo_rustc_env_map().len());
473 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
474 assert_eq!(0, config.cargo_warning().len());
475 Ok(())
476 });
477 assert!(result.is_ok());
478 }
479
480 #[test]
481 #[serial]
482 fn features() {
483 let result = with_cargo_vars(|| {
484 let cargo = CargoBuilder::default().features(true).build()?;
485 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
486 assert_eq!(1, config.cargo_rustc_env_map().len());
487 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
488 assert_eq!(0, config.cargo_warning().len());
489 Ok(())
490 });
491 assert!(result.is_ok());
492 }
493
494 #[test]
495 #[serial]
496 fn opt_level() {
497 let result = with_cargo_vars(|| {
498 let cargo = CargoBuilder::default().opt_level(true).build()?;
499 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
500 assert_eq!(1, config.cargo_rustc_env_map().len());
501 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
502 assert_eq!(0, config.cargo_warning().len());
503 Ok(())
504 });
505 assert!(result.is_ok());
506 }
507
508 #[test]
509 #[serial]
510 fn target_triple() {
511 let result = with_cargo_vars(|| {
512 let cargo = CargoBuilder::default().target_triple(true).build()?;
513 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
514 assert_eq!(1, config.cargo_rustc_env_map().len());
515 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
516 assert_eq!(0, config.cargo_warning().len());
517 Ok(())
518 });
519 assert!(result.is_ok());
520 }
521
522 #[test]
523 #[serial]
524 fn dependencies() {
525 let result = with_cargo_vars(|| {
526 let cargo = CargoBuilder::default()
527 .dependencies(true)
528 .name_filter("anyhow")
529 .build()?;
530 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
531 assert_eq!(1, config.cargo_rustc_env_map().len());
532 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
533 assert_eq!(0, config.cargo_warning().len());
534 Ok(())
535 });
536 assert!(result.is_ok());
537 }
538
539 #[test]
540 #[serial]
541 fn dependencies_bad_name_filter() {
542 let result = with_cargo_vars(|| {
543 let cargo = CargoBuilder::default()
544 .dependencies(true)
545 .name_filter("(")
546 .build()?;
547 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
548 assert_eq!(1, config.cargo_rustc_env_map().len());
549 assert_eq!(0, count_idempotent(config.cargo_rustc_env_map()));
550 assert_eq!(0, config.cargo_warning().len());
551 Ok(())
552 });
553 assert!(result.is_ok());
554 }
555
556 #[test]
557 #[serial]
558 fn bad_env_fails() -> Result<()> {
559 let cargo = CargoBuilder::all_cargo()?;
560 assert!(Emitter::default()
561 .fail_on_error()
562 .add_instructions(&cargo)
563 .is_err());
564 Ok(())
565 }
566
567 #[test]
568 #[serial]
569 fn bad_env_emits_default() -> Result<()> {
570 let cargo = CargoBuilder::all_cargo()?;
571 let config = Emitter::default().add_instructions(&cargo)?.test_emit();
572 assert_eq!(5, config.cargo_rustc_env_map().len());
573 assert_eq!(5, count_idempotent(config.cargo_rustc_env_map()));
574 assert_eq!(5, config.cargo_warning().len());
575 Ok(())
576 }
577
578 #[test]
579 #[serial]
580 fn cargo_debug_override_works() {
581 let result = with_cargo_vars_ext(
582 &[("VERGEN_CARGO_DEBUG", Some("this is a bad date"))],
583 || {
584 let mut stdout_buf = vec![];
585 let cargo = CargoBuilder::all_cargo()?;
586 assert!(Emitter::default()
587 .add_instructions(&cargo)?
588 .emit_to(&mut stdout_buf)
589 .is_ok());
590 let output = String::from_utf8_lossy(&stdout_buf);
591 assert!(output.contains("cargo:rustc-env=VERGEN_CARGO_DEBUG=this is a bad date"));
592 Ok(())
593 },
594 );
595 assert!(result.is_ok());
596 }
597
598 #[test]
599 #[serial]
600 fn cargo_features_override_works() {
601 let result = with_cargo_vars_ext(
602 &[("VERGEN_CARGO_FEATURES", Some("this is a bad date"))],
603 || {
604 let mut stdout_buf = vec![];
605 let cargo = CargoBuilder::all_cargo()?;
606 assert!(Emitter::default()
607 .add_instructions(&cargo)?
608 .emit_to(&mut stdout_buf)
609 .is_ok());
610 let output = String::from_utf8_lossy(&stdout_buf);
611 assert!(output.contains("cargo:rustc-env=VERGEN_CARGO_FEATURES=this is a bad date"));
612 Ok(())
613 },
614 );
615 assert!(result.is_ok());
616 }
617
618 #[test]
619 #[serial]
620 fn cargo_opt_level_override_works() {
621 let result = with_cargo_vars_ext(
622 &[("VERGEN_CARGO_OPT_LEVEL", Some("this is a bad date"))],
623 || {
624 let mut stdout_buf = vec![];
625 let cargo = CargoBuilder::all_cargo()?;
626 assert!(Emitter::default()
627 .add_instructions(&cargo)?
628 .emit_to(&mut stdout_buf)
629 .is_ok());
630 let output = String::from_utf8_lossy(&stdout_buf);
631 assert!(
632 output.contains("cargo:rustc-env=VERGEN_CARGO_OPT_LEVEL=this is a bad date")
633 );
634 Ok(())
635 },
636 );
637 assert!(result.is_ok());
638 }
639
640 #[test]
641 #[serial]
642 fn cargo_target_triple_override_works() {
643 let result = with_cargo_vars_ext(
644 &[("VERGEN_CARGO_TARGET_TRIPLE", Some("this is a bad date"))],
645 || {
646 let mut stdout_buf = vec![];
647 let cargo = CargoBuilder::all_cargo()?;
648 assert!(Emitter::default()
649 .add_instructions(&cargo)?
650 .emit_to(&mut stdout_buf)
651 .is_ok());
652 let output = String::from_utf8_lossy(&stdout_buf);
653 assert!(output
654 .contains("cargo:rustc-env=VERGEN_CARGO_TARGET_TRIPLE=this is a bad date"));
655 Ok(())
656 },
657 );
658 assert!(result.is_ok());
659 }
660
661 #[test]
662 #[serial]
663 fn cargo_dependencies_override_works() {
664 let result = with_cargo_vars_ext(
665 &[("VERGEN_CARGO_DEPENDENCIES", Some("this is a bad date"))],
666 || {
667 let mut stdout_buf = vec![];
668 let cargo = CargoBuilder::all_cargo()?;
669 assert!(Emitter::default()
670 .add_instructions(&cargo)?
671 .emit_to(&mut stdout_buf)
672 .is_ok());
673 let output = String::from_utf8_lossy(&stdout_buf);
674 assert!(
675 output.contains("cargo:rustc-env=VERGEN_CARGO_DEPENDENCIES=this is a bad date")
676 );
677 Ok(())
678 },
679 );
680 assert!(result.is_ok());
681 }
682}