mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
code-mode: fix installation on darwin (#31876)
Installer needs to symlink code-mode-host next to codex on install.
This commit is contained in:
@@ -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 "$@"
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user