#!/usr/bin/env python3 """Pinned native HTTP runtime. Run with python3 scripts/native/build.py on macOS/Linux. Windows uses curl.se's reproducible, statically linked official curl-for-win release. No runtime downloads or system curl fallback. Build products stay outside source archives. """ import os,sys,json,hashlib,urllib.request,tarfile,zipfile,subprocess,platform,shutil from pathlib import Path here=Path(__file__).resolve().parent;app=here.parents[1] lock=json.loads((here/'dependencies.lock.json').read_text());system={'Darwin':'darwin','Linux':'linux','Windows':'win32'}[platform.system()];arch={'arm64':'arm64','aarch64':'arm64','x86_64':'x64','AMD64':'x64'}[platform.machine()] work=Path(os.environ.get('CHJ_NATIVE_WORK',str(app/'build'/'native-work'))).resolve();cache=work/'cache';prefix=work/'prefix';out=app/'build'/'native-runtime'/f'{system}-{arch}' for p in (cache,prefix,out):p.mkdir(parents=True,exist_ok=True) def run(args,**kw): print('+',' '.join(map(str,args)),flush=True);subprocess.run(list(map(str,args)),check=True,**kw) def fetch(p): f=cache/p['url'].split('/')[-1] if not f.exists(): tmp=f.with_suffix(f.suffix+'.partial');urllib.request.urlretrieve(p['url'],tmp);tmp.replace(f) if hashlib.sha256(f.read_bytes()).hexdigest()!=p['sha256']:raise RuntimeError('Source checksum mismatch: '+p['name']) return f pins={p['name']:p for p in lock['sources']} licenses=out/'licenses';licenses.mkdir(exist_ok=True) if system=='win32': if arch!='x64':raise RuntimeError('Windows runtime supports x64 only') with zipfile.ZipFile(fetch(pins['curl-win'])) as z: for n in z.namelist(): if n.endswith('/bin/curl.exe'): (out/'curl.exe').write_bytes(z.read(n)) if not n.endswith('/') and (any(x in Path(n).name for x in ['COPYING','LICENSE']) or Path(n).name in ['BUILD-MANIFEST.txt','BUILD-HASHES.txt']): dst=licenses/('-'.join(Path(n).parts[1:]));dst.write_bytes(z.read(n)) else: jobs=str(min(8,os.cpu_count() or 2));env=os.environ.copy();env['PKG_CONFIG_PATH']=str(prefix/'lib/pkgconfig');env['CMAKE_PREFIX_PATH']=str(prefix) def source(name): p=pins[name];d=work/(name+'-'+p['version']) if not d.exists(): with tarfile.open(fetch(p)) as t:t.extractall(work,filter='data') if sys.version_info>=(3,12) else t.extractall(work) for n in ['COPYING','LICENSE','LICENSE.txt','COPYING.MIT']: if (d/n).exists():shutil.copy2(d/n,licenses/(name+'-'+n)) return d ssl=source('openssl') if not (prefix/'lib/libssl.a').exists(): run(['perl',ssl/'Configure','darwin64-arm64-cc' if system=='darwin' else 'linux-x86_64','no-shared','no-tests','no-module','--prefix='+str(prefix),'--libdir=lib'],cwd=ssl,env=env) run(['make','-j'+jobs],cwd=ssl,env=env);run(['make','install_sw'],cwd=ssl,env=env) def cmake(name,flags): src=source(name);b=work/(name+'-build') run(['cmake','-S',src,'-B',b,'-G','Ninja','-DCMAKE_BUILD_TYPE=Release','-DCMAKE_INSTALL_PREFIX='+str(prefix),'-DCMAKE_INSTALL_LIBDIR=lib','-DCMAKE_PREFIX_PATH='+str(prefix),'-DCMAKE_POSITION_INDEPENDENT_CODE=ON','-DBUILD_SHARED_LIBS=OFF','-DBUILD_STATIC_LIBS=ON','-DBUILD_TESTING=OFF',*flags],env=env) run(['cmake','--build',b,'--parallel',jobs],env=env);run(['cmake','--install',b],env=env) cmake('nghttp2',['-DENABLE_LIB_ONLY=ON','-DENABLE_STATIC_LIB=ON','-DENABLE_SHARED_LIB=OFF']) cmake('nghttp3',['-DENABLE_LIB_ONLY=ON','-DENABLE_STATIC_LIB=ON','-DENABLE_SHARED_LIB=OFF']) cmake('ngtcp2',['-DENABLE_LIB_ONLY=ON','-DENABLE_STATIC_LIB=ON','-DENABLE_SHARED_LIB=OFF','-DENABLE_OPENSSL=ON','-DOPENSSL_ROOT_DIR='+str(prefix),'-DOPENSSL_USE_STATIC_LIBS=TRUE','-DENABLE_GNUTLS=OFF','-DENABLE_BORINGSSL=OFF']) cmake('curl',['-DBUILD_CURL_EXE=ON','-DBUILD_STATIC_LIBS=ON','-DCURL_USE_OPENSSL=ON','-DOPENSSL_ROOT_DIR='+str(prefix),'-DOPENSSL_USE_STATIC_LIBS=TRUE','-DUSE_NGHTTP2=ON','-DUSE_NGHTTP3=ON','-DUSE_NGTCP2=ON','-DCURL_USE_LIBPSL=OFF','-DCURL_ZLIB=OFF','-DCURL_BROTLI=OFF','-DCURL_ZSTD=OFF','-DCURL_USE_LIBSSH2=OFF','-DCURL_USE_LIBSSH=OFF','-DCURL_DISABLE_LDAP=ON','-DCURL_DISABLE_LDAPS=ON','-DCURL_CA_BUNDLE=none','-DCURL_CA_PATH=none']) shutil.copy2(prefix/'bin/curl',out/'curl');(out/'curl').chmod(0o755) exe=out/('curl.exe' if system=='win32' else 'curl') version=subprocess.check_output([str(exe),'--disable','--version'],text=True) if not all(x in version.split('Features:')[-1].split() for x in ['HTTP2','HTTP3','SSL']):raise RuntimeError('Native client lacks required HTTP/2, HTTP/3 or TLS: '+version) # Identical pinned trust store on every platform. ca=(here/'ca-bundle.pem').read_bytes() if hashlib.sha256(ca).hexdigest()!=lock['ca']['sha256']:raise RuntimeError('CA checksum mismatch') (out/'ca-bundle.pem').write_bytes(ca) files={str(p.relative_to(out)).replace('\\','/'):hashlib.sha256(p.read_bytes()).hexdigest() for p in sorted(out.rglob('*')) if p.is_file() and p.name!='manifest.json'} (out/'manifest.json').write_text(json.dumps({'schema':1,'platform':system,'arch':arch,'version':version.strip(),'files':files,'sources':lock['sources'],'caSource':lock['ca']['source']},indent=2)+'\n') print(version);print('Verified runtime:',out)