Skip to content

Commit 2156d30

Browse files
committed
introduce explicit --reproducible mode
1 parent 1017b01 commit 2156d30

9 files changed

Lines changed: 111 additions & 48 deletions

File tree

crates/bridge_core/src/lib.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,18 @@ impl<'a> CoreBridgeLauncher<'a> {
247247
}
248248
}
249249

250-
/// Configure filesystem emulation settings. These default to an "accurate"
251-
/// view of the provided IO subsystem, but can be restricted to provide
252-
/// reproducible file modified timestamps or hide absolute paths.
253-
pub fn with_fs_emulation_settings(&mut self, settings: FsEmulationSettings) -> &mut Self {
254-
self.filesystem_emulation_settings = settings;
250+
/// While absolute paths are useful (for SyncTeX and external tools that
251+
/// resolve paths to TeX sources), we can disable them for reproducibility.
252+
pub fn with_expose_absolute_paths(&mut self, expose_absolute_paths: bool) -> &mut Self {
253+
self.filesystem_emulation_settings.expose_absolute_paths = expose_absolute_paths;
254+
self
255+
}
256+
257+
/// Ditto for file modification timestamps. In reproducible mode, we return
258+
/// the configured build time (i.e. `SOURCE_DATE_EPOCH`) instead of the
259+
/// modification timestamp reported by the IO subsystem.
260+
pub fn with_mtime_override(&mut self, mtime_override: Option<i64>) -> &mut Self {
261+
self.filesystem_emulation_settings.mtime_override = mtime_override;
255262
self
256263
}
257264

@@ -803,15 +810,15 @@ impl Default for SecuritySettings {
803810
/// underlying IO subsystem and have options that stub the respective IO
804811
/// functions with fake / stable values.
805812
#[derive(Clone, Debug)]
806-
pub struct FsEmulationSettings {
813+
struct FsEmulationSettings {
807814
/// While absolute paths are useful (for SyncTeX and external tools that
808815
/// resolve paths to TeX sources), we can disable them for reproducibility.
809-
pub expose_absolute_paths: bool,
816+
expose_absolute_paths: bool,
810817

811818
/// Ditto for file modification timestamps. In reproducible mode, we return
812819
/// the configured build time (i.e. `SOURCE_DATE_EPOCH`) instead of the
813820
/// modification timestamp reported by the IO subsystem.
814-
pub mtime_override: Option<i64>,
821+
mtime_override: Option<i64>,
815822
}
816823

817824
impl Default for FsEmulationSettings {

docs/src/ref/v1cli.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,18 @@ The following are the available flags.
4949
| `-c` | `--chatter <LEVEL>` | How much chatter to print when running [default: default] [possible values: default, minimal] |
5050
| | `--format <PATH>` | The name of the "format" file used to initialize the TeX engine [default: latex] |
5151
| `-h` | `--help` | Prints help information |
52-
| | `--hide <PATH>...` | Tell the engine that no file at `<PATH>` exists, if it tries to read it |
52+
| | `--hide <PATH>...` | Tell the engine that no file at `<PATH>` exists, if it tries to read it |
5353
| `-k` | `--keep-intermediates` | Keep the intermediate files generated during processing |
54-
| | `--keep-logs` | Keep the log files generated during processing |
55-
| | `--makefile-rules <PATH>` | Write Makefile-format rules expressing the dependencies of this run to <PATH> |
54+
| | `--keep-logs` | Keep the log files generated during processing |
55+
| | `--makefile-rules <PATH>` | Write Makefile-format rules expressing the dependencies of this run to `<PATH>` |
5656
| `-C` | `--only-cached` | Use only resource files cached locally |
5757
| `-o` | `--outdir <OUTDIR>` | The directory in which to place output files [default: the directory containing INPUT] |
5858
| | `--outfmt <FORMAT>` | The kind of output to generate [default: pdf] [possible values: pdf, html, xdv, aux, format] |
5959
| | `--pass <PASS>` | Which engines to run [default: default] [possible values: default, tex, bibtex_first] |
6060
| `-p` | `--print` | Print the engine's chatter during processing |
61+
| | `--reproducible` | Ensure deterministic builds |
6162
| `-r` | `--reruns <COUNT>` | Rerun the TeX engine exactly this many times after the first |
6263
| | `--synctex` | Generate SyncTeX data |
64+
| | `--untrusted` | Input is untrusted: disable all known-insecure features |
6365
| `-V` | `--version` | Prints version information |
6466
| `-w` | `--web-bundle <URL>` | Use this URL find resource files instead of the default |

docs/src/v2cli/build.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ cause it to look for that file in the online support bundle.
5050
The `--print` option (or `-p` for short) will cause the engine to print the
5151
regular terminal output of the TeX engine. This output is similar to, but not
5252
identical to, the contents of the log file. By default, this output is only
53-
printed if the engine encounteres a fatal error.
53+
printed if the engine encounters a fatal error.
5454

5555
The `--open` option will open the built document using the system handler.
5656

@@ -64,4 +64,10 @@ be easy to forget to use this option; in cases where untrusted inputs are a
6464
genuine concern, we recommend setting the environment variable
6565
`TECTONIC_UNTRUSTED_MODE` to a non-empty value. This has the same effect as the
6666
`--untrusted` option. Note, however, that a hostile shell user can trivially
67-
clear this variable.
67+
clear this variable.
68+
69+
The `--reproducible` option ensures a fully deterministic build environment.
70+
The most notable difference is a static document build time (`\today`), which can
71+
be configured explicitly by setting the `SOURCE_DATE_EPOCH` environment variable.
72+
There's a few ways to break determinism (shell escape, reading from `/dev/urandom`),
73+
but anything else (especially behaviour in TeXLive packages) is considered a bug.

docs/src/v2cli/compile.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ The following are the available flags.
111111
| | `--outfmt <FORMAT>` | The kind of output to generate. Possible values: `pdf` (the default), `html`, `xdv`, `aux`, `format` |
112112
| | `--pass <PASS>` | Which engines to run. Possible values: `default`, `tex`, `bibtex_first` |
113113
| `-p` | `--print` | Print the engine's chatter during processing |
114+
| | `--reproducible` | Ensure deterministic builds |
114115
| `-r` | `--reruns <COUNT>` | Rerun the TeX engine exactly this many times after the first |
115116
| | `--synctex` | Generate SyncTeX data |
116117
| | `--untrusted` | Input is untrusted: disable all known-insecure features |

docs/src/v2cli/dump.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ one of its parents.
4444
[tectonic-toml]: ../ref/tectonic-toml.md
4545

4646
The “partial build” consists of one pass of the TeX engine. Future versions of
47-
this tool might gain options allowing you specify different passes. This command
48-
can be used to dump any file created by TeX during the build (so long as it's
49-
created on the first pass).
47+
this tool might gain options allowing you to specify different passes. This
48+
command can be used to dump any file created by TeX during the build (so long
49+
as it's created on the first pass).
5050

5151
#### Command-Line Options
5252

src/bin/tectonic/compile.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66
//! `compile` subcommand of the "V2" / "cargo-like" interface.
77
88
use std::{
9-
env,
109
path::{Path, PathBuf},
1110
str::FromStr,
12-
time,
1311
};
1412
use structopt::StructOpt;
1513
use tectonic_bridge_core::{SecuritySettings, SecurityStance};
@@ -91,6 +89,10 @@ pub struct CompileOptions {
9189
#[structopt(long)]
9290
untrusted: bool,
9391

92+
/// Ensure deterministic build environment
93+
#[structopt(long = "reproducible")]
94+
reproducible_mode: bool,
95+
9496
/// Unstable options. Pass -Zhelp to show a list
9597
#[structopt(name = "option", short = "Z", number_of_values = 1)]
9698
unstable: Vec<UnstableArg>,
@@ -119,7 +121,8 @@ impl CompileOptions {
119121
.keep_logs(self.keep_logs)
120122
.keep_intermediates(self.keep_intermediates)
121123
.format_cache_path(config.format_cache_path()?)
122-
.synctex(self.synctex);
124+
.synctex(self.synctex)
125+
.reproducible_mode(self.reproducible_mode);
123126

124127
sess_builder.output_format(OutputFormat::from_str(&self.outfmt).unwrap());
125128

@@ -199,18 +202,7 @@ impl CompileOptions {
199202
} else {
200203
sess_builder.bundle(config.default_bundle(only_cached, status)?);
201204
}
202-
203-
let build_date_str = env::var("SOURCE_DATE_EPOCH").ok();
204-
let build_date = match build_date_str {
205-
Some(s) => {
206-
let epoch = s.parse::<u64>().expect("invalid build date (not a number)");
207-
time::SystemTime::UNIX_EPOCH
208-
.checked_add(time::Duration::from_secs(epoch))
209-
.expect("time overflow")
210-
}
211-
None => time::SystemTime::now(),
212-
};
213-
sess_builder.build_date(build_date);
205+
sess_builder.build_date_from_env(self.reproducible_mode);
214206
run_and_report(sess_builder, status).map(|_| 0)
215207
}
216208
}

src/bin/tectonic/v2cli.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ pub struct BuildCommand {
238238
/// Open built document using system handler
239239
#[structopt(long)]
240240
open: bool,
241+
242+
/// Ensure deterministic build environment
243+
#[structopt(long = "reproducible")]
244+
reproducible_mode: bool,
241245
}
242246

243247
impl BuildCommand {
@@ -260,6 +264,7 @@ impl BuildCommand {
260264
let mut setup_options =
261265
DocumentSetupOptions::new_with_security(SecuritySettings::new(stance));
262266
setup_options.only_cached(self.only_cached);
267+
setup_options.reproducible_mode(self.reproducible_mode);
263268

264269
for output_name in doc.output_names() {
265270
let mut builder = doc.setup_session(output_name, &setup_options, status)?;

src/docmodel.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ pub struct DocumentSetupOptions {
4040

4141
/// Security settings for engine features.
4242
security: SecuritySettings,
43+
44+
/// Ensure a deterministic build environment.
45+
reproducible_mode: bool,
4346
}
4447

4548
impl DocumentSetupOptions {
@@ -48,6 +51,7 @@ impl DocumentSetupOptions {
4851
pub fn new_with_security(security: SecuritySettings) -> Self {
4952
DocumentSetupOptions {
5053
only_cached: false,
54+
reproducible_mode: false,
5155
security,
5256
}
5357
}
@@ -61,6 +65,12 @@ impl DocumentSetupOptions {
6165
self.only_cached = s;
6266
self
6367
}
68+
69+
/// Specify whether we want to ensure a deterministic build environment.
70+
pub fn reproducible_mode(&mut self, s: bool) -> &mut Self {
71+
self.reproducible_mode = s;
72+
self
73+
}
6474
}
6575

6676
pub trait DocumentExt {
@@ -157,7 +167,8 @@ impl DocumentExt for Document {
157167
sess_builder
158168
.output_format(output_format)
159169
.format_name(&profile.tex_format)
160-
.build_date(std::time::SystemTime::now())
170+
.build_date_from_env(setup_options.reproducible_mode)
171+
.reproducible_mode(setup_options.reproducible_mode)
161172
.pass(PassSetting::Default)
162173
.primary_input_buffer(input_buffer.as_bytes())
163174
.tex_input_name(output_profile);

src/driver.rs

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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};
3432
use tectonic_bundles::Bundle;
3533
use tectonic_engine_spx2html::AssetSpecification;
3634
use 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

Comments
 (0)