Skip to content

Commit

Permalink
ci: Added a workflow and necessary support scripts/templates to enabl…
Browse files Browse the repository at this point in the history
…e automated released via GitHub's workflow_dispatch trigger. (#29)
  • Loading branch information
nfelt14 authored Oct 18, 2023
1 parent 731f64e commit 43aa004
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 19 deletions.
140 changes: 140 additions & 0 deletions .github/workflows/package-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
---
name: Create package release and publish binaries to pypi.org
on:
workflow_dispatch:
inputs:
release_level:
type: choice
required: true
description: |
Select the release level,
patch for backward compatible minor changes and bug fixes,
minor for backward compatible larger changes,
major for non-backward compatible changes.
options: [patch, minor, major]
concurrency:
group: pypi
jobs:
pypi-version:
name: Update package version
if: github.repository == 'tektronix/tm_devices' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: package-release-gate
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: x
check-latest: true
- name: Check for unreleased entries in the Changelog
run: python scripts/check_unreleased_changelog_items.py
- name: Copy Changelog to template directory
run: cp CHANGELOG.md python_semantic_release_templates/temp_CHANGELOG_copy.md
- name: Python Semantic Release
id: release
uses: python-semantic-release/[email protected]
with:
root_options: --verbose --strict
force: ${{ inputs.release_level }}
github_token: ${{ secrets.GITHUB_TOKEN }}
outputs:
built-version: ${{ steps.release.outputs.version }}
pypi-build:
name: Build package
needs: [pypi-version]
if: github.repository == 'tektronix/tm_devices' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Build package
uses: hynek/[email protected]
upload-testpypi:
name: Upload package to TestPyPI
needs: [pypi-build]
if: github.repository == 'tektronix/tm_devices' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: package-testpypi
permissions:
id-token: write
steps:
- name: Download built packages
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
- name: Upload package to Test PyPI
uses: pypa/[email protected]
with:
repository-url: https://test.pypi.org/legacy/
upload-pypi:
name: Upload package to PyPI
needs: [upload-testpypi]
if: github.repository == 'tektronix/tm_devices' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: package-release
permissions:
id-token: write
steps:
- name: Download built packages
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
- name: Upload package to PyPI
uses: pypa/[email protected]
upload-github:
name: Upload package to GitHub Release
needs: [upload-pypi]
if: github.repository == 'tektronix/tm_devices' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- name: Download built packages
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
- name: Publish package distributions to GitHub Releases
uses: python-semantic-release/upload-to-gh-release@main
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
pypi-install:
name: Install package
needs:
- pypi-version
- pypi-build
- upload-testpypi
- upload-pypi
- upload-github
if: github.repository == 'tektronix/tm_devices' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
index_urls:
- ''
- ' --index-url=https://test.pypi.org/simple/ --extra-index-url=https://pypi.org/simple'
steps:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: x
check-latest: true
- name: Test installing package
# A retry is used to allow for some downtime before the package is installable
uses: nick-fields/retry@v2
with:
timeout_minutes: 2
max_attempts: 5
retry_wait_seconds: 30
warning_on_retry: false
command: pip install${{ matrix.index_urls }} tm_devices==${{ needs.pypi-version.outputs.built-version }}
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ci:
- pyroma
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.5.0
hooks:
- id: check-yaml
- id: check-toml
Expand Down Expand Up @@ -41,7 +41,7 @@ repos:
- id: check-github-actions
- id: check-github-workflows
- repo: https://github.com/commitizen-tools/commitizen
rev: 3.10.0
rev: 3.11.0
hooks:
- id: commitizen
stages: [commit-msg]
Expand Down Expand Up @@ -122,12 +122,12 @@ repos:
always_run: true
args: [., --min=10]
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.292
rev: v0.1.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- repo: https://github.com/psf/black
rev: 23.9.1
rev: 23.10.0
hooks:
- id: black
- repo: https://github.com/PyCQA/docformatter
Expand Down
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@
The format is based on [Keep a Changelog](https://keepachangelog.com), and this
project adheres to [Semantic Versioning](https://semver.org).

<!--next-version-placeholder-->
Valid subsections within a version are:

- Added
- Changed
- Deprecated
- Removed
- Fixed
- Security

______________________________________________________________________

Expand All @@ -15,7 +22,7 @@ ______________________________________________________________________

______________________________________________________________________

## v0.1.14 (2023-10-03)
## v0.1.14 (2023-10-05)

### Added

Expand Down
16 changes: 8 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,10 @@ recommended IDE for package development is
```
```console
# Linux
source .env/bin/activate
source .venv/bin/activate

# Windows
.env\Scripts\activate.bat
.venv\Scripts\activate.bat
```
```console
python -m pip install -U pip poetry
Expand All @@ -97,7 +97,7 @@ recommended IDE for package development is
git checkout -b name-of-your-bugfix-or-feature
```

4. Update the [CHANGELOG](CHANGELOG.md) using the proper format.
4. Update the **Unreleased** section in the [CHANGELOG](CHANGELOG.md) using the proper format.

5. When you're done making changes, check that your changes conform to any code
formatting requirements and pass any tests.
Expand All @@ -106,10 +106,10 @@ recommended IDE for package development is
Always remember to activate the virtual environment before attempting to run tests or other code.
```console
# Linux
source .env/bin/activate
source .venv/bin/activate
# Windows
.env\Scripts\activate.bat
.venv\Scripts\activate.bat
```
````

Expand Down Expand Up @@ -171,10 +171,10 @@ commands:
Always remember to activate the virtual environment before attempting to run tests or other code.
```console
# Linux
source .env/bin/activate
source .venv/bin/activate
# Windows
.env\Scripts\activate.bat
.venv\Scripts\activate.bat
```
````
Expand All @@ -194,7 +194,7 @@ Before you submit a pull request, check that it meets these guidelines:
2. If the pull request adds functionality, the docs should be updated.
3. The pull request should work for all currently supported operating systems
and versions of Python.
4. The [Changelog](CHANGELOG.md) should be updated.
4. The **Unreleased** section in the [Changelog](CHANGELOG.md) should be updated.
## Project Test Plan
Expand Down
8 changes: 4 additions & 4 deletions docs/troubleshooting/contributions.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,10 @@ then retry the command.

```console
# Linux
source .env/bin/activate
source .venv/bin/activate

# Windows
.env\Scripts\activate.bat
.venv\Scripts\activate.bat

# Update installed dependencies
python -m poetry update
Expand Down Expand Up @@ -326,10 +326,10 @@ file when running pytest.

```console
# Linux
source .env/bin/activate
source .venv/bin/activate
pytest -k "test_docs" --self-contained-html --html=$(pwd)/.results_doctests/results.html

# Windows
.env\Scripts\activate.bat
.venv\Scripts\activate.bat
pytest -k "test_docs" --self-contained-html --html=%CD%\.results_doctests\results.html
```
9 changes: 8 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ pytest-cov = ">=3.0.0"
pytest-html = ">=4.0"
pytest-order = ">=1.0.1"
pytest-profiling = ">=1.7.0"
python-semantic-release = ">=7.31.2"
python-semantic-release = ">=8.0"
ruff = ">=0.0.292"
safety = ">=2.1.1"
sphinx-autoapi = ">=2.0.0"
Expand Down Expand Up @@ -370,6 +370,13 @@ version_toml = [
"pyproject.toml:tool.poetry.version"
]

[tool.semantic_release.changelog]
exclude_commit_patterns = []
template_dir = "python_semantic_release_templates"

[tool.semantic_release.changelog.environment]
extensions = ["jinja2.ext.do"]

[tool.semantic_release.commit_parser_options]
# These settings allow python-semantic-release to be used without triggering on any commits
allowed_tags = []
Expand Down
23 changes: 23 additions & 0 deletions python_semantic_release_templates/CHANGELOG.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{%- set latest_release_dict_entry = (context.history.released.items()|list)[0] %}
{%- set latest_version_number = latest_release_dict_entry[0].as_tag() %}
{%- set latest_version_date = latest_release_dict_entry[1].tagged_date.strftime("%Y-%m-%d") %}
{%- set recently_merged_prs = {} %}
{%- for type_, commits in latest_release_dict_entry[1]["elements"] | dictsort %}
{%- for commit in commits %}
{%- set pr_num = commit.commit.message.rstrip().rsplit("(#", 1)[-1].rsplit(")", 1)[0]|int %}
{%- if pr_num %}
{%- do recently_merged_prs.update({commit.commit.message.split("\n")[0].rsplit("(#", 1)[0]: pr_num}) %}
{%- endif %}
{%- endfor %}
{%- endfor %}

{%- set merged_prs_text_list = ["\n\n### Merged Pull Requests\n\n"] %}
{%- for message, number in recently_merged_prs.items() %}
{%- do merged_prs_text_list.append("- " ~ message ~ "([#" + number|string ~ "](" ~ number|string|pull_request_url ~ "))\n") %}
{%- endfor %}
{%- set merged_prs_text = (merged_prs_text_list|join).rstrip() %}


{%- filter replace("## Unreleased", "## Unreleased\n\n______________________________________________________________________\n\n## " + latest_version_number + " (" + latest_version_date + ")" + merged_prs_text) %}
{%- include "temp_CHANGELOG_copy.md" %}
{% endfilter %}
51 changes: 51 additions & 0 deletions scripts/check_unreleased_changelog_items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""This script will check for unreleased entries in the CHANGELOG.md file.
It will exit with a non-zero exit code if there are no unreleased entries.
"""
import pathlib
import re

CHANGELOG_FILENAME = "CHANGELOG.md"


def main() -> None:
"""Check for entries in the Unreleased section of the CHANGELOG.md file.
Raises:
SystemExit: Indicates no new entries were found.
"""
found_entries = False
with open(
pathlib.Path(__file__).parent.parent / CHANGELOG_FILENAME, encoding="utf-8"
) as changelog_file:
tracking_unreleased = False
tracking_entries = False
for line in changelog_file:
if line.startswith("___"):
tracking_unreleased = False
tracking_entries = False
if line.startswith("## Unreleased"):
tracking_unreleased = True
if tracking_unreleased and line.startswith(
(
"### Added\n",
"### Changed\n",
"### Deprecated\n",
"### Removed\n",
"### Fixed\n",
"### Security\n",
)
):
tracking_entries = True
if tracking_entries:
found_entries = bool(re.match(r"^- \w+", line))
if found_entries:
break

if not found_entries:
msg = f"No unreleased entries were found in {CHANGELOG_FILENAME}."
raise SystemExit(msg)


if __name__ == "__main__":
main()

0 comments on commit 43aa004

Please sign in to comment.