From d84efc7719a8679e6979d513d1c8c60904af7384 Mon Sep 17 00:00:00 2001 From: Will Yardley Date: Tue, 24 Sep 2024 18:55:25 -0700 Subject: [PATCH 01/26] docs: update docstrings to resolve sphinx failures (#1030) set `ignore-module-all` for `autodoc_default_options` to resolve some Sphinx errors about duplicate / ambiguous references https://github.com/sphinx-doc/sphinx/issues/4961#issuecomment-1543858623 Standardize some non-standard (Google-ish) docstrings to Sphinx format, to avoid ruff and Sphinx arguing about underline length. Fix indents and other minor whitespace / formatting changes. Fixes #1029 --- docs/conf.py | 2 + semantic_release/cli/commands/version.py | 17 ++--- semantic_release/hvcs/gitea.py | 9 ++- semantic_release/hvcs/gitlab.py | 81 ++++++++---------------- semantic_release/hvcs/util.py | 8 ++- 5 files changed, 45 insertions(+), 72 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 9c026c4b8..da9f2cda5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,8 @@ "sphinxcontrib.apidoc", ] +autodoc_default_options = {"ignore-module-all": True} + templates_path = ["_templates"] source_suffix = ".rst" master_doc = "index" diff --git a/semantic_release/cli/commands/version.py b/semantic_release/cli/commands/version.py index 503d7bf1e..5461cdd2d 100644 --- a/semantic_release/cli/commands/version.py +++ b/semantic_release/cli/commands/version.py @@ -219,19 +219,12 @@ def build_distributions( """ Run the build command to build the distributions. - Arguments: - --------- - build_command: str | None - The build command to run - build_command_env: Mapping[str, str] | None - The environment variables to use when running the build command - noop: bool - Whether or not to run the build command - - Raises: - ------ - BuildDistributionsError: if the build command fails + :param build_command: The build command to run. + :param build_command_env: The environment variables to use when running the + build command. + :param noop: Whether or not to run the build command. + :raises: BuildDistributionsError: if the build command fails """ if not build_command: rprint("[green]No build command specified, skipping") diff --git a/semantic_release/hvcs/gitea.py b/semantic_release/hvcs/gitea.py index 12f52e850..3ae2e6ab4 100644 --- a/semantic_release/hvcs/gitea.py +++ b/semantic_release/hvcs/gitea.py @@ -95,11 +95,9 @@ def create_release( Ref: https://gitea.com/api/swagger#/repository/repoCreateRelease :param tag: Tag to create release for - :param release_notes: The release notes for this version - :param prerelease: Whether or not this release should be specified as a - prerelease + prerelease :return: Whether the request succeeded """ @@ -181,6 +179,7 @@ def get_release_id_by_tag(self, tag: str) -> int | None: Get a release by its tag name https://gitea.com/api/swagger#/repository/repoGetReleaseByTag :param tag: Tag to get release for + :return: ID of found release """ tag_endpoint = self.create_api_url( @@ -206,6 +205,7 @@ def edit_release_notes(self, release_id: int, release_notes: str) -> int: https://gitea.com/api/swagger#/repository/repoEditRelease :param id: ID of release to update :param release_notes: The release notes for this version + :return: The ID of the release that was edited """ log.info("Updating release %s", release_id) @@ -231,6 +231,7 @@ def create_or_update_release( Post release changelog :param version: The version number :param changelog: The release notes for this version + :return: The status of the request """ log.info("Creating release for %s", tag) @@ -274,6 +275,7 @@ def upload_release_asset( :param release_id: ID of the release to upload to :param file: Path of the file to upload :param label: this parameter has no effect + :return: The status of the request """ url = self.asset_upload_url(release_id) @@ -312,6 +314,7 @@ def upload_dists(self, tag: str, dist_glob: str) -> int: Upload distributions to a release :param tag: Tag to upload for :param path: Path to the dist directory + :return: The number of distributions successfully uploaded """ # Find the release corresponding to this tag diff --git a/semantic_release/hvcs/gitlab.py b/semantic_release/hvcs/gitlab.py index f5452c4ca..1a9437b76 100644 --- a/semantic_release/hvcs/gitlab.py +++ b/semantic_release/hvcs/gitlab.py @@ -106,23 +106,16 @@ def create_release( """ Create a release in a remote VCS, adding any release notes and assets to it - Arguments: - --------- - tag(str): The tag to create the release for - release_notes(str): The changelog description for this version only - prerelease(bool): This parameter has no effect in GitLab - assets(list[str]): A list of paths to files to upload as assets (TODO: not implemented) - noop(bool): If True, do not perform any actions, only log intents - - Returns: - ------- - str: The tag of the release - - Raises: - ------ - GitlabAuthenticationError: If authentication is not correct - GitlabCreateError: If the server cannot perform the request + :param tag: The tag to create the release for + :param release_notes: The changelog description for this version only + :param prerelease: This parameter has no effect in GitLab + :param assets: A list of paths to files to upload as assets (TODO: not implemented) + :param noop: If True, do not perform any actions, only log intents + :return: The tag of the release + + :raises: GitlabAuthenticationError: If authentication is not correct + :raises: GitlabCreateError: If the server cannot perform the request """ if noop: noop_report(f"would have created a release for tag {tag}") @@ -145,20 +138,13 @@ def create_release( @suppress_not_found def get_release_by_tag(self, tag: str) -> gitlab.v4.objects.ProjectRelease | None: """ - Get a release by its tag name - - Arguments: - --------- - tag(str): The tag name to get the release for + Get a release by its tag name. - Returns: - ------- - gitlab.v4.objects.ProjectRelease | None: The release object or None if not found + :param tag: The tag name to get the release for - Raises: - ------ - gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated + :return: gitlab.v4.objects.ProjectRelease or None if not found + :raises: gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated """ try: return self.project.releases.get(tag) @@ -175,21 +161,15 @@ def edit_release_notes( # type: ignore[override] release_notes: str, ) -> str: """ - Update the release notes for a given release + Update the release notes for a given release. - Arguments: - --------- - release(gitlab.v4.objects.ProjectRelease): The release object to update - release_notes(str): The new release notes + :param release: The release object to update + :param release_notes: The new release notes - Returns: - ------- - str: The release id + :return: The release id - Raises: - ------ - GitlabAuthenticationError: If authentication is not correct - GitlabUpdateError: If the server cannot perform the request + :raises: GitlabAuthenticationError: If authentication is not correct + :raises: GitlabUpdateError: If the server cannot perform the request """ log.info( @@ -206,24 +186,17 @@ def create_or_update_release( self, tag: str, release_notes: str, prerelease: bool = False ) -> str: """ - Create or update a release for the given tag in a remote VCS - - Arguments: - --------- - tag(str): The tag to create or update the release for - release_notes(str): The changelog description for this version only - prerelease(bool): This parameter has no effect in GitLab + Create or update a release for the given tag in a remote VCS. - Returns: - ------- - str: The release id + :param tag: The tag to create or update the release for + :param release_notes: The changelog description for this version only + :param prerelease: This parameter has no effect in GitLab - Raises: - ------ - ValueError: If the release could not be created or updated - gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated - GitlabUpdateError: If the server cannot perform the request + :return: The release id + :raises ValueError: If the release could not be created or updated + :raises gitlab.exceptions.GitlabAuthenticationError: If the user is not authenticated + :raises GitlabUpdateError: If the server cannot perform the request """ try: return self.create_release( diff --git a/semantic_release/hvcs/util.py b/semantic_release/hvcs/util.py index fd0d66ebc..3c4f78888 100644 --- a/semantic_release/hvcs/util.py +++ b/semantic_release/hvcs/util.py @@ -21,12 +21,14 @@ def build_requests_session( ) -> Session: """ Create a requests session. + :param raise_for_status: If True, a hook to invoke raise_for_status be installed :param retry: If true, it will use default Retry configuration. if an integer, it - will use default Retry configuration with given integer as total retry - count. if Retry instance, it will use this instance. + will use default Retry configuration with given integer as total retry + count. if Retry instance, it will use this instance. :param auth: Optional TokenAuth instance to be used to provide the Authorization - header to the session + header to the session + :return: configured requests Session """ session = Session() From 1fa0e70c1fd5117f802b3a5f1c02809f987f5ccb Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Wed, 25 Sep 2024 18:27:59 -0600 Subject: [PATCH 02/26] ci(pr): add file type filter to regulate testing jobs (#1031) --- .github/workflows/pr.yml | 64 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index d8ae2fb03..e412f4b3f 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -4,10 +4,68 @@ name: Checks on: pull_request: +permissions: + contents: read + jobs: + + eval-changes: + name: Evaluate changes + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Evaluate | Check specific file types for changes + id: changed-files + uses: tj-actions/changed-files@v45.0.2 + with: + files_yaml: | + build: + - MANIFEST.in + - Dockerfile + - .dockerignore + - scripts/** + ci: + - .github/workflows/** + docs: + - docs/** + - README.rst + - AUTHORS.rst + - CONTRIBUTING.rst + - CHANGELOG.rst + src: + - semantic_release/** + - pyproject.toml + tests: + - tests/** + + - name: Evaluate | Detect if any of the combinations of file sets have changed + id: all-changes + run: | + printf '%s\n' "any_changed=false" >> $GITHUB_OUTPUT + if [ "${{ steps.changed-files.outputs.build_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.ci_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.docs_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.src_any_changed }}" == "true" ] || \ + [ "${{ steps.changed-files.outputs.tests_any_changed }}" == "true" ]; then + printf '%s\n' "any_changed=true" >> $GITHUB_OUTPUT + fi + + outputs: + any-file-changes: ${{ steps.all-changes.outputs.any_changed }} + build-changes: ${{ steps.changed-files.outputs.build_any_changed }} + ci-changes: ${{ steps.changed-files.outputs.ci_any_changed }} + doc-changes: ${{ steps.changed-files.outputs.docs_any_changed }} + src-changes: ${{ steps.changed-files.outputs.src_any_changed }} + test-changes: ${{ steps.changed-files.outputs.tests_any_changed }} + + test-linux: name: Python ${{ matrix.python-version }} on ${{ matrix.os }} tests runs-on: ${{ matrix.os }} + needs: eval-changes + if: ${{ needs.eval-changes.outputs.src-changes == 'true' || needs.eval-changes.outputs.test-changes == 'true' || needs.eval-changes.outputs.ci-changes == 'true' }} strategy: matrix: python-version: @@ -66,6 +124,8 @@ jobs: test-windows: name: Python ${{ matrix.python-version }} on ${{ matrix.os }} tests runs-on: ${{ matrix.os }} + needs: eval-changes + if: ${{ needs.eval-changes.outputs.src-changes == 'true' || needs.eval-changes.outputs.test-changes == 'true' || needs.eval-changes.outputs.ci-changes == 'true' }} strategy: # Since the current test suite takes 10-15 minutes to complete on windows, we are # only going to run it on the oldest version of python we support. The older version @@ -122,7 +182,10 @@ jobs: report_paths: ./tests/reports/*.xml annotate_only: true + lint: + needs: eval-changes + if: ${{ needs.eval-changes.outputs.any-file-changes == 'true' }} runs-on: ubuntu-latest steps: @@ -147,6 +210,7 @@ jobs: - name: mypy run: python -m mypy --ignore-missing-imports semantic_release + commitlint: runs-on: ubuntu-latest From 2307ed29d9990bf1b6821403a4b8db3365ef8bb5 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 01:05:17 -0600 Subject: [PATCH 03/26] chore(gha): update action details for marketplace publishing (#1032) * chore(gha): update action details for marketplace publishing * docs(homepage): re-structure homepage to be separate from project readme * docs(README): simplify README to point at official docs --- README.rst | 54 ++++++++++++-------------------------------------- action.yml | 13 +++++++----- docs/index.rst | 52 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 47 deletions(-) diff --git a/README.rst b/README.rst index 70167f553..c8fdd9f14 100644 --- a/README.rst +++ b/README.rst @@ -1,50 +1,22 @@ Python Semantic Release *********************** -|Ruff| |Test Status| |PyPI Version| |conda-forge version| |Read the Docs Status| |Pre-Commit Enabled| +*Automating Releases via SemVer and Commit Message Conventions* -Automatic Semantic Versioning for Python projects. This is a Python -implementation of `semantic-release`_ for JS by Stephan Bönnemann. If -you find this topic interesting you should check out his `talk from -JSConf Budapest`_. +---- -The general idea is to be able to detect what the next version of the -project should be based on the commits. This tool will use that to -automate the whole release, upload to an artifact repository and post changelogs to -GitHub. You can run the tool on a CI service, or just run it locally. +The official documentation for Python Semantic Release can be found at +`python-semantic-release.readthedocs.io`_. -Installation -============ +GitHub Action +============= -:: +When using the Python Semantic Release GitHub Action, it executes the command +``semantic-release version`` using `python-semantic-release`_. - python3 -m pip install python-semantic-release - semantic-release --help +The usage information and examples for this GitHub Action is available under +the `GitHub Actions section`_ of `python-semantic-release.readthedocs.io`_. -Python Semantic Release is also available from `conda-forge`_ or as a `GitHub Action`_. -Read more about the setup and configuration in our `getting started guide`_. - -.. _semantic-release: https://github.com/semantic-release/semantic-release -.. _talk from JSConf Budapest: https://www.youtube.com/watch?v=tc2UgG5L7WM -.. _getting started guide: https://python-semantic-release.readthedocs.io/en/latest/#getting-started -.. _GitHub Action: https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html -.. _conda-forge: https://anaconda.org/conda-forge/python-semantic-release - -.. |Test Status| image:: https://img.shields.io/github/actions/workflow/status/python-semantic-release/python-semantic-release/main.yml?branch=master&label=Test%20Status&logo=github - :target: https://github.com/python-semantic-release/python-semantic-release/actions/workflows/main.yml - :alt: test-status -.. |PyPI Version| image:: https://img.shields.io/pypi/v/python-semantic-release?label=PyPI&logo=pypi - :target: https://pypi.org/project/python-semantic-release/ - :alt: pypi -.. |conda-forge Version| image:: https://img.shields.io/conda/vn/conda-forge/python-semantic-release?logo=anaconda - :target: https://anaconda.org/conda-forge/python-semantic-release - :alt: conda-forge -.. |Read the Docs Status| image:: https://img.shields.io/readthedocs/python-semantic-release?label=Read%20the%20Docs&logo=Read%20the%20Docs - :target: https://python-semantic-release.readthedocs.io/en/latest/ - :alt: docs -.. |Pre-Commit Enabled| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit - :target: https://github.com/pre-commit/pre-commit - :alt: pre-commit -.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json - :target: https://github.com/astral-sh/ruff - :alt: Ruff +.. _python-semantic-release: https://pypi.org/project/python-semantic-release/ +.. _python-semantic-release.readthedocs.io: https://python-semantic-release.readthedocs.io/en/latest/ +.. _GitHub Actions section: https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html diff --git a/action.yml b/action.yml index 13ea3593b..dd3c1547f 100644 --- a/action.yml +++ b/action.yml @@ -1,7 +1,10 @@ --- name: Python Semantic Release -description: Automatic Semantic Versioning for Python projects +description: Automated Releases via SemVer and Commit Message Conventions + +branding: + color: orange inputs: root_options: @@ -103,14 +106,14 @@ outputs: description: | "true" if a release was made, "false" otherwise - version: - description: | - The newly released version if one was made, otherwise the current version - tag: description: | The Git tag corresponding to the version output + version: + description: | + The newly released version if one was made, otherwise the current version + runs: using: docker image: Dockerfile diff --git a/docs/index.rst b/docs/index.rst index 210d820a9..b51b47128 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,4 +1,54 @@ -.. include:: ../README.rst +Python Semantic Release +*********************** + +|Ruff| |Test Status| |PyPI Version| |conda-forge version| |Read the Docs Status| |Pre-Commit Enabled| + +Automatic Semantic Versioning for Python projects. This is a Python +implementation of `semantic-release`_ for JS by Stephan Bönnemann. If +you find this topic interesting you should check out his `talk from +JSConf Budapest`_. + +The general idea is to be able to detect what the next version of the +project should be based on the commits. This tool will use that to +automate the whole release, upload to an artifact repository and post changelogs to +GitHub. You can run the tool on a CI service, or just run it locally. + +Installation +============ + +:: + + python3 -m pip install python-semantic-release + semantic-release --help + +Python Semantic Release is also available from `conda-forge`_ or as a `GitHub Action`_. +Read more about the setup and configuration in our `getting started guide`_. + +.. _semantic-release: https://github.com/semantic-release/semantic-release +.. _talk from JSConf Budapest: https://www.youtube.com/watch?v=tc2UgG5L7WM +.. _getting started guide: https://python-semantic-release.readthedocs.io/en/latest/#getting-started +.. _GitHub Action: https://python-semantic-release.readthedocs.io/en/latest/automatic-releases/github-actions.html +.. _conda-forge: https://anaconda.org/conda-forge/python-semantic-release + +.. |Test Status| image:: https://img.shields.io/github/actions/workflow/status/python-semantic-release/python-semantic-release/main.yml?branch=master&label=Test%20Status&logo=github + :target: https://github.com/python-semantic-release/python-semantic-release/actions/workflows/main.yml + :alt: test-status +.. |PyPI Version| image:: https://img.shields.io/pypi/v/python-semantic-release?label=PyPI&logo=pypi + :target: https://pypi.org/project/python-semantic-release/ + :alt: pypi +.. |conda-forge Version| image:: https://img.shields.io/conda/vn/conda-forge/python-semantic-release?logo=anaconda + :target: https://anaconda.org/conda-forge/python-semantic-release + :alt: conda-forge +.. |Read the Docs Status| image:: https://img.shields.io/readthedocs/python-semantic-release?label=Read%20the%20Docs&logo=Read%20the%20Docs + :target: https://python-semantic-release.readthedocs.io/en/latest/ + :alt: docs +.. |Pre-Commit Enabled| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit + :target: https://github.com/pre-commit/pre-commit + :alt: pre-commit +.. |Ruff| image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json + :target: https://github.com/astral-sh/ruff + :alt: Ruff + Documentation Contents ====================== From 156915c7d759098f65cf9de7c4e980b40b38d5f1 Mon Sep 17 00:00:00 2001 From: codejedi365 Date: Fri, 27 Sep 2024 01:15:02 -0600 Subject: [PATCH 04/26] fix(version-cmd): improve `version_variables` flexibility w/ quotes (ie. json, yaml, etc) (#1028) * fix(version-cmd): increase `version_variable` flexibility with quotations (ie. json, yaml, etc) Previously json would not work due to the key being wrapped in quotes, yaml also has issues when it does not usually use quotes. The regex we created originally only wrapped the version to be replaced in quotes but now both the key and version can optionally be wrapped in different kind of quotations. Resolves: #601, #706, #962, #1026 * docs(configuration): add clarity to `version_variables` usage & limitations Ref: #941 * fix(version-cmd): ensure `version_variables` do not match partial variable names * build(deps-test): add `PyYAML` as a test dependency * test(fixtures): refactor location of fixture for global use of cli runner * test(stamp-version): add test cases to stamp json, python, & yaml files --- docs/configuration.rst | 29 ++++ pyproject.toml | 5 +- semantic_release/cli/config.py | 13 +- tests/command_line/conftest.py | 6 - tests/conftest.py | 6 + tests/scenario/test_version_stamp.py | 219 +++++++++++++++++++++++++++ 6 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 tests/scenario/test_version_stamp.py diff --git a/docs/configuration.rst b/docs/configuration.rst index e2bfe3bbc..ee63a99b0 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -1102,4 +1102,33 @@ specified in ``file:variable`` format. For example: "docs/conf.py:version", ] +Each version variable will be transformed into a Regular Expression that will be used +to substitute the version number in the file. The replacement algorithm is **ONLY** a +pattern match and replace. It will **NOT** evaluate the code nor will PSR understand +any internal object structures (ie. ``file:object.version`` will not work). + +.. important:: + The Regular Expression expects a version value to exist in the file to be replaced. + It cannot be an empty string or a non-semver compliant string. If this is the very + first time you are using PSR, we recommend you set the version to ``0.0.0``. This + may become more flexible in the future with resolution of issue `#941`_. + +.. _#941: https://github.com/python-semantic-release/python-semantic-release/issues/941 + +Given the pattern matching nature of this feature, the Regular Expression is able to +support most file formats as a variable declaration in most languages is very similar. +We specifically support Python, YAML, and JSON as these have been the most common +requests. This configuration option will also work regardless of file extension +because its only a pattern match. + +.. note:: + This will also work for TOML but we recommend using :ref:`config-version_toml` for + TOML files as it actually will interpret the TOML file and replace the version + number before writing the file back to disk. + +.. warning:: + If the file (ex. JSON) you are replacing has two of the same variable name in it, + this pattern match will not be able to differentiate between the two and will replace + both. This is a limitation of the pattern matching and not a bug. + **Default:** ``[]`` diff --git a/pyproject.toml b/pyproject.toml index 242021dfe..36be341bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ docs = [ ] test = [ "coverage[toml] ~= 7.0", + "pyyaml ~= 6.0", "pytest ~= 8.3", "pytest-env ~= 1.0", "pytest-xdist ~= 3.0", @@ -86,8 +87,8 @@ env = [ ] addopts = [ # TO DEBUG in single process, swap auto to 0 - "-nauto", - # "-n0", + # "-nauto", + "-n0", "-ra", "--diff-symbols", "--cache-clear", diff --git a/semantic_release/cli/config.py b/semantic_release/cli/config.py index ba12f4ce4..5c9f31ef7 100644 --- a/semantic_release/cli/config.py +++ b/semantic_release/cli/config.py @@ -540,7 +540,18 @@ def from_raw_config( # noqa: C901 try: path, variable = decl.split(":", maxsplit=1) # VersionDeclarationABC handles path existence check - search_text = rf"(?x){variable}\s*(:=|[:=])\s*(?P['\"])(?P{SEMVER_REGEX.pattern})(?P=quote)" # noqa: E501 + search_text = str.join( + "", + [ + # Supports optional matching quotations around variable name + # Negative lookbehind to ensure we don't match part of a variable name + f"""(?x)(?P['"])?(?['"])?(?P{SEMVER_REGEX.pattern})(?P=quote2)?""", + ], + ) pd = PatternVersionDeclaration(path, search_text) except ValueError as exc: log.exception("Invalid variable declaration %r", decl) diff --git a/tests/command_line/conftest.py b/tests/command_line/conftest.py index b8160373d..46d68d46e 100644 --- a/tests/command_line/conftest.py +++ b/tests/command_line/conftest.py @@ -6,7 +6,6 @@ from unittest.mock import MagicMock import pytest -from click.testing import CliRunner from requests_mock import ANY from semantic_release.cli import config as cli_config_module @@ -40,11 +39,6 @@ class RetrieveRuntimeContextFn(Protocol): def __call__(self, repo: Repo) -> RuntimeContext: ... -@pytest.fixture -def cli_runner() -> CliRunner: - return CliRunner(mix_stderr=False) - - @pytest.fixture def post_mocker(requests_mock: Mocker) -> Mocker: """Patch all POST requests, mocking a response body for VCS release creation.""" diff --git a/tests/conftest.py b/tests/conftest.py index 3465b236a..911906a7d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING import pytest +from click.testing import CliRunner from git import Commit, Repo from tests.fixtures import * @@ -28,6 +29,11 @@ class TeardownCachedDirFn(Protocol): def __call__(self, directory: Path) -> Path: ... +@pytest.fixture +def cli_runner() -> CliRunner: + return CliRunner(mix_stderr=False) + + @pytest.fixture(scope="session") def default_netrc_username() -> str: return "username" diff --git a/tests/scenario/test_version_stamp.py b/tests/scenario/test_version_stamp.py new file mode 100644 index 000000000..60a408e67 --- /dev/null +++ b/tests/scenario/test_version_stamp.py @@ -0,0 +1,219 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path +from textwrap import dedent +from typing import TYPE_CHECKING + +import pytest +import yaml + +from semantic_release.cli.commands.main import main + +from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD +from tests.fixtures.repos.trunk_based_dev.repo_w_no_tags import ( + repo_with_no_tags_angular_commits, +) +from tests.util import assert_successful_exit_code + +if TYPE_CHECKING: + from click.testing import CliRunner + + from tests.fixtures.example_project import UpdatePyprojectTomlFn + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_python( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + new_version = "0.1.0" + target_file = Path("src/example/_version.py") + + # Set configuration to modify the python file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:__version__"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Load python module for reading the version (ensures the file is valid) + spec = importlib.util.spec_from_file_location("example._version", str(target_file)) + module = importlib.util.module_from_spec(spec) # type: ignore + spec.loader.exec_module(module) # type: ignore + + # Check the version was updated + assert new_version == module.__version__ + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_yaml( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("example.yml") + orig_yaml = dedent( + f"""\ + --- + package: example + version: {orig_version} + date-released: 1970-01-01 + """ + ) + # Write initial text in file + target_file.write_text(orig_yaml) + + # Set configuration to modify the yaml file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_yaml_obj = yaml.safe_load(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_yaml_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_yaml_obj["version"] = orig_version + + assert yaml.safe_load(orig_yaml) == resulting_yaml_obj + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_yaml_cff( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("CITATION.cff") + # Derived format from python-semantic-release/python-semantic-release#962 + orig_yaml = dedent( + f"""\ + --- + cff-version: 1.2.0 + message: "If you use this software, please cite it as below." + authors: + - family-names: Doe + given-names: Jon + orcid: https://orcid.org/1234-6666-2222-5555 + title: "My Research Software" + version: {orig_version} + date-released: 1970-01-01 + """ + ) + # Write initial text in file + target_file.write_text(orig_yaml) + + # Set configuration to modify the yaml file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_yaml_obj = yaml.safe_load(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_yaml_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_yaml_obj["version"] = orig_version + + assert yaml.safe_load(orig_yaml) == resulting_yaml_obj + + +@pytest.mark.usefixtures(repo_with_no_tags_angular_commits.__name__) +def test_stamp_version_variables_json( + cli_runner: CliRunner, + update_pyproject_toml: UpdatePyprojectTomlFn, +) -> None: + orig_version = "0.0.0" + new_version = "0.1.0" + target_file = Path("plugins.json") + orig_json = { + "id": "test-plugin", + "version": orig_version, + "meta": { + "description": "Test plugin", + }, + } + # Write initial text in file + target_file.write_text(json.dumps(orig_json, indent=4)) + + # Set configuration to modify the json file + update_pyproject_toml( + "tool.semantic_release.version_variables", [f"{target_file}:version"] + ) + + # Use the version command and prevent any action besides stamping the version + cli_cmd = [ + MAIN_PROG_NAME, + VERSION_SUBCMD, + "--no-changelog", + "--no-commit", + "--no-tag", + ] + + # Act + result = cli_runner.invoke(main, cli_cmd[1:]) + + # Check the result + assert_successful_exit_code(result, cli_cmd) + + # Read content + resulting_json_obj = json.loads(target_file.read_text()) + + # Check the version was updated + assert new_version == resulting_json_obj["version"] + + # Check the rest of the content is the same (by reseting the version & comparing) + resulting_json_obj["version"] = orig_version + + assert orig_json == resulting_json_obj From cbe8eaaa7a06ef218fce69bd1bc01dd16483dc6d Mon Sep 17 00:00:00 2001 From: semantic-release Date: Fri, 27 Sep 2024 07:35:01 +0000 Subject: [PATCH 05/26] 9.8.9 Automatically generated by python-semantic-release --- CHANGELOG.md | 42 ++++++++++++++++++++++ docs/automatic-releases/github-actions.rst | 6 ++-- docs/github-action.rst | 2 +- pyproject.toml | 2 +- semantic_release/__init__.py | 2 +- 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83681b66b..9fc9c6892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,47 @@ # CHANGELOG +## v9.8.9 (2024-09-27) + +### Documentation + +* docs: update docstrings to resolve sphinx failures (#1030) + +set `ignore-module-all` for `autodoc_default_options` to resolve some +Sphinx errors about duplicate / ambiguous references +https://github.com/sphinx-doc/sphinx/issues/4961#issuecomment-1543858623 + +Standardize some non-standard (Google-ish) docstrings to Sphinx +format, to avoid ruff and Sphinx arguing about underline length. + +Fix indents and other minor whitespace / formatting changes. + +Fixes #1029 ([`d84efc7`](https://github.com/python-semantic-release/python-semantic-release/commit/d84efc7719a8679e6979d513d1c8c60904af7384)) + +### Fix + +* fix(version-cmd): improve `version_variables` flexibility w/ quotes (ie. json, yaml, etc) (#1028) + +* fix(version-cmd): increase `version_variable` flexibility with quotations (ie. json, yaml, etc) + + Previously json would not work due to the key being wrapped in quotes, yaml also has issues + when it does not usually use quotes. The regex we created originally only wrapped the version + to be replaced in quotes but now both the key and version can optionally be wrapped in + different kind of quotations. + + Resolves: #601, #706, #962, #1026 + +* docs(configuration): add clarity to `version_variables` usage & limitations + + Ref: #941 + +* fix(version-cmd): ensure `version_variables` do not match partial variable names + +* build(deps-test): add `PyYAML` as a test dependency + +* test(fixtures): refactor location of fixture for global use of cli runner + +* test(stamp-version): add test cases to stamp json, python, & yaml files ([`156915c`](https://github.com/python-semantic-release/python-semantic-release/commit/156915c7d759098f65cf9de7c4e980b40b38d5f1)) + ## v9.8.8 (2024-09-01) ### Documentation diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index a02e7ab1a..ca6b8661f 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -53,7 +53,7 @@ Example Workflow - name: Python Semantic Release # Adjust tag with desired version if applicable. Version shorthand # is NOT available, e.g. vX or vX.X will not work. - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -94,13 +94,13 @@ multiple projects. .. code:: yaml - name: Release Project 1 - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: directory: ./project1 github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release Project 2 - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: directory: ./project2 github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/github-action.rst b/docs/github-action.rst index 35faf9deb..24a539f77 100644 --- a/docs/github-action.rst +++ b/docs/github-action.rst @@ -131,7 +131,7 @@ provide the following inputs: - name: Python Semantic Release # Adjust tag with desired version if applicable. Version shorthand # is NOT available, e.g. vX or vX.X will not work. - uses: python-semantic-release/python-semantic-release@v9.8.8 + uses: python-semantic-release/python-semantic-release@v9.8.9 with: # ... other options force: "patch" diff --git a/pyproject.toml b/pyproject.toml index 36be341bd..d70c1ecc2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "9.8.8" +version = "9.8.9" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } diff --git a/semantic_release/__init__.py b/semantic_release/__init__.py index a1b08b69b..30801902c 100644 --- a/semantic_release/__init__.py +++ b/semantic_release/__init__.py @@ -24,7 +24,7 @@ tags_and_versions, ) -__version__ = "9.8.8" +__version__ = "9.8.9" __all__ = [ "CommitParser", From 2135c68ccbdad94378809902b52fcad546efd5b3 Mon Sep 17 00:00:00 2001 From: Will Yardley Date: Fri, 27 Sep 2024 15:56:22 -0700 Subject: [PATCH 06/26] docs(github-actions): clarify & consolidate GitHub Actions usage docs (#1011) Resolves: #907 * chore(scripts): remove non-existant file from version bump script * docs(automatic-releases): drop extrenous github push configuration * docs(homepage): remove link to old github config & update token scope config * docs(github-actions): expand descriptions & clarity of actions configs * docs(github-actions): add configuration & description of publish action * docs(github-actions): revert removal of namespace prefix from examples --- docs/automatic-releases/github-actions.rst | 727 +++++++++++++++++++-- docs/automatic-releases/index.rst | 8 - docs/configuration.rst | 2 +- docs/github-action.rst | 139 ---- docs/index.rst | 26 +- scripts/bump_version_in_docs.py | 1 - 6 files changed, 698 insertions(+), 205 deletions(-) delete mode 100644 docs/github-action.rst diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index ca6b8661f..36bf58541 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -1,95 +1,728 @@ -.. _github-actions: +.. _gh_actions: -Setting up python-semantic-release on GitHub Actions -==================================================== +GitHub Actions +============== -Python Semantic Release includes a GitHub Action which runs the ``version`` and -``publish`` commands. The repository is set to ``PyPI``. You can read the full set -of inputs available, and their descriptions in the `action definition`_. +There are two official GitHub Actions for Python Semantic Release: -Your project's configuration file will be used as normal. +1. :ref:`python-semantic-release/python-semantic-release@TAG ` + This is the main action that runs the :ref:`version ` CLI + command. It is used to (1) determine the next version number, (2) stamp the + version number, (3) run the build command, (4) build the changelog, (5) commit + the changes, (6) tag the commit, (7) publish the commit & tag and lastly + (8) create a GitHub release. For more information review the + :ref:`version command documentation ` and see + :ref:`below ` for the Action configuration options. -The GitHub Action provides the following outputs: +2. :ref:`python-semantic-release/publish-action@TAG ` + This action is used to execute the :ref:`publish ` CLI command. + It is used to upload files, such as distribution artifacts and other assets, + to a GitHub release. -+-------------+-----------------------------------------------------------+ -| Output | Description | -+-------------+-----------------------------------------------------------+ -| released | "true" if a release was made, "false" otherwise | -+-------------+-----------------------------------------------------------+ -| version | The newly released version if one was made, otherwise | -| | the current version | -+-------------+-----------------------------------------------------------+ -| tag | The Git tag corresponding to the "version" output. The | -| | format is dictated by your configuration. | -+-------------+-----------------------------------------------------------+ +.. note:: + These GitHub Actions are only simplified wrappers around the + python-semantic-release CLI. Ultimately, they download and install the + published package from PyPI so if you find that you are trying to do something + more advanced or less common, you may need to install and use the CLI directly. -.. _action definition: https://github.com/python-semantic-release/python-semantic-release/blob/master/action.yml +.. _gh_actions-psr: -Example Workflow ----------------- +Python Semantic Release Action +'''''''''''''''''''''''''''''' + +The official `Python Semantic Release GitHub Action`_ is a `GitHub Docker Action`_, +which means at the beginning of the job it will build a Docker image that contains +the Python Semantic Release package and its dependencies. It will then run the +job step inside the Docker Container. This is done to ensure that the environment +is consistent across all GitHub Runners regardless of platform. With this choice, +comes some limitations of non-configurable options like a pre-defined python +version, lack of installed build tools, and an inability to utilize caching. + +The primary benefit of using the GitHub Action is that it is easy to set up and +use for most projects. We handle a lot of the git configuration under the hood, +so you don't have to handle it yourself. There are a plenty of customization +options available which are detailed individually below. + +Most importantly your project's configuration file will be used as normal, as +your project will be mounted into the container for the action to use. + +.. _Python Semantic Release GitHub Action: https://github.com/marketplace/actions/python-semantic-release +.. _GitHub Docker Action: https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-docker-container-action + +.. seealso:: + + `action.yml`__: the code definition of the action + + __ https://github.com/python-semantic-release/python-semantic-release/blob/master/action.yml + +.. _gh_actions-psr-inputs: + +Inputs +------ + +GitHub Action inputs are used for select configuration and provide the necessary +information to execute the action. The inputs are passed to the action using the +``with`` keyword in the workflow file. Many inputs will mirror the command line +options available in the :ref:`version ` command. This section +outlines each supported input and its purpose. + +---- + +.. _gh_actions-psr-inputs-build_metadata: + +``build_metadata`` +"""""""""""""""""" + +**Type:** ``string`` + +Explicitly set the build metadata of the version. This is equivalent to running the command: + +.. code:: shell + + semantic-release version --build-metadata + +**Required:** ``false`` + +.. seealso:: + + - :ref:`cmd-version-option-build-metadata` option for the :ref:`version ` command + +---- + +.. _gh_actions-psr-inputs-changelog: + +``changelog`` +""""""""""""" + +**Type:** ``Literal["true", "false"]`` + +Override whether the action should generate a changelog or not. This option is +equivalent to adding either ``--changelog`` (on ``true``) or ``--no-changelog`` +(on ``false``) to the :ref:`version ` command. + +**Required:** ``false`` + +.. note:: + If not set, the default behavior is defined by the :ref:`version ` + command and any user :ref:`configurations `. + +.. seealso:: + + - :ref:`cmd-version-option-changelog` options for the :ref:`version ` + command + +---- + +.. _gh_actions-psr-inputs-commit: + +``commit`` +"""""""""" + +**Type:** ``Literal["true", "false"]`` + +Override whether the action should commit any changes to the local repository. Changes +include the version stamps, changelog, and any other files that are modified and added +to the index during the build command. This option is equivalent to adding either +``--commit`` (on ``true``) or ``--no-commit`` (on ``false``) to the +:ref:`version ` command. + +**Required:** ``false`` + +.. note:: + If not set, the default behavior is defined by the :ref:`version ` + command and any user :ref:`configurations `. + +.. seealso:: + + - :ref:`cmd-version-option-commit` options for the :ref:`version ` command + +---- + +.. _gh_actions-psr-inputs-directory: + +``directory`` +""""""""""""" + +If the project is not at the root of the repository (like in monorepos), you +can specify a sub-directory to change into before running semantic-release. + +**Required:** ``false`` + +**Default:** ``.`` + +---- + +.. _gh_actions-psr-inputs-force: + +``force`` +""""""""" + +**Type:** ``Literal["prerelease", "patch", "minor", "major"]`` + +Force the next version to be a specific bump type. This is equivalent to running +the command: + +.. code:: shell + + semantic-release version -- + + # Ex: force a patch level version bump + semantic-release version --patch + + +**Required:** ``false`` + +.. seealso:: + + - :ref:`cmd-version-option-force-level` options for the :ref:`version ` command + +---- + +.. _gh_actions-psr-inputs-git_committer_email: + +``git_committer_email`` +""""""""""""""""""""""" + +The email of the account used to commit. If customized, it must be associated +with the provided token. + +**Required:** ``false`` + +---- + +.. _gh_actions-psr-inputs-git_committer_name: + +``git_committer_name`` +"""""""""""""""""""""" + +The name of the account used to commit. If customized, it must be associated +with the provided token. + +**Required:** ``false`` + +---- + +.. _gh_actions-psr-inputs-github_token: + +``github_token`` +"""""""""""""""" + +The GitHub Token is essential for access to your GitHub repository to allow the +push of commits & tags as well as to create a release. Not only do you need to +provide the token as an input but you also need to ensure that the token has the +correct permissions. + +The token should have the following `permissions`_: + +* id-token: write +* contents: write + +**Required:** ``true`` + +.. _permissions: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions + +---- + +.. _gh_actions-psr-inputs-prerelease: + +``prerelease`` +"""""""""""""" + +Force the version to be a prerelease version when set to ``true``. This is equivalent +to running the command: + +.. code:: shell + + semantic-release version --as-prerelease + +**Required:** ``false`` + +.. note:: + If not set, the default behavior is defined by the :ref:`version ` + command and any user :ref:`configurations `. + +.. seealso:: + + - :ref:`cmd-version-option-as-prerelease` option for the :ref:`version ` + command + +---- + +.. _gh_actions-psr-inputs-prerelease_token: + +``prerelease_token`` +"""""""""""""""""""" + +Override any prerelease token in the configuration file with this value, if it is +a pre-release. This will override the matching release branch configuration's +``prerelease_token`` value. If you always want it to be a prerelease then you must +also set the :ref:`gh_actions-psr-inputs-prerelease` input to ``true``. + +This option is equivalent to running the command: + +.. code:: shell + + semantic-release version --prerelease-token + +**Required:** ``false`` + +.. note:: + If not set, the default behavior is defined by the :ref:`version ` + command and any user :ref:`configurations `. + +.. seealso:: + + - :ref:`cmd-version-option-prerelease-token` option for the :ref:`version ` + command + +---- + +.. _gh_actions-psr-inputs-push: + +``push`` +"""""""" + +**Type:** ``Literal["true", "false"]`` + +Override whether the action should push any commits or tags from the local repository +to the remote repository. This option is equivalent to adding either ``--push`` (on +``true``) or ``--no-push`` (on ``false``) to the :ref:`version ` command. + +**Required:** ``false`` + +.. note:: + If not set, the default behavior is defined by the :ref:`version ` + command and any user :ref:`configurations `. + +.. seealso:: + + - :ref:`cmd-version-option-push` options for the :ref:`version ` command + +---- + +.. _gh_actions-psr-inputs-root_options: + +``root_options`` +"""""""""""""""" + +Additional options for the main ``semantic-release`` command, which will come +before the :ref:`version ` subcommand. + + **Example** + + .. code:: yaml + + - uses: python-semantic-release/python-semantic-release@v9.8.9 + with: + root_options: "-vv --noop" + + This configuration would cause the command to be + ``semantic-release -vv --noop version``, which would run the version command + verbosely but in no-operation mode. + +**Required:** ``false`` + +**Default:** ``-v`` + +.. seealso:: + + - :ref:`Options ` for the :ref:`semantic-release ` command + +---- + +.. _gh_actions-psr-inputs-ssh_public_signing_key: + +``ssh_public_signing_key`` +"""""""""""""""""""""""""" + +The public key associated with the private key used in signing a commit and tag. + +**Required:** ``false`` + +---- + +.. _gh_actions-psr-inputs-ssh_private_signing_key: + +``ssh_private_signing_key`` +""""""""""""""""""""""""""" + +The private key used to sign a commit and tag. + +**Required:** ``false`` + +---- + +.. _gh_actions-psr-inputs-tag: + +``tag`` +""""""" + +**Type:** ``Literal["true", "false"]`` + +Override whether the action should create a version tag in the local repository. This +option is equivalent to adding either ``--tag`` (on ``true``) or ``--no-tag`` (on +``false``) to the :ref:`version ` command. + +**Required:** ``false`` + +.. note:: + If not set, the default behavior is defined by the :ref:`version ` + command and any user :ref:`configurations `. + +.. seealso:: + + - :ref:`cmd-version-option-tag` options for the :ref:`version ` command + +---- + +.. _gh_actions-psr-inputs-vcs_release: + +``vcs_release`` +""""""""""""""" + +**Type:** ``Literal["true", "false"]`` + +Override whether the action should create a release on the VCS. This option is +equivalent to adding either ``--vcs-release`` (on ``true``) or ``--no-vcs-release`` +(on ``false``) to the :ref:`version ` command. + +**Required:** ``false`` + +.. note:: + If not set, the default behavior is defined by the :ref:`version ` + command and any user :ref:`configurations `. + +.. seealso:: + + - :ref:`cmd-version-option-vcs-release` options for the :ref:`version ` + command + +---- + +.. _gh_actions-psr-outputs: + +Outputs +------- + +The Python Semantic Release Action also provides outputs that can be used in subsequent +steps of the workflow. These outputs are used to provide information about the release +and any actions that were taken. + +---- + +.. _gh_actions-psr-outputs-released: + +``released`` +"""""""""""" + +**Type:** ``Literal["true", "false"]`` + +A boolean value indicating whether a release was made. + +---- + +.. _gh_actions-psr-outputs-version: + +``version`` +""""""""""" + +**Type:** ``string`` + +The newly released SemVer version string if one was made, +otherwise the current version. + +Example: ``1.2.3`` + +---- + +.. _gh_actions-psr-outputs-tag: + +``tag`` +""""""" + +**Type:** ``string`` + +The Git tag corresponding to the ``version`` output but in +the tag format dictated by your configuration. + +Example: ``v1.2.3`` + +---- + +.. _gh_actions-publish: + +Python Semantic Release Publish Action +'''''''''''''''''''''''''''''''''''''' + +The official `Python Semantic Release Publish Action`_ is a `GitHub Docker Action`_, which +means at the beginning of the job it will build a Docker image that contains the Python +Semantic Release package and its dependencies. It will then run the job step inside the +Docker Container. This is done to ensure that the environment is consistent across all +GitHub Runners regardless of platform. With this choice, comes some limitations of +non-configurable options like a pre-defined python version, lack of additional 3rd party +tools, and an inability to utilize caching. + +The primary benefit of using the GitHub Action is that it is easy to set up and use for +most projects. We handle some additional configuration under the hood, so you don't have +to handle it yourself. We do however provide a few customization options which are detailed +individually below. + +Most importantly your project's configuration file will be used as normal, as your project +will be mounted into the container for the action to use. + +If you have issues with the action, please open an issue on the +`python-semantic-release/publish-action`_ repository. + +.. _Python Semantic Release Publish Action: https://github.com/marketplace/actions/python-semantic-release-publish + +.. seealso:: + + - `action.yml`__: the code definition for the publish action + + __ https://github.com/python-semantic-release/publish-action/blob/main/action.yml + +.. _gh_actions-publish-inputs: + +Inputs +------ + +GitHub Action inputs are used for select configuration and provide the necessary +information to execute the action. The inputs are passed to the action using the +``with`` keyword in the workflow file. Many inputs will mirror the command line +options available in the :ref:`publish ` command and others will be +specific to adjustment of the action environment. This section outlines each +supported input and its purpose. + +---- + +.. _gh_actions-publish-inputs-directory: + +``directory`` +""""""""""""" + +If the project is not at the root of the repository (like in monorepos), you +can specify a sub-directory to change into before running semantic-release. + +**Required:** ``false`` + +**Default:** ``.`` + +---- + +.. _gh_actions-publish-inputs-github_token: + +``github_token`` +"""""""""""""""" + +The GitHub Token is essential for access to your GitHub repository to allow the +publish of assets to a release. Not only do you need to provide the token as an +input but you also need to ensure that the token has the correct permissions. + +The token should have the following `permissions`_: + +* ``contents: write``: Required for modifying a GitHub Release + +**Required:** ``true`` + +.. _permissions: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions + +---- + +.. _gh_actions-publish-inputs-root_options: + +``root_options`` +"""""""""""""""" + +Additional options for the main ``semantic-release`` command, which will come +before the :ref:`publish ` subcommand. + + **Example** + + .. code:: yaml + + - uses: python-semantic-release/publish-action@v9.8.9 + with: + root_options: "-vv --noop" + + This configuration would cause the command to be + ``semantic-release -vv --noop publish``, which would run the publish command + verbosely but in no-operation mode. + +**Required:** ``false`` + +**Default:** ``-v`` + +.. seealso:: + + - :ref:`Options ` for the :ref:`semantic-release ` + command + +---- + +.. _gh_actions-publish-inputs-tag: + +``tag`` +""""""" + +**Type:** ``string`` + +The tag corresponding to the GitHub Release that the artifacts should be published +to. This option is equivalent to running the command: + +.. code:: shell + + semantic-release publish --tag + +Python Semantic Release will automatically determine the latest release if no +``--tag`` option is provided. + +**Required:** ``false`` + +.. seealso:: + + - :ref:`cmd-publish-option-tag` option for the :ref:`publish ` command + +---- + +.. _gh_actions-publish-outputs: + +Outputs +------- + +There are no outputs provided by the Python Semantic Release Publish Action at this time. + +.. note:: + If you would like outputs to be provided by this action, please open an issue + on the `python-semantic-release/publish-action`_ repository. + +.. _python-semantic-release/publish-action: https://github.com/python-semantic-release/publish-action/issues + +---- + +.. _gh_actions-examples: + +Examples +'''''''' + +Common Workflow Example +----------------------- + +The following is a common workflow example that uses both the Python Semantic Release Action +and the Python Semantic Release Publish Action. This workflow will run on every push to the +``main`` branch and will create a new release upon a successful version determination. If a +version is released, the workflow will then publish the package to PyPI and upload the package +to the GitHub Release Assets as well. .. code:: yaml - name: Semantic Release + name: Continuous Delivery on: push: branches: - - master + - main jobs: release: runs-on: ubuntu-latest concurrency: release + permissions: id-token: write contents: write steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Python Semantic Release - # Adjust tag with desired version if applicable. Version shorthand - # is NOT available, e.g. vX or vX.X will not work. + - name: Action | Semantic Version Release + id: release + # Adjust tag with desired version if applicable. uses: python-semantic-release/python-semantic-release@v9.8.9 with: github_token: ${{ secrets.GITHUB_TOKEN }} + git_committer_name: "github-actions" + git_committer_email: "actions@users.noreply.github.com" -``concurrency`` is a `beta feature of GitHub Actions`_ which disallows two or more -release jobs to run in parallel. This prevents race conditions if there are multiple -pushes in a short period of time. + - name: Publish | Upload package to PyPI + uses: pypa/gh-action-pypi-publish@v1 + if: steps.release.outputs.released == 'true' -If you would like to use Python Semantic Release to create GitHub Releases against -your repository, you will need to allow the additional ``contents: write`` permission. -More information can be found in the `permissions for GitHub Apps documentation`_ + - name: Publish | Upload to GitHub Release Assets + uses: python-semantic-release/publish-action@v9.8.9 + if: steps.release.outputs.released == 'true' + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ steps.release.outputs.tag }} -.. _beta feature of GitHub Actions: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency -.. _permissions for GitHub Apps documentation: https://docs.github.com/en/rest/overview/permissions-required-for-github-apps?apiVersion=2022-11-28#contents +.. important:: + The `concurrency`_ directive is used on the job to prevent race conditions of more than + one release job in the case if there are multiple pushes to ``main`` in a short period + of time. .. warning:: - You must set ``fetch-depth`` to 0 when using ``actions/checkout@v2``, since - Python Semantic Release needs access to the full history to determine whether - a release should be made. + You must set ``fetch-depth`` to 0 when using ``actions/checkout@v4``, since + Python Semantic Release needs access to the full history to build a changelog + and at least the latest tags to determine the next version. .. warning:: The ``GITHUB_TOKEN`` secret is automatically configured by GitHub, with the - same permissions as the user who triggered the workflow run. This causes - a problem if your default branch is protected. + same permissions role as the user who triggered the workflow run. This causes + a problem if your default branch is protected to specific users. You can work around this by storing an administrator's Personal Access Token as a separate secret and using that instead of ``GITHUB_TOKEN``. In this case, you will also need to pass the new token to ``actions/checkout`` (as the ``token`` input) in order to gain push access. -Multiple Projects ------------------ +.. _concurrency: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency + +Version Overrides Example +------------------------- + +In the case where you want to provide multiple command line options to the +:ref:`version ` command, you provide them through the ``with`` +directive in the workflow file. In this example, we want to force a patch +version bump, not produce a changelog, and provide specialized build +metadata. As a regular CLI command, this would look like: + +.. code:: shell + + semantic-release version --patch --no-changelog --build-metadata abc123 + +The equivalent GitHub Action configuration would be: + +.. code:: yaml + + # snippet + + - name: Action | Semantic Version Release + # Adjust tag with desired version if applicable. + uses: python-semantic-release/python-semantic-release@v9.8.9 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + force: patch + changelog: false + build_metadata: abc123 + +.. _gh_actions-monorepo: + +Actions with Monorepos +'''''''''''''''''''''' + +While ``python-semantic-release`` does **NOT** have full monorepo support, if you +have multiple projects stored within a single repository (or your project is +not at the root of the repository), you can pass the +:ref:`directory ` input to the action to change +directory before semantic-release execution. + +For multiple packages, you would need to run the action multiple times, to release +each project. The following example demonstrates how to release two projects in +a monorepo. -If you have multiple projects stored within a single repository (or your -project is not at the root of the repository), you can pass the -``directory`` input. The step can be called multiple times to release -multiple projects. +The ``directory`` input directive is also available for the Python Semantic Release +Publish Action. .. code:: yaml diff --git a/docs/automatic-releases/index.rst b/docs/automatic-releases/index.rst index f0147e337..8fd44b1bf 100644 --- a/docs/automatic-releases/index.rst +++ b/docs/automatic-releases/index.rst @@ -16,11 +16,3 @@ Guides travis github-actions cronjobs - -.. _automatic-github: - -Configuring push to Github -^^^^^^^^^^^^^^^^^^^^^^^^^^ -In order to push to Github and post the changelog to Github the environment variable -:ref:`GH_TOKEN ` has to be set. It needs access to the -``public_repo`` scope for public repositories and ``repo`` for private repositories. diff --git a/docs/configuration.rst b/docs/configuration.rst index ee63a99b0..2a8075173 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -575,7 +575,7 @@ Author used in commits in the format ``name ``. ``git_committer_name`` and ``git_committer_email`` inputs. .. seealso:: - - :ref:`github-actions` + - :ref:`gh_actions` **Default:** ``semantic-release `` diff --git a/docs/github-action.rst b/docs/github-action.rst deleted file mode 100644 index 24a539f77..000000000 --- a/docs/github-action.rst +++ /dev/null @@ -1,139 +0,0 @@ -.. _github-action: - -Python Semantic Release GitHub Action -===================================== - -Python Semantic Release is also available as a GitHub action. - -In order to use Python Semantic Release to create GitHub Releases against -your repository, you will need to allow the following permissions for the -token generated by GitHub for each job: - -* id-token: write -* contents: write - -This can be done using the `permissions`_ block in your workflow definition. - -.. _permissions: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idpermissions - -Configuring the action can be done in your workflow's YAML definition. -The action provides the following inputs: - -Tokens ------- -.. _action-github-token: - -``github_token`` -"""""""""""""""" - -The GitHub token used to push release notes and new commits/tags. - -required: false - -Custom Users ------------- - -.. _action-git-committer-name: - -``git_committer_name`` -"""""""""""""""""""""" - -The name of the account used to commit. If customized, it must be associated with the provided token. - -default: ``github-actions`` - -required: false - -.. _action-git-committer-email: - -``git_committer_email`` -""""""""""""""""""""""" - -The email of the account used to commit. If customized, it must be associated with the provided token. - -default: ``actions@github.com>`` - -required: false - -.. _action-ssh-public-signing-key: - -``ssh_public_signing_key`` -"""""""""""""""""""""""""" - -The public key used to verify a commit. If customized, it must be associated with the same account as the provided token. - -required: false - -.. _action-ssh-private-signing-key: - -``ssh_private_signing_key`` -""""""""""""""""""""""""""" - -The private key used to verify a commit. If customized, it must be associated with the same account as the provided token. - -required: false - -Additional Options ------------------- - -.. _action-directory: - -``directory`` -""""""""""""" - -Sub-directory to cd into before running semantic-release - -required: false - -.. _action-root-options: - -``root_options`` -"""""""""""""""""""""" - -Additional options for the main ``semantic-release`` command. Example: ``-vv --noop`` - -required: false - -default: ``-v`` - -Command Line Options --------------------- - -Other inputs which supply additional command-line options to the -:ref:`version ` command can be optionally supplied, and have the same -defaults as their corresponding command line option. - -In general, the input for an action corresponding to a command line option has the same -name, with dashes (``-``) replaced by underscores. - -The command line arguments ``--prerelease``, ``--patch``, ``--minor`` and ``--major`` -are mutually exclusive, and are supplied via the ``force`` input. - -Flags, which require either ``--