Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4faf1c4
feat: add Python bindings, CI/CD pipeline, and cross-platform wheel b…
Neroued Mar 16, 2026
95233d9
fix: Windows potrace build, clang-format, and flaky macOS test
Neroued Mar 16, 2026
b3a1857
fix: clamp SVG coords to image bounds, rewrite CI per ChromaPrint3D
Neroued Mar 16, 2026
aa7c77d
fix: vcpkg use x64-windows-release triplet to skip Debug builds
Neroued Mar 16, 2026
cff071b
fix: test rasterizer handles thin paths, CI supports manual trigger
Neroued Mar 16, 2026
f2a4789
fix: raise dark-pixel thresholds in thin-feature tests
Neroued Mar 16, 2026
1d5c822
fix: lower circle IoU threshold from 0.50 to 0.30
Neroued Mar 16, 2026
ddf5489
fix: use Ninja generator for Windows CI to avoid VS multi-config debu…
Neroued Mar 16, 2026
8759228
perf: drastically speed up Windows CI builds
Neroued Mar 16, 2026
c3cea98
fix: define HAVE_CONFIG_H for potrace build on Windows
Neroued Mar 16, 2026
d0f7558
fix: export VCPKG_INSTALLATION_ROOT to GitHub Actions env context
Neroued Mar 16, 2026
ad44f29
fix: use VCPKG_DEFAULT_TRIPLET env var for vcpkg toolchain detection
Neroued Mar 16, 2026
6c425e0
fix: overhaul Windows CI to fix Python builds and vcpkg caching
Neroued Mar 16, 2026
79a36ff
fix: POTRACE_ROOT must be env var, not cmake define
Neroued Mar 16, 2026
b6fc1eb
fix: add vcpkg DLL directory to PATH for Windows Python tests
Neroued Mar 16, 2026
dba5826
fix: use os.add_dll_directory for Windows DLL loading
Neroued Mar 16, 2026
7382369
feat: add delvewheel repair for Windows wheel DLL bundling
Neroued Mar 16, 2026
0dad862
fix: strip pre-release suffix from git tag for CMake VERSION
Neroued Mar 16, 2026
3dbce00
fix: always build OpenCV/Potrace from source in manylinux container
Neroued Mar 16, 2026
a905ebd
fix: set LD_LIBRARY_PATH for auditwheel and fix vcpkg cache path
Neroued Mar 16, 2026
03e7eb8
fix: pre-download virtualenv to avoid 429 rate limit, trim wheel matrix
Neroued Mar 16, 2026
1391fe8
fix: correct virtualenv cache path on Windows, set macOS deploy target
Neroued Mar 16, 2026
5d285fe
fix: force consistent version via SETUPTOOLS_SCM_PRETEND_VERSION
Neroued Mar 16, 2026
6445058
refactor: unify CI config and clean up workarounds
Neroued Mar 17, 2026
72c8e8a
fix: drop macos-13 runner, build both macOS archs on macos-14
Neroued Mar 17, 2026
bd1c68d
fix: skip x86_64 tests on arm64 macOS runner
Neroued Mar 17, 2026
b55ca5c
chore: remove Linux aarch64 from wheel matrix
Neroued Mar 17, 2026
43b95dc
feat: add set_log_level to Python bindings, minor build improvements
Neroued Mar 17, 2026
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
232 changes: 232 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:

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

# Windows shared constants — single source of truth for vcpkg paths
env:
WIN_VCPKG_ROOT: C:/vcpkg
WIN_VCPKG_TRIPLET: x64-windows-release
WIN_POTRACE_ROOT: C:/potrace

jobs:
# ── C++ format check (changed files only) ─────────────────────────────────
format-check:
name: clang-format
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install clang-format-18
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends clang-format-18

- name: Check formatting of changed files
run: |
BASE="${{ github.event.pull_request.base.sha }}"
FILES=$(git diff --name-only --diff-filter=ACM "$BASE" HEAD \
| grep -E '\.(cpp|h|hpp|cc|cxx)$' \
| grep -E '^(src|include|python|tests|apps|eval)/' || true)

if [ -z "$FILES" ]; then
echo "No C++ files changed, skipping."
exit 0
fi

