diff --git a/justfile b/justfile index d5e9fc3a36e1..53b37e24caa4 100644 --- a/justfile +++ b/justfile @@ -1,7 +1,11 @@ set working-directory := "codex-rs" set positional-arguments +export JUST_SHELL := justfile_directory() / "scripts/just-shell.py" +set shell := ["python3", "-c", 'import os, runpy; runpy.run_path(os.environ["JUST_SHELL"], run_name="__main__")'] +set windows-shell := ["python", "-c", 'import os, runpy; runpy.run_path(os.environ["JUST_SHELL"], run_name="__main__")'] rust_min_stack := "8388608" # 8 MiB +python := if os_family() == "windows" { "python" } else { "python3" } # Display help help: @@ -10,81 +14,115 @@ help: # `codex` alias c := codex codex *args: - cargo run --bin codex -- "$@" + cargo run --bin codex -- {args} # `codex exec` exec *args: - cargo run --bin codex -- exec "$@" + cargo run --bin codex -- exec {args} # Start `codex exec-server` and run codex-tui. +[unix] [no-cd] +[positional-arguments] tui-with-exec-server *args: {{ justfile_directory() }}/scripts/run_tui_with_exec_server.sh "$@" # Run the CLI version of the file-search crate. file-search *args: - cargo run --bin codex-file-search -- "$@" + cargo run --bin codex-file-search -- {args} # Build the CLI and run the app-server test client app-server-test-client *args: cargo build -p codex-cli - cargo run -p codex-app-server-test-client -- --codex-bin ./target/debug/codex "$@" + cargo run -p codex-app-server-test-client -- --codex-bin ./target/debug/codex {args} # Format Rust and Python SDK code. fmt: - cargo fmt -- --config imports_granularity=Item 2>/dev/null + cargo fmt -- --config imports_granularity=Item {stderr-null} uv run --frozen --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python uv run --frozen --project ../sdk/python --extra dev ruff format ../sdk/python fix *args: - cargo clippy --fix --tests --allow-dirty "$@" + cargo clippy --fix --tests --allow-dirty {args} clippy *args: - cargo clippy --tests "$@" + cargo clippy --tests {args} +[unix] install: rustup show active-toolchain cargo fetch +[windows] +install: + #!powershell.exe -File + $pwsh = Get-Command pwsh.exe -ErrorAction SilentlyContinue + if (-not $pwsh) { + winget install --exact --id Microsoft.PowerShell --source winget --accept-package-agreements --accept-source-agreements + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + } + rustup show active-toolchain + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + cargo fetch + exit $LASTEXITCODE + # Run nextest with --no-fail-fast so all tests are run. # # Run `cargo install --locked cargo-nextest` if you don't have it installed. # Prefer this for routine local runs. Workspace crate features are banned, so # there should be no need to add `--all-features`. +[unix] test *args: RUST_MIN_STACK={{ rust_min_stack }} cargo nextest run --no-fail-fast "$@" just bench-smoke +[windows] +test *args: + $env:RUST_MIN_STACK = "{{ rust_min_stack }}"; cargo nextest run --no-fail-fast @($args | Select-Object -Skip 1) + just bench-smoke + # Run explicit workspace benchmark targets. bench *args: - cargo bench --workspace --bench '*' "$@" + cargo bench --workspace --bench '*' {args} # Run benchmark targets once to ensure they start successfully. bench-smoke: just bench -- --test # Build and run Codex from source using Bazel. -# Note we have to use the combination of `[no-cd]` and `--run_under="cd $PWD &&"` -# to ensure that Bazel runs the command in the current working directory. +# On Unix, use `[no-cd]` and `--run_under="cd $PWD &&"` to ensure Bazel runs +# the command in the current working directory. +[unix] [no-cd] bazel-codex *args: bazel run //codex-rs/cli:codex --run_under="cd $PWD &&" -- "$@" +[windows] +bazel-codex *args: + bazel run //codex-rs/cli:codex --run_under='cd /d "{{invocation_directory_native()}}" &&' -- @($args | Select-Object -Skip 1) + [no-cd] bazel-lock-update: bazel mod deps --lockfile_mode=update +[unix] [no-cd] bazel-lock-check: {{ justfile_directory() }}/scripts/check-module-bazel-lock.sh +[windows] +bazel-lock-check: + bazel mod deps --lockfile_mode=error; if ($LASTEXITCODE -ne 0) { Write-Error "MODULE.bazel.lock is out of date. Run 'just bazel-lock-update' and commit the updated lockfile."; exit 1 } + bazel-test: bazel test --test_tag_filters=-argument-comment-lint //... --keep_going +[unix] [no-cd] bazel-clippy: bazel_targets="$({{ justfile_directory() }}/scripts/list-bazel-clippy-targets.sh)" && bazel build --config=clippy -- ${bazel_targets} +[unix] [no-cd] bazel-argument-comment-lint: bazel build --config=argument-comment-lint -- $({{ justfile_directory() }}/tools/argument-comment-lint/list-bazel-targets.sh) @@ -97,7 +135,7 @@ build-for-release: # Run the MCP server mcp-server-run *args: - cargo run -p codex-mcp-server -- "$@" + cargo run -p codex-mcp-server -- {args} # Regenerate the json schema for config.toml from the current config types. write-config-schema: @@ -105,13 +143,14 @@ write-config-schema: # Regenerate vendored app-server protocol schema artifacts. write-app-server-schema *args: - cargo run -p codex-app-server-protocol --bin write_schema_fixtures -- "$@" + cargo run -p codex-app-server-protocol --bin write_schema_fixtures -- {args} [no-cd] write-hooks-schema: cargo run --manifest-path {{ justfile_directory() }}/codex-rs/Cargo.toml -p codex-hooks --bin write_hooks_schema_fixtures # Run the argument-comment Dylint checks across codex-rs. +[unix] [no-cd] argument-comment-lint *args: if [ "$#" -eq 0 ]; then \ @@ -122,8 +161,13 @@ argument-comment-lint *args: [no-cd] argument-comment-lint-from-source *args: - {{ justfile_directory() }}/tools/argument-comment-lint/run.py "$@" + {{ python }} {{ justfile_directory() }}/tools/argument-comment-lint/run.py {args} # Tail logs from the state SQLite database +[unix] log *args: if [ "${1:-}" = "--" ]; then shift; fi; cargo run -p codex-state --bin logs_client -- "$@" + +[windows] +log *args: + $forwarded_args = @($args | Select-Object -Skip 1); if ($forwarded_args.Count -gt 0 -and $forwarded_args[0] -eq "--") { $forwarded_args = @($forwarded_args | Select-Object -Skip 1) }; cargo run -p codex-state --bin logs_client -- @forwarded_args diff --git a/scripts/just-shell.py b/scripts/just-shell.py new file mode 100644 index 000000000000..423e56dafec9 --- /dev/null +++ b/scripts/just-shell.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +"""Cross-platform shell launcher for `just` recipes. + +This keeps recipe bodies as normal shell snippets while giving the justfile one +portable placeholder, `{args}`, for forwarding variadic recipe arguments. +""" + +from __future__ import annotations + +import os +import shutil +import subprocess +import sys + + +ARGS_TOKEN = "{args}" +STDERR_NULL_TOKEN = "{stderr-null}" +POWERSHELL_ARGS = "@($args | Select-Object -Skip 1)" +POWERSHELL_STDERR_NULL = '2>$null; exit $LASTEXITCODE' +SH_ARGS = '"$@"' +SH_STDERR_NULL = "2>/dev/null" + + +def main() -> int: + if len(sys.argv) < 2: + print("just shell adapter expected a recipe command.", file=sys.stderr) + return 1 + + command = sys.argv[1] + recipe_name = sys.argv[2] if len(sys.argv) > 2 else "" + recipe_args = sys.argv[3:] + + if os.name == "nt": + return run_powershell(command, recipe_name, recipe_args) + else: + return run_sh(command, recipe_name, recipe_args) + + +def run_sh(command: str, recipe_name: str, recipe_args: list[str]) -> int: + command = command.replace(ARGS_TOKEN, SH_ARGS) + command = command.replace(STDERR_NULL_TOKEN, SH_STDERR_NULL) + os.execvp("sh", ["sh", "-cu", command, recipe_name, *recipe_args]) + + +def run_powershell(command: str, recipe_name: str, recipe_args: list[str]) -> int: + pwsh = shutil.which("pwsh.exe") or shutil.which("pwsh") + if pwsh is None: + print( + "PowerShell ('pwsh') is required for Windows just recipes. " + "Run 'just install' to install it.", + file=sys.stderr, + ) + return 1 + + command = command.replace(ARGS_TOKEN, POWERSHELL_ARGS) + command = command.replace(STDERR_NULL_TOKEN, POWERSHELL_STDERR_NULL) + return subprocess.run( + [ + pwsh, + "-NoLogo", + "-NoProfile", + "-CommandWithArgs", + command, + recipe_name, + *recipe_args, + ], + check=False, + ).returncode + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/sdk/python/tests/test_artifact_workflow_and_binaries.py b/sdk/python/tests/test_artifact_workflow_and_binaries.py index d3f2f8d0ec1a..a6a338bb9866 100644 --- a/sdk/python/tests/test_artifact_workflow_and_binaries.py +++ b/sdk/python/tests/test_artifact_workflow_and_binaries.py @@ -81,14 +81,16 @@ def test_root_fmt_recipe_formats_rust_and_python_sdk() -> None: fmt_recipe = lines[fmt_index:next_recipe_index] actual = { "working_directory": lines[0], - "previous_attribute": lines[fmt_index - 1], + "previous_comment": next( + line for line in reversed(lines[:fmt_index]) if line.startswith("#") + ), "commands": [line.strip() for line in fmt_recipe[1:] if line.strip()], } expected = { "working_directory": 'set working-directory := "codex-rs"', - "previous_attribute": "# Format Rust and Python SDK code.", + "previous_comment": "# Format Rust and Python SDK code.", "commands": [ - "cargo fmt -- --config imports_granularity=Item 2>/dev/null", + "cargo fmt -- --config imports_granularity=Item {stderr-null}", "uv run --frozen --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python", "uv run --frozen --project ../sdk/python --extra dev ruff format ../sdk/python", ],