vergen/feature/
cargo.rs

1// Copyright (c) 2022 vergen developers
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9use 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/// Configure the emission of `VERGEN_CARGO_*` instructions
23///
24/// **NOTE** - All cargo instructions are considered deterministic.  If you change
25/// the version of cargo you are compiling with, these values should change if
26/// being used in the generated binary.
27///
28/// | Variable | Sample |
29/// | -------  | ------ |
30/// | `VERGEN_CARGO_DEBUG` | true |
31/// | `VERGEN_CARGO_FEATURES` | git,build |
32/// | `VERGEN_CARGO_OPT_LEVEL` | 1 |
33/// | `VERGEN_CARGO_TARGET_TRIPLE` | x86_64-unknown-linux-gnu |
34///
35/// # Example
36/// Emit all of the cargo instructions
37///
38/// ```
39/// # use anyhow::Result;
40/// # use std::env;
41/// # use vergen::Emitter;
42/// # use vergen::CargoBuilder;
43/// #
44/// fn main() -> Result<()> {
45///     temp_env::with_vars([
46///         ("CARGO_FEATURE_BUILD", Some("")),
47///         ("DEBUG", Some("true")),
48///         ("OPT_LEVEL", Some("1")),
49///         ("TARGET", Some("x86_64-unknown-linux-gnu"))
50///     ], || {
51/// #        let result = || -> Result<()> {
52///         let cargo = CargoBuilder::all_cargo()?;
53///         Emitter::default().add_instructions(&cargo)?.emit()?;
54/// #        Ok(())
55/// #        }();
56///     });
57/// #    Ok(())
58/// # }
59/// ```
60/// Emit some of the cargo instructions
61///
62/// ```
63/// # use anyhow::Result;
64/// # use vergen::Emitter;
65/// # use vergen::CargoBuilder;
66/// #
67/// # fn main() -> Result<()> {
68///     temp_env::with_vars([
69///         ("OPT_LEVEL", Some("1")),
70///         ("TARGET", Some("x86_64-unknown-linux-gnu"))
71///     ], || {
72/// #        let result = || -> Result<()> {
73///         let cargo = CargoBuilder::default().opt_level(true).build()?;
74///         Emitter::default().add_instructions(&cargo)?.emit()?;
75/// #        Ok(())
76/// #        }();
77///     });
78/// #    Ok(())
79/// # }
80/// ```
81///
82/// Override output with your own value
83///
84/// ```
85/// # use anyhow::Result;
86/// # use std::env;
87/// # use vergen::Emitter;
88/// # use vergen::CargoBuilder;
89/// #
90/// fn main() -> Result<()> {
91///     temp_env::with_vars([
92///         ("CARGO_FEATURE_BUILD", Some("")),
93///         ("VERGEN_CARGO_DEBUG", Some("my own debug value")),
94///         ("OPT_LEVEL", Some("1")),
95///         ("TARGET", Some("x86_64-unknown-linux-gnu"))
96///      ], || {
97/// #        let result = || -> Result<()> {
98///          let cargo = CargoBuilder::all_cargo()?;
99///          Emitter::default().add_instructions(&cargo)?.emit()?;
100/// #        Ok(())
101/// #        }();
102///      });
103/// #     Ok(())
104/// # }
105/// ```
106///
107#[derive(Clone, Copy, Debug, DeriveBuilder, PartialEq)]
108#[allow(clippy::struct_excessive_bools)]
109pub struct Cargo {
110    /// Emit the DEBUG value set by cargo
111    ///
112    /// ```text
113    /// cargo:rustc-env=VERGEN_CARGO_DEBUG=true|false
114    /// ```
115    ///
116    #[builder(default = "false")]
117    debug: bool,
118    /// Emit the `CARGO_FEATURE_*` values set by cargo
119    ///
120    /// ```text
121    /// cargo:rustc-env=VERGEN_CARGO_FEATURES=<features>
122    /// ```
123    ///
124    #[builder(default = "false")]
125    features: bool,
126    /// Emit the `OPT_LEVEL` value set by cargo
127    ///
128    /// ```text
129    /// cargo:rustc-env=VERGEN_CARGO_OPT_LEVEL=<opt_level>
130    /// ```
131    ///
132    #[builder(default = "false")]
133    opt_level: bool,
134    /// Emit the TARGET value set by cargo
135    ///
136    /// ```text
137    /// cargo:rustc-env=VERGEN_CARGO_TARGET_TRIPLE=<target_triple>
138    /// ```
139    ///
140    #[builder(default = "false")]
141    target_triple: bool,
142    /// Emit the dependencies value derived from `Cargo.toml`
143    ///
144    /// ```text
145    /// cargo:rustc-env=VERGEN_CARGO_DEPENDENCIES=<dependencies>
146    /// ```
147    ///
148    #[builder(default = "false")]
149    dependencies: bool,
150    /// Add a name [`Regex`](regex::Regex) filter for cargo dependencies
151    ///
152    /// ```text
153    /// cargo:rustc-env=VERGEN_CARGO_DEPENDENCIES=<deps_filtered_by_name>
154    /// ```
155    ///
156    #[builder(default = "None", setter(into))]
157    name_filter: Option<&'static str>,
158    /// Add a [`DependencyKind`](cargo_metadata::DependencyKind) filter for cargo dependencies
159    ///
160    /// ```text
161    /// cargo:rustc-env=VERGEN_CARGO_DEPENDENCIES=<deps_filtered_by_kind>
162    /// ```
163    ///
164    #[builder(default = "None", setter(into))]
165    dep_kind_filter: Option<DependencyKind>,
166}
167
168impl CargoBuilder {
169    /// Emit all of the `VERGEN_CARGO_*` instructions
170    ///
171    /// # Errors
172    /// The underlying build function can error.
173    ///
174    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) = &regex_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    /// Add a name [`Regex`](regex::Regex) filter for cargo dependencies
268    ///
269    /// ```text
270    /// cargo:rustc-env=VERGEN_CARGO_DEPENDENCIES=<deps_filtered_by_name>
271    /// ```
272    ///
273    pub fn set_name_filter(&mut self, val: Option<&'static str>) -> &mut Self {
274        self.name_filter = val;
275        self
276    }
277    /// Add a [`DependencyKind`](cargo_metadata::DependencyKind) filter for cargo dependencies
278    ///
279    /// ```text
280    /// cargo:rustc-env=VERGEN_CARGO_DEPENDENCIES=<deps_filtered_by_kind>
281    /// ```
282    ///
283    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}