diff options
author | Jason Madden <jamadden@gmail.com> | 2021-02-26 07:41:55 -0600 |
---|---|---|
committer | Jason Madden <jamadden@gmail.com> | 2021-02-26 08:43:43 -0600 |
commit | a99dc79884acac5e9e35de2fd222266365bd7c53 (patch) | |
tree | 31764afb7ef9b0a9dff096683a8d3bf93a1d75f0 | |
parent | bf0de08af2d53261d7eb0ce1e31f574f93524ef6 (diff) | |
download | zope-interface-a99dc79884acac5e9e35de2fd222266365bd7c53.tar.gz |
First pass at github actions.
Fixes #225. Someone will need to add a `TWINE_PASSWORD` GitHub repository secret that is
a token for `zope.eggbuilder` to upload to zope.interface.
Builds and uploads manylinux32/64/aarch64 wheels.
Builds and uploads Mac wheels.
Builds the docs.
Runs tests with the C extension and without the C extension.
Reports coverage to coveralls.
Has the start of an environment to do linting.
Removes .travis.yml
-rw-r--r-- | .coveragerc | 6 | ||||
-rw-r--r-- | .github/workflows/tests.yml | 422 | ||||
-rwxr-xr-x | .manylinux-install.sh | 19 | ||||
-rwxr-xr-x | .manylinux.sh | 11 | ||||
-rw-r--r-- | .travis.yml | 145 | ||||
-rw-r--r-- | README.rst | 4 |
6 files changed, 459 insertions, 148 deletions
diff --git a/.coveragerc b/.coveragerc index e8d7d25..b1c092e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,8 @@ [run] branch = True source = zope.interface +# New in 5.0; required for the GHA coveralls submission. +relative_files = True [report] show_missing = true @@ -10,3 +12,7 @@ exclude_lines = raise NotImplementedError raise AssertionError self\.fail + +# Local Variables: +# mode: conf +# End: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..1d70e3d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,422 @@ +### +# Initially copied from +# https://github.com/actions/starter-workflows/blob/main/ci/python-package.yml +# And later based on the version I (jamadden) updated at +# gevent/gevent, and then at zodb/relstorage and zodb/perfmetrics +# +# Original comment follows. +### +### +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions +### + +### +# Important notes on GitHub actions: +# +# - We only get 2,000 free minutes a month (private repos) +# - We only get 500MB of artifact storage +# - Cache storage is limited to 7 days and 5GB. +# - macOS minutes are 10x as expensive as Linux minutes +# - windows minutes are twice as expensive. +# +# So keep those workflows light. Note: Currently, they seem to be free +# and unlimited for open source projects. But for how long... +# +# In December 2020, github only supports x86/64. If we wanted to test +# on other architectures, we can use docker emulation, but there's no +# native support. It works, but is slow. +# +# Another major downside: You can't just re-run the job for one part +# of the matrix. So if there's a transient test failure that hit, say, 3.8, +# to get a clean run every version of Python runs again. That's bad. +# https://github.community/t/ability-to-rerun-just-a-single-job-in-a-workflow/17234/65 + +name: tests + + +# Triggers the workflow on push or pull request events +on: [push, pull_request] +# Limiting to particular branches might be helpful to conserve minutes. +#on: + # push: + # branches: [ $default-branch ] + # pull_request: + # branches: [ $default-branch ] + +env: + # Weirdly, this has to be a top-level key, not ``defaults.env`` + PYTHONHASHSEED: 8675309 + PYTHONUNBUFFERED: 1 + PYTHONDONTWRITEBYTECODE: 1 + PYTHONDEVMODE: 1 + PYTHONFAULTHANDLER: 1 + ZOPE_INTERFACE_STRICT_IRO: 1 + + PIP_UPGRADE_STRATEGY: eager + # Don't get warnings about Python 2 support being deprecated. We + # know. The env var works for pip 20. + PIP_NO_PYTHON_VERSION_WARNING: 1 + PIP_NO_WARN_SCRIPT_LOCATION: 1 + + CFLAGS: -Ofast -pipe + CXXFLAGS: -Ofast -pipe + # Uploading built wheels for releases. + # TWINE_PASSWORD is encrypted and stored directly in the + # github repo settings. + TWINE_USERNAME: __token__ + + ### + # caching + # This is where we'd set up ccache, but this compiles so fast its not worth it. + ### + + + +jobs: + # Because sharing code/steps is so hard, and because it can be + # extremely valuable to be able to get binary wheels without + # uploading to PyPI and even if there is some failure, (e.g., for + # other people to test/debug), the strategy is to divide the process + # into several different jobs. The first builds and saves the binary + # wheels. It has dependent jobs that download and install the wheel + # to run tests, build docs, and perform linting. Building the + # manylinux wheels is an independent set of jobs. + # + # This divisin is time-saving for projects that take awhile to + # build, but somewhat less of a clear-cut win given how quick this + # is to compile (at least at this writing). + build-zope_interface: + # Sigh. Note that the matrix must be kept in sync + # with `test`, and `docs` must use a subset. + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [2.7, pypy-2.7, pypy-3.6, 3.5, 3.6, 3.7, 3.8, 3.9] + os: [ubuntu-20.04, macos-latest] + exclude: + - os: macos-latest + python-version: pypy-2.7 + - os: macos-latest + python-version: pypy-3.6 + - os: macos-latest + python-version: 3.5 + + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + ### + # Caching. + # This actually *restores* a cache and schedules a cleanup action + # to save the cache. So it must come before the thing we want to use + # the cache. + ### + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Install Build Dependencies + run: | + pip install -U pip + pip install -U setuptools wheel twine + pip install -U coveralls coverage + + - name: Build zope.interface + run: | + # Next, build the wheel *in place*. This helps ccache, and also lets us cache the configure + # output (pip install uses a random temporary directory, making this difficult). + python setup.py build_ext -i + python setup.py bdist_wheel + # Also install it, so that we get dependencies in the (pip) cache. + pip install -U coverage + pip install -U 'faulthandler; python_version == "2.7" and platform_python_implementation == "CPython"' + pip install .[test] + + - name: Check zope.interface build + run: | + ls -l dist + twine check dist/* + - name: Upload zope.interface wheel + uses: actions/upload-artifact@v2 + with: + name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}.whl + path: dist/*whl + - name: Publish package to PyPI (mac) + # We cannot 'uses: pypa/gh-action-pypi-publish@v1.4.1' because + # that's apparently a container action, and those don't run on + # the Mac. + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') && startsWith(runner.os, 'Mac') + env: + TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} + run: | + twine upload --skip-existing dist/* + + test: + needs: build-zope_interface + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [2.7, pypy-2.7, pypy-3.6, 3.5, 3.6, 3.7, 3.8, 3.9] + os: [ubuntu-20.04, macos-latest] + exclude: + - os: macos-latest + python-version: pypy-2.7 + - os: macos-latest + python-version: pypy-3.6 + - os: macos-latest + python-version: 3.5 + + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Download zope.interface wheel + uses: actions/download-artifact@v2 + with: + name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}.whl + path: dist/ + - name: Install zope.interface + # ``python -m unittest discover`` only works with editable + # installs, so we have to duplicate some work and can't + # install the built wheel. (zope.testrunner + # works fine with non-editable installs.) + run: | + pip install -U wheel + pip install -U coverage + pip install -U 'faulthandler; python_version == "2.7" and platform_python_implementation == "CPython"' + # Unzip into src/ so that testrunner can find the .so files + # when we ask it to load tests from that directory. This + # might also save some build time? + unzip -n dist/zope.interface-*whl -d src + pip install -U -e .[test] + - name: Run tests and report coverage + # Once with C extensions, once without. Yes, this runs them + # twice on PyPy. + run: | + coverage run -p -m unittest discover -s src + PURE_PYTHON=1 coverage run -p -m unittest discover -s src + coverage combine + coverage report -i + - name: Submit to Coveralls + # This is a container action, which only runs on Linux. + if: ${{ startsWith(runner.os, 'Linux') }} + uses: AndreMiras/coveralls-python-action@develop + with: + parallel: true + + coveralls_finish: + needs: test + runs-on: ubuntu-20.04 + steps: + - name: Coveralls Finished + uses: AndreMiras/coveralls-python-action@develop + with: + parallel-finished: true + + docs: + needs: build-zope_interface + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [3.9] + os: [ubuntu-20.04] + + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Download zope.interface wheel + uses: actions/download-artifact@v2 + with: + name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}.whl + path: dist/ + - name: Install zope.interface + run: | + pip install -U wheel + pip install -U coverage + pip install -U 'faulthandler; python_version == "2.7" and platform_python_implementation == "CPython"' + pip install -U "`ls dist/zope.interface-*.whl`[docs]" + - name: Build docs + env: + ZOPE_INTERFACE_STRICT_IRO: 0 + run: | + sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html + sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest + + + lint: + needs: build-zope_interface + runs-on: ${{ matrix.os }} + strategy: + matrix: + python-version: [3.9] + os: [ubuntu-20.04] + + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Download zope.interface wheel + uses: actions/download-artifact@v2 + with: + name: zope.interface-${{ runner.os }}-${{ matrix.python-version }}.whl + path: dist/ + - name: Install zope.interface + run: | + pip install -U pip + pip install -U wheel + pip install -U `ls dist/zope.interface-*`[test] + - name: Lint + # We only need to do this on one version, and it should be Python 3, because + # pylint has stopped updating for Python 2. + # TODO: Pick a linter and configuration and make this step right. + run: | + pip install -U pylint + # python -m pylint --limit-inference-results=1 --rcfile=.pylintrc zope.interface -f parseable -r n + + manylinux: + runs-on: ubuntu-20.04 + # We use a regular Python matrix entry to share as much code as possible. + strategy: + matrix: + python-version: [3.9] + image: [manylinux2010_x86_64, manylinux2010_i686, manylinux2014_aarch64] + + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip_manylinux-${{ matrix.image }}-${{ matrix.python-version }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Update pip + run: pip install -U pip + - name: Build zope.interface (x86_64) + if: matrix.image == 'manylinux2010_x86_64' + # An alternate way to do this is to run the container directly with a uses: + # and then the script runs inside it. That may work better with caching. + # See https://github.com/pyca/bcrypt/blob/f6b5ee2eda76d077c531362ac65e16f045cf1f29/.github/workflows/wheel-builder.yml + # The 2010 image is the last one that comes with Python 2.7. + env: + DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }} + run: | + bash .manylinux.sh + - name: Build zope.interface (i686) + if: matrix.image == 'manylinux2010_i686' + env: + DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }} + PRE_CMD: linux32 + run: | + bash .manylinux.sh + - name: Build zope.interface (aarch64) + if: matrix.image == 'manylinux2014_aarch64' + env: + DOCKER_IMAGE: quay.io/pypa/${{ matrix.image }} + run: | + # First we must enable emulation + docker run --rm --privileged hypriot/qemu-register + bash .manylinux.sh + + - name: Upload zope.interface wheels + uses: actions/upload-artifact@v2 + with: + path: wheelhouse/*whl + name: manylinux_${{ matrix.image }}_wheels.zip + - name: Restore pip cache permissions + run: sudo chown -R $(whoami) ${{ steps.pip-cache.outputs.dir }} + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@v1.4.1 + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + with: + user: __token__ + password: ${{ secrets.TWINE_PASSWORD }} + skip_existing: true + packages_dir: wheelhouse/ + +# TODO: +# * Use YAML syntax to share snippets, like the old .travis.yml did +# Sadly, as of 2021-02-01, Github Actions does not support anchors at +# all. Just having an anchor results in an error: +# +# The workflow is not valid. .github/workflows/tests.yml: Anchors +# are not currently supported. Remove the anchor 'an-strategy' +# +# The alternative of using composite actions doesn't work either, +# because composite actions are limited to running shell scripts. +# Steps in them can't call other actions with `uses:`, and nor can +# they be conditional with `if:`. diff --git a/.manylinux-install.sh b/.manylinux-install.sh index ea41c49..c403ed4 100755 --- a/.manylinux-install.sh +++ b/.manylinux-install.sh @@ -2,6 +2,25 @@ set -e -x +# Running inside docker +# Set a cache directory for pip. This was +# mounted to be the same as it is outside docker so it +# can be persisted. +export XDG_CACHE_HOME="/cache" +# XXX: This works for macOS, where everything bind-mounted +# is seen as owned by root in the container. But when the host is Linux +# the actual UIDs come through to the container, triggering +# pip to disable the cache when it detects that the owner doesn't match. +# The below is an attempt to fix that, taken frob bcrypt. It seems to work on +# Github Actions. +if [ -n "$GITHUB_ACTIONS" ]; then + echo Adjusting pip cache permissions + mkdir -p $XDG_CACHE_HOME/pip + chown -R $(whoami) $XDG_CACHE_HOME +fi +ls -ld /cache +ls -ld /cache/pip + # Compile wheels for PYBIN in /opt/python/*/bin; do if [[ "${PYBIN}" == *"cp27"* ]] || \ diff --git a/.manylinux.sh b/.manylinux.sh index 2fed778..520d68e 100755 --- a/.manylinux.sh +++ b/.manylinux.sh @@ -2,4 +2,13 @@ set -e -x -docker run --rm -v "$(pwd)":/io $DOCKER_IMAGE $PRE_CMD /io/.manylinux-install.sh +# Mount the current directory as /io +# Mount the pip cache directory as /cache +# `pip cache` requires pip 20.1 +echo Setting up caching +python --version +python -mpip --version +LCACHE="$(dirname `python -mpip cache dir`)" +echo Sharing pip cache at $LCACHE $(ls -ld $LCACHE) + +docker run --rm -e GITHUB_ACTIONS -v "$(pwd)":/io -v "$LCACHE:/cache" $DOCKER_IMAGE $PRE_CMD /io/.manylinux-install.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6d4701b..0000000 --- a/.travis.yml +++ /dev/null @@ -1,145 +0,0 @@ -language: python -dist: focal -env: - global: - ZOPE_INTERFACE_STRICT_IRO: 1 - TWINE_USERNAME: zope.wheelbuilder - TWINE_PASSWORD: - secure: "AyR5QxUuZKdmywiepdBG0r8hgFQfaEf2hTxOg/HiXisVNcs1sUPjephBP4MqQBbf1/qxLM95F6Mw3sKneO3gDDQSGCsmvY1MWlDc+6R5TgqVBuOoONji5zGQH7v9RR8IPOyple8BNlFePl1hQ8r0dOT1U6rDVh5FkNJgHYE9OJ4=" - -python: - - 2.7 - - 3.5 - - 3.6 - - 3.7 - - 3.8 - - 3.9 - - pypy2 - - pypy3 - -jobs: - include: - - - name: Documentation - python: 3.6 - install: - - pip install -U -e .[docs] - script: - - sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html - - sphinx-build -b doctest -d docs/_build/doctrees docs docs/_build/doctest - after_success: - env: ZOPE_INTERFACE_STRICT_IRO=0 - - - name: CPython No C Extension - env: PURE_PYTHON=1 - python: 3.8 - - # manylinux wheel builds - - name: 64-bit manylinux wheels (all Pythons) - services: docker - env: DOCKER_IMAGE=quay.io/pypa/manylinux2010_x86_64 - install: docker pull $DOCKER_IMAGE - script: bash .manylinux.sh - - - name: 32-bit manylinux wheels (all Pythons) - services: docker - env: DOCKER_IMAGE=quay.io/pypa/manylinux2010_i686 PRE_CMD=linux32 - install: docker pull $DOCKER_IMAGE - script: bash .manylinux.sh - - - name: aarch64 wheels - dist: bionic - group: edge - arch: arm64 - virt: lxd - env: DOCKER_IMAGE=quay.io/pypa/manylinux2014_aarch64 - install: docker pull $DOCKER_IMAGE - script: bash .manylinux.sh - - # It's important to use 'macpython' builds to get the least - # restrictive wheel tag. It's also important to avoid - # 'homebrew 3' because it floats instead of being a specific version. - - name: Python 2.7 wheels for MacOS - os: osx - language: generic - # We require at least 2.7.15 to upload wheels. - # See https://github.com/zopefoundation/BTrees/issues/113 - env: TERRYFY_PYTHON='macpython 2.7.17' - - name: Python 3.5 wheels for MacOS - os: osx - language: generic - env: TERRYFY_PYTHON='macpython 3.5' - - name: Python 3.6 wheels for MacOS - os: osx - language: generic - # NB: 3.6.0 causes https://github.com/nedbat/coveragepy/issues/703 - # NB: 3.6.1 had that ABI regression (fixed in 3.6.2) and would be a bad - # version to use - env: TERRYFY_PYTHON='macpython 3.6.2' - - name: Python 3.7 wheels for MacOS - os: osx - language: generic - env: TERRYFY_PYTHON='macpython 3.7.0' - - name: Python 3.8 wheels for MacOS - os: osx - language: generic - env: TERRYFY_PYTHON='macpython 3.8.0' - - name: Python 3.9 wheels for MacOS - os: osx - language: generic - env: TERRYFY_PYTHON='macpython 3.9.0' - -before_install: - - | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - git clone https://github.com/MacPython/terryfy - source terryfy/travis_tools.sh - get_python_environment $TERRYFY_PYTHON venv - fi - -install: - # virtualenv 20.0 together with terryfy macOS is broken, at least - # with CPython 2.7.17 (https://github.com/pypa/virtualenv/issues/1555): - # it doesn't install or activate the virtualenv correctly and PATH - # isn't setup right. So try to use `python -m` to workaround that. - - python -m pip install -U pip setuptools - - python -m pip install -U coveralls coverage - - python -m pip install -U -e ".[test]" - -script: - - which python - - python --version - - python -m coverage run -m unittest discover -s src - - python setup.py -q bdist_wheel - -after_success: - - python -m coveralls - - | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - # macpython 3.5 doesn't support recent TLS protocols which causes twine - # upload to fail, so we use the system Python to run twine - /usr/bin/python -m ensurepip --user - /usr/bin/python -m pip install --user -U pip - /usr/bin/python -m pip install --user -U -I twine - /usr/bin/python -m twine check dist/* - if [[ $TRAVIS_TAG ]]; then - printf "Uploading tagged release %s\n" "$TRAVIS_TAG" - /usr/bin/python -m twine upload --skip-existing dist/* - fi - fi - - | - if [[ -n "$DOCKER_IMAGE" ]]; then - pip install twine - twine check wheelhouse/* - if [[ $TRAVIS_TAG ]]; then - printf "Uploading tagged release %s\n" "$TRAVIS_TAG" - twine upload --skip-existing wheelhouse/* - fi - fi - -notifications: - email: false - -cache: pip -before_cache: - - rm -f $HOME/.cache/pip/log/debug.log @@ -10,8 +10,8 @@ :target: https://pypi.org/project/zope.interface/ :alt: Supported Python versions -.. image:: https://travis-ci.com/zopefoundation/zope.interface.svg?branch=master - :target: https://travis-ci.com/zopefoundation/zope.interface +.. image:: https://github.com/zopefoundation/zope.interface/actions/workflows/tests.yml/badge.svg + :target: https://github.com/zopefoundation/zope.interface/actions/workflows/tests.yml .. image:: https://readthedocs.org/projects/zopeinterface/badge/?version=latest :target: https://zopeinterface.readthedocs.io/en/latest/ |