diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abb29d4..ec279fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,7 +156,75 @@ jobs: env: PYTHON_ZLIB_NG_LINK_DYNAMIC: True - deploy: + # Run the tests on CPython builds with AddressSanitizer and ThreadSanitizer + # from https://github.com/nascheme/cpython_sanity. + test-sanitizers: + runs-on: ubuntu-latest + # A crash under ThreadSanitizer can hang instead of exiting. + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - sanitizer: address + image: ghcr.io/nascheme/cpython-asan:3.14 + # The free-threaded build runs Python threads in parallel. + - sanitizer: thread + image: ghcr.io/nascheme/cpython-tsan:3.14t + container: + image: ${{ matrix.image }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install build dependencies + run: apt-get update && apt-get install -y cmake + - name: Install python-isal with ${{ matrix.sanitizer }} sanitizer + # setuptools-scm needs git, and the container user does not own the + # repository. + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + python -m pip install . pytest pytest-timeout + env: + # The extension modules get -fsanitize from CPython. CFLAGS adds it + # to the C code of ISA-L. + CFLAGS: -fsanitize=${{ matrix.sanitizer }} -fno-omit-frame-pointer -g + - name: Run tests + # Do not set PYTHONDEVMODE. Its debug allocator pads each allocation, + # so AddressSanitizer misses small overflows. --capture=sys prints + # sanitizer reports from C code even when they end the process, and + # -v prints the name of the test before its report. + run: python -m pytest -v tests --capture=sys + env: + # Exit at the first data race. A crash after a race can hang. + TSAN_OPTIONS: halt_on_error=1 + + make_sdist: + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + needs: + - lint + - package-checks + - test-static + - test-dynamic + - test-arch + - test-sanitizers + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # Optional, use if you use setuptools_scm + submodules: true # Optional, use if you have submodules + persist-credentials: false # Highly recommended as a good security practice to not store credentials in disk, unless you need this for your specific use case + + - name: Build SDist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz + + build_wheels: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') runs-on: ${{ matrix.os }} needs: @@ -165,6 +233,7 @@ jobs: - test-static - test-dynamic - test-arch + - test-sanitizers strategy: matrix: os: @@ -239,19 +308,33 @@ jobs: python -m build --sdist - uses: actions/upload-artifact@v7 with: - name: "dist-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cibw_archs_linux }}" + name: "cibw-${{ runner.os }}-${{ runner.arch }}-${{ matrix.cibw_archs_linux }}" path: "dist/" - - name: Publish package to TestPyPI - # pypa/gh-action-pypi-publish@master does not work on OSX + + upload_to_pypi: + needs: [ build_wheels, make_sdist ] + environment: pypi + permissions: + id-token: write + runs-on: ubuntu-latest + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + steps: + - uses: actions/download-artifact@v5 + with: + pattern: cibw-* + path: dist + merge-multiple: true + + - name: Publish to test PyPI # Alpha, Beta and dev releases contain a - in the tag. - if: contains(github.ref, '-') && startsWith(github.ref, 'refs/tags') - run: twine upload --skip-existing -r testpypi dist/* - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.TEST_PYPI_API_TOKEN }} - - name: Publish package to PyPI + if: "contains(github.ref, '-') && startsWith(github.ref, 'refs/tags')" + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + skip-existing: true + + - name: Publish to PyPI if: "!contains(github.ref, '-') && startsWith(github.ref, 'refs/tags')" - run: twine upload --skip-existing dist/* - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true