22
33from __future__ import annotations
44
5+ import functools
56import json
67import os
78import shutil
2223
2324ROOT = Path (__file__ ).resolve ().parents [1 ]
2425DIST_DIR = ROOT / "target" / "dist"
26+ OPENSSL_VENDOR_ROOT = ROOT / "target" / "vendored-openssl"
27+ OPENSSL_BUILD_CACHE = ROOT / "target" / "openssl-build"
2528
2629
2730def 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+
209383def 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]:
222396def 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