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
81 changes: 31 additions & 50 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: CI

on:
workflow_dispatch:
push:
branches: [main, develop]
pull_request:
Expand All @@ -11,7 +12,7 @@ env:

jobs:
lint:
name: Lint
name: Python Checks
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -22,17 +23,26 @@ jobs:
with:
python-version: "3.11"

- name: Install linting tools
run: pip install ruff black mypy
- name: Install Python check deps
run: python -m pip install pyyaml

- name: Run Ruff
run: ruff check .
- name: Validate cross-platform wheel tag helper
run: |
python packaging/wheel/get_wheel_plat_name.py --system linux --arch x86_64
python packaging/wheel/get_wheel_plat_name.py --system macos --arch arm64
python packaging/wheel/get_wheel_plat_name.py --system macos --arch x86_64

- name: Run Black
run: black --check .
- name: Byte-compile packaging + CLI modules
run: |
python -m py_compile \
packaging/wheel/create_wheel.py \
packaging/wheel/get_wheel_plat_name.py \
packaging/wheel/platform_tags.py \
compiler/frontend/pycircuit/packaged_toolchain.py \
compiler/frontend/pycircuit/cli.py

- name: Run MyPy
run: mypy compiler/frontend/pycircuit || true
- name: Run API hygiene gate
run: python flows/tools/check_api_hygiene.py compiler/frontend/pycircuit designs/examples docs README.md

build-linux:
name: Build Toolchain (Linux)
Expand Down Expand Up @@ -74,6 +84,14 @@ jobs:
--install-dir "$PWD/.pycircuit_out/toolchain/install" \
--out-dir "$PWD/dist"

- name: Run examples
run: |
export PYC_TOOLCHAIN_ROOT="$PWD/.pycircuit_out/toolchain/install"
export PATH="$PYC_TOOLCHAIN_ROOT/bin:$PATH"
export PYC_SKIP_SEMANTIC_REGRESSIONS=1
unset PYCC
bash flows/scripts/run_examples.sh

