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
97 changes: 80 additions & 17 deletions .github/actions/setup-python-deps/action.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
name: Setup Python Dependencies
description: Setup Python 3.11, uv, and install project dependencies.

inputs:
build-all:
description: Build native dependencies from source instead of downloading prebuilt native wheels.
required: false
default: "false"

runs:
using: composite
steps:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup Python 3.11
shell: bash
run: echo "/opt/python/cp311-cp311/bin" >> "$GITHUB_PATH"

- name: Setup uv
uses: astral-sh/setup-uv@v3
with:
version: latest
enable-cache: "true"

- name: Trust GitHub workspace
shell: bash
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

- name: Install native build dependencies
shell: bash
run: |
dnf install -y epel-release dnf-plugins-core
dnf config-manager --set-enabled crb || true
dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
dnf install -y \
cmake ninja-build pkgconfig patchelf mold lld \
boost-devel cairo-devel gflags-devel glog-devel \
flex flex-devel bison eigen3-devel gtest-devel \
tbb-devel hwloc-devel libcurl-devel libunwind-devel \
metis-devel gmp-devel tcl-devel \
unzip zip gh

- name: Setup Rust
if: ${{ inputs.build-all == 'true' }}
uses: dtolnay/rust-toolchain@stable

- name: Setup sccache
if: ${{ inputs.build-all == 'true' }}
uses: mozilla-actions/sccache-action@v0.0.9

- name: Install native tool wheels
if: ${{ inputs.build-all != 'true' }}
shell: bash
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -25,24 +57,55 @@ runs:
mkdir -p dist/native-wheels
uv venv --python 3.11

dreamplace_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' chipcompiler/thirdparty/ecc-dreamplace/pyproject.toml)"
ecc_tools_version="$(sed -n 's/^version = "\(.*\)"$/\1/p' chipcompiler/thirdparty/ecc-tools/pyproject.toml)"
download_wheel_artifact() {
local repo="$1"
local submodule_path="$2"
local artifact_name="$3"
local commit
local run_id

commit="$(git rev-parse "HEAD:${submodule_path}")"
run_id="$(
gh api "repos/${repo}/actions/workflows/ci.yml/runs?head_sha=${commit}&per_page=20" \
--jq '[.workflow_runs[] | select(.conclusion == "success") | .id][0] // empty'
)"

if [ -z "$run_id" ]; then
echo "::error::No successful CI artifact found for ${repo} at submodule commit ${commit}"
exit 1
fi

echo "Downloading ${artifact_name} from ${repo} CI run ${run_id} (${commit})"
gh run download "$run_id" \
--repo "$repo" \
--name "$artifact_name" \
--dir dist/native-wheels
}

gh release download "v$dreamplace_version" \
--repo "openecos-projects/ecc-dreamplace" \
--pattern '*.whl' \
--dir dist/native-wheels
download_wheel_artifact \
"openecos-projects/ecc-dreamplace" \
"chipcompiler/thirdparty/ecc-dreamplace" \
"ecc-dreamplace-wheel"

gh release download "v$ecc_tools_version" \
--repo "openecos-projects/ecc-tools" \
--pattern '*.whl' \
--dir dist/native-wheels
download_wheel_artifact \
"openecos-projects/ecc-tools" \
"chipcompiler/thirdparty/ecc-tools" \
"ecc-tools-wheel"

