99import sys
1010import tempfile
1111from pathlib import Path
12+ from typing import Sequence
13+
14+ from install_native_deps import CODEX_TARGETS , VENDOR_DIR_NAME
1215
1316SCRIPT_DIR = Path (__file__ ).resolve ().parent
1417CODEX_CLI_ROOT = SCRIPT_DIR .parent
1518REPO_ROOT = CODEX_CLI_ROOT .parent
1619GITHUB_REPO = "openai/codex"
1720
21+ TARGET_TO_SLICE_TAG = {
22+ "x86_64-unknown-linux-musl" : "linux-x64" ,
23+ "aarch64-unknown-linux-musl" : "linux-arm64" ,
24+ "x86_64-apple-darwin" : "darwin-x64" ,
25+ "aarch64-apple-darwin" : "darwin-arm64" ,
26+ "x86_64-pc-windows-msvc" : "win32-x64" ,
27+ "aarch64-pc-windows-msvc" : "win32-arm64" ,
28+ }
29+
30+ _SLICE_ACCUMULATOR : dict [str , list [str ]] = {}
31+ for target in CODEX_TARGETS :
32+ slice_tag = TARGET_TO_SLICE_TAG .get (target )
33+ if slice_tag is None :
34+ raise RuntimeError (f"Missing slice tag mapping for target '{ target } '." )
35+ _SLICE_ACCUMULATOR .setdefault (slice_tag , []).append (target )
36+
37+ SLICE_TAG_TO_TARGETS = {tag : tuple (targets ) for tag , targets in _SLICE_ACCUMULATOR .items ()}
38+
39+ DEFAULT_SLICE_TAGS = tuple (SLICE_TAG_TO_TARGETS )
40+
1841
1942def parse_args () -> argparse .Namespace :
2043 parser = argparse .ArgumentParser (description = "Build or stage the Codex CLI npm package." )
@@ -52,12 +75,29 @@ def parse_args() -> argparse.Namespace:
5275 type = Path ,
5376 help = "Path where the generated npm tarball should be written." ,
5477 )
78+ parser .add_argument (
79+ "--slice-pack-dir" ,
80+ type = Path ,
81+ help = (
82+ "Directory where per-platform slice npm tarballs should be written. "
83+ "When provided, all known slices are packed unless --slices is given."
84+ ),
85+ )
86+ parser .add_argument (
87+ "--slices" ,
88+ nargs = "+" ,
89+ choices = sorted (DEFAULT_SLICE_TAGS ),
90+ help = "Optional subset of slice tags to pack." ,
91+ )
5592 return parser .parse_args ()
5693
5794
5895def main () -> int :
5996 args = parse_args ()
6097
98+ if args .slices and args .slice_pack_dir is None :
99+ raise RuntimeError ("--slice-pack-dir is required when specifying --slices." )
100+
61101 version = args .version
62102 release_version = args .release_version
63103 if release_version :
@@ -97,6 +137,16 @@ def main() -> int:
97137
98138 install_native_binaries (staging_dir , workflow_url )
99139
140+ slice_outputs : list [tuple [str , Path ]] = []
141+ if args .slice_pack_dir is not None :
142+ slice_tags = tuple (args .slices or DEFAULT_SLICE_TAGS )
143+ slice_outputs = build_slice_packages (
144+ staging_dir ,
145+ version ,
146+ args .slice_pack_dir ,
147+ slice_tags ,
148+ )
149+
100150 if release_version :
101151 staging_dir_str = str (staging_dir )
102152 print (
@@ -111,6 +161,9 @@ def main() -> int:
111161 if args .pack_output is not None :
112162 output_path = run_npm_pack (staging_dir , args .pack_output )
113163 print (f"npm pack output written to { output_path } " )
164+
165+ for slice_tag , output_path in slice_outputs :
166+ print (f"built slice { slice_tag } tarball at { output_path } " )
114167 finally :
115168 if created_temp :
116169 # Preserve the staging directory for further inspection.
@@ -161,6 +214,63 @@ def install_native_binaries(staging_dir: Path, workflow_url: str | None) -> None
161214 subprocess .check_call (cmd , cwd = CODEX_CLI_ROOT )
162215
163216
217+ def build_slice_packages (
218+ base_staging_dir : Path ,
219+ version : str ,
220+ output_dir : Path ,
221+ slice_tags : Sequence [str ],
222+ ) -> list [tuple [str , Path ]]:
223+ if not slice_tags :
224+ return []
225+
226+ base_vendor = base_staging_dir / VENDOR_DIR_NAME
227+ if not base_vendor .exists ():
228+ raise RuntimeError (
229+ f"Base staging directory { base_staging_dir } does not include native vendor binaries."
230+ )
231+
232+ output_dir = output_dir .resolve ()
233+ output_dir .mkdir (parents = True , exist_ok = True )
234+
235+ results : list [tuple [str , Path ]] = []
236+ for slice_tag in slice_tags :
237+ targets = SLICE_TAG_TO_TARGETS .get (slice_tag )
238+ if not targets :
239+ raise RuntimeError (f"Unknown slice tag '{ slice_tag } '." )
240+
241+ missing = [target for target in targets if not (base_vendor / target ).exists ()]
242+ if missing :
243+ missing_label = ", " .join (missing )
244+ raise RuntimeError (
245+ f"Missing native binaries for slice '{ slice_tag } ': { missing_label } is absent in vendor."
246+ )
247+
248+ with tempfile .TemporaryDirectory (prefix = f"codex-npm-slice-{ slice_tag } -" ) as slice_dir_str :
249+ slice_dir = Path (slice_dir_str )
250+ stage_sources (slice_dir , version )
251+ slice_vendor = slice_dir / VENDOR_DIR_NAME
252+ copy_vendor_slice (base_vendor , slice_vendor , targets )
253+ output_path = output_dir / f"codex-npm-{ version } -{ slice_tag } .tgz"
254+ run_npm_pack (slice_dir , output_path )
255+ results .append ((slice_tag , output_path ))
256+
257+ return results
258+
259+
260+ def copy_vendor_slice (base_vendor : Path , dest_vendor : Path , targets : Sequence [str ]) -> None :
261+ dest_vendor .parent .mkdir (parents = True , exist_ok = True )
262+ dest_vendor .mkdir (parents = True , exist_ok = True )
263+
264+ for entry in base_vendor .iterdir ():
265+ if entry .is_file ():
266+ shutil .copy2 (entry , dest_vendor / entry .name )
267+
268+ for target in targets :
269+ src = base_vendor / target
270+ dest = dest_vendor / target
271+ shutil .copytree (src , dest )
272+
273+
164274def resolve_latest_alpha_workflow_url () -> str :
165275 version = determine_latest_alpha_version ()
166276 workflow_url = fetch_workflow_url_for_version (version )
0 commit comments