From 639032ff1788fd0f4beeeeeabdc40265131868a9 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sat, 16 May 2020 13:02:46 -0400 Subject: [PATCH 1/8] Allow choice between binary and sdist --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 87c5b258..a36d28ba 100644 --- a/tox.ini +++ b/tox.ini @@ -127,5 +127,5 @@ deps = pep517 twine commands = - python -m pep517.build -s -b {toxinidir} -o {toxinidir}/dist + python -m pep517.build {posargs} {toxinidir} -o {toxinidir}/dist twine check {toxinidir}/dist/* From 56b69177f5921b178d0652dd9ef21b5967071f21 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sat, 16 May 2020 13:02:55 -0400 Subject: [PATCH 2/8] Add tox release environment --- tox.ini | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tox.ini b/tox.ini index a36d28ba..784194ff 100644 --- a/tox.ini +++ b/tox.ini @@ -129,3 +129,18 @@ deps = commands = python -m pep517.build {posargs} {toxinidir} -o {toxinidir}/dist twine check {toxinidir}/dist/* + +[testenv:release] +description = Make a release; must be called after "build" +skip_install = True +deps = + twine +depends = + build + auditwheel +passenv = + TWINE_* +commands = + twine check {toxinidir}/dist/* + twine upload {toxinidir}/dist/* \ + {posargs:-r {env:TWINE_REPOSITORY:testpypi} --non-interactive} From 0c86b16ab0a695c6fdc6c66ea3085949c276d21b Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Mon, 18 May 2020 18:38:28 -0400 Subject: [PATCH 3/8] Move build check to a separate env --- .github/workflows/python-tests.yml | 1 + tox.ini | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 7303f761..69d3d725 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -79,6 +79,7 @@ jobs: CFLAGS+=" -Wno-unused-parameter" CFLAGS+=" -Wno-missing-field-initializers" export CFLAGS="${CFLAGS}" + TOXENV="build,build-check" fi if [[ $TOXENV == "docs" ]]; then diff --git a/tox.ini b/tox.ini index 784194ff..276fd34d 100644 --- a/tox.ini +++ b/tox.ini @@ -125,10 +125,17 @@ skip_install = True passenv = CFLAGS deps = pep517 - twine commands = python -m pep517.build {posargs} {toxinidir} -o {toxinidir}/dist - twine check {toxinidir}/dist/* + +[testenv:build-check] +description = Build a wheel and source distribution +skip_install = True +deps = + twine +depends = build +commands = + twine check dist/* [testenv:release] description = Make a release; must be called after "build" From 39a40b7f49314cb7b72db44503d06e9fdd31baba Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 20 May 2020 16:54:32 -0400 Subject: [PATCH 4/8] Add script to check that the tag and version match Since the tag and the version are two different sources for the same information, we want to ensure that the two are always in sync when making a release. --- scripts/check_tag.py | 19 +++++++++++++++++++ tox.ini | 6 ++++++ 2 files changed, 25 insertions(+) create mode 100644 scripts/check_tag.py diff --git a/scripts/check_tag.py b/scripts/check_tag.py new file mode 100644 index 00000000..31387ecc --- /dev/null +++ b/scripts/check_tag.py @@ -0,0 +1,19 @@ +import subprocess +import sys + +from backports.zoneinfo import __version__ as VERSION + + +def get_current_tag(): + p = subprocess.run( + ["git", "describe", "--tag"], check=True, stdout=subprocess.PIPE + ) + + return p.stdout.strip().decode() + + +if __name__ == "__main__": + tag = get_current_tag() + if tag != VERSION: + print(f"Tag does not match version: {tag!r} != {VERSION!r}") + sys.exit(1) diff --git a/tox.ini b/tox.ini index 276fd34d..ab9403eb 100644 --- a/tox.ini +++ b/tox.ini @@ -137,6 +137,12 @@ depends = build commands = twine check dist/* +[testenv:check-version-tag] +description = Ensure that the current version matches the current tag +deps = +commands = + python scripts/check_tag.py + [testenv:release] description = Make a release; must be called after "build" skip_install = True From e99b8a9ef904639e6bed2c649885aa5fbf40aa81 Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Wed, 20 May 2020 17:12:24 -0400 Subject: [PATCH 5/8] Add script to tag releases --- scripts/tag_release.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 scripts/tag_release.sh diff --git a/scripts/tag_release.sh b/scripts/tag_release.sh new file mode 100755 index 00000000..b2b3138b --- /dev/null +++ b/scripts/tag_release.sh @@ -0,0 +1,11 @@ +#!/usr/bin/bash +# +# Script to tag the repository with the current version of the library +set -e + +VERSION_LINE=$(grep '__version__ =' 'src/backports/zoneinfo/_version.py' -m 1) +VERSION=$(echo "$VERSION_LINE" | sed 's/__version__ = "\([^"]\+\)"/\1/') +echo "Found version: $VERSION" + +git tag -s -m "Version $VERSION" $VERSION || exit "Failed to tag!" +echo "Success" From 78fcb48a1ae7bceab38e0288201aa544c560c06b Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sun, 17 May 2020 13:25:43 -0400 Subject: [PATCH 6/8] Add script to build manylinux wheels --- scripts/build_manylinux_wheel.sh | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 scripts/build_manylinux_wheel.sh diff --git a/scripts/build_manylinux_wheel.sh b/scripts/build_manylinux_wheel.sh new file mode 100755 index 00000000..4eae3a91 --- /dev/null +++ b/scripts/build_manylinux_wheel.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e -x + +function repair_wheel { + wheel=$1 + if ! auditwheel show "$wheel"; then + echo "Skipping non-platform wheel $wheel" + else + auditwheel repair "$wheel" --plat "$PLAT" -w /io/wheelhouse + fi +} + +cd /io/ + +for tag in $PYTHON_TAGS; do + PYBIN="/opt/python/$tag/bin/" + ${PYBIN}/pip install tox + CFLAGS="-std=c99 -O3" ${PYBIN}/tox -e build -- -b +done + +mv dist/ raw_wheels + +for whl in raw_wheels/*.whl; do + repair_wheel "$whl" +done + +mkdir dist +mv wheelhouse/*.whl dist/ From 5eee15333a6c30a0a16b0cd1a43ab3fa25df7bdd Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Sat, 16 May 2020 13:03:22 -0400 Subject: [PATCH 7/8] Add publishing workflow This builds the distribution on every commit, uploads it to Test PyPI on tags, and uploads it to PyPI when a GitHub release is made. --- .github/workflows/build-publish.yml | 154 ++++++++++++++++++ ...nux_wheel.sh => build_manylinux_wheels.sh} | 0 tox.ini | 2 +- 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-publish.yml rename scripts/{build_manylinux_wheel.sh => build_manylinux_wheels.sh} (100%) diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml new file mode 100644 index 00000000..a4461a0d --- /dev/null +++ b/.github/workflows/build-publish.yml @@ -0,0 +1,154 @@ +# This workflow is used to build all the wheels and source distributions for +# the project and, on tags and releases, upload them. It is enabled on every +# commit to ensure that the wheels can be built on all platforms, but releases +# are only triggered in two situations: +# +# 1. When a tag is created, the workflow will upload the package to +# test.pypi.org. +# 2. When a release is made, the workflow will upload the package to pypi.org. +# +# It is done this way until PyPI has draft reviews, to allow for a two-stage +# upload with a chance for manual intervention before the final publication. +name: Build and release + +on: + push: + release: + types: [created] + +jobs: + build_sdist: + runs-on: 'ubuntu-latest' + name: Build sdist + steps: + - uses: actions/checkout@v2 + - name: Setup python + uses: actions/setup-python@v2 + with: + python-version: '3.8' + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip3 install tox + - name: Build sdist + run: tox -e build -- -s + - uses: actions/upload-artifact@v2 + with: + name: dist + path: dist + + build_manylinux_wheels: + runs-on: 'ubuntu-latest' + strategy: + fail-fast: false + matrix: + platform: + - 'manylinux1_x86_64' + - 'manylinux1_i686' + name: Build a ${{ matrix.platform }} for ${{ matrix.python_tag }} + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: sudo apt-get install docker + - name: Install docker image + run: | + DOCKER_IMAGE="quay.io/pypa/${{ matrix.platform }}" + echo "::set-env name=DOCKER_IMAGE::$DOCKER_IMAGE" + docker pull $DOCKER_IMAGE + - name: Build wheels + env: + PYTHON_TAGS: "cp38-cp38" + PRE_CMD: ${{ matrix.platform == 'manylinux1_i686' && 'linux32' || '' }} + run: | + docker run --rm \ + -e PLAT=${{ matrix.platform }} \ + -e PYTHON_TAGS=$PYTHON_TAGS \ + -v `pwd`:/io $DOCKER_IMAGE \ + $PRE_CMD \ + /io/scripts/build_manylinux_wheels.sh + - uses: actions/upload-artifact@v2 + with: + name: dist + path: dist + + build_wheel: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python_version: [ '3.8' ] + arch: [ 'x86', 'x64' ] + os: + - 'windows-latest' + - 'macos-latest' + exclude: + - os: 'macos-latest' + arch: 'x86' + + name: 'Build wheel: ${{ matrix.os }} ${{ matrix.python_version }} (${{ matrix.arch }})' + steps: + - uses: actions/checkout@v2 + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.0 + if: matrix.os == 'windows-latest' + - name: Setup python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python_version }} + architecture: ${{ matrix.arch }} + - name: Install dependencies + run: | + python -m pip install -U pip + pip install -U tox + - name: Create tox environment + run: tox -e build --notest + - name: Build wheel + run: | + tox -e build -- -b + - uses: actions/upload-artifact@v2 + with: + name: dist + path: dist + + deploy: + runs-on: 'ubuntu-latest' + needs: [build_sdist, build_wheel, build_manylinux_wheels] + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + - uses: actions/download-artifact@v2 + with: + name: dist + path: dist + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + pip3 install tox + - name: Check that version and tag matches + if: >- + startsWith(github.ref, 'refs/tags') + run: tox -e check-version-tag + - name: Run twine check + run: tox -e build-check + - name: Publish package + if: >- + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) || + (github.event_name == 'release') + env: + TWINE_USERNAME: "__token__" + run: | + if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then + export TWINE_REPOSITORY_URL="https://test.pypi.org/legacy/" + export TWINE_PASSWORD="${{ secrets.TEST_PYPI_UPLOAD_TOKEN }}" + elif [[ "$GITHUB_EVENT_NAME" == "release" ]]; then + export TWINE_REPOSITORY="pypi" + export TWINE_PASSWORD="${{ secrets.PYPI_UPLOAD_TOKEN }}" + else + echo "Unknown event name: ${GITHUB_EVENT_NAME}" + exit 1 + fi + + tox -e release diff --git a/scripts/build_manylinux_wheel.sh b/scripts/build_manylinux_wheels.sh similarity index 100% rename from scripts/build_manylinux_wheel.sh rename to scripts/build_manylinux_wheels.sh diff --git a/tox.ini b/tox.ini index ab9403eb..a4bce40b 100644 --- a/tox.ini +++ b/tox.ini @@ -122,7 +122,7 @@ commands = [testenv:build] description = Build a wheel and source distribution skip_install = True -passenv = CFLAGS +passenv = * deps = pep517 commands = From 2b2f5f4287fce61a1c04d811700a81c18517b20c Mon Sep 17 00:00:00 2001 From: Paul Ganssle Date: Thu, 21 May 2020 08:26:44 -0400 Subject: [PATCH 8/8] Add maintainer's guide --- docs/index.rst | 1 + docs/maintaining.rst | 67 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 docs/maintaining.rst diff --git a/docs/index.rst b/docs/index.rst index d5d673f3..496589c2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -14,6 +14,7 @@ Contents: :maxdepth: 1 zoneinfo + maintaining Indices and tables diff --git a/docs/maintaining.rst b/docs/maintaining.rst new file mode 100644 index 00000000..97339c31 --- /dev/null +++ b/docs/maintaining.rst @@ -0,0 +1,67 @@ +Maintainer's Guide +================== + +Although this was the original implementation of the ``zoneinfo`` module, after +Python 3.9, it is now a backport, and to the extent that there is a "canonical" +repository, the `CPython repository `_ has a +stronger claim than this one. Accepting outside PRs against this repository is +difficult because we are not set up to collect CLAs for CPython. It is easier +to accept PRs against CPython and import them here if possible. + +The code layout is very different between the two, and unfortunately (partially +because of the different layouts, and the different module names), the code has +diverged, so keeping the two in sync is not as simple as copy-pasting one into +the other. For now, the two will need to be kept in sync manually. + + +Development environment +----------------------- + +Maintenance scripts, releases, and tests are orchestrated using |tox|_ +environments to manage the requirements of each script. The details of each +environment can be found in the ``tox.ini`` file in the repository root. + +The repository also has pre-commit configured to automatically enforce various +code formatting rules on commit. To use it, install `pre-commit +`_ and run ``pre-commit install`` in the repository +root to install the git commit hooks. + + +Making a release +---------------- + +Releases are automated via the ``build-release.yml`` GitHub Actions workflow. +The project is built on every push; whenever a *tag* is pushed, the build +artifacts are released to `Test PyPI `_, and when a +GitHub release is made, the project is built and released to `PyPI +`_ (this is a workaround for the lack of "draft releases" +on PyPI, and the two actions can be unified when that feature is added). + +To make a release: + +1. Update the version number in ``src/backports/zoneinfo/_version.py`` and + make a PR (if you want to be cautious, start with a ``.devN`` release + intended only for PyPI). +2. Tag the repository with the current version – you can use the + ``scripts/tag_release.sh`` script in the repository root to source the + version automatically from the current module version. +3. Push the tag to GitHub (e.g. ``git push upstream 0.1.0.dev0``). This will + trigger a release to Test PyPI. The PR does not need to be merged at this + point if you are only planning to release to TestPyPI, but any "test only" + tags should be deleted when the process is complete. +4. Wait for the GitHub action to succeed, then check the results on + https://test.pypi.org/project/backports.zoneinfo . +5. If everything looks good, go into the GitHub repository's `"releases" tab + `_ and click "Draft a new + release"; type the name of the tag into the box, fill out the remainder of + the form, and click "Publish". (Only do this step for non-dev releases). +6. Check that the release action has succeeded, then check that everything + looks OK on https://pypi.org/project/backports.zoneinfo/ . + +If there's a problem with the release, make a post release by appending +``.postN`` to the current version, e.g. ``0.1.1`` → ``0.1.1.post0``. If the +problem is sufficiently serious, yank the broken version. + +.. Links +.. |tox| replace:: ``tox`` +.. _tox: https://tox.readthedocs.io/en/latest/