Files
mail/scripts/stage-pdfium.py

86 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Stage a checksum-pinned, non-V8 PDFium runtime for desktop and mobile app packaging.
No downloads happen at application startup or during cargo build. Release
packaging calls this explicitly; developers can target target/debug.
"""
import argparse
import hashlib
import io
import json
import pathlib
import shutil
import tarfile
import urllib.request
RELEASE = "chromium/8044"
ASSETS = {
"linux-x64": ("eb142f416aed3a72fc5a02dbd5884868a16cb99dc0cf53e6bdd64afbf67b05f4", "lib/libpdfium.so"),
"linux-arm64": ("e98400ef5f005f27cfba5c14f72d464e25187298f04950de46646033cf24cef0", "lib/libpdfium.so"),
"mac-arm64": ("61424884d4a7f153b808deba6437848e4400834ce30aaf95d3050da44df8f420", "lib/libpdfium.dylib"),
"mac-x64": ("a93d44238e05de20028446561b951d50988b849efbbe56fe40c0d376c05b45e8", "lib/libpdfium.dylib"),
"android-arm64": ("37686e64fa005484d619550a78805500de4c5a6f4df7aa06d8576145bfbee98f", "lib/libpdfium.so"),
"android-x64": ("e7e3072b8ad6f36ef58db999c2d91a22fa165a904d69385f13db4537c233d736", "lib/libpdfium.so"),
"ios-device-arm64": ("52be735d07b6c498ed8ce68b7f0cb5a209bbe61593c7a86c556756b80abbb938", "lib/libpdfium.dylib"),
"ios-simulator-arm64": ("544537fce5257146e0df501c632d490d1aa95768737770f1912c8e23b1dd238d", "lib/libpdfium.dylib"),
"ios-simulator-x64": ("8d4e33e415e29894aa98481bc9a9162a42039a1672796db14c9fed9cd545c881", "lib/libpdfium.dylib"),
"win-x64": ("78a17d9a5f14467631c26a3ac8741b27a0471ecc05bd6a119b523598160a0537", "bin/pdfium.dll"),
"win-arm64": ("6c9ac0ddc69edd8a18d47b95098a5b843eaed5c5bbdcb9587a18c196457449f8", "bin/pdfium.dll"),
}
def stage(target, destination, cache=None, licenses_destination=None):
checksum, library = ASSETS[target]
url = f"https://github.com/bblanchon/pdfium-binaries/releases/download/{RELEASE}/pdfium-{target}.tgz"
cached = cache / f"{checksum}.tgz" if cache else None
if cached and cached.is_file():
data = cached.read_bytes()
else:
with urllib.request.urlopen(url, timeout=60) as response:
data = response.read(64 * 1024 * 1024 + 1)
if hashlib.sha256(data).hexdigest() != checksum:
raise ValueError("PDFium archive checksum mismatch")
if cached and not cached.is_file():
cache.mkdir(parents=True, exist_ok=True)
# Concurrent Xcode architecture builds must never observe a partial cache.
import tempfile
with tempfile.NamedTemporaryFile(dir=cache, delete=False) as temp:
temp.write(data)
temporary = pathlib.Path(temp.name)
temporary.replace(cached)
destination.mkdir(parents=True, exist_ok=True)
licenses_destination = licenses_destination or destination / "pdfium-licenses"
with tarfile.open(fileobj=io.BytesIO(data), mode="r:gz") as archive:
members = {member.name.removeprefix("./"): member for member in archive.getmembers()}
for name, member in members.items():
if name != library and not name.startswith("licenses/") and name != "LICENSE":
continue
if not member.isfile() or ".." in pathlib.PurePosixPath(name).parts:
raise ValueError("Invalid PDFium archive member")
output = (
destination / pathlib.Path(library).name
if name == library
else licenses_destination / name
)
output.parent.mkdir(parents=True, exist_ok=True)
with archive.extractfile(member) as source, output.open("wb") as sink:
shutil.copyfileobj(source, sink)
if not (destination / pathlib.Path(library).name).is_file():
raise ValueError("PDFium runtime missing from archive")
licenses_destination.mkdir(parents=True, exist_ok=True)
(licenses_destination / "build.json").write_text(json.dumps({"release": RELEASE, "sha256": checksum, "url": url}, indent=2) + "\n")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("target", choices=ASSETS)
parser.add_argument("destination", type=pathlib.Path)
parser.add_argument("--cache", type=pathlib.Path, help="Verified archive cache for repeated packaging builds")
parser.add_argument(
"--licenses-destination",
type=pathlib.Path,
help="place PDFium notices outside the runtime-library directory",
)
args = parser.parse_args()
stage(args.target, args.destination, args.cache, args.licenses_destination)