Skip to content

Commit f930289

Browse files
authored
Merge 98df710 into sapling-pr-archive-bolinfest
2 parents e63e173 + 98df710 commit f930289

File tree

18 files changed

+467
-143
lines changed

18 files changed

+467
-143
lines changed

.github/workflows/rust-release.yml

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ jobs:
175175
tag: ${{ github.ref_name }}
176176
should_publish_npm: ${{ steps.npm_publish_settings.outputs.should_publish }}
177177
npm_tag: ${{ steps.npm_publish_settings.outputs.npm_tag }}
178+
slice_tags: ${{ steps.slice_tags.outputs.value }}
178179

179180
steps:
180181
- name: Checkout repository
@@ -214,6 +215,25 @@ jobs:
214215
echo "npm_tag=" >> "$GITHUB_OUTPUT"
215216
fi
216217
218+
- name: Determine slice tags
219+
id: slice_tags
220+
run: |
221+
set -euo pipefail
222+
slice_tags=$(python - <<'PY'
223+
import importlib.util
224+
import sys
225+
from pathlib import Path
226+
227+
module_path = Path("codex-cli/scripts/build_npm_package.py").resolve()
228+
sys.path.insert(0, str(module_path.parent))
229+
spec = importlib.util.spec_from_file_location("build_npm_package", module_path)
230+
module = importlib.util.module_from_spec(spec)
231+
spec.loader.exec_module(module)
232+
print(" ".join(module.DEFAULT_SLICE_TAGS), end="")
233+
PY
234+
)
235+
echo "value=${slice_tags}" >> "$GITHUB_OUTPUT"
236+
217237
# build_npm_package.py requires DotSlash when staging releases.
218238
- uses: facebook/install-dotslash@v2
219239
- name: Stage npm package
@@ -222,10 +242,13 @@ jobs:
222242
run: |
223243
set -euo pipefail
224244
TMP_DIR="${RUNNER_TEMP}/npm-stage"
245+
OUTPUT_DIR="${GITHUB_WORKSPACE}/dist/npm"
246+
mkdir -p "${OUTPUT_DIR}"
225247
./codex-cli/scripts/build_npm_package.py \
226248
--release-version "${{ steps.release_name.outputs.name }}" \
227249
--staging-dir "${TMP_DIR}" \
228-
--pack-output "${GITHUB_WORKSPACE}/dist/npm/codex-npm-${{ steps.release_name.outputs.name }}.tgz"
250+
--pack-output "${OUTPUT_DIR}/codex-npm-${{ steps.release_name.outputs.name }}.tgz" \
251+
--slice-pack-dir "${OUTPUT_DIR}"
229252
230253
- name: Create GitHub Release
231254
uses: softprops/action-gh-release@v2
@@ -279,7 +302,7 @@ jobs:
279302
mkdir -p dist/npm
280303
gh release download "$tag" \
281304
--repo "${GITHUB_REPOSITORY}" \
282-
--pattern "codex-npm-${version}.tgz" \
305+
--pattern "codex-npm-${version}*.tgz" \
283306
--dir dist/npm
284307
285308
# No NODE_AUTH_TOKEN needed because we use OIDC.
@@ -296,6 +319,35 @@ jobs:
296319
297320
npm publish "${GITHUB_WORKSPACE}/dist/npm/codex-npm-${VERSION}.tgz" "${tag_args[@]}"
298321
322+
- name: Publish slice npm packages
323+
env:
324+
VERSION: ${{ needs.release.outputs.version }}
325+
NPM_TAG: ${{ needs.release.outputs.npm_tag }}
326+
SLICE_TAGS: ${{ needs.release.outputs.slice_tags }}
327+
run: |
328+
set -euo pipefail
329+
if [[ -z "${SLICE_TAGS}" ]]; then
330+
echo "No slice tags defined; skipping slice publish." >&2
331+
exit 0
332+
fi
333+
334+
IFS=' ' read -r -a slice_tags <<<"${SLICE_TAGS}"
335+
for slice_tag in "${slice_tags[@]}"; do
336+
tarball="${GITHUB_WORKSPACE}/dist/npm/codex-npm-${VERSION}-${slice_tag}.tgz"
337+
if [[ ! -f "${tarball}" ]]; then
338+
echo "Missing slice tarball ${tarball}" >&2
339+
exit 1
340+
fi
341+
342+
publish_tag="${slice_tag}"
343+
if [[ -n "${NPM_TAG}" ]]; then
344+
publish_tag="${NPM_TAG}-${slice_tag}"
345+
fi
346+
347+
echo "Publishing ${tarball} with npm tag '${publish_tag}'"
348+
npm publish "${tarball}" --tag "${publish_tag}"
349+
done
350+
299351
update-branch:
300352
name: Update latest-alpha-cli branch
301353
permissions:

codex-cli/scripts/README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,13 @@ To build the 0.2.x or later version of the npm module, which runs the Rust versi
88
./codex-cli/scripts/build_npm_package.py --release-version 0.6.0
99
```
1010

11-
Note this will create `./codex-cli/vendor/` as a side-effect.
11+
To produce per-platform "slice" tarballs in addition to the fat package, supply the
12+
`--slice-pack-dir` flag to write the outputs. For example:
13+
14+
```bash
15+
./codex-cli/scripts/build_npm_package.py --release-version 0.6.0 --slice-pack-dir dist/npm
16+
```
17+
18+
The command above writes the full tarball plus the per-platform archives named with the
19+
VS Code-style identifiers (for example, `codex-npm-0.6.0-darwin-arm64.tgz`). Note this will
20+
create `./codex-cli/vendor/` as a side-effect.

codex-cli/scripts/build_npm_package.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,35 @@
99
import sys
1010
import tempfile
1111
from pathlib import Path
12+
from typing import Sequence
13+
14+
from install_native_deps import CODEX_TARGETS, VENDOR_DIR_NAME
1215

1316
SCRIPT_DIR = Path(__file__).resolve().parent
1417
CODEX_CLI_ROOT = SCRIPT_DIR.parent
1518
REPO_ROOT = CODEX_CLI_ROOT.parent
1619
GITHUB_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

1942
def 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

5895
def 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+
164274
def resolve_latest_alpha_workflow_url() -> str:
165275
version = determine_latest_alpha_version()
166276
workflow_url = fetch_workflow_url_for_version(version)

0 commit comments

Comments
 (0)