Unverified Commit 0ba12358 authored by Can H. Tartanoglu's avatar Can H. Tartanoglu
Browse files

codex-acp: 0.9.2 -> 0.12.0

parent 7911ee5c
Loading
Loading
Loading
Loading
+0 −8
Original line number Diff line number Diff line
{
  "version": "0.9.4",
  "hash": "sha256-sVmy7t1+z88WmYuupVmUA3GYA2kkv3nY7Z3Ic99f5UY=",
  "cargoHash": "sha256-Ik6pewc6f+cmVKiqVj1g0h7cIxLhE6xOd9p/ySo/EPg=",
  "codexRev": "c34b30a3c128bb75fcec27ef838c93c99b92fc61",
  "codexSrcHash": "sha256-SnJHiecKNCHhkiMpbsEwpUarpKLpxn1JOHLHy2vgRog=",
  "nodeVersionHash": "sha256-q/bOpgF6/0K3MDKXAC+bi1Rb/vCHNhKZpNDbhyYH+oc="
}
+35 −34
Original line number Diff line number Diff line
@@ -7,53 +7,56 @@
  pkg-config,
  openssl,
  libcap,
  librusty_v8 ? fetchurl {
    name = "librusty_v8-146.4.0";
    url = "https://github.com/denoland/rusty_v8/releases/download/v146.4.0/librusty_v8_release_${stdenv.hostPlatform.rust.rustcTarget}.a.gz";
    hash =
      {
        x86_64-linux = "sha256-5ktNmeSuKTouhGJEqJuAF4uhA4LBP7WRwfppaPUpEVM=";
        aarch64-linux = "sha256-2/FlsHyBvbBUvARrQ9I+afz3vMGkwbW0d2mDpxBi7Ng=";
        x86_64-darwin = "sha256-YwzSQPG77NsHFBfcGDh6uBz2fFScHFFaC0/Pnrpke7c=";
        aarch64-darwin = "sha256-v+LJvjKlbChUbw+WWCXuaPv2BkBfMQzE4XtEilaM+Yo=";
      }
      .${stdenv.hostPlatform.system};
    meta = {
      version = "146.4.0";
      sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
    };
  },
}:
let
  versionData = builtins.fromJSON (builtins.readFile ./hashes.json);
  inherit (versionData)
    version
    hash
    cargoHash
    codexRev
    codexSrcHash
    nodeVersionHash
    ;

  # codex-core uses include_str!("../../../../node-version.txt"), so we need
  # to place node-version.txt at the vendored workspace root.
  nodeVersionFile = fetchurl {
    url = "https://raw.githubusercontent.com/zed-industries/codex/${codexRev}/codex-rs/node-version.txt";
    hash = nodeVersionHash;
  };

  # codex-linux-sandbox compiles a patched bubblewrap source tree from
  # codex-rs/vendor/bubblewrap. Cargo vendoring flattens workspace layout,
  # so this directory must be provided explicitly.
  # codex-acp 0.12.0 pins openai/codex rust-v0.124.0 in Cargo.lock.
  codexRev = "e9fb49366c93a1478ec71cc41ecee415a197d036";
  codexSrc = fetchFromGitHub {
    owner = "zed-industries";
    owner = "openai";
    repo = "codex";
    rev = codexRev;
    hash = codexSrcHash;
    hash = "sha256-YFnzzwCm9/b30qLDMbkf/rEizuTjeqdCgoBZeS0wNBo=";
  };
