Skip to content

Commit f41cacd

Browse files
authored
Allow overriding web bundle at _build time_ (#1131)
2 parents 5194d43 + e98864c commit f41cacd

9 files changed

Lines changed: 261 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ members = [
6060
"crates/xdv",
6161
"crates/xetex_format",
6262
"crates/xetex_layout",
63+
"tests/bundle_env_overrides_test",
6364
]
6465

6566
[lib]

crates/bundles/src/lib.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ use ttb_fs::TTBFsBundle;
3737
use ttb_net::TTBNetBundle;
3838
use zip::ZipBundle;
3939

40+
/// The current hardcoded default prefix for tectonic's bundle.
41+
const TECTONIC_BUNDLE_PREFIX_DEFAULT: &str = "https://relay.fullyjustified.net";
42+
4043
// How many times network bundles should retry
4144
// a download, and how long they should wait
4245
// between attempts.
@@ -261,18 +264,34 @@ pub fn detect_bundle(
261264
/// roughly corresponds to a TeXLive version, and the engine and TeXLive files
262265
/// are fairly closely coupled.
263266
///
267+
/// The hardcoded default can be overridden at compile time by setting either:
268+
/// - `${TECTONIC_BUNDLE_PREFIX}`, which would lead to a URL in the form of
269+
/// `${TECTONIC_BUNDLE_PREFIX}/default_bundle_v${FORMAT_VERSION}.tar`, or
270+
/// - `${TECTONIC_BUNDLE_LOCKED}`, which can be used to pin the default bundle
271+
/// to a specific "snapshot" if specified. This would be useful for
272+
/// reproducible builds.
273+
///
264274
/// The URL template used in this function will be embedded in the binaries that
265275
/// you create, which may be used for years into the future, so it needs to be
266276
/// durable and reliable. We used `archive.org` for a while, but it had
267277
/// low-level reliability problems and was blocked in China. We now use a custom
268278
/// webservice.
269279
pub fn get_fallback_bundle_url(format_version: u32) -> String {
280+
let bundle_locked = option_env!("TECTONIC_BUNDLE_LOCKED").unwrap_or("");
281+
let bundle_prefix =
282+
option_env!("TECTONIC_BUNDLE_PREFIX").unwrap_or(TECTONIC_BUNDLE_PREFIX_DEFAULT);
283+
284+
// Simply return the locked url when it is specified:
285+
if !bundle_locked.is_empty() {
286+
return bundle_locked.to_owned();
287+
}
288+
270289
// Format version 32 (TeXLive 2021) was when we introduced versioning to the
271290
// URL.
272291
if format_version < 32 {
273-
"https://relay.fullyjustified.net/default_bundle.tar".to_owned()
292+
format!("{bundle_prefix}/default_bundle.tar")
274293
} else {
275-
format!("https://relay.fullyjustified.net/default_bundle_v{format_version}.tar")
294+
format!("{bundle_prefix}/default_bundle_v{format_version}.tar")
276295
}
277296
}
278297

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# How To: Build Tectonic: Configure the Default Bundle
2+
3+
Tectonic supports overriding the default bundle configuration at build time through environment variables. This feature is particularly useful for:
4+
5+
- System packagers who need to specify bundle sources during compilation
6+
- Environments requiring reproducible builds
7+
- Users who want to use mirrored bundles for better reliability
8+
9+
## Build-time Environment Variables for the Default Bundle
10+
11+
Two environment variables control the bundle configuration:
12+
13+
### `TECTONIC_BUNDLE_PREFIX`
14+
15+
This variable is **required** at compile time and specifies the base URL prefix for bundles.
16+
A default value is provided in the build script `crates/bundles/build.rs` in the source repository.
17+
18+
The variable is used to construct bundle URLs in the following pattern:
19+
```bash
20+
$TECTONIC_BUNDLE_PREFIX/default_bundle_v$FORMAT_VERSION.tar
21+
```
22+
where `$FORMAT_VERSION` is a suffix from an internal variable, used to distinguish different versions
23+
of the bundle's format for backward compatibility.
24+
25+
For example, the current default is given by:
26+
```bash
27+
# as of January 2025
28+
TECTONIC_BUNDLE_PREFIX=https://relay.fullyjustified.net
29+
```
30+
so the full bundle URL is `https://relay.fullyjustified.net/default_bundle_v33.tar`,
31+
in which `_v33` indicates the version of the bundle format.
32+
Note that this hardcoded default may change, and the default value documented here may be outdated.
33+
Please refer to the source code of Tectonic for the latest default.
34+
35+
### `TECTONIC_BUNDLE_LOCKED`
36+
37+
This variable is **optional** and, when set, provides a fixed URL for the bundle. If this variable contains any non-empty value, it takes precedence over the format-version-specific URL construction using `TECTONIC_BUNDLE_PREFIX`.
38+
39+
## Usage Examples
40+
41+
To build Tectonic with a custom bundle prefix:
42+
43+
```sh
44+
TECTONIC_BUNDLE_PREFIX="https://mirror.example.com/tectonic-bundles" cargo build
45+
```
46+
47+
To build Tectonic with a locked bundle URL:
48+
49+
```sh
50+
TECTONIC_BUNDLE_LOCKED="https://mirror.example.com/tectonic-bundles/my-fixed-bundle.tar" cargo build
51+
```
52+
53+
### Notes
54+
55+
- The bundle URL configuration happens at build time and may be overridden by appropriate `--bundle` flags at runtime.
56+
- If using `TECTONIC_BUNDLE_LOCKED`, ensure the URL points to a compatible bundle version.

tests/bundle_env_overrides.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//! Integration tests of the bundles crate.
2+
3+
use std::env;
4+
use std::path::PathBuf;
5+
use std::process::Command;
6+
7+
const PRE_FORMAT_VERSION: u32 = 31;
8+
const TEST_FORMAT_VERSION: u32 = 32;
9+
const TEST_BUNDLE_LOCKED: &str = "https://example.com/locked_bundle.tar";
10+
const TEST_BUNDLE_PREFIX: &str = "https://custom.example.com";
11+
12+
/// Runs the test project and gets the output
13+
fn run_test_program(format_version: u32, env_vars: &[(&str, &str)]) -> String {
14+
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
15+
let test_dir = PathBuf::from(&manifest_dir)
16+
.join("tests")
17+
.join("bundle_env_overrides_test");
18+
let target_platform = env::var("TARGET").unwrap();
19+
20+
let mut cmd = Command::new("cargo");
21+
cmd.arg("run")
22+
.arg("--quiet")
23+
.arg("--target")
24+
.arg(target_platform)
25+
.arg("--")
26+
.arg(format_version.to_string())
27+
.current_dir(&test_dir);
28+
29+
for (key, value) in env_vars {
30+
cmd.env(key, value);
31+
}
32+
33+
let output = cmd.output().expect("Failed to run test program");
34+
assert!(
35+
output.status.success(),
36+
"Test program failed: {}",
37+
String::from_utf8_lossy(&output.stderr)
38+
);
39+
String::from_utf8(output.stdout).unwrap().trim().to_string()
40+
}
41+
42+
#[test]
43+
fn test_bundle_locked() {
44+
let url_locked = run_test_program(
45+
PRE_FORMAT_VERSION,
46+
&[("TECTONIC_BUNDLE_LOCKED", TEST_BUNDLE_LOCKED)],
47+
);
48+
let url_locked_versioned = run_test_program(
49+
TEST_FORMAT_VERSION,
50+
&[("TECTONIC_BUNDLE_LOCKED", TEST_BUNDLE_LOCKED)],
51+
);
52+
53+
assert_eq!(url_locked, TEST_BUNDLE_LOCKED);
54+
assert_eq!(url_locked_versioned, TEST_BUNDLE_LOCKED);
55+
}
56+
57+
#[test]
58+
fn test_bundle_prefix() {
59+
let url_prefixed = run_test_program(
60+
PRE_FORMAT_VERSION,
61+
&[("TECTONIC_BUNDLE_PREFIX", TEST_BUNDLE_PREFIX)],
62+
);
63+
let url_prefixed_versioned = run_test_program(
64+
TEST_FORMAT_VERSION,
65+
&[("TECTONIC_BUNDLE_PREFIX", TEST_BUNDLE_PREFIX)],
66+
);
67+
68+
assert_eq!(
69+
url_prefixed,
70+
format!("{TEST_BUNDLE_PREFIX}/default_bundle.tar")
71+
);
72+
assert_eq!(
73+
url_prefixed_versioned,
74+
format!("{TEST_BUNDLE_PREFIX}/default_bundle_v{TEST_FORMAT_VERSION}.tar")
75+
);
76+
}
77+
78+
#[test]
79+
fn test_precedence_locked_over_prefix() {
80+
let url_both_env_set = run_test_program(
81+
TEST_FORMAT_VERSION,
82+
&[
83+
("TECTONIC_BUNDLE_LOCKED", TEST_BUNDLE_LOCKED),
84+
("TECTONIC_BUNDLE_PREFIX", TEST_BUNDLE_PREFIX),
85+
],
86+
);
87+
assert_eq!(url_both_env_set, TEST_BUNDLE_LOCKED);
88+
}
89+
90+
#[test]
91+
fn test_empty_locked_bundle_ignored() {
92+
let url_empty_locked = run_test_program(
93+
TEST_FORMAT_VERSION,
94+
&[
95+
("TECTONIC_BUNDLE_LOCKED", ""),
96+
("TECTONIC_BUNDLE_PREFIX", TEST_BUNDLE_PREFIX),
97+
],
98+
);
99+
assert_eq!(
100+
url_empty_locked,
101+
format!("{TEST_BUNDLE_PREFIX}/default_bundle_v{TEST_FORMAT_VERSION}.tar",)
102+
);
103+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "bundle_env_overrides_test"
3+
description = "Mini project for testing compile time bundle URL overrides"
4+
version = "0.0.0-dev.0"
5+
edition = "2021"
6+
7+
[dependencies]
8+
tectonic_bundles = { path = "../../crates/bundles", version = "0.0.0-dev.0" }
9+
10+
[package.metadata.internal_dep_versions]
11+
tectonic_bundles = "thiscommit:2025-07-26:IKt5CJV"
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Bundle URL overrides Test
2+
3+
This is a simple test program used by the integration tests in the `tectonic_bundles` crate to test the `get_fallback_bundle_url` function with different compile-time environment variable overrides.
4+
5+
## Purpose
6+
7+
The `get_fallback_bundle_url` function reads compile-time environment variables
8+
- `$TECTONIC_BUNDLE_LOCKED`, and
9+
- `$TECTONIC_BUNDLE_PREFIX`
10+
11+
via Rust's `option_env!` macro. Since these values are baked in at compile time, the only way to properly test different override scenarios is to compile separate instances of a test program with different environment variables set.
12+
13+
## Usage
14+
15+
This test program is invoked by the integration tests with different environment variable overrides:
16+
17+
```bash
18+
# Test default behavior with format version 32
19+
cargo run -- 32
20+
21+
# Test with locked bundle
22+
TECTONIC_BUNDLE_LOCKED="https://example.com/bundle.tar" cargo run -- 32
23+
24+
# Test with custom prefix
25+
TECTONIC_BUNDLE_PREFIX="https://custom.mirror.com" cargo run -- 31
26+
```
27+
28+
## Arguments
29+
30+
- First argument: Format version number (required)
31+
32+
## Output
33+
34+
The program outputs the URL returned by `get_fallback_bundle_url()` to stdout.
35+
36+
## Environment Variables Tested
37+
38+
- `TECTONIC_BUNDLE_LOCKED`: When set to a non-empty value, this URL is returned regardless of format version
39+
- `TECTONIC_BUNDLE_PREFIX`: hard codes a custom prefix for bundle URLs
40+
41+
## Integration with Tests
42+
43+
This test program is used by [`../bundle_env_overrides.rs`](../bundle_env_overrides.rs) to verify bundle override behavior:
44+
1. Locked bundle behavior (same URL for all format versions)
45+
2. Custom prefix behavior
46+
3. Format version transitions (v < 32 vs v >= 32)
47+
4. Environment variable precedence (locked takes priority over prefix)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![doc = include_str!("../README.md")]
2+
3+
use tectonic_bundles::get_fallback_bundle_url;
4+
5+
fn main() {
6+
let args: Vec<String> = std::env::args().collect();
7+
let format_version: u32 = args
8+
.get(1)
9+
.and_then(|s| s.parse().ok())
10+
.expect("one must provide a valid format version to `get_fallback_bundle_url`");
11+
12+
let url = get_fallback_bundle_url(format_version);
13+
println!("{url}");
14+
}

0 commit comments

Comments
 (0)