uv pip install --python .venv/bin/python --no-deps dist/native-wheels/*.whl

- name: Install Python dependencies
shell: bash
run: |
uv sync --frozen --all-groups --python 3.11 --inexact \
--no-install-package ecc-dreamplace \
--no-install-package ecc-tools-bin
if [ "${{ inputs.build-all }}" = "true" ]; then
export SCCACHE_GHA_ENABLED="true"
export CMAKE_ARGS="-DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache"
uv sync --frozen --all-groups --python 3.11 \
--no-build-isolation-package ecc-dreamplace \
--no-build-isolation-package ecc-tools-bin \
--verbose
else
uv sync --frozen --all-groups --python 3.11 --inexact \
--no-install-package ecc-dreamplace \
--no-install-package ecc-tools-bin
fi
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ name: CI

on:
workflow_dispatch:
inputs:
build-all:
description: Build native dependencies from source instead of downloading prebuilt native wheels.
required: false
type: boolean
default: false
push:
branches: [main]
pull_request:
Expand All @@ -27,6 +33,7 @@ concurrency:
cancel-in-progress: true

permissions:
actions: read
contents: read

env:
Expand All @@ -47,6 +54,7 @@ jobs:
name: Test
needs: check-version
runs-on: ubuntu-latest
container: quay.io/pypa/manylinux_2_34_x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -55,6 +63,8 @@ jobs:

- name: Setup Python dependencies
uses: ./.github/actions/setup-python-deps
with:
build-all: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs['build-all'] == 'true' && 'true' || 'false' }}

- name: Setup PDK
run: |
Expand Down Expand Up @@ -97,6 +107,7 @@ jobs:
name: Build PyInstaller Bundle
needs: check-version
runs-on: ubuntu-latest
container: quay.io/pypa/manylinux_2_34_x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -105,6 +116,8 @@ jobs:

- name: Setup Python dependencies
uses: ./.github/actions/setup-python-deps
with:
build-all: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs['build-all'] == 'true' && 'true' || 'false' }}

- name: Smoke test native toolchain wheels
run: |
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:
default: 'v0.1.0-alpha.3'

permissions:
actions: read
contents: write

jobs:
Expand All @@ -32,6 +33,7 @@ jobs:
name: Build CLI Bundle
needs: check-version
runs-on: ubuntu-22.04
container: quay.io/pypa/manylinux_2_34_x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
73 changes: 40 additions & 33 deletions test/cli/commands/test_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@
from chipcompiler.cli import main as cli_main


def _make_path_unreadable(monkeypatch, unreadable_path):
real_open = open
unreadable_path = os.fspath(unreadable_path)

def open_unless_target(path, *args, **kwargs):
if os.fspath(path) == unreadable_path:
raise PermissionError("cannot read")
return real_open(path, *args, **kwargs)

monkeypatch.setattr("builtins.open", open_unless_target)


class TestLog:
def test_log_step_errors(self, tmp_path, capsys, create_cli_project):
project_dir = create_cli_project()
Expand Down Expand Up @@ -536,43 +548,39 @@ def test_artifacts_log_disclosure_no_errors(
class TestLogUnreadableFile:
"""AC-9: Unreadable log files return non-zero with OS error."""

def test_unreadable_log_returns_nonzero(self, tmp_path, capsys, create_cli_project):
def test_unreadable_log_returns_nonzero(
self, tmp_path, monkeypatch, capsys, create_cli_project
):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")
step_dir = os.path.join(run_dir, "Synthesis_yosys", "log")
os.makedirs(step_dir, exist_ok=True)
log_path = os.path.join(step_dir, "synthesis.log")
with open(log_path, "w") as f:
f.write("content\n")
os.chmod(log_path, 0o000)
_make_path_unreadable(monkeypatch, log_path)

try:
rc = cli_main.run(["log", "synthesis", "--project", project_dir])
assert rc == 1
out = capsys.readouterr().out
assert "unreadable" in out
finally:
os.chmod(log_path, 0o644)
rc = cli_main.run(["log", "synthesis", "--project", project_dir])
assert rc == 1
out = capsys.readouterr().out
assert "unreadable" in out

def test_unreadable_log_jsonl(self, tmp_path, capsys, create_cli_project):
def test_unreadable_log_jsonl(self, tmp_path, monkeypatch, capsys, create_cli_project):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")
step_dir = os.path.join(run_dir, "Synthesis_yosys", "log")
os.makedirs(step_dir, exist_ok=True)
log_path = os.path.join(step_dir, "synthesis.log")
with open(log_path, "w") as f:
f.write("content\n")
os.chmod(log_path, 0o000)
_make_path_unreadable(monkeypatch, log_path)

try:
rc = cli_main.run(["log", "synthesis", "--jsonl", "--project", project_dir])
assert rc == 1
record = json.loads(capsys.readouterr().out.strip())
assert record["log_status"] == "unreadable"
assert "source" in record
assert "error" in record
finally:
os.chmod(log_path, 0o644)
rc = cli_main.run(["log", "synthesis", "--jsonl", "--project", project_dir])
assert rc == 1
record = json.loads(capsys.readouterr().out.strip())
assert record["log_status"] == "unreadable"
assert "source" in record
assert "error" in record


class TestLogMultiSource:
Expand Down Expand Up @@ -958,23 +966,22 @@ def test_step_jsonl_unchanged(self, tmp_path, capsys, create_cli_project):
class TestLogListingUnreadable:
"""Unreadable logs in listing mode must omit tail, keep path+inspect, no traceback."""

def test_unreadable_step_log_in_listing(self, tmp_path, capsys, create_cli_project):
def test_unreadable_step_log_in_listing(
self, tmp_path, monkeypatch, capsys, create_cli_project
):
project_dir = create_cli_project()
run_dir = os.path.join(project_dir, "runs", "default")
step_dir = os.path.join(run_dir, "Synthesis_yosys", "log")
os.makedirs(step_dir, exist_ok=True)
log_path = os.path.join(step_dir, "synthesis.log")
with open(log_path, "w") as f:
f.write("content\n")
os.chmod(log_path, 0o000)

try:
rc = cli_main.run(["log", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert "tail:" not in out
assert "Synthesis_yosys" in out
assert "inspect:" in out
assert "Traceback" not in out
finally:
os.chmod(log_path, 0o644)
_make_path_unreadable(monkeypatch, log_path)

rc = cli_main.run(["log", "--project", project_dir])
assert rc == 0
out = capsys.readouterr().out
assert "tail:" not in out
assert "Synthesis_yosys" in out
assert "inspect:" in out
assert "Traceback" not in out
17 changes: 8 additions & 9 deletions test/cli/rendering/test_log_view.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import os

from chipcompiler.cli.inspection.log_view import (
LineKind,
annotate_log_lines,
Expand Down Expand Up @@ -852,17 +850,18 @@ def test_bel_and_backspace_stripped(self, tmp_path):
result = tail_lines_for_log(str(log_file))
assert result == ["abc", "done"]

def test_unreadable_file_returns_empty(self, tmp_path):
def test_unreadable_file_returns_empty(self, monkeypatch, tmp_path):
from chipcompiler.cli.inspection.log_view import tail_lines_for_log

log_file = tmp_path / "test.log"
log_file.write_text("content\n")
os.chmod(str(log_file), 0o000)
try:
result = tail_lines_for_log(str(log_file))
assert result == []
finally:
os.chmod(str(log_file), 0o644)

def raise_permission_error(*args, **kwargs):
raise PermissionError("cannot read")

monkeypatch.setattr("builtins.open", raise_permission_error)
result = tail_lines_for_log(str(log_file))
assert result == []


class TestListingTailRendering:
Expand Down
Loading