Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,13 @@ jobs:

- uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
with:
tool: just
tool: just@1.51.0
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
version: "0.11.3"
- name: Ruff format Python scripts (run `just fmt` to fix)
run: just fmt-scripts-check
- name: Check formatting (run `just fmt` to fix)
run: just fmt-check

- name: Prettier (run `pnpm run format:fix` to fix)
run: pnpm run format
27 changes: 12 additions & 15 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ exec *args:
cargo run --bin codex -- exec {args}

# Start `codex exec-server` and run codex-tui.
[unix]
[no-cd]
[positional-arguments]
[unix]
tui-with-exec-server *args:
{{ justfile_directory() }}/scripts/run_tui_with_exec_server.sh "$@"

Expand All @@ -36,16 +36,13 @@ app-server-test-client *args:
cargo build -p codex-cli
cargo run -p codex-app-server-test-client -- --codex-bin ./target/debug/codex {args}

# Format Rust, Python SDK code, and Python scripts.
# Format the justfile, Rust, Python SDK code, and Python scripts.
fmt:
cargo fmt -- --config imports_granularity=Item {stderr-null}
uv run --frozen --project ../sdk/python --extra dev ruff check --fix --fix-only ../sdk/python
Comment thread
anp-oai marked this conversation as resolved.
uv run --frozen --project ../sdk/python --extra dev ruff format ../sdk/python
# Root scripts have their own locked Ruff environment.
uv run --frozen --project ../scripts ruff format ../scripts
{{ python }} ../scripts/format.py

fmt-scripts-check:
uv run --frozen --project ../scripts ruff format --check ../scripts
# Check formatting without modifying files.
fmt-check:
Comment thread
anp-oai marked this conversation as resolved.
{{ python }} ../scripts/format.py --check

fix *args:
cargo clippy --fix --tests --allow-dirty {args}
Expand Down Expand Up @@ -97,21 +94,21 @@ bench-smoke:
# Build and run Codex from source using Bazel.
# On Unix, use `[no-cd]` and `--run_under="cd $PWD &&"` to ensure Bazel runs
# the command in the current working directory.
[unix]
[no-cd]
[unix]
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)
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]
[unix]
bazel-lock-check:
{{ justfile_directory() }}/scripts/check-module-bazel-lock.sh

Expand All @@ -122,13 +119,13 @@ bazel-lock-check:
bazel-test:
bazel test --test_tag_filters=-argument-comment-lint //... --keep_going

[unix]
[no-cd]
[unix]
bazel-clippy:
bazel_targets="$({{ justfile_directory() }}/scripts/list-bazel-clippy-targets.sh)" && bazel build --config=clippy -- ${bazel_targets}

[unix]
[no-cd]
[unix]
bazel-argument-comment-lint:
bazel build --config=argument-comment-lint -- $({{ justfile_directory() }}/tools/argument-comment-lint/list-bazel-targets.sh)

Expand All @@ -155,8 +152,8 @@ 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]
[unix]
argument-comment-lint *args:
if [ "$#" -eq 0 ]; then \
bazel build --config=argument-comment-lint -- $({{ justfile_directory() }}/tools/argument-comment-lint/list-bazel-targets.sh); \
Expand Down
177 changes: 177 additions & 0 deletions scripts/format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""Format repository sources or check that they are already formatted."""

import argparse
import shlex
import subprocess
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parents[1]
CODEX_RS_ROOT = REPO_ROOT / "codex-rs"


@dataclass(frozen=True)
class Command:
args: tuple[str, ...]
cwd: Path = REPO_ROOT
discard_stderr: bool = False


@dataclass(frozen=True)
class FormatterGroup:
name: str
commands: tuple[Command, ...]


@dataclass(frozen=True)
class FormatterResult:
name: str
output: str
returncode: int


def formatter_groups(*, check: bool) -> tuple[FormatterGroup, ...]:
just_args = ["just", "--unstable", "--fmt"]
cargo_args = ["cargo", "fmt", "--", "--config", "imports_granularity=Item"]
# Use an unpinned overlay so Ruff is available without syncing project
# dependencies. Each `--project` still retains its local configuration context.
sdk_uv_run_args = [
"uv",
"run",
"--frozen",
"--project",
"sdk/python",
"--no-sync",
"--with",
"ruff",
]
scripts_uv_run_args = [
"uv",
"run",
"--frozen",
"--project",
"scripts",
"--no-sync",
"--with",
"ruff",
]
sdk_format_args = [
*sdk_uv_run_args,
"ruff",
"format",
]
scripts_format_args = [
*scripts_uv_run_args,
"ruff",
"format",
]

if check:
just_args.append("--check")
cargo_args.append("--check")
sdk_format_args.append("--check")
scripts_format_args.append("--check")
# `ruff check --diff` reports lint-driven rewrites without changing files.
# It is the check-mode counterpart of `--fix --fix-only`, not a full lint gate.
sdk_lint_args = ["ruff", "check", "--diff"]
else:
# Ruff's lint fixer and formatter are separate passes: the first applies
# fixable lint rewrites, while the second formats source layout.
sdk_lint_args = ["ruff", "check", "--fix", "--fix-only"]

return (
FormatterGroup("Just", (Command(tuple(just_args)),)),
FormatterGroup(
"Rust",
# Stable rustfmt repeats a nightly-only `imports_granularity` warning
# for each crate, so suppress that expected stderr noise.
(Command(tuple(cargo_args), CODEX_RS_ROOT, discard_stderr=True),),
),
FormatterGroup(
"Python SDK",
(
Command(
(
*sdk_uv_run_args,
*sdk_lint_args,
"sdk/python",
)
),
Command((*sdk_format_args, "sdk/python")),
),
),
FormatterGroup(
"Python scripts",
(
# The SDK and internal scripts intentionally use separate project
# roots so uv and Ruff retain each project's configuration context.
Command((*scripts_format_args, "scripts")),
),
),
)


def run_formatter_group(group: FormatterGroup) -> FormatterResult:
"""Run one formatter group sequentially and return its buffered output."""
output: list[str] = []
for command in group.commands:
output.append(f"$ {shlex.join(command.args)}\n")
try:
process = subprocess.run(
command.args,
cwd=command.cwd,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
if command.discard_stderr
else subprocess.STDOUT,
text=True,
check=False,
)
except OSError as error:
output.append(f"{error}\n")
return FormatterResult(group.name, "".join(output), 1)

output.append(process.stdout)
if process.stdout and not process.stdout.endswith("\n"):
output.append("\n")
if process.returncode != 0:
return FormatterResult(group.name, "".join(output), process.returncode)

return FormatterResult(group.name, "".join(output), 0)


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument(
"--check",
action="store_true",
help="check formatting without modifying files",
)
args = parser.parse_args()
groups = formatter_groups(check=args.check)

failures: list[str] = []
with ThreadPoolExecutor(max_workers=len(groups)) as executor:
futures = {}
for group in groups:
print(f"Starting {group.name} formatter...", flush=True)
futures[executor.submit(run_formatter_group, group)] = group.name
for future in as_completed(futures):
result = future.result()
print(f"==> {result.name} formatter finished")
print(result.output, end="")
if result.returncode != 0:
failures.append(result.name)

if failures:
print(f"Formatting failed: {', '.join(failures)}", file=sys.stderr)
return 1
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading