Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
54 changes: 54 additions & 0 deletions .github/actions/build-wheel/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Build ECC-Tools Wheel
description: CMake build + auditwheel repair + smoke test. Requires manylinux_2_34_x86_64 container.

runs:
using: composite
steps:
- name: Install system dependencies
shell: bash
run: |
dnf install -y epel-release
dnf config-manager --set-enabled crb || true
dnf install -y \
cmake ninja-build pkgconfig patchelf \
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

- name: Setup Python 3.11
shell: bash
run: echo "/opt/python/cp311-cp311/bin" >> "$GITHUB_PATH"

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9

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

- name: Install Python dev dependencies
shell: bash
run: |
uv sync --all-groups --python 3.11
echo "${{ github.workspace }}/.venv/bin" >> "$GITHUB_PATH"

Comment thread
Emin017 marked this conversation as resolved.
- name: Restore CMake build cache
uses: actions/cache@v4
with:
path: build
key: cmake-${{ runner.os }}-${{ hashFiles('CMakeLists.txt', 'src/**/CMakeLists.txt') }}
restore-keys: |
cmake-${{ runner.os }}-

- name: Build repaired wheel
shell: bash
env:
SCCACHE_GHA_ENABLED: "true"
run: .github/scripts/build-wheel.sh
114 changes: 114 additions & 0 deletions .github/scripts/build-wheel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env bash
set -euo pipefail

# Build ecc-tools wheel: CMake build -> collect .so -> uv build -> auditwheel repair -> smoke test
# Intended to run inside manylinux_2_34_x86_64 container or equivalent.

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"

cd "$REPO_ROOT"

die() { echo "ERROR: $1" >&2; exit 1; }

require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "required command not found: $1"
}

# Validate environment
[[ "${OSTYPE:-}" == linux* ]] || die "wheel build is only supported on Linux"

PYTHON3="${PYTHON3:-$(command -v python3 || true)}"
[[ -x "${PYTHON3:-}" ]] || die "python3 not found"

for cmd in cmake ninja auditwheel uv sha256sum; do
require_cmd "$cmd"
done

# CMake configure
echo "[build-wheel] CMake configure"
cmake_launcher_args=()
if command -v sccache >/dev/null 2>&1; then
cmake_launcher_args+=(
-DCMAKE_CXX_COMPILER_LAUNCHER=sccache
-DCMAKE_C_COMPILER_LAUNCHER=sccache
)
echo "[build-wheel] sccache enabled"
fi

cmake -B build -G Ninja \
-DBUILD_ECOS=ON \
-DBUILD_PYTHON=ON \
-DBUILD_STATIC_LIB=OFF \
-DCOMPATIBILITY_MODE=ON \
-DCMAKE_BUILD_TYPE=Release \
-DPython3_EXECUTABLE="$PYTHON3" \
-DPython3_ROOT_DIR="$("$PYTHON3" -c "import sys; print(sys.prefix)")" \
-DPython3_FIND_STRATEGY=LOCATION \
"${cmake_launcher_args[@]}"

# CMake build
echo "[build-wheel] CMake build ($(nproc) jobs)"
cmake --build build --target ecc_py -j"$(nproc)"

# Collect artifacts
ECC_PY_ABI="$("$PYTHON3" -c "import sysconfig; print(sysconfig.get_config_var('EXT_SUFFIX'))")"
ecc_py_so="$(find build bin -name "ecc_py${ECC_PY_ABI}" -print -quit 2>/dev/null)"
[[ -n "$ecc_py_so" ]] || die "ecc_py${ECC_PY_ABI} not found in build/ or bin/"
echo "[build-wheel] Found: $ecc_py_so"
cp -f "$ecc_py_so" "ecc_tools_bin/"

# Build raw wheel
echo "[build-wheel] Building raw wheel"
raw_out="dist/wheel/raw"
mkdir -p "$raw_out"
uv build --wheel --out-dir "$raw_out"

raw_whl="$(find "$raw_out" -name 'ecc_tools-*.whl' -print -quit)"
[[ -n "$raw_whl" ]] || die "raw wheel not found in $raw_out"
echo "[build-wheel] Raw wheel: $raw_whl"

# auditwheel repair
echo "[build-wheel] auditwheel show/repair"
repair_out="dist/wheel/repaired"
report_out="dist/wheel/reports"
mkdir -p "$repair_out" "$report_out"

show_report="$report_out/show.txt"
{
echo "=== $(basename "$raw_whl") ==="
auditwheel show "$raw_whl"
echo
} > "$show_report"

auditwheel repair "$raw_whl" -w "$repair_out"

repaired_whl="$(find "$repair_out" -name 'ecc_tools-*.whl' -print -quit)"
[[ -n "$repaired_whl" ]] || die "no repaired ecc_tools wheel found in $repair_out"

# Smoke test
echo "[build-wheel] Running smoke test"
smoke_dir="$(mktemp -d)"
trap 'rm -rf "$smoke_dir"' EXIT