in
rustPlatform.buildRustPackage {
rustPlatform.buildRustPackage (finalAttrs: {
  pname = "codex-acp";
  inherit version;
  version = "0.12.0";

  src = fetchFromGitHub {
    owner = "zed-industries";
    repo = "codex-acp";
    rev = "v${version}";
    inherit hash;
    tag = "v${finalAttrs.version}";
    hash = "sha256-qPqg95FpXHBtyHBJtrfJUwu9GokfmOJgKgqLKQ48u+8=";
  };

  inherit cargoHash;
  cargoHash = "sha256-/BZ82qiTy/mPwhf5v5CFrNSB6AxCRFdmHB72L0+KjJw=";

  preBuild = ''
    cp ${nodeVersionFile} "$NIX_BUILD_TOP/codex-acp-${version}-vendor/node-version.txt"
  # fetchCargoVendor only keeps the individual git crate subtrees, so restore
  # the workspace-root file that codex-core includes via ../../../../node-version.txt.
  postPatch = ''
    cp ${codexSrc}/codex-rs/node-version.txt "$cargoDepsCopy/source-git-0/node-version.txt"
  '';

  env = lib.optionalAttrs stdenv.hostPlatform.isLinux {
  env = {
    RUSTY_V8_ARCHIVE = librusty_v8;
  }
  // lib.optionalAttrs stdenv.hostPlatform.isLinux {
    CODEX_BWRAP_SOURCE_DIR = "${codexSrc}/codex-rs/vendor/bubblewrap";
  };

@@ -70,16 +73,14 @@ rustPlatform.buildRustPackage {

  doCheck = false;

  passthru.updateScript = ./update.py;

  meta = {
    description = "An ACP-compatible coding agent powered by Codex";
    homepage = "https://github.com/zed-industries/codex-acp";
    changelog = "https://github.com/zed-industries/codex-acp/releases/tag/v${version}";
    changelog = "https://github.com/zed-industries/codex-acp/releases/tag/v${finalAttrs.version}";
    license = lib.licenses.asl20;
    maintainers = with lib.maintainers; [ tlvince ];
    platforms = lib.platforms.unix;
    sourceProvenance = with lib.sourceTypes; [ fromSource ];
    mainProgram = "codex-acp";
  };
}
})
+0 −216
Original line number Diff line number Diff line
#!/usr/bin/env nix-shell
#!nix-shell -I nixpkgs=./. -i python3 -p python3 nix cacert

"""Update script for codex-acp package.

codex-acp depends on crates from zed-industries/codex via a git dependency.
To keep the Nix expression up to date, we need to:
- update codex-acp source hash,
- extract the pinned codex git revision from Cargo.lock,
- refresh node-version.txt hash for that codex revision,
- refresh codex source hash for vendored bubblewrap on Linux,
- recompute cargoHash.
"""

from __future__ import annotations

import json
import os
import re
import subprocess
import tarfile
import tempfile
import urllib.request
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
NIXPKGS_ROOT = SCRIPT_DIR.parents[4]
HASHES_FILE = SCRIPT_DIR / "hashes.json"

OWNER = "zed-industries"
REPO = "codex-acp"
DUMMY_CARGO_HASH = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
ANSI_ESCAPE_RE = re.compile(r"\x1b\[[0-9;]*m")


def run(cmd: list[str], cwd: Path | None = None) -> str:
    result = subprocess.run(
        cmd,
        cwd=str(cwd) if cwd else None,
        check=False,
        capture_output=True,
        text=True,
    )
    if result.returncode != 0:
        output = result.stderr.strip() or result.stdout.strip()
        msg = f"Command failed ({result.returncode}): {' '.join(cmd)}"
        if output:
            msg = f"{msg}\n{output}"
        raise RuntimeError(msg)
    return result.stdout.strip()


def github_request(url: str) -> dict:
    headers = {
        "Accept": "application/vnd.github+json",
    }
    token = os.environ.get("GITHUB_TOKEN")
    if token:
        headers["Authorization"] = f"Bearer {token}"

    req = urllib.request.Request(url, headers=headers)
    with urllib.request.urlopen(req) as response:
        return json.loads(response.read().decode("utf-8"))


def fetch_latest_release(owner: str, repo: str) -> str:
    data = github_request(f"https://api.github.com/repos/{owner}/{repo}/releases/latest")
    tag_name = data["tag_name"]
    return tag_name[1:] if tag_name.startswith("v") else tag_name


def version_key(version: str) -> tuple[int, ...]:
    parts = re.findall(r"\d+", version)
    return tuple(int(part) for part in parts)


def should_update(current: str, latest: str) -> bool:
    return version_key(latest) > version_key(current)


def load_hashes(path: Path) -> dict[str, str]:
    with path.open() as f:
        return json.load(f)


def save_hashes(path: Path, data: dict[str, str]) -> None:
    with path.open("w") as f:
        json.dump(data, f, indent=2)
        f.write("\n")


def prefetch_sri(url: str, *, unpack: bool = False) -> str:
    cmd = ["nix-prefetch-url", "--type", "sha256"]
    if unpack:
        cmd.append("--unpack")
    cmd.append(url)

    raw_hash = run(cmd, cwd=NIXPKGS_ROOT)
    return run(
        [
            "nix",
            "--extra-experimental-features",
            "nix-command",
            "hash",
            "to-sri",
            "--type",
            "sha256",
            raw_hash,
        ],
        cwd=NIXPKGS_ROOT,
    )


def extract_codex_rev_from_tarball(tag: str) -> str:
    """Extract zed-industries/codex git revision from codex-acp Cargo.lock."""
    url = f"https://github.com/{OWNER}/{REPO}/archive/refs/tags/{tag}.tar.gz"

    with tempfile.TemporaryDirectory() as tmpdir:
        tarball_path = Path(tmpdir) / "source.tar.gz"
        urllib.request.urlretrieve(url, tarball_path)

        with tarfile.open(tarball_path, "r:gz") as tar:
            for member in tar.getmembers():
                if not member.name.endswith("Cargo.lock"):
                    continue
                cargo_lock = tar.extractfile(member)
                if cargo_lock is None:
                    continue

                content = cargo_lock.read().decode("utf-8")
                match = re.search(r"zed-industries/codex\?branch=acp#([a-f0-9]+)", content)
                if match:
                    return match.group(1)

    raise RuntimeError("Could not extract codex git revision from Cargo.lock")


def calculate_dependency_hash(attr_path: str) -> str:
    result = subprocess.run(
        ["nix-build", "--no-out-link", "-A", attr_path],
        cwd=str(NIXPKGS_ROOT),
        check=False,
        capture_output=True,
        text=True,
    )
    output = ANSI_ESCAPE_RE.sub("", f"{result.stdout}\n{result.stderr}")

    match = re.search(r"got:\s*(sha256-[A-Za-z0-9+/=]+)", output)
    if match:
        return match.group(1)

    if result.returncode == 0:
        raise RuntimeError("nix-build unexpectedly succeeded with placeholder cargoHash")

    raise RuntimeError("Failed to parse cargoHash from nix-build output")


def main() -> None:
    data = load_hashes(HASHES_FILE)
    current = data["version"]
    latest = fetch_latest_release(OWNER, REPO)

    print(f"Current: {current}, Latest: {latest}")

    if not should_update(current, latest):
        print("Already up to date")
        return

    tag = f"v{latest}"
    print(f"Updating codex-acp to {latest}...")

    source_url = f"https://github.com/{OWNER}/{REPO}/archive/refs/tags/{tag}.tar.gz"
    print("Calculating source hash...")
    source_hash = prefetch_sri(source_url, unpack=True)
    print(f"  hash: {source_hash}")

    print("Extracting codex git revision from Cargo.lock...")
    codex_rev = extract_codex_rev_from_tarball(tag)
    print(f"  codexRev: {codex_rev}")

    codex_src_url = f"https://github.com/zed-industries/codex/archive/{codex_rev}.tar.gz"
    print("Calculating codex source hash...")
    codex_src_hash = prefetch_sri(codex_src_url, unpack=True)
    print(f"  codexSrcHash: {codex_src_hash}")

    node_version_url = (
        f"https://raw.githubusercontent.com/zed-industries/codex/{codex_rev}/"
        "codex-rs/node-version.txt"
    )
    print("Calculating node-version.txt hash...")
    node_version_hash = prefetch_sri(node_version_url, unpack=False)
    print(f"  nodeVersionHash: {node_version_hash}")

    data = {
        "version": latest,
        "hash": source_hash,
        "cargoHash": DUMMY_CARGO_HASH,
        "codexRev": codex_rev,
        "codexSrcHash": codex_src_hash,
        "nodeVersionHash": node_version_hash,
    }
    save_hashes(HASHES_FILE, data)

    print("Calculating cargoHash...")
    attr_path = os.environ.get("UPDATE_NIX_ATTR_PATH", "codex-acp")
    cargo_hash = calculate_dependency_hash(attr_path)
    print(f"  cargoHash: {cargo_hash}")

    data["cargoHash"] = cargo_hash
    save_hashes(HASHES_FILE, data)

    print(f"Updated to {latest}")


if __name__ == "__main__":
    main()