@@ -26,11 +26,9 @@ use std::{
2626 rc:: Rc ,
2727 result:: Result as StdResult ,
2828 str:: FromStr ,
29- time:: SystemTime ,
30- } ;
31- use tectonic_bridge_core:: {
32- CoreBridgeLauncher , DriverHooks , FsEmulationSettings , SecuritySettings , SystemRequestError ,
29+ time:: { Duration , SystemTime } ,
3330} ;
31+ use tectonic_bridge_core:: { CoreBridgeLauncher , DriverHooks , SecuritySettings , SystemRequestError } ;
3432use tectonic_bundles:: Bundle ;
3533use tectonic_engine_spx2html:: AssetSpecification ;
3634use tectonic_io_base:: {
@@ -819,6 +817,7 @@ pub struct ProcessingSessionBuilder {
819817 build_date : Option < SystemTime > ,
820818 unstables : UnstableOptions ,
821819 shell_escape_mode : ShellEscapeMode ,
820+ reproducible_mode : bool ,
822821 html_assets_spec_path : Option < String > ,
823822 html_precomputed_assets : Option < AssetSpecification > ,
824823 html_do_not_emit_files : bool ,
@@ -987,6 +986,28 @@ impl ProcessingSessionBuilder {
987986 self
988987 }
989988
989+ /// Configures the date and time of the processing session from the environment:
990+ /// If `SOURCE_DATE_EPOCH` is set, it's used as the build date.
991+ /// If `force_reproducible` is set, we fall back to UNIX_EPOCH.
992+ /// Otherwise, we use the current system time.
993+ pub fn build_date_from_env ( & mut self , force_reproducible : bool ) -> & mut Self {
994+ let build_date_str = std:: env:: var ( "SOURCE_DATE_EPOCH" ) . ok ( ) ;
995+ let build_date = match ( force_reproducible, build_date_str) {
996+ ( _, Some ( s) ) => {
997+ let epoch = s
998+ . parse :: < u64 > ( )
999+ . expect ( "invalid SOURCE_DATE_EPOCH (not a number)" ) ;
1000+
1001+ SystemTime :: UNIX_EPOCH
1002+ . checked_add ( Duration :: from_secs ( epoch) )
1003+ . expect ( "time overflow" )
1004+ }
1005+ ( true , None ) => SystemTime :: UNIX_EPOCH ,
1006+ ( false , None ) => SystemTime :: now ( ) ,
1007+ } ;
1008+ self . build_date ( build_date)
1009+ }
1010+
9901011 /// Loads unstable options into the processing session
9911012 pub fn unstables ( & mut self , opts : UnstableOptions ) -> & mut Self {
9921013 self . unstables = opts;
@@ -1026,6 +1047,21 @@ impl ProcessingSessionBuilder {
10261047 self
10271048 }
10281049
1050+ /// Ensure a deterministic build environment.
1051+ ///
1052+ /// The most significant user-facing difference is a static document build
1053+ /// date, but this is already covered by [`build_date_from_env`], which
1054+ /// accepts a `reproducible` flag. Additionally, reproducible mode spoofs
1055+ /// file modification times and hides absolute paths from the engine.
1056+ ///
1057+ /// There's a few ways to break determinism (shell escape, reading from
1058+ /// `/dev/urandom`), but anything else (especially behaviour in TeXLive
1059+ /// packages) is considered a bug.
1060+ pub fn reproducible_mode ( & mut self , reproducible : bool ) -> & mut Self {
1061+ self . reproducible_mode = reproducible;
1062+ self
1063+ }
1064+
10291065 /// When using HTML mode, emit an asset specification file instead of actual
10301066 /// asset files.
10311067 ///
@@ -1241,6 +1277,7 @@ impl ProcessingSessionBuilder {
12411277 build_date : self . build_date . unwrap_or ( SystemTime :: UNIX_EPOCH ) ,
12421278 unstables : self . unstables ,
12431279 shell_escape_mode,
1280+ reproducible_mode : self . reproducible_mode ,
12441281 html_assets_spec_path : self . html_assets_spec_path ,
12451282 html_precomputed_assets : self . html_precomputed_assets ,
12461283 html_emit_files : !self . html_do_not_emit_files ,
@@ -1308,6 +1345,8 @@ pub struct ProcessingSession {
13081345
13091346 unstables : UnstableOptions ,
13101347
1348+ reproducible_mode : bool ,
1349+
13111350 /// How to handle shell-escape. The `Defaulted` option will never
13121351 /// be used here.
13131352 shell_escape_mode : ShellEscapeMode ,
@@ -1831,18 +1870,18 @@ impl ProcessingSession {
18311870
18321871 let mut launcher =
18331872 CoreBridgeLauncher :: new_with_security ( & mut self . bs , status, self . security . clone ( ) ) ;
1834- launcher . with_fs_emulation_settings ( FsEmulationSettings {
1835- // We enable absolute paths if synctex was requested or other
1836- // external tools are involved.
1837- expose_absolute_paths : self . synctex_enabled || self . security . allow_shell_escape ( ) ,
1838- // Note: We always return "dummy" modification times. Should
1839- // there be a configuration knob to return the real mtime?
1840- mtime_override : self
1841- . build_date
1842- . duration_since ( SystemTime :: UNIX_EPOCH )
1843- . map ( |x| x . as_secs ( ) as i64 )
1844- . ok ( ) ,
1845- } ) ;
1873+
1874+ // In reproducible mode, we stub a few aspects of the environment.
1875+ // They default to a "realistic" view, but we override them with static values:
1876+ if self . reproducible_mode {
1877+ launcher . with_expose_absolute_paths ( false ) ;
1878+ launcher . with_mtime_override ( Some (
1879+ self . build_date
1880+ . duration_since ( SystemTime :: UNIX_EPOCH )
1881+ . map ( |x| x . as_secs ( ) as i64 )
1882+ . expect ( "invalid build date in reproducible mode" ) ,
1883+ ) ) ;
1884+ }
18461885
18471886 TexEngine :: default ( )
18481887 . halt_on_error_mode ( !self . unstables . continue_on_errors )
0 commit comments