Skip to content

Commit ed6d371

Browse files
committed
wip
1 parent 1f39946 commit ed6d371

File tree

2 files changed

+178
-1
lines changed

2 files changed

+178
-1
lines changed
22.7 KB
Binary file not shown.

scripts/release.py

Lines changed: 178 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import functools
56
import json
67
import os
78
import shutil
@@ -22,6 +23,8 @@
2223

2324
ROOT = Path(__file__).resolve().parents[1]
2425
DIST_DIR = ROOT / "target" / "dist"
26+
OPENSSL_VENDOR_ROOT = ROOT / "target" / "vendored-openssl"
27+
OPENSSL_BUILD_CACHE = ROOT / "target" / "openssl-build"
2528

2629

2730
def extend_path() -> None:
@@ -206,6 +209,177 @@ def determine_build_command(target: str, host: str) -> list[str]:
206209
return ["cargo", "build", "--release", "--target", target]
207210

208211

212+
def is_linux_target(target: str) -> bool:
213+
return target.endswith("unknown-linux-gnu")
214+
215+
216+
@functools.lru_cache()
217+
def detect_openssl_sys_version() -> str:
218+
lock_path = ROOT / "Cargo.lock"
219+
if not lock_path.exists():
220+
sys.exit(
221+
"Cargo.lock is required to determine the openssl-sys version for vendoring."
222+
)
223+
224+
try:
225+
lock_data = tomllib.loads(lock_path.read_text())
226+
except tomllib.TOMLDecodeError as exc:
227+
sys.exit(f"Unable to parse Cargo.lock while preparing OpenSSL: {exc}")
228+
229+
for package in lock_data.get("package", []):
230+
if package.get("name") == "openssl-sys":
231+
version = package.get("version")
232+
if version:
233+
return version
234+
235+
sys.exit(
236+
"Unable to determine the openssl-sys version from Cargo.lock; "
237+
"ensure openssl-sys is listed and try again."
238+
)
239+
240+
241+
OPENSSL_ENV_CACHE: dict[str, dict[str, str]] = {}
242+
243+
244+
def vendored_openssl_env(target: str, build_cmd: list[str]) -> dict[str, str]:
245+
if not is_linux_target(target):
246+
return {}
247+
248+
if target in OPENSSL_ENV_CACHE:
249+
return OPENSSL_ENV_CACHE[target]
250+
251+
vendor_dir = OPENSSL_VENDOR_ROOT / target
252+
openssl_version = detect_openssl_sys_version()
253+
metadata_path = vendor_dir / "metadata.json"
254+
255+
if vendor_dir.exists():
256+
try:
257+
metadata = json.loads(metadata_path.read_text())
258+
except FileNotFoundError:
259+
metadata = {}
260+
except json.JSONDecodeError as exc:
261+
print(f"Warning: ignoring corrupt OpenSSL metadata for {target}: {exc}")
262+
metadata = {}
263+
264+
if metadata.get("openssl-sys-version") != openssl_version:
265+
print(
266+
f"Regenerating vendored OpenSSL for {target} "
267+
f"(expected openssl-sys {openssl_version}, found {metadata.get('openssl-sys-version')})."
268+
)
269+
shutil.rmtree(vendor_dir, ignore_errors=True)
270+
else:
271+
env = build_env_from_vendor(vendor_dir)
272+
OPENSSL_ENV_CACHE[target] = env
273+
return env
274+
275+
if not vendor_dir.exists():
276+
build_vendored_openssl(target, build_cmd, vendor_dir, openssl_version)
277+
278+
env = build_env_from_vendor(vendor_dir)
279+
OPENSSL_ENV_CACHE[target] = env
280+
return env
281+
282+
283+
def build_env_from_vendor(vendor_dir: Path) -> dict[str, str]:
284+
lib_dir = vendor_dir / "lib"
285+
include_dir = vendor_dir / "include"
286+
287+
if not lib_dir.exists() or not include_dir.exists():
288+
sys.exit(
289+
f"Vendored OpenSSL appears incomplete at {vendor_dir}; "
290+
"remove it and re-run the release script."
291+
)
292+
293+
env: dict[str, str] = {
294+
"OPENSSL_STATIC": "1",
295+
"OPENSSL_LIB_DIR": str(lib_dir),
296+
"OPENSSL_INCLUDE_DIR": str(include_dir),
297+
"OPENSSL_DIR": str(vendor_dir),
298+
"OPENSSL_NO_VENDOR": "1",
299+
"PKG_CONFIG_ALLOW_CROSS": "1",
300+
}
301+
302+
pkgconfig_dir = lib_dir / "pkgconfig"
303+
if pkgconfig_dir.exists():
304+
existing = os.environ.get("PKG_CONFIG_PATH")
305+
env["PKG_CONFIG_PATH"] = (
306+
f"{pkgconfig_dir}{os.pathsep}{existing}"
307+
if existing
308+
else str(pkgconfig_dir)
309+
)
310+
311+
return env
312+
313+
314+
def build_vendored_openssl(
315+
target: str, build_cmd: list[str], vendor_dir: Path, openssl_version: str
316+
) -> None:
317+
print(f"Preparing vendored OpenSSL ({openssl_version}) for {target}")
318+
OPENSSL_VENDOR_ROOT.mkdir(parents=True, exist_ok=True)
319+
OPENSSL_BUILD_CACHE.mkdir(parents=True, exist_ok=True)
320+
321+
helper_toml = textwrap.dedent(
322+
f"""\
323+
[package]
324+
name = "openssl-vendor-helper"
325+
version = "0.1.0"
326+
edition = "2021"
327+
publish = false
328+
329+
[lib]
330+
path = "lib.rs"
331+
332+
[dependencies]
333+
openssl-sys = {{ version = "{openssl_version}", features = ["vendored"] }}
334+
"""
335+
)
336+
337+
with TemporaryDirectory(dir=ROOT / "target") as tmp_dir_str:
338+
tmp_dir = Path(tmp_dir_str)
339+
(tmp_dir / "lib.rs").write_text("pub fn _vendored_openssl_marker() {}\n")
340+
(tmp_dir / "Cargo.toml").write_text(helper_toml)
341+
342+
helper_env = os.environ.copy()
343+
helper_env.update(
344+
{
345+
"OPENSSL_STATIC": "1",
346+
"PKG_CONFIG_ALLOW_CROSS": "1",
347+
"CARGO_TARGET_DIR": str(OPENSSL_BUILD_CACHE),
348+
}
349+
)
350+
351+
run(build_cmd, cwd=tmp_dir, env=helper_env)
352+
353+
build_root = OPENSSL_BUILD_CACHE / target / "release" / "build"
354+
install_dirs = sorted(
355+
build_root.glob("openssl-sys-*/out/openssl-build/install"),
356+
key=lambda path: path.stat().st_mtime,
357+
reverse=True,
358+
)
359+
360+
if not install_dirs:
361+
sys.exit(
362+
"Unable to locate the vendored OpenSSL build artifacts. "
363+
"Check the build output above for details."
364+
)
365+
366+
install_dir = install_dirs[0]
367+
temp_target = vendor_dir.with_name(f".{vendor_dir.name}.tmp")
368+
if temp_target.exists():
369+
shutil.rmtree(temp_target)
370+
shutil.copytree(install_dir, temp_target)
371+
372+
metadata = {
373+
"openssl-sys-version": openssl_version,
374+
"generated-by": "scripts/release.py",
375+
"target": target,
376+
}
377+
(temp_target / "metadata.json").write_text(json.dumps(metadata, indent=2))
378+
379+
if vendor_dir.exists():
380+
shutil.rmtree(vendor_dir)
381+
temp_target.rename(vendor_dir)
382+
209383
def order_targets(targets: list[str], host: str) -> list[str]:
210384
def priority(target: str) -> tuple[int, str]:
211385
if target == host:
@@ -222,13 +396,16 @@ def priority(target: str) -> tuple[int, str]:
222396
def build_release_binaries(targets: list[str], host: str) -> tuple[list[str], list[tuple[str, str]]]:
223397
built: list[str] = []
224398
skipped: list[tuple[str, str]] = []
399+
base_env = os.environ.copy()
225400
for target in order_targets(targets, host):
226401
try:
227402
cmd = determine_build_command(target, host)
228403
except RuntimeError as exc:
229404
skipped.append((target, str(exc)))
230405
continue
231-
run(cmd)
406+
env = base_env.copy()
407+
env.update(vendored_openssl_env(target, cmd))
408+
run(cmd, env=env)
232409
built.append(target)
233410
return built, skipped
234411

0 commit comments

Comments
 (0)