From 60818d7b31943f5a25e036cb60a2355a2f1cf572 Mon Sep 17 00:00:00 2001 From: Dan Ryan Date: Fri, 24 Apr 2020 18:01:13 -0400 Subject: [PATCH] Automate release workflow - Automatically release when tags are pushed to master - Release automation will build wheel and sdist + upload to pypi - Added test pypi as initial target - Updated version update scripts to help with automatic version management Signed-off-by: Dan Ryan --- .github/workflows/pypi_upload.yml | 63 ++++++++++++++++++++++++++++ Pipfile.lock | 62 ++++++++++++++-------------- tasks/release.py | 68 +++++++++++++++++++++++-------- 3 files changed, 144 insertions(+), 49 deletions(-) create mode 100644 .github/workflows/pypi_upload.yml diff --git a/.github/workflows/pypi_upload.yml b/.github/workflows/pypi_upload.yml new file mode 100644 index 00000000..fdbb728d --- /dev/null +++ b/.github/workflows/pypi_upload.yml @@ -0,0 +1,63 @@ +name: Create Release & Upload To PyPI + +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - v?[0-9]+.[0-9]+.[0-9]+ # add .* to allow dev releases + +jobs: + build: + name: pipenv PyPI Upload + runs-on: ubuntu-latest + env: + CI: "1" + + steps: + - name: Checkout code + uses: actions/checkout@v1 + + - uses: webfactory/ssh-agent@v0.1.1 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + + - name: Install latest tools for build + run: | + python -m pip install --upgrade --upgrade-strategy=eager pip setuptools wheel invoke + python -m pip install . + python -m pipenv install --dev + - name: Build wheels + run: | + python -m pipenv runpython setup.py sdist bdist_wheel + # to upload to test pypi, pass repository_url: https://test.pypi.org/legacy/ and use secrets.TEST_PYPI_TOKEN + - name: Publish a Python distribution to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + packages_dir: dist/ + # git push https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:master + # we need to use a deploy key for this to get around branch protection as the default token fails + - name: Pre-bump + run: | + git config --local user.name 'Github Action' + git config --local user.email action@github.com + python -m pipenv run inv release.bump-version --dev --commit + git push git@github.com:${{ github.repository }}.git HEAD:master diff --git a/Pipfile.lock b/Pipfile.lock index 5d58f7dd..c02913fc 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -7,9 +7,9 @@ "requires": {}, "sources": [ { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true } ] }, @@ -193,30 +193,28 @@ }, "cryptography": { "hashes": [ - "sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c", - "sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595", - "sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad", - "sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651", - "sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2", - "sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff", - "sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d", - "sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42", - "sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d", - "sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e", - "sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912", - "sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793", - "sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13", - "sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7", - "sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0", - "sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879", - "sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f", - "sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9", - "sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2", - "sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf", - "sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8" + "sha256:091d31c42f444c6f519485ed528d8b451d1a0c7bf30e8ca583a0cac44b8a0df6", + "sha256:18452582a3c85b96014b45686af264563e3e5d99d226589f057ace56196ec78b", + "sha256:1dfa985f62b137909496e7fc182dac687206d8d089dd03eaeb28ae16eec8e7d5", + "sha256:1e4014639d3d73fbc5ceff206049c5a9a849cefd106a49fa7aaaa25cc0ce35cf", + "sha256:22e91636a51170df0ae4dcbd250d318fd28c9f491c4e50b625a49964b24fe46e", + "sha256:3b3eba865ea2754738616f87292b7f29448aec342a7c720956f8083d252bf28b", + "sha256:651448cd2e3a6bc2bb76c3663785133c40d5e1a8c1a9c5429e4354201c6024ae", + "sha256:726086c17f94747cedbee6efa77e99ae170caebeb1116353c6cf0ab67ea6829b", + "sha256:844a76bc04472e5135b909da6aed84360f522ff5dfa47f93e3dd2a0b84a89fa0", + "sha256:88c881dd5a147e08d1bdcf2315c04972381d026cdb803325c03fe2b4a8ed858b", + "sha256:96c080ae7118c10fcbe6229ab43eb8b090fccd31a09ef55f83f690d1ef619a1d", + "sha256:a0c30272fb4ddda5f5ffc1089d7405b7a71b0b0f51993cb4e5dbb4590b2fc229", + "sha256:bb1f0281887d89617b4c68e8db9a2c42b9efebf2702a3c5bf70599421a8623e3", + "sha256:c447cf087cf2dbddc1add6987bbe2f767ed5317adb2d08af940db517dd704365", + "sha256:c4fd17d92e9d55b84707f4fd09992081ba872d1a0c610c109c18e062e06a2e55", + "sha256:d0d5aeaedd29be304848f1c5059074a740fa9f6f26b84c5b63e8b29e73dfc270", + "sha256:daf54a4b07d67ad437ff239c8a4080cfd1cc7213df57d33c97de7b4738048d5e", + "sha256:e993468c859d084d5579e2ebee101de8f5a27ce8e2159959b6673b418fd8c785", + "sha256:f118a95c7480f5be0df8afeb9a11bd199aa20afab7a96bcf20409b411a3a85f0" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8" + "version": "==2.9.2" }, "decorator": { "hashes": [ @@ -513,10 +511,10 @@ }, "pathspec": { "hashes": [ - "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", - "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96" - ], - "version": "==0.7.0" + "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0", + "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061" + ], + "version": "==0.8.0" }, "pbr": { "hashes": [ @@ -640,10 +638,10 @@ }, "readme-renderer": { "hashes": [ - "sha256:1b6d8dd1673a0b293766b4106af766b6eff3654605f9c4f239e65de6076bc222", - "sha256:e67d64242f0174a63c3b727801a2fff4c1f38ebe5d71d95ff7ece081945a6cd4" + "sha256:cbe9db71defedd2428a1589cdc545f9bd98e59297449f69d721ef8f1cfced68d", + "sha256:cc4957a803106e820d05d14f71033092537a22daa4f406dfbdd61177e0936376" ], - "version": "==25.0" + "version": "==26.0" }, "regex": { "hashes": [ diff --git a/tasks/release.py b/tasks/release.py index ed1575d4..8ad5c8bc 100644 --- a/tasks/release.py +++ b/tasks/release.py @@ -149,7 +149,6 @@ def build_dists(ctx): ctx.run(f"pipenv run python setup.py sdist bdist_wheel", env=env) - @invoke.task(build_dists) def upload_dists(ctx, repo="pypi"): dist_pattern = f'{PACKAGE_NAME.replace("-", "[-_]")}-*' @@ -239,28 +238,63 @@ def tag_version(ctx, push=False): ctx.run("git push --tags") +def add_one_day(dt): + return dt + datetime.timedelta(days=1) + + +def date_offset(dt, month_offset=0, day_offset=0, truncate=False): + new_month = (dt.month + month_offset) % 12 + year_offset = month_offset // 12 + replace_args = { + "month": dt.month + month_offset, + "year": dt.year + year_offset, + } + log("Getting updated date from date: {0} using month offset: {1} and year offset {2}".format( + dt, new_month, replace_args["year"] + )) + if day_offset: + dt = dt + datetime.timedelta(days=day_offset) + log("updated date using day offset: {0} => {1}".format(day_offset, dt)) + if truncate: + log("Truncating...") + replace_args["day"] = 1 + return dt.replace(**replace_args) + + @invoke.task -def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=False): +def bump_version(ctx, dry_run=False, dev=False, pre=False, tag=None, commit=False, month_offset="0", trunc_month=False): current_version = Version.parse(__version__) today = datetime.date.today() + day_offset = 0 tomorrow = today + datetime.timedelta(days=1) - if pre and not tag: - print('Using "pre" requires a corresponding tag.') - return - if not (dev or pre or tag): - new_version = current_version.replace(release=today.timetuple()[:3]).clear( - pre=True, dev=True - ) + month_offset = int(month_offset) + if month_offset: + # if we are offsetting by a month, grab the first day of the month + trunc_month = True + else: + target_day = today + if dev or pre: + target_day = date_offset(today, day_offset=1) + target_day = date_offset( + today, + month_offset=month_offset, + day_offset=day_offset, + truncate=trunc_month + ) + log("target_day: {0}".format(target_day)) + target_timetuple = target_day.timetuple()[:3] + new_version = current_version.replace(release=target_timetuple) if pre and dev: raise RuntimeError("Can't use 'pre' and 'dev' together!") - if dev or pre: - new_version = current_version.replace(release=tomorrow.timetuple()[:3]).clear( - pre=True, dev=True - ) - if dev: - new_version = new_version.bump_dev() - else: - new_version = new_version.bump_pre(tag=tag) + if dev: + new_version = new_version.replace(pre=None).bump_dev() + elif pre: + if not tag: + print('Using "pre" requires a corresponding tag.') + return + new_version = new_version.bump_pre(tag=tag) + else: + new_version = new_version.replace(pre=None, dev=None) log("Updating version to %s" % new_version.normalize()) version = find_version(ctx) log("Found current version: %s" % version)