fix for pipx #15
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: ['v*'] | |
| branches: [release] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: '' | |
| required: false | |
| jobs: | |
| build: | |
| name: Build ${{ matrix.artifact }} | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # ubuntu-22.04 pins glibc 2.35 as the portability floor. Newer runners | |
| # raise glibc and break older distros. | |
| - os: ubuntu-22.04 | |
| artifact: linux-x86_64 | |
| # - os: macos-13 | |
| # artifact: macos-x86_64 | |
| - os: macos-14 | |
| artifact: macos-arm64 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| - name: Resolve version | |
| id: version | |
| shell: bash | |
| run: | | |
| tag="${{ github.event.inputs.tag }}" | |
| [ -n "$tag" ] || tag="${GITHUB_REF_NAME}" | |
| echo "tag=$tag" >> "$GITHUB_OUTPUT" | |
| - name: Install system dependencies (Linux) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update -q | |
| sudo apt-get install -y \ | |
| cmake build-essential \ | |
| libeigen3-dev libboost-dev libboost-program-options-dev \ | |
| libgmp-dev libmpfr-dev \ | |
| autoconf automake libtool \ | |
| texlive-latex-base texlive-latex-recommended \ | |
| lp-solve liblpsolve55-dev \ | |
| patchelf \ | |
| pkg-config | |
| - name: Install system dependencies (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| brew install \ | |
| cmake \ | |
| eigen boost \ | |
| gmp mpfr \ | |
| autoconf automake libtool \ | |
| basictex \ | |
| pkg-config | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: '3.11' | |
| - name: Compute dependency cache key | |
| id: deps-cache-key | |
| shell: bash | |
| run: | | |
| hash256() { | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| sha256sum "$@" | |
| else | |
| shasum -a 256 "$@" | |
| fi | |
| } | |
| submodule_hash=$(git submodule status --cached --recursive | hash256 | awk '{print $1}') | |
| recipe_hash=$(hash256 configure.sh .gitmodules | hash256 | awk '{print $1}') | |
| toolchain_hash=$(printf '%s\n%s\n%s\n' \ | |
| "$(uname -m)" \ | |
| "$(cmake --version | head -n1)" \ | |
| "$(${CC:-cc} --version | head -n1 2>/dev/null || true)" \ | |
| | hash256 | awk '{print $1}') | |
| echo "key=${RUNNER_OS}-deps-v1-${submodule_hash}-${recipe_hash}-${toolchain_hash}" >> "$GITHUB_OUTPUT" | |
| - name: Restore dependency cache | |
| id: deps-cache | |
| uses: actions/cache/restore@v4 | |
| with: | |
| key: ${{ steps.deps-cache-key.outputs.key }} | |
| path: | | |
| bin/deps/cddlib/install | |
| bin/deps/VolEsti/external/_deps | |
| bin/deps/VolEsti/examples/volume/build | |
| bin/deps/allsat-circuits/build | |
| bin/deps/pact/build | |
| bin/deps/upstream-lrslib | |
| bin/cvc5 | |
| bin/hall_tool | |
| bin/lrs | |
| bin/sample | |
| bin/volume | |
| - name: Build native dependencies | |
| run: bash configure.sh | |
| - name: Save dependency cache | |
| if: steps.deps-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@v4 | |
| with: | |
| key: ${{ steps.deps-cache-key.outputs.key }} | |
| path: | | |
| bin/deps/cddlib/install | |
| bin/deps/VolEsti/external/_deps | |
| bin/deps/VolEsti/examples/volume/build | |
| bin/deps/allsat-circuits/build | |
| bin/deps/pact/build | |
| bin/deps/upstream-lrslib | |
| bin/cvc5 | |
| bin/hall_tool | |
| bin/lrs | |
| bin/sample | |
| bin/volume | |
| - name: Report native binary linkage | |
| shell: bash | |
| run: | | |
| binaries=( | |
| bin/cvc5 | |
| bin/hall_tool | |
| bin/lrs | |
| bin/sample | |
| bin/volume | |
| ) | |
| report_linux_linkage() { | |
| local bin="$1" | |
| local output | |
| output=$(ldd "$bin" 2>&1 || true) | |
| printf '%s\n' "$output" | |
| case "$output" in | |
| *"not a dynamic executable"*|*"statically linked"*) | |
| echo "Summary: statically linked" | |
| ;; | |
| *) | |
| echo "Summary: dynamically linked or mixed" | |
| ;; | |
| esac | |
| } | |
| report_macos_linkage() { | |
| local bin="$1" | |
| local output | |
| output=$(otool -L "$bin" 2>&1 || true) | |
| printf '%s\n' "$output" | |
| if printf '%s\n' "$output" | tail -n +2 | grep -Eq '^[[:space:]]+(/|@)'; then | |
| echo "Summary: dynamically linked" | |
| else | |
| echo "Summary: unable to confirm dynamic linkage" | |
| fi | |
| } | |
| for bin in "${binaries[@]}"; do | |
| echo "::group::${bin}" | |
| if [ ! -e "$bin" ]; then | |
| echo "Summary: binary not present" | |
| echo "::endgroup::" | |
| continue | |
| fi | |
| file_output=$(file "$bin" 2>&1 || true) | |
| printf '%s\n' "$file_output" | |
| case "$file_output" in | |
| *"script"*|*"text executable"*) | |
| echo "Summary: not a native binary (script)" | |
| echo "::endgroup::" | |
| continue | |
| ;; | |
| esac | |
| if command -v ldd >/dev/null 2>&1; then | |
| report_linux_linkage "$bin" | |
| elif command -v otool >/dev/null 2>&1; then | |
| report_macos_linkage "$bin" | |
| else | |
| echo "Summary: no supported linkage inspection tool available" | |
| fi | |
| echo "::endgroup::" | |
| done | |
| - name: Install wheel build/repair tooling | |
| shell: bash | |
| run: | | |
| python -m pip install --upgrade pip wheel | |
| if [ "$(uname)" = "Darwin" ]; then | |
| python -m pip install delocate | |
| else | |
| python -m pip install auditwheel | |
| fi | |
| - name: Build pycddlib wheel against bundled cddlib | |
| shell: bash | |
| run: | | |
| CDDLIB_PREFIX="$PWD/bin/deps/cddlib/install" | |
| GMP_PREFIX=$(brew --prefix gmp 2>/dev/null || echo "") | |
| GMP_CFLAGS=${GMP_PREFIX:+-I$GMP_PREFIX/include} | |
| GMP_LDFLAGS=${GMP_PREFIX:+-L$GMP_PREFIX/lib} | |
| ARCHFLAGS="" | |
| WHEEL_ENV=( | |
| "CFLAGS=-I$CDDLIB_PREFIX/include -I$CDDLIB_PREFIX/include/cddlib $GMP_CFLAGS" | |
| "LDFLAGS=-L$CDDLIB_PREFIX/lib $GMP_LDFLAGS" | |
| "ARCHFLAGS=$ARCHFLAGS" | |
| ) | |
| PYTHON_HOST_PLATFORM="" | |
| MACOSX_DEPLOYMENT_TARGET="" | |
| if [ "$(uname)" = "Darwin" ]; then | |
| ARCHFLAGS="-arch $(uname -m)" | |
| # GitHub's macOS Python 3.11 can default wheel tags to universal2 | |
| # even when we only build arm64 binaries. Force an arm64 platform | |
| # tag so delocate does not require a missing x86_64 slice. | |
| MACOSX_DEPLOYMENT_TARGET="11.0" | |
| PYTHON_HOST_PLATFORM="macosx-${MACOSX_DEPLOYMENT_TARGET}-$(uname -m)" | |
| WHEEL_ENV[2]="ARCHFLAGS=$ARCHFLAGS" | |
| WHEEL_ENV+=( | |
| "MACOSX_DEPLOYMENT_TARGET=$MACOSX_DEPLOYMENT_TARGET" | |
| "_PYTHON_HOST_PLATFORM=$PYTHON_HOST_PLATFORM" | |
| ) | |
| fi | |
| mkdir -p dist-raw | |
| env "${WHEEL_ENV[@]}" \ | |
| pip wheel --no-binary pycddlib --no-deps -w dist-raw pycddlib | |
| if [ "$(uname)" = "Darwin" ]; then | |
| raw_wheel=$(echo dist-raw/pycddlib-*.whl) | |
| case "$raw_wheel" in | |
| *arm64.whl) ;; | |
| *) | |
| echo "ERROR: expected an arm64 pycddlib wheel, got: $raw_wheel" | |
| exit 1 | |
| ;; | |
| esac | |
| fi | |
| - name: Repair pycddlib wheel (vendor libcddgmp + libgmp) | |
| shell: bash | |
| run: | | |
| mkdir -p wheels | |
| CDDLIB_PREFIX="$PWD/bin/deps/cddlib/install" | |
| if [ "$(uname)" = "Darwin" ]; then | |
| DYLD_LIBRARY_PATH="$CDDLIB_PREFIX/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" \ | |
| delocate-wheel -v -w wheels dist-raw/pycddlib-*.whl | |
| else | |
| # Target manylinux_2_35 to match the ubuntu-22.04 runner's glibc. | |
| # Raise this if you bump the runner; lower it only if you build | |
| # inside an older manylinux container. | |
| LD_LIBRARY_PATH="$CDDLIB_PREFIX/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \ | |
| auditwheel repair --plat manylinux_2_35_x86_64 -w wheels dist-raw/pycddlib-*.whl | |
| fi | |
| - name: Build polytope wheel from source | |
| shell: bash | |
| run: | | |
| mkdir -p wheels | |
| pip wheel --no-binary polytope --no-deps -w wheels polytope | |
| if ! compgen -G 'wheels/polytope-*.whl' > /dev/null; then | |
| echo "ERROR: expected a built polytope wheel" | |
| exit 1 | |
| fi | |
| - name: Collect other dependency wheels | |
| shell: bash | |
| run: | | |
| # The archive requirements should still list every runtime package, | |
| # including pycddlib and polytope, because those wheels are bundled | |
| # locally and installed offline by install.sh. | |
| cp requirements.txt requirements-release.txt | |
| if [ "$(uname)" = "Darwin" ]; then | |
| # z3-solver 4.16.0.0 only publishes macOS ARM64 wheels tagged for | |
| # macOS 15.0+, which a macOS 14 builder will not vendor into an | |
| # offline archive. Pin the macOS release artifact to the newest Z3 | |
| # wheel that still targets macOS 11.0+ ARM64. | |
| awk ' | |
| /^z3-solver([[:space:]]|$)/ { print "z3-solver==4.13.1.0"; next } | |
| { print } | |
| ' requirements-release.txt > requirements-release.tmp | |
| mv requirements-release.tmp requirements-release.txt | |
| fi | |
| # Derive the download list AFTER the pin so pip fetches the exact | |
| # versions requirements.txt inside the archive will ask for. | |
| grep -Ev '^(pycddlib|polytope)$' requirements-release.txt > requirements-download.txt | |
| pip download --only-binary=:all: -d wheels -r requirements-download.txt | |
| pip download --only-binary=:all: -d wheels scipy networkx | |
| if [ "$(uname)" = "Darwin" ] && ! compgen -G 'wheels/z3_solver-*.whl' > /dev/null; then | |
| echo "ERROR: expected a bundled z3-solver wheel for the macOS archive" | |
| exit 1 | |
| fi | |
| for pkg in pycddlib polytope; do | |
| if ! grep -Fxq "$pkg" requirements-release.txt; then | |
| echo "ERROR: release requirements are missing $pkg" | |
| exit 1 | |
| fi | |
| done | |
| - name: Stage release payload | |
| shell: bash | |
| run: | | |
| TAG="${{ steps.version.outputs.tag }}" | |
| STAGE="ttc-${TAG#v}-${{ matrix.artifact }}" | |
| mkdir -p "$STAGE/bin" "$STAGE/wheels" | |
| install -m 755 ttc.py "$STAGE/ttc" | |
| cp ttc.py _bootstrap.py README.md LICENSE "$STAGE/" | |
| cp requirements-release.txt "$STAGE/requirements.txt" | |
| cp -R src example "$STAGE/" | |
| for b in cvc5 hall_tool lrs sample volume; do | |
| cp "bin/$b" "$STAGE/bin/" | |
| done | |
| cp wheels/*.whl "$STAGE/wheels/" | |
| install -m 755 utils/install.sh "$STAGE/install.sh" | |
| tar czf "${STAGE}.tar.gz" "$STAGE" | |
| echo "ARCHIVE=${STAGE}.tar.gz" >> "$GITHUB_ENV" | |
| echo "STAGE=${STAGE}" >> "$GITHUB_ENV" | |
| - name: Smoke-test the archive in a clean directory | |
| shell: bash | |
| run: | | |
| WORK="$(mktemp -d)" | |
| tar xzf "$ARCHIVE" -C "$WORK" | |
| cd "$WORK/$STAGE" | |
| ./install.sh | |
| set +e | |
| # Use ./ttc (shebang rewritten to the .venv python by install.sh), | |
| # not `python ttc.py` — plain `python` here is the workflow's | |
| # setup-python, which has no pycddlib/polytope installed. | |
| output=$(./ttc example/box_or_lra.smt2 2>&1) | |
| status=$? | |
| set -e | |
| printf '%s\n' "$output" | |
| [ "$status" -eq 0 ] || { echo "ERROR: ttc exited $status"; exit "$status"; } | |
| echo "$output" | grep -E '^s vol [0-9]+(\.[0-9]+)?' \ | |
| || { echo "ERROR: expected 's vol <float>' line not found"; exit 1; } | |
| - name: End-to-end pipx test against local archive | |
| if: startsWith(github.ref, 'refs/tags/') | |
| shell: bash | |
| run: | | |
| # Serve the just-built tarball so _bootstrap.py can fetch it locally, | |
| # simulating the published-release download path without needing the | |
| # tag to be published yet. | |
| python -m http.server 8765 --directory "$PWD" >/tmp/http.log 2>&1 & | |
| HTTP_PID=$! | |
| trap "kill $HTTP_PID 2>/dev/null || true" EXIT | |
| for i in 1 2 3 4 5; do | |
| if curl -fsS "http://localhost:8765/${ARCHIVE}" -o /dev/null; then | |
| break | |
| fi | |
| if [ "$i" = "5" ]; then | |
| echo "HTTP server never came up. Log:" | |
| cat /tmp/http.log | |
| exit 1 | |
| fi | |
| sleep 1 | |
| done | |
| python -m pip install --quiet --upgrade pipx | |
| python -m pipx install --python "$(command -v python)" . | |
| PIPX_BIN_DIR=$(python -m pipx environment --value PIPX_BIN_DIR) | |
| export PATH="$PIPX_BIN_DIR:$PATH" | |
| export TTC_ARCHIVE_URL="http://localhost:8765/${ARCHIVE}" | |
| set +e | |
| output=$(ttc example/box_or_lra.smt2 2>&1) | |
| status=$? | |
| set -e | |
| printf '%s\n' "$output" | |
| [ "$status" -eq 0 ] || { echo "ERROR: ttc exited $status"; exit "$status"; } | |
| echo "$output" | grep -E '^s vol [0-9]+(\.[0-9]+)?' \ | |
| || { echo "ERROR: expected 's vol <float>' line not found"; exit 1; } | |
| # Second invocation should hit the cache — unset the URL to prove | |
| # no network is touched on re-run. | |
| unset TTC_ARCHIVE_URL | |
| ttc example/box_or_lra.smt2 >/dev/null | |
| python -m pipx uninstall ttc | |
| - name: Upload archive as workflow artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ttc-${{ matrix.artifact }} | |
| path: ${{ env.ARCHIVE }} | |
| retention-days: 14 | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| merge-multiple: true | |
| - name: Create GitHub release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: artifacts/*.tar.gz | |
| draft: false | |
| generate_release_notes: true |