1use 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#[derive(Clone, Copy, Debug, Builder, PartialEq)]
147#[allow(clippy::struct_excessive_bools, clippy::struct_field_names)]
148pub struct Build {
149 #[builder(field)]
153 all: bool,
154 #[builder(default = all)]
156 build_date: bool,
157 #[builder(default = all)]
159 build_timestamp: bool,
160 #[builder(default = false)]
162 use_local: bool,
163}
164
165impl<S: build_builder::State> BuildBuilder<S> {
166 fn all(mut self) -> Self {
171 self.all = true;
172 self
173 }
174}
175
176impl Build {
177 #[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 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 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}