code-mode: fix installation on darwin (#31876)

Installer needs to symlink code-mode-host next to codex on install.
This commit is contained in:
Channing Conger
2026-07-09 13:11:28 -07:00
committed by GitHub
parent 2e8c3756f9
commit 22781d4001
2 changed files with 201 additions and 61 deletions

View File

@@ -7,6 +7,7 @@ NON_INTERACTIVE="${CODEX_NON_INTERACTIVE:-false}"
BIN_DIR="${CODEX_INSTALL_DIR:-$HOME/.local/bin}"
BIN_PATH="$BIN_DIR/codex"
CODE_MODE_HOST_BIN_PATH="$BIN_DIR/codex-code-mode-host"
CODEX_HOME_DIR="${CODEX_HOME:-$HOME/.codex}"
STANDALONE_ROOT="$CODEX_HOME_DIR/packages/standalone"
RELEASES_DIR="$STANDALONE_ROOT/releases"
@@ -869,10 +870,23 @@ update_visible_command() {
codex_relative_path="$(release_codex_relative_path "$release_dir")"
replace_path_with_symlink "$BIN_PATH" "$CURRENT_LINK/$codex_relative_path" "$tmp_link"
if [ "$os" = "darwin" ] && [ -x "$release_dir/bin/codex-code-mode-host" ]; then
replace_path_with_symlink \
"$CODE_MODE_HOST_BIN_PATH" \
"$CURRENT_LINK/bin/codex-code-mode-host" \
"$tmp_link"
elif [ "$(readlink "$CODE_MODE_HOST_BIN_PATH" 2>/dev/null || true)" = \
"$CURRENT_LINK/bin/codex-code-mode-host" ]; then
rm -f "$CODE_MODE_HOST_BIN_PATH"
fi
}
verify_visible_command() {
"$BIN_PATH" --version >/dev/null
if [ "$os" = "darwin" ] && [ "$install_layout" = "package" ]; then
[ -x "$CODE_MODE_HOST_BIN_PATH" ]
fi
}
parse_args "$@"

View File

@@ -1,9 +1,11 @@
#!/usr/bin/env python3
import hashlib
import json
import os
from pathlib import Path
import subprocess
import tarfile
import tempfile
import textwrap
import unittest
@@ -86,6 +88,32 @@ class InstallShTest(unittest.TestCase):
self.assertIn("/codex-npm-", requests[1])
self.assertNotIn("codex-package_SHA256SUMS", requests[1])
def test_macos_install_exposes_code_mode_host_beside_codex(self) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
archive_path, checksum_path, metadata_json = create_package_release(root)
result, _requests = run_installer_in(
root,
VERSION,
metadata_json=metadata_json,
archive_path=archive_path,
checksum_path=checksum_path,
force_macos=True,
)
self.assertEqual(result.returncode, 0, result.stderr)
install_bin = root / "install-bin"
current = root / "codex-home" / "packages" / "standalone" / "current"
codex_path = install_bin / "codex"
host_path = install_bin / "codex-code-mode-host"
self.assertEqual(os.readlink(codex_path), str(current / "bin" / "codex"))
self.assertEqual(
os.readlink(host_path),
str(current / "bin" / "codex-code-mode-host"),
)
self.assertTrue(os.access(host_path, os.X_OK))
def run_installer(
release: str,
@@ -94,71 +122,169 @@ def run_installer(
metadata_json: str | None = None,
) -> tuple[subprocess.CompletedProcess[str], list[str]]:
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
bin_dir = root / "bin"
bin_dir.mkdir()
request_log = root / "requests.log"
fake_curl = bin_dir / "curl"
fake_curl.write_text(
textwrap.dedent(
"""\
#!/bin/sh
url=""
for arg in "$@"; do
case "$arg" in
https://*) url="$arg" ;;
esac
done
printf '%s\n' "$url" >>"$CODEX_TEST_REQUEST_LOG"
return run_installer_in(
Path(temp_dir),
release,
metadata_failure=metadata_failure,
metadata_json=metadata_json,
)
case "$url" in
https://api.github.com/*)
if [ "$CODEX_TEST_METADATA_FAILURE" = "1" ]; then
echo "curl: (22) The requested URL returned error: 403" >&2
exit 22
fi
printf '%s\n' "$CODEX_TEST_METADATA_JSON"
;;
*)
exit 22
;;
esac
"""
),
def run_installer_in(
root: Path,
release: str,
*,
metadata_failure: bool = False,
metadata_json: str | None = None,
archive_path: Path | None = None,
checksum_path: Path | None = None,
force_macos: bool = False,
) -> tuple[subprocess.CompletedProcess[str], list[str]]:
bin_dir = root / "bin"
bin_dir.mkdir()
request_log = root / "requests.log"
fake_curl = bin_dir / "curl"
fake_curl.write_text(
textwrap.dedent(
"""\
#!/bin/sh
url=""
output=""
previous=""
for arg in "$@"; do
case "$arg" in
https://*) url="$arg" ;;
esac
if [ "$previous" = "-o" ]; then
output="$arg"
fi
previous="$arg"
done
printf '%s\n' "$url" >>"$CODEX_TEST_REQUEST_LOG"
case "$url" in
https://api.github.com/*)
if [ "$CODEX_TEST_METADATA_FAILURE" = "1" ]; then
echo "curl: (22) The requested URL returned error: 403" >&2
exit 22
fi
printf '%s\n' "$CODEX_TEST_METADATA_JSON"
;;
*/codex-package_SHA256SUMS)
if [ -n "$CODEX_TEST_CHECKSUM_PATH" ]; then
cp "$CODEX_TEST_CHECKSUM_PATH" "$output"
else
exit 22
fi
;;
*/codex-package-*.tar.gz)
if [ -n "$CODEX_TEST_ARCHIVE_PATH" ]; then
cp "$CODEX_TEST_ARCHIVE_PATH" "$output"
else
exit 22
fi
;;
*)
exit 22
;;
esac
"""
),
encoding="utf-8",
)
fake_curl.chmod(0o755)
if force_macos:
fake_uname = bin_dir / "uname"
fake_uname.write_text(
"#!/bin/sh\n"
'case "$1" in\n'
" -s) printf 'Darwin\\n' ;;\n"
" -m) printf 'arm64\\n' ;;\n"
"esac\n",
encoding="utf-8",
)
fake_curl.chmod(0o755)
fake_uname.chmod(0o755)
env = os.environ.copy()
env.update(
{
"CODEX_HOME": str(root / "codex-home"),
"CODEX_INSTALL_DIR": str(root / "install-bin"),
"CODEX_NON_INTERACTIVE": "1",
"CODEX_RELEASE": release,
"CODEX_TEST_METADATA_FAILURE": "1" if metadata_failure else "0",
"CODEX_TEST_METADATA_JSON": (
metadata_json if metadata_json is not None else release_metadata()
),
"CODEX_TEST_REQUEST_LOG": str(request_log),
"HOME": str(root / "home"),
"PATH": f"{bin_dir}:/usr/bin:/bin",
"SHELL": "/bin/sh",
}
)
result = subprocess.run(
["/bin/sh", str(INSTALL_SCRIPT)],
capture_output=True,
check=False,
env=env,
text=True,
)
requests = (
request_log.read_text(encoding="utf-8").splitlines()
if request_log.exists()
else []
)
return result, requests
home = root / "home"
home.mkdir()
env = os.environ.copy()
env.update(
{
"CODEX_HOME": str(root / "codex-home"),
"CODEX_INSTALL_DIR": str(root / "install-bin"),
"CODEX_NON_INTERACTIVE": "1",
"CODEX_RELEASE": release,
"CODEX_TEST_ARCHIVE_PATH": str(archive_path or ""),
"CODEX_TEST_CHECKSUM_PATH": str(checksum_path or ""),
"CODEX_TEST_METADATA_FAILURE": "1" if metadata_failure else "0",
"CODEX_TEST_METADATA_JSON": (
metadata_json if metadata_json is not None else release_metadata()
),
"CODEX_TEST_REQUEST_LOG": str(request_log),
"HOME": str(home),
"PATH": f"{bin_dir}:/usr/bin:/bin",
"SHELL": "/bin/sh",
}
)
result = subprocess.run(
["/bin/sh", str(INSTALL_SCRIPT)],
capture_output=True,
check=False,
env=env,
text=True,
)
requests = (
request_log.read_text(encoding="utf-8").splitlines()
if request_log.exists()
else []
)
return result, requests
def create_package_release(root: Path) -> tuple[Path, Path, str]:
package_dir = root / "package"
(package_dir / "bin").mkdir(parents=True)
(package_dir / "codex-path").mkdir()
(package_dir / "codex-package.json").write_text("{}\n", encoding="utf-8")
write_executable(
package_dir / "bin" / "codex",
f"#!/bin/sh\nprintf 'codex-cli {VERSION}\\n'\n",
)
write_executable(
package_dir / "bin" / "codex-code-mode-host",
"#!/bin/sh\nexit 0\n",
)
write_executable(package_dir / "codex-path" / "rg", "#!/bin/sh\nexit 0\n")
asset = "codex-package-aarch64-apple-darwin.tar.gz"
archive_path = root / asset
with tarfile.open(archive_path, "w:gz") as archive:
for path in package_dir.iterdir():
archive.add(path, arcname=path.name)
archive_digest = hashlib.sha256(archive_path.read_bytes()).hexdigest()
checksum_path = root / "codex-package_SHA256SUMS"
checksum_path.write_text(f"{archive_digest} {asset}\n", encoding="utf-8")
checksum_digest = hashlib.sha256(checksum_path.read_bytes()).hexdigest()
metadata_json = json.dumps(
{
"assets": [
{"name": asset, "digest": f"sha256:{archive_digest}"},
{
"name": "codex-package_SHA256SUMS",
"digest": f"sha256:{checksum_digest}",
},
],
"tag_name": f"rust-v{VERSION}",
},
indent=2,
)
return archive_path, checksum_path, metadata_json
def write_executable(path: Path, contents: str) -> None:
path.write_text(contents, encoding="utf-8")
path.chmod(0o755)
def release_metadata(*, compact: bool = False, reorder: bool = False) -> str: