summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMario Corchero <mcorcherojim@bloomberg.net>2021-07-05 13:26:33 +0200
committerPaul Ganssle <paul@ganssle.io>2021-07-06 12:26:41 -0400
commit6b337ea412d399fb48771c544b1a6880763b46c6 (patch)
tree52fab2c07b4c4e11d71896325b2b585900a8695f
parent9c2ad8f981ece1bdb3d52527f1cb39523b11d862 (diff)
downloaddateutil-git-6b337ea412d399fb48771c544b1a6880763b46c6.tar.gz
Automate cutting new releases
This replaces the manual workflow that involved invoking `release.py` with a GitHub workflow triggered either manually or via pushing tags and cutting releases.
-rw-r--r--.github/workflows/publish.yml58
-rw-r--r--RELEASING68
-rw-r--r--release.py81
-rw-r--r--tox.ini24
4 files changed, 112 insertions, 119 deletions
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..39b2e87
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,58 @@
+# This workflow is triggered three ways:
+#
+# 1. Manually triggering the workflow via the GitHub UI (Actions > Upload
+# package) will upload to test.pypi.org without the need to create a tag.
+# 2. When a tag is created, the workflow will upload the package to
+# test.pypi.org.
+# 3. When a GitHub 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: Upload package
+
+on:
+ release:
+ types: [created]
+ push:
+ tags:
+ - '*'
+ workflow_dispatch:
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.x'
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -U tox
+ - name: Create tox environments
+ run: |
+ tox -p -e py,build,release --notest
+ - name: Run tests
+ run: |
+ tox -e py
+ - name: Build package
+ run: |
+ tox -e build
+ - name: Publish package
+ env:
+ TWINE_USERNAME: "__token__"
+ run: |
+ if [[ "$GITHUB_EVENT_NAME" == "push" || \
+ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]]; 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/RELEASING b/RELEASING
index 27a3be8..e943b38 100644
--- a/RELEASING
+++ b/RELEASING
@@ -13,10 +13,8 @@ Release Checklist
interesting to anyone consuming the package (e.g. changes to CI) with
a reference to the Github PR.
[ ] Commit the changes in git and make a pull request.
-[ ] Accept the pull request and tag the repository with the release number.
-[ ] Add the contents of the NEWS file to the Github release notes for the
- release.
-[ ] Upload the source and binary distributions with `tox -e release`.
+[ ] Follow the "Releasing" steps below
+
Optional:
----------
@@ -24,12 +22,6 @@ Optional:
[ ] Check that the documentation builds correctly (cd docs, make html)
-Instructions
------------------------------------------
-See the instructions at https://packaging.python.org/en/latest/distributing/
-for more details.
-
-
Versioning
----------
Try and keep to a semantic versioning scheme (http://semver.org/). The versions
@@ -37,29 +29,51 @@ are managed with `setuptools_scm`, so to update the version, simply tag the
relevant commit with the new version number.
+Instructions
+-----------------------------------------
+See the instructions at https://packaging.python.org/en/latest/distributing/
+for more details.
+
+
Building and Releasing
----------------------
-Building and releasing can be done using the `release.py` script, which
-automates building, signing and uploading. Since it uses GPG for signing and
-for decrypting a stored token, it requires that `gpg` be installed on your
-system. Because it has python dependencies, the best way to use the
-`release.py` script is to invoke it using `tox`. To build the source and binary
-distributions, use:
+Releasing is automated via the `publish.yml` GitHub Actions workflow. When a
+new tag is pushed to the repository, the project is automatically built and
+uploaded to Test PyPI. When the publish action is triggered manually (see
+https://docs.github.com/en/actions/managing-workflow-runs/manually-running-a-workflow
+for more details), the result is uploaded to PyPI.
+
+To make a release:
- tox -e build
+1. After having made a PR with all the relevant changes, trigger the "Upload
+ package" to trigger an upload to Test PyPI. If desired, you can push a
+ `.dev0` or `.rc0` tag first, so that all uploads will have a prefix for the
+ *next* version rather than the previous version (e.g. if you are releasing
+ `3.1.2`, without making a new tag releases will have a version like
+ `3.1.1+gff8893e.d20220603`; if you push a `3.1.2.dev0` tag first, the version
+ number will be `3.1.2.dev0`, and subsequent commits will be things like
+ `3.1.2.dev0+fe9dacc4.d20220603`).
-This will build the distributions in `dist/`. Once that is done, you can release
-them with:
+2. Check the Test PyPI page for `python-dateutil` to ensure that the dev release
+ worked correctly: https://test.pypi.org/project/python-dateutil/
- tox -e release
+ Dev releases may not appear as the default page, so click "Release history"
+ and navigate to the release you are trying to check. Make sure that the
+ metadata looks right and in particular that the `Requires` metadata is
+ present.
-if you have the token stored in your `~/.pypirc` file. If you have stored the
-relevant token in an encrypted file, use the `--passfile` argument:
+4. If the release failed or was unsatisfactory in some way, make the required
+ changes and got back to step 1. Pushing a new tag is not necessary.
- tox -e release -- --passfile .token.gpg
+5. When everything looks good, merge the release PR, pull the result to your
+ local branch and create a new tag with a non-dev version number,
+ e.g. `3.1.2`. Push this to the repository, wait for the Test PyPI run to
+ trigger, and ensure that the upload worked.
-The `release` command defaults to uploading to test.pypi.org. To upload to
-pypi.org, use the `--release` flag, so putting it all together, we have:
+6. Create a new GitHub release with the new entries from `NEWS` in the
+ description. This will trigger the workflow to build and release the final
+ version to PyPI.org. Check https://pypi.org/project/python-dateutil to
+ ensure that everything worked correctly.
- tox -e build
- tox -e release -- --passfile .token.gpg --release
+7. Delete any dev tags created during the testing process from your upstream
+ and local branches.
diff --git a/release.py b/release.py
deleted file mode 100644
index 3608d7f..0000000
--- a/release.py
+++ /dev/null
@@ -1,81 +0,0 @@
-"""
-Release script
-"""
-
-import glob
-import os
-import shutil
-import subprocess
-import sys
-
-import click
-
-@click.group()
-def cli():
- pass
-
-@cli.command()
-def build():
- DIST_PATH = 'dist'
- if os.path.exists(DIST_PATH) and os.listdir(DIST_PATH):
- if click.confirm('{} is not empty - delete contents?'.format(DIST_PATH)):
- shutil.rmtree(DIST_PATH)
- os.makedirs(DIST_PATH)
- else:
- click.echo('Aborting')
- sys.exit(1)
-
- subprocess.check_call(['python', '-m', 'build',
- '--wheel', '--sdist',
- '--outdir', DIST_PATH,
- '.'])
-
-@cli.command()
-def sign():
- # Sign all the distribution files
- for fpath in glob.glob('dist/*'):
- subprocess.check_call(['gpg', '--armor', '--output', fpath + '.asc',
- '--detach-sig', fpath])
-
- # Verify the distribution files
- for fpath in glob.glob('dist/*'):
- if fpath.endswith('.asc'):
- continue
-
- subprocess.check_call(['gpg', '--verify', fpath + '.asc', fpath])
-
-
-@cli.command()
-@click.option('--passfile', default=None,
- help='File path to read and decrypt with gpg.')
-@click.option('--release/--no-release', default=False)
-def upload(passfile, release):
- if release:
- repository='pypi'
- else:
- repository='pypitest'
-
- env = os.environ.copy()
- if passfile is not None:
- gpg_call = subprocess.run(['gpg', '-d', passfile],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
-
- username, password = gpg_call.stdout.decode('utf-8').split('\n')
- env['TWINE_USERNAME'] = username
- env['TWINE_PASSWORD'] = password
-
- dist_files = glob.glob('dist/*')
- for dist_file in dist_files:
- if dist_file.endswith('.asc'):
- continue
- if dist_file + '.asc' not in dist_files:
- raise ValueError('Missing signature file for: {}'.format(dist_file))
-
- args = ['twine', 'upload', '-r', repository] + dist_files
-
- p = subprocess.Popen(args, env=env)
- p.wait()
-
-if __name__ == "__main__":
- cli()
diff --git a/tox.ini b/tox.ini
index 10e2cde..a89bbc2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -98,18 +98,20 @@ description = Build an sdist and bdist
basepython = python3.7
skip_install = true
passenv = *
-deps = click >= 7.0
- build[virtualenv] >= 0.3.0
+deps = build[virtualenv] >= 0.3.0
commands =
- python release.py build
+ python -m build --wheel --sdist --outdir dist .
[testenv:release]
-description = Sign and upload the built distributions to PyPI
-basepython = python3.7
-skip_install = true
-passenv = *
-deps = click >= 7.0
- twine >= 2.0.0
+description = Make a release; must be called after "build"
+skip_install = True
+deps =
+ twine
+depends =
+ build
+passenv =
+ TWINE_*
commands =
- python release.py sign
- python release.py upload {posargs}
+ twine check {toxinidir}/dist/*
+ twine upload {toxinidir}/dist/* \
+ {posargs:-r {env:TWINE_REPOSITORY:testpypi} --non-interactive}