"$PYTHON3" -m pip install --target "$smoke_dir/site" "$repair_out"/ecc_tools-*.whl
PYTHONPATH="$smoke_dir/site" "$PYTHON3" -c "
from ecc_tools_bin import ecc_py

required = ['flow_init', 'flow_exit', 'db_init', 'def_init', 'lef_init',
'def_save', 'run_placer', 'run_cts', 'run_rt', 'run_drc',
'run_filler', 'init_floorplan', 'report_db', 'feature_summary']
missing = [f for f in required if not callable(getattr(ecc_py, f, None))]
assert not missing, f'missing or non-callable bindings: {missing}'

print(f'ecc_py smoke test passed: {len(required)} bindings verified')
"

# Checksums
sha256sum "$repair_out"/*.whl > dist/wheel/SHA256SUMS

echo "[build-wheel] done"
echo "[build-wheel] raw wheels: $raw_out"
echo "[build-wheel] repaired wheels: $repair_out"
echo "[build-wheel] report: $show_report"
echo "[build-wheel] checksums: dist/wheel/SHA256SUMS"
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
pull_request:
push:
branches: [main]

concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
build-wheel:
name: Build Wheel
runs-on: ubuntu-latest
container: quay.io/pypa/manylinux_2_34_x86_64
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build wheel
uses: ./.github/actions/build-wheel

- name: Upload repaired wheel
uses: actions/upload-artifact@v4
with:
name: ecc-tools-wheel
path: dist/wheel/repaired/ecc_tools-*.whl
retention-days: 7
if-no-files-found: error

- name: Upload checksums
uses: actions/upload-artifact@v4
with:
name: checksums
path: dist/wheel/SHA256SUMS
retention-days: 7
100 changes: 100 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Release

on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag_name:
description: 'Tag to release (e.g., v0.1.0-alpha)'
required: true
default: 'v0.1.0-alpha'

permissions:
contents: write

jobs:
build:
name: Build Wheel
runs-on: ubuntu-latest
container: quay.io/pypa/manylinux_2_34_x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag_name || github.ref }}
submodules: recursive
fetch-depth: 0

Comment thread
Emin017 marked this conversation as resolved.
- name: Build wheel
uses: ./.github/actions/build-wheel

- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: ecc-tools-wheel
path: |
dist/wheel/repaired/ecc_tools-*.whl
dist/wheel/SHA256SUMS

release:
name: Create Release
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag_name || github.ref }}
fetch-depth: 0

- name: Download wheel artifact
uses: actions/download-artifact@v4
with:
name: ecc-tools-wheel
path: dist/wheel
Comment thread
Emin017 marked this conversation as resolved.

- name: Determine tag version
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
FULL_TAG="${{ github.event.inputs.tag_name }}"
TAG_VERSION="${FULL_TAG#v}"
else
FULL_TAG="${GITHUB_REF_NAME}"
TAG_VERSION="${GITHUB_REF_NAME#v}"
fi
echo "version=$TAG_VERSION" >> "$GITHUB_OUTPUT"
echo "full_tag=$FULL_TAG" >> "$GITHUB_OUTPUT"

- name: Generate release notes
id: notes
run: |
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
if [[ -n "$PREV_TAG" ]]; then
RANGE="${PREV_TAG}..HEAD"
else
RANGE="HEAD"
fi
{
echo "## Changes"
echo ""
git log --oneline --no-merges "$RANGE" | sed 's/^/- /'
echo ""
echo "## Checksums"
echo ""
echo '```'
cat dist/wheel/SHA256SUMS
echo '```'
} > release-notes.md
cat release-notes.md

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ steps.version.outputs.full_tag }}" \
--title "ecc-tools ${{ steps.version.outputs.full_tag }}" \
--notes-file release-notes.md \
dist/wheel/repaired/ecc_tools-*.whl \
dist/wheel/SHA256SUMS
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ rpt*/
target/
*-stamp/
tmp/
scripts
/scripts
*.patch
CMakePresets.json
.venv/
Expand Down
2 changes: 2 additions & 0 deletions ecc_tools_bin/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Intentionally minimal — consumers import ecc_py directly:
# from ecc_tools_bin import ecc_py
30 changes: 30 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[build-system]
requires = ["uv_build>=0.8.5,<0.12"]
build-backend = "uv_build"

[project]
name = "ecc-tools"
version = "0.1.0-alpha"
Comment thread
Emin017 marked this conversation as resolved.
requires-python = ">=3.11"
dependencies = []

[tool.uv]
build-backend.module-name = ["ecc_tools_bin"]
build-backend.module-root = ""
build-backend.source-exclude = [
"src/**",
"build/**",
"scripts/**",
".github/**",
".gitee/**",
]
build-backend.wheel-exclude = [
"src/**",
"build/**",
"scripts/**",
".github/**",
".gitee/**",
]

[dependency-groups]
dev = ["auditwheel"]
Loading