diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..259e59ab --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,29 @@ +FROM ubuntu:22.04 + +ARG DEBIAN_FRONTEND=noninteractive +# enable 'universe' because musl-tools & clang live there +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + software-properties-common && \ + add-apt-repository --yes universe + +# now install build deps +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + build-essential curl git ca-certificates \ + pkg-config clang musl-tools libssl-dev && \ + rm -rf /var/lib/apt/lists/* + +# non-root dev user +ARG USER=dev +ARG UID=1000 +RUN useradd -m -u $UID $USER +USER $USER + +# install Rust + musl target as dev user +RUN curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal && \ + ~/.cargo/bin/rustup target add aarch64-unknown-linux-musl + +ENV PATH="/home/${USER}/.cargo/bin:${PATH}" + +WORKDIR /workspace diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 00000000..58e4458a --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,30 @@ +# Containerized Development + +We provide the following options to facilitate Codex development in a container. This is particularly useful for verifying the Linux build when working on a macOS host. + +## Docker + +To build the Docker image locally for x64 and then run it with the repo mounted under `/workspace`: + +```shell +CODEX_DOCKER_IMAGE_NAME=codex-linux-dev +docker build --platform=linux/amd64 -t "$CODEX_DOCKER_IMAGE_NAME" ./.devcontainer +docker run --platform=linux/amd64 --rm -it -e CARGO_TARGET_DIR=/workspace/codex-rs/target-amd64 -v "$PWD":/workspace -w /workspace/codex-rs "$CODEX_DOCKER_IMAGE_NAME" +``` + +Note that `/workspace/target` will contain the binaries built for your host platform, so we include `-e CARGO_TARGET_DIR=/workspace/codex-rs/target-amd64` in the `docker run` command so that the binaries built inside your container are written to a separate directory. + +For arm64, specify `--platform=linux/amd64` instead for both `docker build` and `docker run`. + +Currently, the `Dockerfile` works for both x64 and arm64 Linux, though you need to run `rustup target add x86_64-unknown-linux-musl` yourself to install the musl toolchain for x64. + +## VS Code + +VS Code recognizes the `devcontainer.json` file and gives you the option to develop Codex in a container. Currently, `devcontainer.json` builds and runs the `arm64` flavor of the container. + +From the integrated terminal in VS Code, you can build either flavor of the `arm64` build (GNU or musl): + +```shell +cargo build --target aarch64-unknown-linux-musl +cargo build --target aarch64-unknown-linux-gnu +``` diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..17aee914 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +{ + "name": "Codex", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "platform": "linux/arm64" + }, + + /* Force VS Code to run the container as arm64 in + case your host is x86 (or vice-versa). */ + "runArgs": ["--platform=linux/arm64"], + + "containerEnv": { + "RUST_BACKTRACE": "1", + "CARGO_TARGET_DIR": "${containerWorkspaceFolder}/codex-rs/target-arm64" + }, + + "remoteUser": "dev", + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.defaultProfile.linux": "bash" + }, + "extensions": [ + "rust-lang.rust-analyzer" + ], + } + } +} diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index f0eadaf2..3c836b8a 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -55,6 +55,10 @@ jobs: target: x86_64-unknown-linux-musl - runner: ubuntu-24.04 target: x86_64-unknown-linux-gnu + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu - runner: windows-latest target: x86_64-pc-windows-msvc @@ -75,7 +79,7 @@ jobs: ${{ github.workspace }}/codex-rs/target/ key: cargo-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} name: Install musl build tools run: | sudo apt install -y musl-tools pkg-config diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index bb5ca67c..7c896223 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -69,6 +69,8 @@ jobs: target: x86_64-unknown-linux-musl - runner: ubuntu-24.04 target: x86_64-unknown-linux-gnu + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-musl - runner: ubuntu-24.04-arm target: aarch64-unknown-linux-gnu @@ -88,7 +90,7 @@ jobs: ${{ github.workspace }}/codex-rs/target/ key: cargo-release-${{ matrix.runner }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }} - - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }} + - if: ${{ matrix.target == 'x86_64-unknown-linux-musl' || matrix.target == 'aarch64-unknown-linux-musl'}} name: Install musl build tools run: | sudo apt install -y musl-tools pkg-config diff --git a/codex-rs/.gitignore b/codex-rs/.gitignore index b83d2226..e9962537 100644 --- a/codex-rs/.gitignore +++ b/codex-rs/.gitignore @@ -1 +1,7 @@ /target/ + +# Recommended value of CARGO_TARGET_DIR when using Docker as explained in .devcontainer/README.md. +/target-amd64/ + +# Value of CARGO_TARGET_DIR when using .devcontainer/devcontainer.json. +/target-arm64/ diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 38f84461..a3ea3365 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -59,6 +59,10 @@ seccompiler = "0.5.0" [target.x86_64-unknown-linux-musl.dependencies] openssl-sys = { version = "*", features = ["vendored"] } +# Build OpenSSL from source for musl builds. +[target.aarch64-unknown-linux-musl.dependencies] +openssl-sys = { version = "*", features = ["vendored"] } + [dev-dependencies] assert_cmd = "2" maplit = "1.0.2" diff --git a/codex-rs/linux-sandbox/tests/landlock.rs b/codex-rs/linux-sandbox/tests/landlock.rs index 95ca11a2..17bdd9d8 100644 --- a/codex-rs/linux-sandbox/tests/landlock.rs +++ b/codex-rs/linux-sandbox/tests/landlock.rs @@ -15,6 +15,23 @@ use std::sync::Arc; use tempfile::NamedTempFile; use tokio::sync::Notify; +// At least on GitHub CI, the arm64 tests appear to need longer timeouts. + +#[cfg(not(target_arch = "aarch64"))] +const SHORT_TIMEOUT_MS: u64 = 200; +#[cfg(target_arch = "aarch64")] +const SHORT_TIMEOUT_MS: u64 = 5_000; + +#[cfg(not(target_arch = "aarch64"))] +const LONG_TIMEOUT_MS: u64 = 1_000; +#[cfg(target_arch = "aarch64")] +const LONG_TIMEOUT_MS: u64 = 5_000; + +#[cfg(not(target_arch = "aarch64"))] +const NETWORK_TIMEOUT_MS: u64 = 2_000; +#[cfg(target_arch = "aarch64")] +const NETWORK_TIMEOUT_MS: u64 = 10_000; + fn create_env_from_core_vars() -> HashMap { let policy = ShellEnvironmentPolicy::default(); create_env(&policy) @@ -52,7 +69,7 @@ async fn run_cmd(cmd: &[&str], writable_roots: &[PathBuf], timeout_ms: u64) { #[tokio::test] async fn test_root_read() { - run_cmd(&["ls", "-l", "/bin"], &[], 200).await; + run_cmd(&["ls", "-l", "/bin"], &[], SHORT_TIMEOUT_MS).await; } #[tokio::test] @@ -63,7 +80,7 @@ async fn test_root_write() { run_cmd( &["bash", "-lc", &format!("echo blah > {}", tmpfile_path)], &[], - 200, + SHORT_TIMEOUT_MS, ) .await; } @@ -75,7 +92,7 @@ async fn test_dev_null_write() { &[], // We have seen timeouts when running this test in CI on GitHub, // so we are using a generous timeout until we can diagnose further. - 1_000, + LONG_TIMEOUT_MS, ) .await; } @@ -93,7 +110,7 @@ async fn test_writable_root() { &[tmpdir.path().to_path_buf()], // We have seen timeouts when running this test in CI on GitHub, // so we are using a generous timeout until we can diagnose further. - 1_000, + LONG_TIMEOUT_MS, ) .await; } @@ -115,7 +132,7 @@ async fn assert_network_blocked(cmd: &[&str]) { cwd, // Give the tool a generous 2-second timeout so even slow DNS timeouts // do not stall the suite. - timeout_ms: Some(2_000), + timeout_ms: Some(NETWORK_TIMEOUT_MS), env: create_env_from_core_vars(), };