Skip to main content

tectonic/
unstable_opts.rs

1// src/bin/tectonic.rs -- Command-line driver for the Tectonic engine.
2// Copyright 2020-2022 the Tectonic Project
3// Licensed under the MIT License.
4
5//! Unstable options for the Tectonic engine.
6//!
7//! This is similar to the -Z options on rustc - they're unstable options that are not guaranteed
8//! to be reliable or very polished. In particular, many of these prevent the build from being
9//! reproducible.
10
11use std::default::Default;
12use std::path::PathBuf;
13use std::str::FromStr;
14
15const HELPMSG: &str = r#"Available unstable options:
16
17    -Z help                     List all unstable options
18    -Z continue-on-errors       Keep compiling even when severe errors occur
19    -Z min-crossrefs=<num>      Equivalent to bibtex's -min-crossrefs flag - "include after <num>
20                                    crossrefs" [default: 2]
21    -Z paper-size=<spec>        Change the initial paper size [default: letter]
22    -Z search-path=<path>       Also look in <path> for files (unless --untrusted has been specified),
23                                    like TEXINPUTS. Can be specified multiple times.
24    -Z shell-escape             Enable \write18 (unless --untrusted has been specified)
25    -Z shell-escape-cwd=<path>  Working directory to use for \write18. Use $(pwd) for same behaviour as
26                                    most other engines (e.g. for relative paths in \inputminted).
27                                    Implies -Z shell-escape
28    -Z deterministic-mode       Force a deterministic build environment. Note that setting
29                                    `SOURCE_DATE_EPOCH` is usually sufficient for reproducible builds,
30                                    and this option makes some extra functionality trade-offs.
31                                    Specifically, deterministic mode breaks SyncTeX's auxiliary files
32                                    as they include and rely on absolute file paths
33"#;
34
35// Each entry of this should correspond to a field of UnstableOptions.
36#[doc(hidden)]
37#[derive(Debug, Clone)]
38pub enum UnstableArg {
39    ContinueOnErrors,
40    Help,
41    MinCrossrefs(u32),
42    PaperSize(String),
43    SearchPath(PathBuf),
44    ShellEscapeEnabled,
45    ShellEscapeCwd(String),
46    DeterministicModeEnabled,
47}
48
49impl FromStr for UnstableArg {
50    type Err = Box<dyn std::error::Error + Send + Sync + 'static>;
51
52    /// Parse from the argument to -Z
53    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
54        let mut splitter = s.splitn(2, '=');
55        let arg = splitter.next().unwrap(); // splitn will always have at least 1 item
56        let value = splitter.next();
57
58        // For structopt/clap, if you pass a value to a flag which doesn't accept one, it's
59        // silently ignored.
60
61        let require_value = |value_name| {
62            value.ok_or_else(|| {
63                format!("'-Z {arg}=<{value_name}>' requires a value but none was supplied",).into()
64            })
65        };
66
67        let require_no_value = |unwanted_value: Option<&str>, builtin_value: UnstableArg| {
68            if let Some(value) = unwanted_value {
69                Err(format!(
70                    "'-Z {arg}={value}', was supplied but '-Z {arg}' does not take a value."
71                )
72                .into())
73            } else {
74                Ok(builtin_value)
75            }
76        };
77
78        match arg {
79            "help" => Ok(UnstableArg::Help),
80
81            "continue-on-errors" => Ok(UnstableArg::ContinueOnErrors),
82
83            "min-crossrefs" => require_value("num")
84                .and_then(|s| {
85                    FromStr::from_str(s).map_err(|e| format!("-Z min-crossrefs: {e}").into())
86                })
87                .map(UnstableArg::MinCrossrefs),
88
89            "paper-size" => require_value("spec").map(|s| UnstableArg::PaperSize(s.to_string())),
90
91            "search-path" => require_value("path").map(|s| UnstableArg::SearchPath(s.into())),
92
93            "shell-escape" => require_no_value(value, UnstableArg::ShellEscapeEnabled),
94
95            "shell-escape-cwd" => {
96                require_value("path").map(|s| UnstableArg::ShellEscapeCwd(s.to_string()))
97            }
98
99            "deterministic-mode" => require_no_value(value, UnstableArg::DeterministicModeEnabled),
100
101            _ => Err(format!("Unknown unstable option '{arg}'").into()),
102        }
103    }
104}
105
106/// Unstable options available for engine backends. These options may be added or removed in minor
107/// releases, and are not considered breaking.
108///
109/// These options may affect the reproducibility of built documents.
110#[derive(Debug, Default)]
111pub struct UnstableOptions {
112    /// Don't stop on errors - attempt to generate a document anyway, for all but the most fatal of
113    /// problems.
114    pub continue_on_errors: bool,
115
116    /// Set the paper size used by the output document.
117    pub paper_size: Option<String>,
118
119    /// Allow using shell commands during document compilation. All shell escapes will be executed
120    /// within a custom temporary directory that lives for the duration of the compilation session.
121    /// [`Self::shell_escape_cwd`] will take precedence over this flag.
122    pub shell_escape: bool,
123
124    /// Minimum number of cross-references in `bibtex` before an item gets its own standalone entry.
125    pub min_crossrefs: Option<u32>,
126
127    /// Extra directories to search for input files during a processing session.
128    pub extra_search_paths: Vec<PathBuf>,
129
130    /// The working directory to use for shell escapes. The directory will be preserved after
131    /// compilation is complete. This overrides [`Self::shell_escape`].
132    pub shell_escape_cwd: Option<String>,
133
134    /// Ensure a deterministic build environment.
135    ///
136    /// The most significant user-facing difference is a static document build
137    /// date, but this is already covered by [`crate::driver::ProcessingSessionBuilder::build_date_from_env`],
138    /// which accepts a `deterministic` flag. Additionally, deterministic mode
139    /// spoofs file modification times and hides absolute paths from the engine.
140    ///
141    /// There's a few ways to break determinism (shell escape, reading from
142    /// `/dev/urandom`), but anything else (especially behaviour in TeXLive
143    /// packages) is considered a bug.
144    pub deterministic_mode: bool,
145}
146
147impl UnstableOptions {
148    #[doc(hidden)]
149    pub fn from_unstable_args<I>(uargs: I) -> Self
150    where
151        I: Iterator<Item = UnstableArg>,
152    {
153        let mut opts = UnstableOptions::default();
154
155        for u in uargs {
156            use UnstableArg::*;
157            match u {
158                Help => print_unstable_help_and_exit(),
159                ContinueOnErrors => opts.continue_on_errors = true,
160                MinCrossrefs(num) => opts.min_crossrefs = Some(num),
161                PaperSize(size) => opts.paper_size = Some(size),
162                ShellEscapeEnabled => opts.shell_escape = true,
163                SearchPath(p) => opts.extra_search_paths.push(p),
164                ShellEscapeCwd(p) => {
165                    opts.shell_escape_cwd = Some(p);
166                    opts.shell_escape = true;
167                }
168                DeterministicModeEnabled => opts.deterministic_mode = true,
169            }
170        }
171
172        opts
173    }
174}
175
176#[doc(hidden)]
177pub fn print_unstable_help_and_exit() {
178    print!("{HELPMSG}");
179    std::process::exit(0);
180}