echo "Checking formatting for:"
echo "$FILES"
FAILED=0
for f in $FILES; do
if ! clang-format-18 --dry-run --Werror "$f" 2>/dev/null; then
echo "::error file=$f::Format mismatch. Run: clang-format -i $f"
FAILED=1
fi
done

if [ "$FAILED" -eq 1 ]; then
echo "::error::Some files need formatting."
exit 1
fi
echo "All changed C++ files are correctly formatted."

# ── C++ build & test ──────────────────────────────────────────────────────
cpp-build:
name: C++ (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-14, windows-latest]

steps:
- uses: actions/checkout@v4
with:
submodules: recursive

# ── Linux ─────────────────────────────────────────────────────────────
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libopencv-dev libpotrace-dev ninja-build ccache
mkdir -p ~/.cache/ccache

- name: Cache ccache (Linux)
if: runner.os == 'Linux'
uses: actions/cache@v4
with:
path: ~/.cache/ccache
key: ccache-linux-${{ hashFiles('CMakeLists.txt') }}
restore-keys: ccache-linux-

- name: Configure (Linux)
if: runner.os == 'Linux'
env:
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
run: |
ccache --set-config=max_size=1G
ccache -z
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DNV_BUILD_TESTS=ON -DNV_BUILD_EVAL=ON -DNV_BUILD_APPS=ON

# ── macOS ─────────────────────────────────────────────────────────────
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: brew install opencv potrace ninja ccache

- name: Cache ccache (macOS)
if: runner.os == 'macOS'
uses: actions/cache@v4
with:
path: ~/Library/Caches/ccache
key: ccache-macos-${{ hashFiles('CMakeLists.txt') }}
restore-keys: ccache-macos-

- name: Configure (macOS)
if: runner.os == 'macOS'
env:
CMAKE_C_COMPILER_LAUNCHER: ccache
CMAKE_CXX_COMPILER_LAUNCHER: ccache
run: |
ccache --set-config=max_size=1G
ccache -z
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DNV_BUILD_TESTS=ON -DNV_BUILD_EVAL=ON -DNV_BUILD_APPS=ON

# ── Windows ───────────────────────────────────────────────────────────
- name: Cache vcpkg packages (Windows)
if: runner.os == 'Windows'
uses: actions/cache@v4
with:
path: ${{ env.WIN_VCPKG_ROOT }}/installed
key: vcpkg-win-${{ hashFiles('ci/install-deps-windows.bat', 'ci/potrace-CMakeLists.txt') }}
restore-keys: vcpkg-win-

- name: Install dependencies (Windows)
if: runner.os == 'Windows'
shell: cmd
run: ci\install-deps-windows.bat

- uses: ilammy/msvc-dev-cmd@v1
if: runner.os == 'Windows'

- name: Configure (Windows)
if: runner.os == 'Windows'
run: >
cmake -S . -B build -G Ninja
-DCMAKE_TOOLCHAIN_FILE="${{ env.WIN_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake"
-DVCPKG_TARGET_TRIPLET=${{ env.WIN_VCPKG_TRIPLET }}
-DPOTRACE_ROOT=${{ env.WIN_POTRACE_ROOT }}
-DCMAKE_BUILD_TYPE=Release
-DNV_BUILD_TESTS=ON -DNV_BUILD_EVAL=ON -DNV_BUILD_APPS=ON

# ── Build & test ──────────────────────────────────────────────────────
- name: Build
run: cmake --build build --config Release -j4

- name: Test
run: ctest --test-dir build --build-config Release --output-on-failure

- name: ccache stats
if: always() && runner.os != 'Windows'
run: ccache -s

