dsh-ops 0.0.0-stage → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +202 -0
- package/LICENSE +30 -0
- package/NOTICE +106 -0
- package/PROVENANCE.md +435 -0
- package/README.en.md +126 -0
- package/README.md +115 -2
- package/README.zh.md +116 -0
- package/bin/dsh-ops.mjs +1216 -0
- package/cordis.patch.yml +160 -0
- package/docs/manual-validation.md +53 -0
- package/docs/release-0.2.1.md +72 -0
- package/docs/schema-baseline.json +64 -0
- package/docs/schema-current.json +84 -0
- package/docs/schema-measurement.md +17 -0
- package/dsh-plugin.json +88 -0
- package/icon.svg +12 -0
- package/lib/binary.js +409 -0
- package/lib/config.js +198 -0
- package/lib/handshake.js +252 -0
- package/lib/index.js +108 -0
- package/lib/jobs.js +42 -0
- package/lib/policy.js +64 -0
- package/lib/presentation.js +63 -0
- package/lib/profile-install.js +61 -0
- package/lib/rust.js +194 -0
- package/lib/session-shells.js +78 -0
- package/lib/shells.js +998 -0
- package/lib/tools.js +657 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +114 -4
- package/vendor/fastctx/Cargo.lock +3210 -0
- package/vendor/fastctx/Cargo.toml +94 -0
- package/vendor/fastctx/FORK.md +119 -0
- package/vendor/fastctx/LICENSE-APACHE +201 -0
- package/vendor/fastctx/NOTICE +40 -0
- package/vendor/fastctx/README.md +439 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES.md +17 -0
- package/vendor/fastctx/THIRD_PARTY_LICENSES_RUST.md +7914 -0
- package/vendor/fastctx/UPSTREAM.md +49 -0
- package/vendor/fastctx/build.rs +413 -0
- package/vendor/fastctx/src/background_status.rs +403 -0
- package/vendor/fastctx/src/binary.rs +75 -0
- package/vendor/fastctx/src/bounded_sort.rs +500 -0
- package/vendor/fastctx/src/budget.rs +781 -0
- package/vendor/fastctx/src/cli/mod.rs +110 -0
- package/vendor/fastctx/src/context_guard.rs +289 -0
- package/vendor/fastctx/src/control/mod.rs +6 -0
- package/vendor/fastctx/src/control/paths.rs +49 -0
- package/vendor/fastctx/src/control/settings.rs +753 -0
- package/vendor/fastctx/src/control/transaction.rs +531 -0
- package/vendor/fastctx/src/edit/document.rs +535 -0
- package/vendor/fastctx/src/edit/locks.rs +371 -0
- package/vendor/fastctx/src/edit/mod.rs +213 -0
- package/vendor/fastctx/src/edit/private_storage/unix.rs +315 -0
- package/vendor/fastctx/src/edit/private_storage/windows.rs +793 -0
- package/vendor/fastctx/src/edit/private_storage.rs +234 -0
- package/vendor/fastctx/src/edit/replace.rs +1030 -0
- package/vendor/fastctx/src/edit_server.rs +53 -0
- package/vendor/fastctx/src/encoding/reference_v011.rs +587 -0
- package/vendor/fastctx/src/encoding/snapshot_pipeline.rs +1678 -0
- package/vendor/fastctx/src/encoding.rs +1118 -0
- package/vendor/fastctx/src/file_executor.rs +1151 -0
- package/vendor/fastctx/src/file_snapshot.rs +1491 -0
- package/vendor/fastctx/src/glob_filter.rs +98 -0
- package/vendor/fastctx/src/glob_tool.rs +653 -0
- package/vendor/fastctx/src/grep_sink.rs +1162 -0
- package/vendor/fastctx/src/grep_tool.rs +2449 -0
- package/vendor/fastctx/src/lib.rs +45 -0
- package/vendor/fastctx/src/main.rs +15 -0
- package/vendor/fastctx/src/model.rs +51 -0
- package/vendor/fastctx/src/model_guidance.rs +62 -0
- package/vendor/fastctx/src/operation.rs +356 -0
- package/vendor/fastctx/src/ordered_window.rs +1235 -0
- package/vendor/fastctx/src/os_environment.rs +414 -0
- package/vendor/fastctx/src/path_codec.rs +850 -0
- package/vendor/fastctx/src/paths.rs +244 -0
- package/vendor/fastctx/src/process_identity.rs +763 -0
- package/vendor/fastctx/src/process_policy.rs +74 -0
- package/vendor/fastctx/src/read_tool/batch.rs +496 -0
- package/vendor/fastctx/src/read_tool/hex_file.rs +141 -0
- package/vendor/fastctx/src/read_tool/image_file.rs +88 -0
- package/vendor/fastctx/src/read_tool/mod.rs +245 -0
- package/vendor/fastctx/src/read_tool/pdf.rs +470 -0
- package/vendor/fastctx/src/read_tool/pdf_disabled.rs +47 -0
- package/vendor/fastctx/src/read_tool/pdf_engine.rs +664 -0
- package/vendor/fastctx/src/read_tool/text_file.rs +351 -0
- package/vendor/fastctx/src/render_plan.rs +468 -0
- package/vendor/fastctx/src/runtime/activity.rs +159 -0
- package/vendor/fastctx/src/runtime/hosts.rs +99 -0
- package/vendor/fastctx/src/runtime/journal.rs +556 -0
- package/vendor/fastctx/src/runtime/local_ipc.rs +186 -0
- package/vendor/fastctx/src/runtime/mod.rs +746 -0
- package/vendor/fastctx/src/runtime/protocol.rs +296 -0
- package/vendor/fastctx/src/runtime/session.rs +536 -0
- package/vendor/fastctx/src/runtime/windows_process.rs +66 -0
- package/vendor/fastctx/src/search_parallelism.rs +106 -0
- package/vendor/fastctx/src/search_text.rs +227 -0
- package/vendor/fastctx/src/server.rs +359 -0
- package/vendor/fastctx/src/server_manifest.rs +468 -0
- package/vendor/fastctx/src/server_support.rs +826 -0
- package/vendor/fastctx/src/session.rs +629 -0
- package/vendor/fastctx/src/shell/apply_patch_hint.rs +41 -0
- package/vendor/fastctx/src/shell/bash.rs +263 -0
- package/vendor/fastctx/src/shell/buffer.rs +108 -0
- package/vendor/fastctx/src/shell/encoding.rs +403 -0
- package/vendor/fastctx/src/shell/foreground.rs +115 -0
- package/vendor/fastctx/src/shell/jobs/admission.rs +91 -0
- package/vendor/fastctx/src/shell/jobs/background.rs +146 -0
- package/vendor/fastctx/src/shell/jobs/host.rs +830 -0
- package/vendor/fastctx/src/shell/jobs/identity.rs +29 -0
- package/vendor/fastctx/src/shell/jobs/mod.rs +1513 -0
- package/vendor/fastctx/src/shell/jobs/model.rs +244 -0
- package/vendor/fastctx/src/shell/jobs/output_log.rs +1148 -0
- package/vendor/fastctx/src/shell/jobs/store.rs +1300 -0
- package/vendor/fastctx/src/shell/mod.rs +345 -0
- package/vendor/fastctx/src/shell/normalize.rs +389 -0
- package/vendor/fastctx/src/shell/output.rs +406 -0
- package/vendor/fastctx/src/shell/process.rs +493 -0
- package/vendor/fastctx/src/shell_server.rs +156 -0
- package/vendor/fastctx/src/skip_report.rs +83 -0
- package/vendor/fastctx/src/stdio_transport.rs +177 -0
- package/vendor/fastctx/src/tool_schema.rs +204 -0
- package/vendor/fastctx/src/traversal.rs +846 -0
- package/vendor/fastctx/third-party/pdfium-7763/LICENSE +9 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/abseil.txt +202 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/agg23.txt +14 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/fast_float.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/freetype.txt +169 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/icu.txt +542 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/lcms.txt +27 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.ijg +260 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libjpeg_turbo.md +135 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libopenjpeg.txt +32 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libpng.txt +134 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/libtiff.txt +21 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/llvm-libc.txt +278 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/pdfium.txt +230 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/simdutf.txt +18 -0
- package/vendor/fastctx/third-party/pdfium-7763/licenses/zlib.txt +29 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Vendored FastCtx — provenance
|
|
2
|
+
|
|
3
|
+
This directory is a copy of the FastCtx source, taken from upstream `main`. It is
|
|
4
|
+
**not** a submodule and not a fork repository: it is a pinned snapshot that lives
|
|
5
|
+
in this repository so that the runtime this plugin hosts can be built from
|
|
6
|
+
source, audited, and patch-rebased without a second checkout.
|
|
7
|
+
|
|
8
|
+
## What was taken
|
|
9
|
+
|
|
10
|
+
| Fact | Value |
|
|
11
|
+
| --- | --- |
|
|
12
|
+
| Upstream | https://github.com/yc-duan/fastctx |
|
|
13
|
+
| License | Apache-2.0 (see `LICENSE-APACHE` and `NOTICE` in this directory) |
|
|
14
|
+
| Source ref | `main` |
|
|
15
|
+
| Source commit | `ccaa157790d02328a60786eb94ee5ad698995a5f` |
|
|
16
|
+
| Commit date | 2026-08-30 04:09:42 +0800 |
|
|
17
|
+
| Commit subject | `test(contract): update the frozen run description for the Killed terminal` |
|
|
18
|
+
| Cargo package | `fastctx` version `0.2.6` |
|
|
19
|
+
| Copied | 2026-10-08 |
|
|
20
|
+
|
|
21
|
+
The upstream release tag `v0.2.6` points at `22b40ea324a186a921dfe38c205e3ffacdf6546d`
|
|
22
|
+
(2026-08-23), which is an ancestor of the commit copied here. The snapshot is
|
|
23
|
+
therefore **newer than the released `0.2.6` tag** while still declaring package
|
|
24
|
+
version `0.2.6`. That is upstream's own state, not an edit made here.
|
|
25
|
+
|
|
26
|
+
The published platform packages this plugin can fall back to
|
|
27
|
+
(`@fastctx/win32-x64` and siblings, version `0.2.6`) are built from the tag, so a
|
|
28
|
+
source build and a package fallback are close but not byte-identical. The plugin
|
|
29
|
+
resolves the source build first when one exists, and reports which one it used.
|
|
30
|
+
|
|
31
|
+
## Why a snapshot instead of a submodule
|
|
32
|
+
|
|
33
|
+
- The runtime is a native binary; a checkout that cannot build it has to be able
|
|
34
|
+
to explain exactly which source produced which executable.
|
|
35
|
+
- `FORK.md` next to this file records every difference from upstream, so a
|
|
36
|
+
re-vendor is a diff against a documented state rather than a guess.
|
|
37
|
+
- Nothing here imports from the network at build time except the pinned Pdfium
|
|
38
|
+
artifact `build.rs` downloads for the default `pdf` feature.
|
|
39
|
+
|
|
40
|
+
## Re-vendoring
|
|
41
|
+
|
|
42
|
+
Replacing this tree is a deliberate act:
|
|
43
|
+
|
|
44
|
+
1. Check out the intended upstream commit in a scratch clone.
|
|
45
|
+
2. Copy it over this directory excluding `.git/`, `.github/`, `packages/`, and
|
|
46
|
+
`scripts/` — the removals `FORK.md` documents.
|
|
47
|
+
3. Update the table above, and re-record the removals in `FORK.md`.
|
|
48
|
+
4. Rebuild: `node bin/dsh-ops.mjs build --force`.
|
|
49
|
+
5. Re-run the gate: `npm run verify`.
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
use flate2::read::GzDecoder;
|
|
2
|
+
use sha2::{Digest, Sha256};
|
|
3
|
+
use std::env;
|
|
4
|
+
use std::fs;
|
|
5
|
+
use std::io::{Cursor, Read};
|
|
6
|
+
use std::path::{Path, PathBuf};
|
|
7
|
+
use tar::Archive;
|
|
8
|
+
|
|
9
|
+
const RELEASE_TAG: &str = "chromium/7763";
|
|
10
|
+
const LOCAL_ARCHIVE_ENV: &str = "FASTCTX_PDFIUM_ARCHIVE";
|
|
11
|
+
const CACHE_DIRECTORY_ENV: &str = "FASTCTX_PDFIUM_CACHE_DIR";
|
|
12
|
+
const DISTRIBUTION_ENV: &str = "FASTCTX_DISTRIBUTION";
|
|
13
|
+
const MAX_ARCHIVE_BYTES: u64 = 64 * 1024 * 1024;
|
|
14
|
+
const MAX_LIBRARY_BYTES: u64 = 64 * 1024 * 1024;
|
|
15
|
+
const DOWNLOAD_ATTEMPTS: u32 = 6;
|
|
16
|
+
|
|
17
|
+
struct Artifact {
|
|
18
|
+
asset: &'static str,
|
|
19
|
+
archive_sha256: &'static str,
|
|
20
|
+
member: &'static str,
|
|
21
|
+
library_sha256: &'static str,
|
|
22
|
+
filename: &'static str,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fn main() {
|
|
26
|
+
println!("cargo:rerun-if-changed=build.rs");
|
|
27
|
+
println!("cargo:rerun-if-changed=Cargo.toml");
|
|
28
|
+
println!("cargo:rerun-if-changed=Cargo.lock");
|
|
29
|
+
println!("cargo:rerun-if-changed=src");
|
|
30
|
+
println!("cargo:rerun-if-env-changed={DISTRIBUTION_ENV}");
|
|
31
|
+
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_PDF");
|
|
32
|
+
emit_build_id();
|
|
33
|
+
if env::var_os("CARGO_FEATURE_PDF").is_none() {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
println!("cargo:rerun-if-env-changed={LOCAL_ARCHIVE_ENV}");
|
|
37
|
+
|
|
38
|
+
let target = env::var("TARGET").expect("Cargo did not provide TARGET");
|
|
39
|
+
let target_env = format!(
|
|
40
|
+
"{LOCAL_ARCHIVE_ENV}_{}",
|
|
41
|
+
target.replace('-', "_").to_ascii_uppercase()
|
|
42
|
+
);
|
|
43
|
+
println!("cargo:rerun-if-env-changed={target_env}");
|
|
44
|
+
|
|
45
|
+
let artifact = artifact_for_target(&target).unwrap_or_else(|| {
|
|
46
|
+
panic!(
|
|
47
|
+
"bundled PDF support does not have a pinned Pdfium artifact for target {target}; supported targets are Windows x64, Windows arm64, Linux x64, macOS x64, and macOS arm64"
|
|
48
|
+
)
|
|
49
|
+
});
|
|
50
|
+
let archive_bytes = load_archive(&artifact, &target_env);
|
|
51
|
+
verify_sha256(
|
|
52
|
+
&archive_bytes,
|
|
53
|
+
artifact.archive_sha256,
|
|
54
|
+
"Pdfium release archive",
|
|
55
|
+
);
|
|
56
|
+
let library_bytes = extract_member(&archive_bytes, artifact.member);
|
|
57
|
+
verify_sha256(
|
|
58
|
+
&library_bytes,
|
|
59
|
+
artifact.library_sha256,
|
|
60
|
+
"Pdfium dynamic library",
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("Cargo did not provide OUT_DIR"));
|
|
64
|
+
let embedded_dir = out_dir.join("bundled-pdfium");
|
|
65
|
+
fs::create_dir_all(&embedded_dir).expect("failed to create Pdfium build output directory");
|
|
66
|
+
let library_path = embedded_dir.join(artifact.filename);
|
|
67
|
+
fs::write(&library_path, library_bytes).expect("failed to write extracted Pdfium library");
|
|
68
|
+
write_generated_module(&out_dir, &library_path, &artifact);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fn emit_build_id() {
|
|
72
|
+
let mut files = vec![
|
|
73
|
+
PathBuf::from("Cargo.toml"),
|
|
74
|
+
PathBuf::from("Cargo.lock"),
|
|
75
|
+
PathBuf::from("build.rs"),
|
|
76
|
+
];
|
|
77
|
+
collect_source_files(Path::new("src"), &mut files);
|
|
78
|
+
files.sort();
|
|
79
|
+
let mut hasher = Sha256::new();
|
|
80
|
+
for path in files {
|
|
81
|
+
hasher.update(path.to_string_lossy().as_bytes());
|
|
82
|
+
hasher.update([0]);
|
|
83
|
+
hasher.update(fs::read(&path).unwrap_or_else(|error| {
|
|
84
|
+
panic!("failed to hash {} for build id: {error}", path.display())
|
|
85
|
+
}));
|
|
86
|
+
hasher.update([0xff]);
|
|
87
|
+
}
|
|
88
|
+
for name in [
|
|
89
|
+
"CARGO_PKG_VERSION",
|
|
90
|
+
"TARGET",
|
|
91
|
+
"PROFILE",
|
|
92
|
+
"CARGO_FEATURE_PDF",
|
|
93
|
+
DISTRIBUTION_ENV,
|
|
94
|
+
] {
|
|
95
|
+
hasher.update(name.as_bytes());
|
|
96
|
+
hasher.update(*b"=");
|
|
97
|
+
if let Some(value) = env::var_os(name) {
|
|
98
|
+
hasher.update(value.to_string_lossy().as_bytes());
|
|
99
|
+
}
|
|
100
|
+
hasher.update([0]);
|
|
101
|
+
}
|
|
102
|
+
let build_id = hex::encode(hasher.finalize());
|
|
103
|
+
println!("cargo:rustc-env=FASTCTX_BUILD_ID={}", &build_id[..16]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn collect_source_files(directory: &Path, output: &mut Vec<PathBuf>) {
|
|
107
|
+
let mut entries = fs::read_dir(directory)
|
|
108
|
+
.unwrap_or_else(|error| {
|
|
109
|
+
panic!(
|
|
110
|
+
"failed to enumerate {} for build id: {error}",
|
|
111
|
+
directory.display()
|
|
112
|
+
)
|
|
113
|
+
})
|
|
114
|
+
.map(|entry| entry.expect("failed to enumerate a source entry").path())
|
|
115
|
+
.collect::<Vec<_>>();
|
|
116
|
+
entries.sort();
|
|
117
|
+
for path in entries {
|
|
118
|
+
if path.is_dir() {
|
|
119
|
+
collect_source_files(&path, output);
|
|
120
|
+
} else if path.extension().is_some_and(|extension| extension == "rs") {
|
|
121
|
+
output.push(path);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fn artifact_for_target(target: &str) -> Option<Artifact> {
|
|
127
|
+
let artifact = match target {
|
|
128
|
+
"x86_64-pc-windows-msvc" | "x86_64-pc-windows-gnu" => Artifact {
|
|
129
|
+
asset: "pdfium-win-x64.tgz",
|
|
130
|
+
archive_sha256: "45c4cc5d052ef8ec6380b946b548a76100f4675e38362000a4c732e16d5e8eda",
|
|
131
|
+
member: "bin/pdfium.dll",
|
|
132
|
+
library_sha256: "a63949dc46a7314bba619ac6cc1b3849627e137f542ae31b2b36b302841f77ae",
|
|
133
|
+
filename: "pdfium.dll",
|
|
134
|
+
},
|
|
135
|
+
"aarch64-pc-windows-msvc" => Artifact {
|
|
136
|
+
asset: "pdfium-win-arm64.tgz",
|
|
137
|
+
archive_sha256: "e99570d74211a88d41589feb8861ef9b40d78c8d26825270ad4fb7a9a1d02f6d",
|
|
138
|
+
member: "bin/pdfium.dll",
|
|
139
|
+
library_sha256: "682ee648af5629c1194bb3649e67252d162bdab06e00cded3e2ebbe88be7bf49",
|
|
140
|
+
filename: "pdfium.dll",
|
|
141
|
+
},
|
|
142
|
+
"x86_64-unknown-linux-gnu" => Artifact {
|
|
143
|
+
asset: "pdfium-linux-x64.tgz",
|
|
144
|
+
archive_sha256: "e3f0c66b2daad710cb6c8edd4a8c45c8902995e359dc0775917fc16e2e56349d",
|
|
145
|
+
member: "lib/libpdfium.so",
|
|
146
|
+
library_sha256: "9167f6d9190f217fab5bfb864620108e280c124b7f7762cc4ef66e1078e0ec62",
|
|
147
|
+
filename: "libpdfium.so",
|
|
148
|
+
},
|
|
149
|
+
"x86_64-apple-darwin" => Artifact {
|
|
150
|
+
asset: "pdfium-mac-x64.tgz",
|
|
151
|
+
archive_sha256: "f455e0868ef7e5174a315de8789ee2b7a5544638d0ac7a3312ea7b68ebbc99cb",
|
|
152
|
+
member: "lib/libpdfium.dylib",
|
|
153
|
+
library_sha256: "b67d8bc289bf9916f697add53b163730ff22243ea896e97f942e09cb634e8a14",
|
|
154
|
+
filename: "libpdfium.dylib",
|
|
155
|
+
},
|
|
156
|
+
"aarch64-apple-darwin" => Artifact {
|
|
157
|
+
asset: "pdfium-mac-arm64.tgz",
|
|
158
|
+
archive_sha256: "9acf49e46c68992cd40810e88264b1ad171805d02fd41c4cca336aad6653b333",
|
|
159
|
+
member: "lib/libpdfium.dylib",
|
|
160
|
+
library_sha256: "0501a43035c44ccd498d77c1bf7fb8aa88facdd0963d423f51cfea2d4d46f52b",
|
|
161
|
+
filename: "libpdfium.dylib",
|
|
162
|
+
},
|
|
163
|
+
_ => return None,
|
|
164
|
+
};
|
|
165
|
+
Some(artifact)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Resolves the pinned archive from the cheapest source that can supply it: an
|
|
169
|
+
/// explicit local path, then the on-disk cache, then the release CDN.
|
|
170
|
+
///
|
|
171
|
+
/// Cargo gives every feature-and-profile combination its own build script run, and CI
|
|
172
|
+
/// visits several of them per job, so without a cache one job fetches the same pinned
|
|
173
|
+
/// bytes once per combination - measured at four over a subset of this repository's own
|
|
174
|
+
/// sequence. Every one of those is an independent chance to land in a release CDN
|
|
175
|
+
/// outage, which is how a green build turns red for no local reason.
|
|
176
|
+
fn load_archive(artifact: &Artifact, target_env: &str) -> Vec<u8> {
|
|
177
|
+
if let Some(path) = env::var_os(target_env).or_else(|| env::var_os(LOCAL_ARCHIVE_ENV)) {
|
|
178
|
+
println!("cargo:rerun-if-changed={}", Path::new(&path).display());
|
|
179
|
+
let file = fs::File::open(&path).unwrap_or_else(|error| {
|
|
180
|
+
panic!(
|
|
181
|
+
"failed to read local Pdfium archive {}: {error}",
|
|
182
|
+
Path::new(&path).display()
|
|
183
|
+
)
|
|
184
|
+
});
|
|
185
|
+
return read_limited(file, MAX_ARCHIVE_BYTES, "local Pdfium archive");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let cached = cache_path(artifact);
|
|
189
|
+
if let Some(bytes) = cached
|
|
190
|
+
.as_deref()
|
|
191
|
+
.and_then(|path| read_cached_archive(path, artifact))
|
|
192
|
+
{
|
|
193
|
+
return bytes;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let url = format!(
|
|
197
|
+
"https://github.com/bblanchon/pdfium-binaries/releases/download/{RELEASE_TAG}/{}",
|
|
198
|
+
artifact.asset
|
|
199
|
+
);
|
|
200
|
+
let bytes = read_limited(
|
|
201
|
+
download(&url).into_reader(),
|
|
202
|
+
MAX_ARCHIVE_BYTES,
|
|
203
|
+
"downloaded Pdfium archive",
|
|
204
|
+
);
|
|
205
|
+
if let Some(path) = cached.as_deref() {
|
|
206
|
+
store_cached_archive(path, &bytes);
|
|
207
|
+
}
|
|
208
|
+
bytes
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Names the cache entry after the digest the build already pins, so an entry can
|
|
212
|
+
/// only ever be the exact bytes this build wants. A damaged or half-written file is
|
|
213
|
+
/// a miss rather than a silent substitution, and `main` verifies the result whatever
|
|
214
|
+
/// source produced it.
|
|
215
|
+
fn cache_path(artifact: &Artifact) -> Option<PathBuf> {
|
|
216
|
+
let directory = cache_directory()?;
|
|
217
|
+
Some(directory.join(format!("{}-{}", artifact.archive_sha256, artifact.asset)))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fn cache_directory() -> Option<PathBuf> {
|
|
221
|
+
// Deliberately not declared with `cargo:rerun-if-env-changed`: the pinned digest
|
|
222
|
+
// makes the produced bytes independent of where they were found, so tracking this
|
|
223
|
+
// variable would buy nothing but extra build script runs - the very cost the cache
|
|
224
|
+
// exists to remove. An empty value opts a hermetic build out entirely.
|
|
225
|
+
if let Some(explicit) = env::var_os(CACHE_DIRECTORY_ENV) {
|
|
226
|
+
if explicit.is_empty() {
|
|
227
|
+
return None;
|
|
228
|
+
}
|
|
229
|
+
return Some(PathBuf::from(explicit));
|
|
230
|
+
}
|
|
231
|
+
default_cache_root().map(|root| root.join("fastctx").join("pdfium"))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// `cfg!` rather than `#[cfg]` keeps every branch compiled on every host. This
|
|
235
|
+
/// repository has a single local build gate, so platform-gated code that exists only
|
|
236
|
+
/// on the other targets is precisely what it cannot check before CI.
|
|
237
|
+
fn default_cache_root() -> Option<PathBuf> {
|
|
238
|
+
if cfg!(windows) {
|
|
239
|
+
return env::var_os("LOCALAPPDATA").map(PathBuf::from);
|
|
240
|
+
}
|
|
241
|
+
if cfg!(target_os = "macos") {
|
|
242
|
+
return env::var_os("HOME").map(|home| PathBuf::from(home).join("Library/Caches"));
|
|
243
|
+
}
|
|
244
|
+
if let Some(directory) = env::var_os("XDG_CACHE_HOME") {
|
|
245
|
+
return Some(PathBuf::from(directory));
|
|
246
|
+
}
|
|
247
|
+
env::var_os("HOME").map(|home| PathBuf::from(home).join(".cache"))
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fn read_cached_archive(path: &Path, artifact: &Artifact) -> Option<Vec<u8>> {
|
|
251
|
+
let file = fs::File::open(path).ok()?;
|
|
252
|
+
let bytes = try_read_limited(file, MAX_ARCHIVE_BYTES).ok()?;
|
|
253
|
+
if hex::encode(Sha256::digest(&bytes)) == artifact.archive_sha256 {
|
|
254
|
+
return Some(bytes);
|
|
255
|
+
}
|
|
256
|
+
// An entry that no longer matches the digest it is named after was damaged after
|
|
257
|
+
// it was written. Say so - a cache quietly missing forever is how a workaround
|
|
258
|
+
// becomes permanent - then fall back to the network instead of failing a build
|
|
259
|
+
// over storage this crate owns and can rebuild.
|
|
260
|
+
println!(
|
|
261
|
+
"cargo:warning=cached Pdfium archive {} no longer matches its pinned digest; refetching",
|
|
262
|
+
path.display()
|
|
263
|
+
);
|
|
264
|
+
let _ = fs::remove_file(path);
|
|
265
|
+
None
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/// Best effort by design: a build that cannot populate the cache is slower, never
|
|
269
|
+
/// wrong, so an unwritable directory leaves the download path carrying the build.
|
|
270
|
+
fn store_cached_archive(path: &Path, bytes: &[u8]) {
|
|
271
|
+
let (Some(directory), Some(name)) = (path.parent(), path.file_name().and_then(|n| n.to_str()))
|
|
272
|
+
else {
|
|
273
|
+
return;
|
|
274
|
+
};
|
|
275
|
+
if fs::create_dir_all(directory).is_err() {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// Publish through a rename so a cargo invocation reading the same entry in
|
|
279
|
+
// parallel never observes a partial archive under the final name.
|
|
280
|
+
let staging = directory.join(format!("{name}.{}.partial", std::process::id()));
|
|
281
|
+
if fs::write(&staging, bytes).is_ok() && fs::rename(&staging, path).is_ok() {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
let _ = fs::remove_file(&staging);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/// Fetches the pinned release asset, retrying only the failures a later attempt can
|
|
288
|
+
/// clear. Retrying cannot widen what the build accepts: the bytes are still checked
|
|
289
|
+
/// against the pinned digest afterwards. The attempt count spans roughly a minute of
|
|
290
|
+
/// backoff because the release CDN goes down for stretches longer than a handful of
|
|
291
|
+
/// seconds; widening a wait like this one only delays the report of a real outage,
|
|
292
|
+
/// while keeping it tight reports a healthy build as broken.
|
|
293
|
+
fn download(url: &str) -> ureq::Response {
|
|
294
|
+
let mut attempt = 1;
|
|
295
|
+
loop {
|
|
296
|
+
let error = match ureq::get(url)
|
|
297
|
+
.timeout(std::time::Duration::from_secs(150))
|
|
298
|
+
.call()
|
|
299
|
+
{
|
|
300
|
+
Ok(response) => return response,
|
|
301
|
+
Err(error) => error,
|
|
302
|
+
};
|
|
303
|
+
if attempt >= DOWNLOAD_ATTEMPTS || !is_retryable(&error) {
|
|
304
|
+
panic!(
|
|
305
|
+
"failed to download pinned Pdfium archive from {url}: {error}. For an offline build, set {LOCAL_ARCHIVE_ENV} to the matching archive path"
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
println!("cargo:warning=attempt {attempt} to download {url} failed ({error}); retrying");
|
|
309
|
+
std::thread::sleep(std::time::Duration::from_secs(2u64.pow(attempt)));
|
|
310
|
+
attempt += 1;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/// An absent or forbidden asset means the pinned release tag is wrong, which fails
|
|
315
|
+
/// identically however often it is asked; only transport faults and server-side
|
|
316
|
+
/// failures are worth repeating.
|
|
317
|
+
fn is_retryable(error: &ureq::Error) -> bool {
|
|
318
|
+
match error {
|
|
319
|
+
ureq::Error::Status(code, _) => *code == 408 || *code == 429 || *code >= 500,
|
|
320
|
+
ureq::Error::Transport(_) => true,
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
fn read_limited(reader: impl Read, limit: u64, label: &str) -> Vec<u8> {
|
|
325
|
+
try_read_limited(reader, limit).unwrap_or_else(|error| panic!("{label}: {error}"))
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// The fallible half exists for the cache, where an unreadable or oversized entry has
|
|
329
|
+
/// a recovery - refetch - that a missing download does not.
|
|
330
|
+
fn try_read_limited(mut reader: impl Read, limit: u64) -> Result<Vec<u8>, String> {
|
|
331
|
+
let mut bytes = Vec::new();
|
|
332
|
+
reader
|
|
333
|
+
.by_ref()
|
|
334
|
+
.take(limit + 1)
|
|
335
|
+
.read_to_end(&mut bytes)
|
|
336
|
+
.map_err(|error| format!("read failed: {error}"))?;
|
|
337
|
+
if bytes.len() as u64 > limit {
|
|
338
|
+
return Err(format!(
|
|
339
|
+
"exceeds the {} MiB build safety limit",
|
|
340
|
+
limit / (1024 * 1024)
|
|
341
|
+
));
|
|
342
|
+
}
|
|
343
|
+
Ok(bytes)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
fn verify_sha256(bytes: &[u8], expected: &str, label: &str) {
|
|
347
|
+
let actual = hex::encode(Sha256::digest(bytes));
|
|
348
|
+
assert_eq!(
|
|
349
|
+
actual, expected,
|
|
350
|
+
"{label} SHA-256 mismatch; expected {expected}, got {actual}"
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
fn extract_member(archive_bytes: &[u8], expected_member: &str) -> Vec<u8> {
|
|
355
|
+
let decoder = GzDecoder::new(Cursor::new(archive_bytes));
|
|
356
|
+
let mut archive = Archive::new(decoder);
|
|
357
|
+
let mut found = None;
|
|
358
|
+
for entry in archive
|
|
359
|
+
.entries()
|
|
360
|
+
.expect("failed to read Pdfium tar archive")
|
|
361
|
+
{
|
|
362
|
+
let mut entry = entry.expect("failed to read an entry from Pdfium tar archive");
|
|
363
|
+
let path = entry
|
|
364
|
+
.path()
|
|
365
|
+
.expect("Pdfium archive contains an invalid path")
|
|
366
|
+
.into_owned();
|
|
367
|
+
assert!(
|
|
368
|
+
!path.is_absolute()
|
|
369
|
+
&& !path
|
|
370
|
+
.components()
|
|
371
|
+
.any(|part| matches!(part, std::path::Component::ParentDir)),
|
|
372
|
+
"Pdfium archive contains an unsafe path: {}",
|
|
373
|
+
path.display()
|
|
374
|
+
);
|
|
375
|
+
if path == Path::new(expected_member) {
|
|
376
|
+
assert!(
|
|
377
|
+
found.is_none(),
|
|
378
|
+
"Pdfium archive contains the library more than once"
|
|
379
|
+
);
|
|
380
|
+
assert!(
|
|
381
|
+
entry.size() <= MAX_LIBRARY_BYTES,
|
|
382
|
+
"Pdfium dynamic library exceeds the {} MiB build safety limit",
|
|
383
|
+
MAX_LIBRARY_BYTES / (1024 * 1024)
|
|
384
|
+
);
|
|
385
|
+
let mut bytes = Vec::new();
|
|
386
|
+
entry
|
|
387
|
+
.by_ref()
|
|
388
|
+
.take(MAX_LIBRARY_BYTES + 1)
|
|
389
|
+
.read_to_end(&mut bytes)
|
|
390
|
+
.expect("failed to extract Pdfium dynamic library");
|
|
391
|
+
assert!(
|
|
392
|
+
bytes.len() as u64 <= MAX_LIBRARY_BYTES,
|
|
393
|
+
"Pdfium dynamic library exceeds the {} MiB build safety limit",
|
|
394
|
+
MAX_LIBRARY_BYTES / (1024 * 1024)
|
|
395
|
+
);
|
|
396
|
+
found = Some(bytes);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
found.unwrap_or_else(|| panic!("Pdfium archive did not contain {expected_member}"))
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
fn write_generated_module(out_dir: &Path, library_path: &Path, artifact: &Artifact) {
|
|
403
|
+
let path_literal = format!("{:?}", library_path.to_string_lossy());
|
|
404
|
+
let source = format!(
|
|
405
|
+
"pub const PDFIUM_BYTES: &[u8] = include_bytes!({path_literal});\n\
|
|
406
|
+
pub const PDFIUM_FILENAME: &str = {:?};\n\
|
|
407
|
+
pub const PDFIUM_SHA256: &str = {:?};\n\
|
|
408
|
+
pub const PDFIUM_RELEASE_TAG: &str = {:?};\n",
|
|
409
|
+
artifact.filename, artifact.library_sha256, RELEASE_TAG
|
|
410
|
+
);
|
|
411
|
+
fs::write(out_dir.join("pdfium_embedded.rs"), source)
|
|
412
|
+
.expect("failed to generate Pdfium embedding module");
|
|
413
|
+
}
|