vergen/feature/
build.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::{Context, Error, Result};
10use bon::Builder;
11use std::{
12    env::{self, VarError},
13    str::FromStr,
14};
15use time::{
16    OffsetDateTime,
17    format_description::{self, well_known::Iso8601},
18};
19use vergen_lib::{
20    AddEntries, CargoRerunIfChanged, CargoRustcEnvMap, CargoWarning, DefaultConfig, VergenKey,
21    add_default_map_entry, add_map_entry,
22    constants::{BUILD_DATE_NAME, BUILD_TIMESTAMP_NAME},
23};
24
25/// The `VERGEN_BUILD_*` configuration features
26///
27/// | Variable | Sample |
28/// | -------  | ------ |
29/// | `VERGEN_BUILD_DATE` | 2021-02-25 |
30/// | `VERGEN_BUILD_TIMESTAMP` | 2021-02-25T23:28:39.493201+00:00 |
31///
32/// # Example
33/// Emit all of the build instructions
34///
35/// ```
36/// # use anyhow::Result;
37/// # use vergen::Emitter;
38/// # use vergen::Build;
39/// #
40/// # fn main() -> Result<()> {
41/// let build = Build::all_build();
42/// Emitter::new().add_instructions(&build)?.emit()?;
43/// #     Ok(())
44/// # }
45/// ```
46///
47/// Emit some of the build instructions
48///
49/// ```
50/// # use anyhow::Result;
51/// # use vergen::Emitter;
52/// # use vergen::Build;
53/// #
54/// # fn main() -> Result<()> {
55/// let build = Build::builder().build_timestamp(true).build();
56/// Emitter::new().add_instructions(&build)?.emit()?;
57/// #     Ok(())
58/// # }
59/// ```
60///
61/// Override output with your own value
62///
63/// ```
64/// # use anyhow::Result;
65/// # use std::env;
66/// # use vergen::Emitter;
67/// # use vergen::Build;
68/// #
69/// # fn main() -> Result<()> {
70/// temp_env::with_var("VERGEN_BUILD_DATE", Some("01/01/2023"), || {
71///     let result = || -> Result<()> {
72///         let build = Build::builder().build_date(true).build();
73///         Emitter::new().add_instructions(&build)?.emit()?;
74///         Ok(())
75///     }();
76///     assert!(result.is_ok());
77/// });
78/// #     Ok(())
79/// # }
80/// ```
81///
82/// # Example
83/// This feature can also be used in conjuction with the [`SOURCE_DATE_EPOCH`](https://reproducible-builds.org/docs/source-date-epoch/)
84/// environment variable to generate deterministic timestamps based off the
85/// last modification time of the source/package
86///
87/// ```
88/// # use anyhow::Result;
89/// # use std::env;
90/// # use vergen::Emitter;
91/// # use vergen::Build;
92/// #
93/// # fn main() -> Result<()> {
94/// temp_env::with_var("SOURCE_DATE_EPOCH", Some("1671809360"), || {
95///     let result = || -> Result<()> {
96///         let build = Build::all_build();
97///         Emitter::new().add_instructions(&build)?.emit()?;
98///         Ok(())
99///     }();
100/// });
101/// #   Ok(())
102/// # }
103/// ```
104///
105/// The above will always generate the following output for the timestamp
106/// related instructions
107///
108/// ```text
109/// cargo:rustc-env=VERGEN_BUILD_DATE=2022-12-23
110/// cargo:rustc-env=VERGEN_BUILD_TIMESTAMP=2022-12-23T15:29:20.000000000Z
111/// ```
112///
113/// # Example
114/// This feature also recognizes the idempotent flag.
115///
116/// **NOTE** - `SOURCE_DATE_EPOCH` takes precedence over the idempotent flag. If you
117/// use both, the output will be based off `SOURCE_DATE_EPOCH`.  This would still be
118/// deterministic.
119///
120/// ```
121/// # use anyhow::Result;
122/// # use vergen::Emitter;
123/// # use vergen::Build;
124/// #
125/// # fn main() -> Result<()> {
126/// let build = Build::builder().build();
127/// Emitter::new().idempotent().add_instructions(&build)?.emit()?;
128/// #   Ok(())
129/// # }
130/// ```
131///
132/// The above will always generate the following output for the timestamp
133/// related instructions unless you also use quiet, then the warnings will
134/// be suppressed.
135///
136/// ```text
137/// cargo:rustc-env=VERGEN_BUILD_DATE=VERGEN_IDEMPOTENT_OUTPUT
138/// cargo:rustc-env=VERGEN_BUILD_TIMESTAMP=VERGEN_IDEMPOTENT_OUTPUT
139/// cargo:warning=VERGEN_BUILD_DATE set to default
140/// cargo:warning=VERGEN_BUILD_TIMESTAMP set to default
141/// cargo:rerun-if-changed=build.rs
142/// cargo:rerun-if-env-changed=VERGEN_IDEMPOTENT
143/// cargo:rerun-if-env-changed=SOURCE_DATE_EPOCH
144/// ```
145///
146#[derive(Clone, Copy, Debug, Builder, PartialEq)]
147#[allow(clippy::struct_excessive_bools, clippy::struct_field_names)]
148pub struct Build {
149    /// Configures the default values.
150    /// If set to `true` all defaults are in "enabled" state.
151    /// If set to `false` all defaults are in "disabled" state.
152    #[builder(field)]
153    all: bool,
154    /// Enable the `VERGEN_BUILD_DATE` date output
155    #[builder(default = all)]
156    build_date: bool,
157    /// Enable the `VERGEN_BUILD_TIMESTAMP` date output
158    #[builder(default = all)]
159    build_timestamp: bool,
160    /// Enable local offset date/timestamp output
161    #[builder(default = false)]
162    use_local: bool,
163}
164
165impl<S: build_builder::State> BuildBuilder<S> {
166    /// Convenience method that switches the defaults of [`BuildBuilder`]
167    /// to enable all of the `VERGEN_BUILD_*` instructions. It can only be
168    /// called at the start of the building process, i.e. when no config
169    /// has been set yet to avoid overwrites.
170    fn all(mut self) -> Self {
171        self.all = true;
172        self
173    }
174}
175
176impl Build {
177    /// Enable all of the `VERGEN_BUILD_*` options
178    ///
179    /// # Errors
180    /// The build function can error
181    ///
182    #[must_use]
183    pub fn all_build() -> Self {
184        Self::builder().all().build()
185    }
186
187    fn any(self) -> bool {
188        self.build_date || self.build_timestamp
189    }
190
191    fn add_timestamp_entries(
192        self,
193        idempotent: bool,
194        cargo_rustc_env: &mut CargoRustcEnvMap,
195        cargo_warning: &mut CargoWarning,
196    ) -> Result<()> {
197        let (sde, ts) = match env::var("SOURCE_DATE_EPOCH") {
198            Ok(v) => (
199                true,
200                OffsetDateTime::from_unix_timestamp(i64::from_str(&v)?)?,
201            ),
202            Err(VarError::NotPresent) => {
203                if self.use_local {
204                    (false, OffsetDateTime::now_local()?)
205                } else {
206                    (false, OffsetDateTime::now_utc())
207                }
208            }
209            Err(e) => return Err(e.into()),
210        };
211
212        self.add_date_entry(idempotent, sde, &ts, cargo_rustc_env, cargo_warning)?;
213        self.add_timestamp_entry(idempotent, sde, &ts, cargo_rustc_env, cargo_warning)?;
214        Ok(())
215    }
216
217    fn add_date_entry(
218        self,
219        idempotent: bool,
220        source_date_epoch: bool,
221        ts: &OffsetDateTime,
222        cargo_rustc_env: &mut CargoRustcEnvMap,
223        cargo_warning: &mut CargoWarning,
224    ) -> Result<()> {
225        if self.build_date {
226            if let Ok(value) = env::var(BUILD_DATE_NAME) {
227                add_map_entry(VergenKey::BuildDate, value, cargo_rustc_env);
228            } else if idempotent && !source_date_epoch {
229                add_default_map_entry(
230                    idempotent,
231                    VergenKey::BuildDate,
232                    cargo_rustc_env,
233                    cargo_warning,
234                );
235            } else {
236                let format = format_description::parse("[year]-[month]-[day]")?;
237                add_map_entry(VergenKey::BuildDate, ts.format(&format)?, cargo_rustc_env);
238            }
239        }
240        Ok(())
241    }
242
243    fn add_timestamp_entry(
244        self,
245        idempotent: bool,
246        source_date_epoch: bool,
247        ts: &OffsetDateTime,
248        cargo_rustc_env: &mut CargoRustcEnvMap,
249        cargo_warning: &mut CargoWarning,
250    ) -> Result<()> {
251        if self.build_timestamp {
252            if let Ok(value) = env::var(BUILD_TIMESTAMP_NAME) {
253                add_map_entry(VergenKey::BuildTimestamp, value, cargo_rustc_env);
254            } else if idempotent && !source_date_epoch {
255                add_default_map_entry(
256                    idempotent,
257                    VergenKey::BuildTimestamp,
258                    cargo_rustc_env,
259                    cargo_warning,
260                );
261            } else {
262                add_map_entry(
263                    VergenKey::BuildTimestamp,
264                    ts.format(&Iso8601::DEFAULT)?,
265                    cargo_rustc_env,
266                );
267            }
268        }
269        Ok(())
270    }
271}
272
273impl AddEntries for Build {
274    fn add_map_entries(
275        &self,
276        idempotent: bool,
277        cargo_rustc_env: &mut CargoRustcEnvMap,
278        _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
279        cargo_warning: &mut CargoWarning,
280    ) -> Result<()> {
281        if self.any() {
282            self.add_timestamp_entries(idempotent, cargo_rustc_env, cargo_warning)
283                .with_context(|| "Error adding build timestamp entries")?;
284        }
285        Ok(())
286    }
287
288    fn add_default_entries(
289        &self,
290        config: &DefaultConfig,
291        cargo_rustc_env_map: &mut CargoRustcEnvMap,
292        _cargo_rerun_if_changed: &mut CargoRerunIfChanged,
293        cargo_warning: &mut CargoWarning,
294    ) -> Result<()> {
295        if *config.fail_on_error() {
296            let error = Error::msg(format!("{:?}", config.error()));
297            Err(error)
298        } else {
299            if self.build_date {
300                add_default_map_entry(
301                    *config.idempotent(),
302                    VergenKey::BuildDate,
303                    cargo_rustc_env_map,
304                    cargo_warning,
305                );
306            }
307            if self.build_timestamp {
308                add_default_map_entry(
309                    *config.idempotent(),
310                    VergenKey::BuildTimestamp,
311                    cargo_rustc_env_map,
312                    cargo_warning,
313                );
314            }
315            Ok(())
316        }
317    }
318}
319
320#[cfg(test)]
321mod test {
322    use super::Build;
323    use crate::Emitter;
324    use anyhow::Result;
325    use serial_test::serial;
326    use std::io::Write;
327    use vergen_lib::{CustomInsGen, count_idempotent};
328
329    #[test]
330    #[serial]
331    #[allow(clippy::clone_on_copy, clippy::redundant_clone)]
332    fn build_clone() {
333        let build = Build::all_build();
334        let another = build.clone();
335        assert_eq!(another, build);
336    }
337
338    #[test]
339    #[serial]
340    fn build_debug() -> Result<()> {
341        let build = Build::all_build();
342        let mut buf = vec![];
343        write!(buf, "{build:?}")?;
344        assert!(!buf.is_empty());
345        Ok(())
346    }
347
348    #[test]
349    #[serial]
350    fn build_default() -> Result<()> {
351        let build = Build::builder().build();
352        let emitter = Emitter::default().add_instructions(&build)?.test_emit();
353        assert_eq!(0, emitter.cargo_rustc_env_map().len());
354        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
355        assert_eq!(0, emitter.cargo_warning().len());
356        Ok(())
357    }
358
359    #[test]
360    #[serial]
361    fn build_all_idempotent() -> Result<()> {
362        let build = Build::all_build();
363        let emitter = Emitter::new()
364            .idempotent()
365            .add_instructions(&build)?
366            .test_emit();
367        assert_eq!(2, emitter.cargo_rustc_env_map().len());
368        assert_eq!(2, count_idempotent(emitter.cargo_rustc_env_map()));
369        assert_eq!(2, emitter.cargo_warning().len());
370        Ok(())
371    }
372
373    #[test]
374    #[serial]
375    fn build_all() -> Result<()> {
376        let build = Build::all_build();
377        let emitter = Emitter::new().add_instructions(&build)?.test_emit();
378        assert_eq!(2, emitter.cargo_rustc_env_map().len());
379        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
380        assert_eq!(0, emitter.cargo_warning().len());
381        Ok(())
382    }
383
384    #[test]
385    #[serial]
386    fn build_date() -> Result<()> {
387        let build = Build::builder().build_date(true).build();
388        let emitter = Emitter::new().add_instructions(&build)?.test_emit();
389        assert_eq!(1, emitter.cargo_rustc_env_map().len());
390        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
391        assert_eq!(0, emitter.cargo_warning().len());
392        Ok(())
393    }
394
395    #[cfg(any(unix, target_os = "macos"))]
396    #[test]
397    #[serial]
398    fn build_date_local() -> Result<()> {
399        // use local is unsound on nix
400        let build = Build::builder().build_date(true).use_local(true).build();
401        let emitter = Emitter::new().add_instructions(&build)?.test_emit();
402        assert_eq!(1, emitter.cargo_rustc_env_map().len());
403        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
404        assert_eq!(0, emitter.cargo_warning().len());
405        Ok(())
406    }
407
408    #[cfg(windows)]
409    #[test]
410    #[serial]
411    fn build_date_local() -> Result<()> {
412        let build = Build::builder().build_date(true).use_local(true).build();
413        let emitter = Emitter::new().add_instructions(&build)?.test_emit();
414        assert_eq!(1, emitter.cargo_rustc_env_map().len());
415        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
416        assert_eq!(0, emitter.cargo_warning().len());
417        Ok(())
418    }
419
420    #[test]
421    #[serial]
422    fn build_timestamp() -> Result<()> {
423        let build = Build::builder().build_timestamp(true).build();
424        let emitter = Emitter::new().add_instructions(&build)?.test_emit();
425        assert_eq!(1, emitter.cargo_rustc_env_map().len());
426        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
427        assert_eq!(0, emitter.cargo_warning().len());
428        Ok(())
429    }
430
431    #[cfg(any(unix, target_os = "macos"))]
432    #[test]
433    #[serial]
434    fn build_timestamp_local() -> Result<()> {
435        // use local is unsound on nix
436        let build = Build::builder()
437            .build_timestamp(true)
438            .use_local(true)
439            .build();
440        let emitter = Emitter::new().add_instructions(&build)?.test_emit();
441        assert_eq!(1, emitter.cargo_rustc_env_map().len());
442        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
443        assert_eq!(0, emitter.cargo_warning().len());
444        Ok(())
445    }
446
447    #[cfg(windows)]
448    #[test]
449    #[serial]
450    fn build_timestamp_local() -> Result<()> {
451        let build = Build::builder()
452            .build_timestamp(true)
453            .use_local(true)
454            .build();
455        let emitter = Emitter::new().add_instructions(&build)?.test_emit();
456        assert_eq!(1, emitter.cargo_rustc_env_map().len());
457        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
458        assert_eq!(0, emitter.cargo_warning().len());
459        Ok(())
460    }
461
462    #[test]
463    #[serial]
464    fn source_date_epoch_works() {
465        temp_env::with_var("SOURCE_DATE_EPOCH", Some("1671809360"), || {
466            let result = || -> Result<()> {
467                let mut stdout_buf = vec![];
468                let build = Build::all_build();
469                _ = Emitter::new()
470                    .idempotent()
471                    .add_instructions(&build)?
472                    .emit_to(&mut stdout_buf)?;
473                let output = String::from_utf8_lossy(&stdout_buf);
474                for (idx, line) in output.lines().enumerate() {
475                    if idx == 0 {
476                        assert_eq!("cargo:rustc-env=VERGEN_BUILD_DATE=2022-12-23", line);
477                    } else if idx == 1 {
478                        assert_eq!(
479                            "cargo:rustc-env=VERGEN_BUILD_TIMESTAMP=2022-12-23T15:29:20.000000000Z",
480                            line
481                        );
482                    }
483                }
484                Ok(())
485            }();
486            assert!(result.is_ok());
487        });
488    }
489
490    #[test]
491    #[serial]
492    #[cfg(unix)]
493    fn bad_source_date_epoch_fails() {
494        use std::ffi::OsStr;
495        use std::os::unix::prelude::OsStrExt;
496
497        let source = [0x66, 0x6f, 0x80, 0x6f];
498        let os_str = OsStr::from_bytes(&source[..]);
499        temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
500            let result = || -> Result<bool> {
501                let mut stdout_buf = vec![];
502                let build = Build::all_build();
503                Emitter::new()
504                    .idempotent()
505                    .fail_on_error()
506                    .add_instructions(&build)?
507                    .emit_to(&mut stdout_buf)
508            }();
509            assert!(result.is_err());
510        });
511    }
512
513    #[test]
514    #[serial]
515    #[cfg(unix)]
516    fn bad_source_date_epoch_defaults() {
517        use std::ffi::OsStr;
518        use std::os::unix::prelude::OsStrExt;
519
520        let source = [0x66, 0x6f, 0x80, 0x6f];
521        let os_str = OsStr::from_bytes(&source[..]);
522        temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
523            let result = || -> Result<bool> {
524                let mut stdout_buf = vec![];
525                let build = Build::all_build();
526                Emitter::new()
527                    .idempotent()
528                    .add_instructions(&build)?
529                    .emit_to(&mut stdout_buf)
530            }();
531            assert!(result.is_ok());
532        });
533    }
534
535    #[test]
536    #[serial]
537    #[cfg(windows)]
538    fn bad_source_date_epoch_fails() {
539        use std::{ffi::OsString, os::windows::prelude::OsStringExt};
540
541        let source = [0x0066, 0x006f, 0xD800, 0x006f];
542        let os_string = OsString::from_wide(&source[..]);
543        let os_str = os_string.as_os_str();
544        temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
545            let result = || -> Result<bool> {
546                let mut stdout_buf = vec![];
547                let build = Build::all_build();
548                Emitter::new()
549                    .fail_on_error()
550                    .idempotent()
551                    .add_instructions(&build)?
552                    .emit_to(&mut stdout_buf)
553            }();
554            assert!(result.is_err());
555        });
556    }
557
558    #[test]
559    #[serial]
560    #[cfg(windows)]
561    fn bad_source_date_epoch_defaults() {
562        use std::{ffi::OsString, os::windows::prelude::OsStringExt};
563
564        let source = [0x0066, 0x006f, 0xD800, 0x006f];
565        let os_string = OsString::from_wide(&source[..]);
566        let os_str = os_string.as_os_str();
567        temp_env::with_var("SOURCE_DATE_EPOCH", Some(os_str), || {
568            let result = || -> Result<bool> {
569                let mut stdout_buf = vec![];
570                let build = Build::all_build();
571                Emitter::new()
572                    .idempotent()
573                    .add_instructions(&build)?
574                    .emit_to(&mut stdout_buf)
575            }();
576            assert!(result.is_ok());
577        });
578    }
579
580    #[test]
581    #[serial]
582    fn build_date_override_works() {
583        temp_env::with_var("VERGEN_BUILD_DATE", Some("this is a bad date"), || {
584            let result = || -> Result<()> {
585                let mut stdout_buf = vec![];
586                let build = Build::all_build();
587                assert!(
588                    Emitter::default()
589                        .add_instructions(&build)?
590                        .emit_to(&mut stdout_buf)
591                        .is_ok()
592                );
593                let output = String::from_utf8_lossy(&stdout_buf);
594                assert!(output.contains("cargo:rustc-env=VERGEN_BUILD_DATE=this is a bad date"));
595                Ok(())
596            }();
597            assert!(result.is_ok());
598        });
599    }
600
601    #[test]
602    #[serial]
603    fn build_timestamp_override_works() {
604        temp_env::with_var(
605            "VERGEN_BUILD_TIMESTAMP",
606            Some("this is a bad timestamp"),
607            || {
608                let result = || -> Result<()> {
609                    let mut stdout_buf = vec![];
610                    let build = Build::all_build();
611                    assert!(
612                        Emitter::default()
613                            .add_instructions(&build)?
614                            .emit_to(&mut stdout_buf)
615                            .is_ok()
616                    );
617                    let output = String::from_utf8_lossy(&stdout_buf);
618                    assert!(output.contains(
619                        "cargo:rustc-env=VERGEN_BUILD_TIMESTAMP=this is a bad timestamp"
620                    ));
621                    Ok(())
622                }();
623                assert!(result.is_ok());
624            },
625        );
626    }
627
628    #[test]
629    #[serial]
630    fn custom_emit_works() -> Result<()> {
631        let cust_gen = CustomInsGen::default();
632        let build = Build::all_build();
633        let emitter = Emitter::default()
634            .add_instructions(&build)?
635            .add_custom_instructions(&cust_gen)?
636            .test_emit();
637        assert_eq!(2, emitter.cargo_rustc_env_map().len());
638        assert_eq!(1, emitter.cargo_rustc_env_map_custom().len());
639        assert_eq!(0, count_idempotent(emitter.cargo_rustc_env_map()));
640        assert_eq!(0, emitter.cargo_warning().len());
641        Ok(())
642    }
643}