# ── Python bindings build & test ──────────────────────────────────────────
python-test:
name: Python ${{ matrix.python-version }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-14, windows-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Install system dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends libopencv-dev libpotrace-dev

- name: Install system dependencies (macOS)
if: runner.os == 'macOS'
run: brew install opencv potrace

- name: Cache vcpkg packages (Windows)
if: runner.os == 'Windows'
uses: actions/cache@v4
with:
path: ${{ env.WIN_VCPKG_ROOT }}/installed
key: vcpkg-win-${{ hashFiles('ci/install-deps-windows.bat', 'ci/potrace-CMakeLists.txt') }}
restore-keys: vcpkg-win-

- name: Install system dependencies (Windows)
if: runner.os == 'Windows'
shell: cmd
run: ci\install-deps-windows.bat

- uses: ilammy/msvc-dev-cmd@v1
if: runner.os == 'Windows'

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install package (Unix)
if: runner.os != 'Windows'
run: pip install --verbose .

- name: Install package (Windows)
if: runner.os == 'Windows'
run: pip install --verbose .
env:
CMAKE_TOOLCHAIN_FILE: ${{ env.WIN_VCPKG_ROOT }}/scripts/buildsystems/vcpkg.cmake
SKBUILD_CMAKE_DEFINE: "VCPKG_TARGET_TRIPLET=${{ env.WIN_VCPKG_TRIPLET }};POTRACE_ROOT=${{ env.WIN_POTRACE_ROOT }}"

- name: Run tests
run: |
pip install pytest numpy opencv-python-headless
pytest python/tests -v
env:
NV_DLL_DIR: ${{ runner.os == 'Windows' && format('{0}/installed/{1}/bin', env.WIN_VCPKG_ROOT, env.WIN_VCPKG_TRIPLET) || '' }}
137 changes: 137 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Build wheels

on:
push:
tags: ["v*"]
workflow_dispatch:

jobs:
# ── Build wheels on each platform ─────────────────────────────────────────
build-wheels:
name: Wheels (${{ matrix.os }}, ${{ matrix.arch }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-24.04
arch: x86_64
- os: macos-14
arch: "x86_64 arm64"
- os: windows-latest
arch: AMD64

steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Get version from tag
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV

- name: Cache vcpkg packages (Windows)
if: runner.os == 'Windows'
uses: actions/cache@v4
with:
path: C:/vcpkg/installed
key: vcpkg-win-${{ hashFiles('ci/install-deps-windows.bat', 'ci/potrace-CMakeLists.txt') }}
restore-keys: vcpkg-win-

- uses: pypa/cibuildwheel@v2.23
env:
CIBW_ARCHS: ${{ matrix.arch }}
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.SETUPTOOLS_SCM_PRETEND_VERSION }}

- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.os }}-${{ matrix.arch }}
path: wheelhouse/*.whl
retention-days: 14

# ── Build source distribution ─────────────────────────────────────────────
build-sdist:
name: Source distribution
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Get version from tag
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: echo "SETUPTOOLS_SCM_PRETEND_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Build sdist
run: pipx run build --sdist
env:
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.SETUPTOOLS_SCM_PRETEND_VERSION }}

- uses: actions/upload-artifact@v4
with:
name: sdist
path: dist/*.tar.gz
retention-days: 14

# ── Publish to TestPyPI (pre-release tags) ────────────────────────────────
publish-testpypi:
name: Publish to TestPyPI
needs: [build-wheels, build-sdist]
runs-on: ubuntu-24.04
if: >-
github.event_name == 'push'
&& startsWith(github.ref, 'refs/tags/v')
&& (contains(github.ref, 'rc') || contains(github.ref, 'a') || contains(github.ref, 'b'))
environment:
name: testpypi
url: https://test.pypi.org/p/neroued-vectorizer
permissions:
id-token: write

steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true

- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
repository-url: https://test.pypi.org/legacy/
skip-existing: true

# ── Publish to PyPI (stable tags) ─────────────────────────────────────────
publish-pypi:
name: Publish to PyPI
needs: [build-wheels, build-sdist]
runs-on: ubuntu-24.04
if: >-
github.event_name == 'push'
&& startsWith(github.ref, 'refs/tags/v')
&& !contains(github.ref, 'rc')
&& !contains(github.ref, 'a')
&& !contains(github.ref, 'b')
environment:
name: pypi
url: https://pypi.org/p/neroued-vectorizer
permissions:
id-token: write

steps:
- uses: actions/download-artifact@v4
with:
path: dist
merge-multiple: true

- uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
skip-existing: true
Loading
Loading