diff --git a/Makefile b/Makefile index 0bd23919..11702697 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,6 @@ # These commands on all OS platforms in the active Python environment: # python # pip -# twine # These commands on Linux and macOS: # uname # Environment variables: @@ -209,7 +208,7 @@ check_py_files := \ $(test_end2end_py_files) \ # Packages whose dependencies are checked using pip-missing-reqs -check_reqs_packages := pip_check_reqs virtualenv tox pipdeptree build pytest coverage coveralls flake8 ruff pylint twine safety bandit sphinx towncrier +check_reqs_packages := pip_check_reqs virtualenv tox pipdeptree build pytest coverage coveralls flake8 ruff pylint safety bandit sphinx towncrier ifdef TESTCASES pytest_opts := $(TESTOPTS) -k "$(TESTCASES)" @@ -254,11 +253,14 @@ help: @echo ' build - Build the distribution files in $(dist_dir): $(dist_files)' @echo ' builddoc - Build documentation in: $(doc_build_dir)' @echo ' all - Do all of the above' + @echo " release_branch - Create a release branch when releasing a version (requires VERSION and optionally BRANCH to be set)" + @echo " release_publish - Publish to PyPI when releasing a version (requires VERSION and optionally BRANCH to be set)" + @echo " start_branch - Create a start branch when starting a new version (requires VERSION and optionally BRANCH to be set)" + @echo " start_tag - Create a start tag when starting a new version (requires VERSION and optionally BRANCH to be set)" @echo " end2end - Run end2end tests (adds to coverage results)" @echo " end2end_show - Show HMCs defined for end2end tests" @echo ' authors - Generate AUTHORS.md file from git log' @echo ' uninstall - Uninstall package from active Python environment' - @echo ' upload - Upload the distribution files to PyPI (includes uninstall+build)' @echo ' clean - Remove any temporary files' @echo ' clobber - Remove any build products (includes uninstall+clean)' @echo " platform - Display the information about the platform as seen by make" @@ -282,6 +284,8 @@ help: @echo " Optional, defaults to 'latest'." @echo ' PYTHON_CMD=... - Name of python command. Default: python' @echo ' PIP_CMD=... - Name of pip command. Default: pip' + @echo " VERSION=... - M.N.U version to be released or started" + @echo " BRANCH=... - Name of branch to be released or started (default is derived from VERSION)" .PHONY: platform platform: @@ -458,23 +462,97 @@ clean: @echo 'Done: Cleaned out all temporary files.' @echo "Makefile: $@ done." -.PHONY: all -all: install develop check_reqs check ruff pylint test build builddoc safety bandit +.PHONY: release_branch +release_branch: + @bash -c 'if [ -z "$(VERSION)" ]; then echo ""; echo "Error: VERSION env var is not set"; echo ""; false; fi' + @bash -c 'if [ -n "$$(git status -s)" ]; then echo ""; echo "Error: Local git repo has uncommitted files:"; echo ""; git status; false; fi' + git fetch origin + @bash -c 'if [ -z "$$(git tag -l $(VERSION)a0)" ]; then echo ""; echo "Error: Release start tag $(VERSION)a0 does not exist (the version has not been started)"; echo ""; false; fi' + @bash -c 'if [ -n "$$(git tag -l $(VERSION))" ]; then echo ""; echo "Error: Release tag $(VERSION) already exists (the version has already been released)"; echo ""; false; fi' + @bash -c 'if [[ -n "$${BRANCH}" ]]; then echo $${BRANCH} >branch.tmp; elif [[ "$${VERSION#*.*.}" == "0" ]]; then echo "master" >branch.tmp; else echo "stable_$${VERSION%.*}" >branch.tmp; fi' + @bash -c 'if [ -z "$$(git branch --contains $(VERSION)a0 $$(cat branch.tmp))" ]; then echo ""; echo "Error: Release start tag $(VERSION)a0 is not in target branch $$(cat branch.tmp), but in:"; echo ""; git branch --contains $(VERSION)a0;. false; fi' + @echo "==> This will start the release of $(package_name) version $(VERSION) to PyPI using target branch $$(cat branch.tmp)" + @echo -n '==> Continue? [yN] ' + @bash -c 'read answer; if [ "$$answer" != "y" ]; then echo "Aborted."; false; fi' + bash -c 'git checkout $$(cat branch.tmp)' + git pull + @bash -c 'if [ -z "$$(git branch -l release_$(VERSION))" ]; then echo "Creating release branch release_$(VERSION)"; git checkout -b release_$(VERSION); fi' + git checkout release_$(VERSION) + make authors + towncrier build --version $(VERSION) --yes + git commit -asm "Release $(VERSION)" + git push --set-upstream origin release_$(VERSION) + rm -f branch.tmp + @echo "Done: Pushed the release branch to GitHub - now go there and create a PR." @echo "Makefile: $@ done." -.PHONY: upload -upload: $(dist_files) -ifeq (,$(findstring .dev,$(package_version))) - @echo '==> This will upload $(package_name) version $(package_version) to PyPI!' +.PHONY: release_publish +release_publish: + @bash -c 'if [ -z "$(VERSION)" ]; then echo ""; echo "Error: VERSION env var is not set"; echo ""; false; fi' + @bash -c 'if [ -n "$$(git status -s)" ]; then echo ""; echo "Error: Local git repo has uncommitted files:"; echo ""; git status; false; fi' + git fetch origin + @bash -c 'if [ -n "$$(git tag -l $(VERSION))" ]; then echo ""; echo "Error: Release tag $(VERSION) already exists (the version has already been released)"; echo ""; false; fi' + @bash -c 'if [[ -n "$${BRANCH}" ]]; then echo $${BRANCH} >branch.tmp; elif [[ "$${VERSION#*.*.}" == "0" ]]; then echo "master" >branch.tmp; else echo "stable_$${VERSION%.*}" >branch.tmp; fi' + @bash -c 'if [ "$$(git log --format=format:%s $$(cat branch.tmp)~..$$(cat branch.tmp))" != "Release $(VERSION)" ]; then echo ""; echo "Error: Release branch has not been created yet"; echo ""; false; fi' + @echo "==> This will publish $(package_name) version $(VERSION) to PyPI using target branch $$(cat branch.tmp)" @echo -n '==> Continue? [yN] ' @bash -c 'read answer; if [ "$$answer" != "y" ]; then echo "Aborted."; false; fi' - twine upload $(dist_files) - @echo 'Done: Uploaded $(package_name) version to PyPI: $(package_version)' + bash -c 'git checkout $$(cat branch.tmp)' + git pull + git tag -f $(VERSION) + git push -f --tags + git branch -D release_$(VERSION) + git branch -D -r origin/release_$(VERSION) + rm -f branch.tmp + @echo "Done: Triggered the publish workflow - now wait for it to finish and verify the publishing." + @echo "Makefile: $@ done." + +.PHONY: start_branch +start_branch: + @bash -c 'if [ -z "$(VERSION)" ]; then echo ""; echo "Error: VERSION env var is not set"; echo ""; false; fi' + @bash -c 'if [ -n "$$(git status -s)" ]; then echo ""; echo "Error: Local git repo has uncommitted files:"; echo ""; git status; false; fi' + git fetch origin + @bash -c 'if [ -n "$$(git tag -l $(VERSION))" ]; then echo ""; echo "Error: Release tag $(VERSION) already exists (the version has already been released)"; echo ""; false; fi' + @bash -c 'if [ -n "$$(git tag -l $(VERSION)a0)" ]; then echo ""; echo "Error: Release start tag $(VERSION)a0 already exists (the new version has alreay been started)"; echo ""; false; fi' + @bash -c 'if [ -n "$$(git branch -l start_$(VERSION))" ]; then echo ""; echo "Error: Start branch start_$(VERSION) already exists (the start of the new version is already underway)"; echo ""; false; fi' + @bash -c 'if [[ -n "$${BRANCH}" ]]; then echo $${BRANCH} >branch.tmp; elif [[ "$${VERSION#*.*.}" == "0" ]]; then echo "master" >branch.tmp; else echo "stable_$${VERSION%.*}" >branch.tmp; fi' + @echo "==> This will start new version $(VERSION) using target branch $$(cat branch.tmp)" + @echo -n '==> Continue? [yN] ' + @bash -c 'read answer; if [ "$$answer" != "y" ]; then echo "Aborted."; false; fi' + bash -c 'git checkout $$(cat branch.tmp)' + git pull + git checkout -b start_$(VERSION) + echo "Dummy change for starting new version $(VERSION)" >changes/noissue.$(VERSION).notshown.rst + git add changes/noissue.$(VERSION).notshown.rst + git commit -asm "Start $(VERSION)" + git push --set-upstream origin start_$(VERSION) + @echo "Done: Pushed the start branch to GitHub - now go there and create a PR." + @echo "Makefile: $@ done." + +.PHONY: start_tag +start_tag: + @bash -c 'if [ -z "$(VERSION)" ]; then echo ""; echo "Error: VERSION env var is not set"; echo ""; false; fi' + @bash -c 'if [ -n "$$(git status -s)" ]; then echo ""; echo "Error: Local git repo has uncommitted files:"; echo ""; git status; false; fi' + git fetch origin + @bash -c 'if [ -n "$$(git tag -l $(VERSION)a0)" ]; then echo ""; echo "Error: Release start tag $(VERSION)a0 already exists (the new version has alreay been started)"; echo ""; false; fi' + @bash -c 'if [[ -n "$${BRANCH}" ]]; then echo $${BRANCH} >branch.tmp; elif [[ "$${VERSION#*.*.}" == "0" ]]; then echo "master" >branch.tmp; else echo "stable_$${VERSION%.*}" >branch.tmp; fi' + @bash -c 'if [ "$$(git log --format=format:%s $$(cat branch.tmp)~..$$(cat branch.tmp))" != "Start $(VERSION)" ]; then echo ""; echo "Error: Start branch $$(cat branch.tmp) has not been created yet"; echo ""; false; fi' + @echo "==> This will complete the start of new version $(VERSION) using target branch $$(cat branch.tmp)" + @echo -n '==> Continue? [yN] ' + @bash -c 'read answer; if [ "$$answer" != "y" ]; then echo "Aborted."; false; fi' + bash -c 'git checkout $$(cat branch.tmp)' + git pull + git tag -f $(VERSION)a0 + git push -f --tags + git branch -D start_$(VERSION) + git branch -D -r origin/start_$(VERSION) + rm -f branch.tmp + @echo "Done: Pushed the release start tag and cleaned up the release start branch." + @echo "Makefile: $@ done." + +.PHONY: all +all: install develop check_reqs check ruff pylint test build builddoc safety bandit @echo "Makefile: $@ done." -else - @echo 'Error: A development version $(package_version) of $(package_name) cannot be uploaded to PyPI!' - @false -endif $(sdist_file): $(done_dir)/develop_$(pymn)_$(PACKAGE_LEVEL).done pyproject.toml $(dist_dependent_files) @echo "Makefile: Building the source distribution archive: $(sdist_file)" diff --git a/changes/645.cleanup.rst b/changes/645.cleanup.rst new file mode 100644 index 00000000..c4a0bc00 --- /dev/null +++ b/changes/645.cleanup.rst @@ -0,0 +1,2 @@ +Dev: Dropped the 'make upload' target, because the release to PyPI has +been migrated to using a publish workflow. diff --git a/changes/645.feature.1.rst b/changes/645.feature.1.rst new file mode 100644 index 00000000..399bdc52 --- /dev/null +++ b/changes/645.feature.1.rst @@ -0,0 +1,3 @@ +Dev: Encapsulated the releasing of a version to PyPI into new 'release_branch' +and 'release_publish' make targets. See the development documentation for +details. diff --git a/changes/645.feature.2.rst b/changes/645.feature.2.rst new file mode 100644 index 00000000..a04f5dca --- /dev/null +++ b/changes/645.feature.2.rst @@ -0,0 +1,2 @@ +Dev: Encapsulated the starting of a new version into new 'start_branch' and +'start_tag' make targets. See the development documentation for details. diff --git a/dev-requirements.txt b/dev-requirements.txt index a20bca70..8aa56878 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -104,11 +104,6 @@ entrypoints>=0.3.0 # Ruff checker (no imports, invoked via ruff script): ruff>=0.3.5 -# Twine (no imports, invoked via twine script): -twine>=3.0.0 -# readme-renderer 25.0 or higher is needed to address issue on Windows with py39 -readme-renderer>=25.0 - # Unit test (indirect dependencies): pluggy>=1.3.0 diff --git a/docs/development.rst b/docs/development.rst index d307dc16..32e2990d 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -416,7 +416,8 @@ This section shows the steps for releasing a version to `PyPI It covers all variants of versions that can be released: * Releasing a new major version (Mnew.0.0) based on the master branch -* Releasing a new minor version (M.Nnew.0) based on the master branch +* Releasing a new minor version (M.Nnew.0) based on the master branch or based + on an earlier stable branch * Releasing a new update version (M.N.Unew) based on the stable branch of its minor version @@ -440,42 +441,7 @@ local clone of the zhmccli Git repo. milestones to a future version, and proceed with the release process. You may need to create the milestone for the future version. -2. Set shell variables for the version that is being released and the branch - it is based on: - - * ``MNU`` - Full version M.N.U that is being released - * ``MN`` - Major and minor version M.N of that full version - * ``BRANCH`` - Name of the branch the version that is being released is - based on - - When releasing a new major version (e.g. ``1.0.0``) based on the master - branch: - - .. code-block:: sh - - MNU=1.0.0 - MN=1.0 - BRANCH=master - - When releasing a new minor version (e.g. ``0.9.0``) based on the master - branch: - - .. code-block:: sh - - MNU=0.9.0 - MN=0.9 - BRANCH=master - - When releasing a new update version (e.g. ``0.8.1``) based on the stable - branch of its minor version: - - .. code-block:: sh - - MNU=0.8.1 - MN=0.8 - BRANCH=stable_${MN} - -3. Run the Safety tool: +2. Run the Safety tool: .. code-block:: sh @@ -486,53 +452,41 @@ local clone of the zhmccli Git repo. Roll back the PR into any maintained stable branches. -4. Check for any +3. Check for any `dependabot alerts `_. If there are any dependabot alerts, fix them in a separate branch/PR. Roll back the PR into any maintained stable branches. -5. Create a topic branch for the version that is being released: +4. Create and push the release branch (replace M,N,U accordingly): .. code-block:: sh - git checkout ${BRANCH} - git pull - git checkout -b release_${MNU} - -6. Update the change log: - - First make a dry-run to print the change log as it would be: - - .. code-block:: sh - - towncrier build --draft - - If you are satisfied with the change log, update the change log: - - .. code-block:: sh - - towncrier build --yes - - This will update the change log file ``docs/changes.rst`` with the - information from the change fragment files in the ``changes`` directory, and - will delete these change fragment files. + VERSION=M.N.U make release_branch -7. Update the authors: + This uses the default branch determined from ``VERSION``: For ``M.N.0``, + the ``master`` branch is used, otherwise the ``stable_M.N`` branch is used. + That covers for all cases except if you want to release a new minor version + based on an earlier stable branch. In that case, you need to specify that + branch: .. code-block:: sh - make authors + VERSION=M.N.0 BRANCH=stable_M.N make release_branch -8. Commit your changes and push the topic branch to the remote repo: + This includes the following steps: - .. code-block:: sh + * create the release branch (``release_M.N.U``), if not yet existing + * make sure the AUTHORS.md file is up to date + * update the change log from the change fragment files, and delete those + * commit the changes to the release branch + * push the release branch - git commit -asm "Release ${MNU}" - git push --set-upstream origin release_${MNU} + If this command fails, the fix can be committed to the release branch + and the command above can be retried. -9. On GitHub, create a Pull Request for branch ``release_M.N.U``. +5. On GitHub, create a Pull Request for branch ``release_M.N.U``. Important: When creating Pull Requests, GitHub by default targets the ``master`` branch. When releasing based on a stable branch, you need to @@ -547,34 +501,40 @@ local clone of the zhmccli Git repo. tests for all defined environments, since it discovers by the branch name that this is a PR for a release. -10. On GitHub, once the checks for that Pull Request have succeeded, merge the +6. On GitHub, once the checks for that Pull Request have succeeded, merge the Pull Request (no review is needed). This automatically deletes the branch on GitHub. If the PR did not succeed, fix the issues. -11. On GitHub, close milestone ``M.N.U``. +7. On GitHub, close milestone ``M.N.U``. Verify that the milestone has no open items anymore. If it does have open items, investigate why and fix (probably step 1 was not performed). -12. Publish the package +8. Publish the package (replace M,N,U accordingly): .. code-block:: sh - git checkout ${BRANCH} - git pull - git branch -D release_${MNU} - git branch -D -r origin/release_${MNU} - git tag -f ${MNU} - git push -f --tags + VERSION=M.N.U make release_publish + + or (see step 4): + + .. code-block:: sh + + VERSION=M.N.0 BRANCH=stable_M.N make release_publish + + This includes the following steps: - Pushing the new tag will cause the "publish" workflow to run. That workflow + * create and push the release tag + * clean up the release branch + + Pushing the release tag will cause the "publish" workflow to run. That workflow builds the package, publishes it on PyPI, creates a release for it on - Github, and finally creates a new stable branch on Github if the master + GitHub, and finally creates a new stable branch on GitHub if the master branch was released. -13. Verify the publishing +9. Verify the publishing Wait for the "publish" workflow for the new release to have completed: https://github.com/zhmcclient/zhmccli/actions/workflows/publish.yml @@ -620,63 +580,34 @@ has the remote name ``origin`` in your local clone. Any commands in the following steps are executed in the main directory of your local clone of the zhmccli Git repo. -1. Set shell variables for the version that is being started and the branch it - is based on: - - * ``MNU`` - Full version M.N.U that is being started - * ``MN`` - Major and minor version M.N of that full version - * ``BRANCH`` - Name of the branch the version that is being started is - based on - - When starting a new major version (e.g. ``1.0.0``) based on the master - branch: +1. Create and push the start branch (replace M,N,U accordingly): .. code-block:: sh - MNU=1.0.0 - MN=1.0 - BRANCH=master + VERSION=M.N.U make start_branch - When starting a new minor version (e.g. ``0.9.0``) based on the master + This uses the default branch determined from ``VERSION``: For ``M.N.0``, + the ``master`` branch is used, otherwise the ``stable_M.N`` branch is used. + That covers for all cases except if you want to start a new minor version + based on an earlier stable branch. In that case, you need to specify that branch: .. code-block:: sh - MNU=0.9.0 - MN=0.9 - BRANCH=master - - When starting a new minor version (e.g. ``0.8.1``) based on the stable - branch of its minor version: - - .. code-block:: sh - - MNU=0.8.1 - MN=0.8 - BRANCH=stable_${MN} + VERSION=M.N.0 BRANCH=stable_M.N make start_branch -2. Create a topic branch for the version that is being started: - - .. code-block:: sh + This includes the following steps: - git fetch origin - git checkout ${BRANCH} - git pull - git checkout -b start_${MNU} - -3. Commit your changes and push them to the remote repo: - - .. code-block:: sh + * create the start branch (``start_M.N.U``), if not yet existing + * create a dummy change + * commit and push the start branch (``start_M.N.U``) - git commit -asm "Start ${MNU}" - git push --set-upstream origin start_${MNU} - -4. On GitHub, create a milestone for the new version ``M.N.U``. +2. On GitHub, create a milestone for the new version ``M.N.U``. You can create a milestone in GitHub via Issues -> Milestones -> New Milestone. -5. On GitHub, create a Pull Request for branch ``start_M.N.U``. +3. On GitHub, create a Pull Request for branch ``start_M.N.U``. Important: When creating Pull Requests, GitHub by default targets the ``master`` branch. When starting a version based on a stable branch, you @@ -686,7 +617,7 @@ local clone of the zhmccli Git repo. Set the milestone of that PR to the new version ``M.N.U``. -6. On GitHub, go through all open issues and pull requests that still have +4. On GitHub, go through all open issues and pull requests that still have milestones for previous releases set, and either set them to the new milestone, or to have no milestone. @@ -694,22 +625,24 @@ local clone of the zhmccli Git repo. should not be any such issues or pull requests anymore. So this step here is just an additional safeguard. -7. On GitHub, once the checks for the Pull Request for branch ``start_M.N.U`` +5. On GitHub, once the checks for the Pull Request for branch ``start_M.N.U`` have succeeded, merge the Pull Request (no review is needed). This automatically deletes the branch on GitHub. -8. Update and clean up the local repo: +6. Update and clean up the local repo (replace M,N,U accordingly): + + .. code-block:: sh + + VERSION=M.N.U make start_tag - Note: An initial tag is necessary because the automatic version calculation - done by setuptools-scm uses the most recent tag in the commit history and - increases the least significant part of the version by one, without - providing any controls to change that behavior. + or (see step 1): .. code-block:: sh - git checkout ${BRANCH} - git pull - git branch -D start_${MNU} - git branch -D -r origin/start_${MNU} - git tag -f ${MNU}a0 - git push -f --tags + VERSION=M.N.0 BRANCH=stable_M.N make start_tag + + This includes the following steps: + + * checkout and pull the branch that was started (``master`` or ``stable_M.N``) + * delete the start branch (``start_M.N.U``) locally and remotely + * create and push the start tag (``M.N.Ua0``) diff --git a/minimum-constraints-develop.txt b/minimum-constraints-develop.txt index 6fd82af9..4fe134d1 100644 --- a/minimum-constraints-develop.txt +++ b/minimum-constraints-develop.txt @@ -91,11 +91,6 @@ entrypoints==0.3.0 # Ruff checker (no imports, invoked via ruff script): ruff==0.3.5 -# Twine (no imports, invoked via twine script): -twine==3.0.0 -# readme-renderer (used by twine, uses Pygments) -readme-renderer==25.0 - # Unit test (indirect dependencies): pluggy==1.3.0 # used by pytest, tox @@ -127,13 +122,10 @@ Jinja2==3.1.4 keyring==17.0.0 MarkupSafe==2.0.0 more-itertools==5.0.0 -# twine 3.0.0 depends on pkginfo>=1.4.2 -pkginfo==1.4.2 pyparsing==3.0.7 pyproject-api==1.6.1 # used by tox since its 4.0.0 requests-toolbelt==0.8.0 rich==12.0.0 -rfc3986==1.4.0 # used by twine since its 3.2.0 which requires py36 smmap==3.0.1 snowballstemmer==2.0.0 stevedore==5.2.0