- name: Upload toolchain
uses: actions/upload-artifact@v4
with:
Expand All @@ -88,44 +106,6 @@ jobs:
path: dist/*.whl
retention-days: 1

examples-linux:
name: Examples + Sims (Linux)
runs-on: ubuntu-latest
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Install simulation deps
run: |
sudo apt-get update
sudo apt-get install -y verilator iverilog

- name: Download toolchain
uses: actions/download-artifact@v4
with:
name: pyc-toolchain-linux
path: .pycircuit_out/toolchain/install

- name: Run examples
run: |
export PYC_TOOLCHAIN_ROOT="$PWD/.pycircuit_out/toolchain/install"
export PATH="$PYC_TOOLCHAIN_ROOT/bin:$PATH"
unset PYCC
bash flows/scripts/run_examples.sh

- name: Run simulations
run: |
export PYC_TOOLCHAIN_ROOT="$PWD/.pycircuit_out/toolchain/install"
export PATH="$PYC_TOOLCHAIN_ROOT/bin:$PATH"
unset PYCC
bash flows/scripts/run_sims.sh

wheel-linux:
name: Wheel Smoke (Linux)
runs-on: ubuntu-latest
Expand Down Expand Up @@ -159,6 +139,7 @@ jobs:
pip install dist/*.whl
export PYC_TOOLCHAIN_ROOT="$(python -c 'import pycircuit, pathlib; print((pathlib.Path(pycircuit.__file__).resolve().parent / "_toolchain").as_posix())')"
export PYC_USE_INSTALLED_PYTHON_PACKAGE=1
export PYC_SKIP_SEMANTIC_REGRESSIONS=1
unset PYCC
bash flows/scripts/run_examples.sh

Expand Down Expand Up @@ -193,16 +174,15 @@ jobs:

- name: Build platform wheel
run: |
python packaging/wheel/get_macos_wheel_plat_name.py "$(uname -m)" > .pycircuit_out/macos_plat_name.txt
python packaging/wheel/create_wheel.py \
--install-dir "$PWD/.pycircuit_out/toolchain/install" \
--out-dir "$PWD/dist" \
--wheel-plat-name "$(cat .pycircuit_out/macos_plat_name.txt)"
--out-dir "$PWD/dist"

- name: Run example compile gate
run: |
export PYC_TOOLCHAIN_ROOT="$PWD/.pycircuit_out/toolchain/install"
export PATH="$PYC_TOOLCHAIN_ROOT/bin:$PATH"
export PYC_SKIP_SEMANTIC_REGRESSIONS=1
unset PYCC
bash flows/scripts/run_examples.sh

Expand All @@ -215,6 +195,7 @@ jobs:
pip install dist/*.whl
export PYC_TOOLCHAIN_ROOT="$(python -c 'import pycircuit, pathlib; print((pathlib.Path(pycircuit.__file__).resolve().parent / "_toolchain").as_posix())')"
export PYC_USE_INSTALLED_PYTHON_PACKAGE=1
export PYC_SKIP_SEMANTIC_REGRESSIONS=1
unset PYCC
bash flows/scripts/run_examples.sh

Expand Down
8 changes: 2 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,16 @@ jobs:
mkdir -p dist
mv ".pycircuit_out/toolchain/build/${PKG}.tar.gz" "dist/${PKG}.tar.gz"

WHEEL_ARGS=()
if [[ "${RUNNER_OS}" == "macOS" ]]; then
WHEEL_ARGS+=(--wheel-plat-name "$(python3 packaging/wheel/get_macos_wheel_plat_name.py "$(uname -m)")")
fi
python3 packaging/wheel/create_wheel.py \
--install-dir "$PWD/.pycircuit_out/toolchain/install" \
--out-dir "$PWD/dist" \
"${WHEEL_ARGS[@]}"
--out-dir "$PWD/dist"

python3 -m venv .venv-wheel
source .venv-wheel/bin/activate
pip install dist/*.whl
export PYC_TOOLCHAIN_ROOT="$(python -c 'import pycircuit, pathlib; print((pathlib.Path(pycircuit.__file__).resolve().parent / "_toolchain").as_posix())')"
export PYC_USE_INSTALLED_PYTHON_PACKAGE=1
export PYC_SKIP_SEMANTIC_REGRESSIONS=1
unset PYCC
bash flows/scripts/run_examples.sh

Expand Down
12 changes: 8 additions & 4 deletions flows/scripts/run_examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1212,10 +1212,14 @@ fi
cp -f "${decision_report}" "${docs_gate_dir}/decision_status_report.json" >/dev/null 2>&1 || true

pyc_log "running v4.0 semantic regression lane"
if ! PYC_GATE_RUN_ID="${gate_run_id}" bash "${PYC_ROOT_DIR}/flows/scripts/run_semantic_regressions_v40.sh" \
>"${docs_gate_dir}/semantic_regressions.stdout" 2>"${docs_gate_dir}/semantic_regressions.stderr"; then
pyc_warn "semantic regression lane failed"
fail=1
if [[ "${PYC_SKIP_SEMANTIC_REGRESSIONS:-0}" == "1" ]]; then
pyc_log "skipping v4.0 semantic regression lane (PYC_SKIP_SEMANTIC_REGRESSIONS=1)"
else
if ! PYC_GATE_RUN_ID="${gate_run_id}" bash "${PYC_ROOT_DIR}/flows/scripts/run_semantic_regressions_v40.sh" \
>"${docs_gate_dir}/semantic_regressions.stdout" 2>"${docs_gate_dir}/semantic_regressions.stderr"; then
pyc_warn "semantic regression lane failed"
fail=1
fi
fi

if [[ "${fail}" -eq 0 ]]; then
Expand Down
18 changes: 13 additions & 5 deletions packaging/wheel/create_wheel.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@
import subprocess
import sys
import tempfile
import tomllib
from pathlib import Path

from platform_tags import wheel_plat_name

try:
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python 3.11+ in CI, but keep 3.9/3.10 usable.
try:
import tomli as tomllib # type: ignore[no-redef]
except ModuleNotFoundError as exc: # pragma: no cover - depends on local Python version.
raise SystemExit("wheel packaging requires Python 3.11+ or `tomli` on Python 3.9/3.10") from exc


def _repo_root() -> Path:
return Path(__file__).resolve().parents[2]
Expand Down Expand Up @@ -73,14 +82,13 @@ def main(argv: list[str] | None = None) -> int:
_copy_file(repo_root / "packaging" / "wheel" / "setup.py", stage / "setup.py")
_copy_file(repo_root / "packaging" / "wheel" / "pyproject.toml", stage / "pyproject.toml")

plat_name = args.wheel_plat_name or wheel_plat_name()

env = os.environ.copy()
env["PYC_WHEEL_VERSION"] = version
if args.wheel_plat_name:
env["PYC_WHEEL_PLAT_NAME"] = args.wheel_plat_name

cmd = [sys.executable, "setup.py", "bdist_wheel", "--dist-dir", str(out_dir)]
if args.wheel_plat_name:
cmd.extend(["--plat-name", args.wheel_plat_name])
cmd.extend(["--plat-name", plat_name])

subprocess.run(cmd, check=True, cwd=stage, env=env)
return 0
Expand Down
19 changes: 0 additions & 19 deletions packaging/wheel/get_macos_wheel_plat_name.py

This file was deleted.

24 changes: 24 additions & 0 deletions packaging/wheel/get_wheel_plat_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
from __future__ import annotations

import argparse

from platform_tags import wheel_plat_name


def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description="Print the wheel platform tag for the current or requested host.")
ap.add_argument("--system", default=None, help="Override platform.system() for cross-platform checks")
ap.add_argument("--arch", default=None, help="Override platform.machine() for cross-platform checks")
ap.add_argument(
"--deployment-target",
default=None,
help="Override MACOSX_DEPLOYMENT_TARGET when computing macOS wheel tags",
)
args = ap.parse_args(argv)
print(wheel_plat_name(system=args.system, arch=args.arch, deployment_target=args.deployment_target))
return 0


if __name__ == "__main__":
raise SystemExit(main())
71 changes: 71 additions & 0 deletions packaging/wheel/platform_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from __future__ import annotations

import os
import platform


def _normalize_system(system: str | None = None) -> str:
raw = (system or platform.system()).strip().lower()
aliases = {
"darwin": "macos",
"macos": "macos",
"mac": "macos",
"linux": "linux",
"windows": "windows",
"win32": "windows",
"cygwin": "windows",
"msys": "windows",
}
return aliases.get(raw, raw)


def _normalize_arch(system: str, arch: str | None = None) -> str:
raw = (arch or platform.machine()).strip().lower()
if system == "macos":
aliases = {
"aarch64": "arm64",
"arm64": "arm64",
"x86_64": "x86_64",
"amd64": "x86_64",
}
return aliases.get(raw, raw)
if system == "windows":
aliases = {
"amd64": "amd64",
"x86_64": "amd64",
"arm64": "arm64",
"aarch64": "arm64",
}
return aliases.get(raw, raw)
aliases = {
"amd64": "x86_64",
"x64": "x86_64",
"x86_64": "x86_64",
"arm64": "aarch64",
"aarch64": "aarch64",
}
return aliases.get(raw, raw)


def _macos_target(arch: str, deployment_target: str | None = None) -> tuple[str, str]:
target = (deployment_target or os.environ.get("MACOSX_DEPLOYMENT_TARGET", "")).strip()
if not target:
target = "11.0" if arch == "arm64" else "10.13"
parts = (target.split(".") + ["0", "0"])[:2]
return parts[0], parts[1]


def wheel_plat_name(
*, system: str | None = None, arch: str | None = None, deployment_target: str | None = None
) -> str:
normalized_system = _normalize_system(system)
normalized_arch = _normalize_arch(normalized_system, arch)

if normalized_system == "macos":
major, minor = _macos_target(normalized_arch, deployment_target)
return f"macosx_{major}_{minor}_{normalized_arch}"
if normalized_system == "linux":
return f"linux_{normalized_arch}"
if normalized_system == "windows":
return f"win_{normalized_arch}"
raise SystemExit(f"unsupported platform for wheel packaging: {normalized_system}")
Loading