Compare commits

...

41 Commits

Author SHA1 Message Date
umarcor
d8264eab8a v0.4.0 2022-01-09 20:58:08 +01:00
Patrick Lehmann
b9d3839abb Extend Unit Tests to multiple systems (ubuntu, windows, msys2 and macos) 2022-01-09 20:56:58 +01:00
umarcor
997d548e60 Parameters: update py36 warning, add py311 notice 2022-01-09 20:39:36 +01:00
umarcor
83cd572694 UnitTesting: update description of input 'jobs' 2022-01-09 20:39:12 +01:00
Unai Martinez-Corral
68a446b9b6 UnitTesting: workarounds for MSYS2-MinGW64 (pacman) (#35) 2022-01-09 20:19:56 +01:00
Patrick Lehmann
43f0b79e88 UnitTesting/msys2: install system packages through 'pacboy'; refactor 2022-01-09 20:05:51 +01:00
umarcor
b3d8a9c5ec UnitTesting: refactor 2022-01-09 20:02:09 +01:00
umarcor
edb6ca364e UnitTesting/msys2: install system packages through 'pacboy' 2022-01-09 20:02:09 +01:00
Patrick Lehmann
e00f5cf53d Added MSYS2-MinGW64 specific code. 2022-01-09 20:00:36 +01:00
Unai Martinez-Corral
0da8c5a5c5 UnitTesting: install wheel; support py3.11; fix scripting for Windows (PowerShell commands) (#34) 2022-01-09 19:34:23 +01:00
Patrick Lehmann
c9bee6fe65 Require wheel to be installed before installing requirements. 2022-01-09 18:44:06 +01:00
Patrick Lehmann
94bb01d586 Renamed Python 3.11 version. 2022-01-09 18:25:02 +01:00
Patrick Lehmann
0fdef33cb4 Improved PoSh code. 2022-01-09 18:10:42 +01:00
Patrick Lehmann
e1f7599d79 Added Python 3.11 (currently RC). 2022-01-09 18:07:29 +01:00
Patrick Lehmann
dad5e71bfe Added PowerShell code. 2022-01-09 17:39:16 +01:00
umarcor
60d77c2292 Parameters: support system 'msys2' (MINGW64); update UnitTesting accordingly 2022-01-07 01:40:42 +01:00
umarcor
3f489f0bed Parameters: add option 'system_list'; UnitTesting now requires field 'system' in the matrix 2022-01-07 00:39:14 +01:00
umarcor
26afa43fa4 Parameters: remove 3.6 from default python_version_list 2022-01-07 00:37:22 +01:00
Patrick Lehmann
c8003f1a0e Added PyCharm project files. 2021-12-26 11:11:30 +01:00
Unai Martinez-Corral
6413469cdf v0.3.0 2021-12-26 01:40:09 +01:00
Unai Martinez-Corral
8dbacda32c Pytest using pyproject.toml (#29)
# New Features
* Allow configuration of the unit test directory (default: `tests/unit`).
* Allow configuration of a `pyproject.toml` or `.coveragerc` file.
* Extract values from `pyproject.toml` or `.coveragerc`.

# Changes
* Jobs deriving from template job `CoverageCollection` need to specify their `.coveragerc` content and `pytest.ini` content in a `pyproject.toml` file or provide the job parameter `coverage_config` with the path to the `.coveragerc` file.

# Bug Fixes
*None*

-------------
* Closes #2.
2021-12-25 23:40:50 +01:00
Patrick Lehmann
78b225195f Updated README according to latest changes. 2021-12-24 21:05:04 +01:00
umarcor
1fbeef36d6 CoverageCollection: skip config file if empty 2021-12-24 16:37:45 +01:00
umarcor
9846c9e60c CoverageCollection: use 'pyproject.toml' by default 2021-12-24 16:31:47 +01:00
umarcor
b8564eb389 CoverageCollection: xmlFile defaults to './coverage.xml' 2021-12-24 16:24:55 +01:00
umarcor
62cd2d1d0f CoverageCollection: htmlDirectory defaults to 'htmlcov' 2021-12-24 16:22:14 +01:00
umarcor
9bd8004dfb CoverageCollection: pass output directory to coverage html 2021-12-24 15:59:23 +01:00
umarcor
925b44a8a8 CoverageCollection: fix variable name 2021-12-24 15:54:09 +01:00
Patrick Lehmann
9d8c1ecc05 Merge branch 'dev' into toml 2021-12-24 14:15:28 +01:00
umarcor
66c7b4b619 ExamplePipeline/ArtifactCleanup: needs PublishTestResults instead of UnitTesting 2021-12-24 14:11:37 +01:00
Patrick Lehmann
b399aa8f93 Parameters: mark Python 3.6 black, update others, warn about unsupported versions 2021-12-24 14:00:34 +01:00
umarcor
dcd0a4b617 Parameters: mark Python 3.6 black, update others, warn about unsupported versions 2021-12-24 13:52:30 +01:00
Patrick Lehmann
d7c765ba79 Fixed how to access complex nested key-value pairs. 2021-12-24 13:04:31 +01:00
Patrick Lehmann
fa10ed076c Install dependency tomli before script execution. 2021-12-24 12:56:03 +01:00
Patrick Lehmann
9dfafd588e Changed scripting from bash to Python. Also use .coveragerc as fallback. 2021-12-24 12:55:59 +01:00
Patrick Lehmann
09f7504de4 Fixed path to project root. 2021-12-24 12:55:56 +01:00
Patrick Lehmann
6ad23eabf5 Extract information from TOML file. 2021-12-24 12:55:50 +01:00
Patrick Lehmann
bb855d572d Pytest using pyproject.toml. 2021-12-24 12:54:14 +01:00
umarcor
250cceb80d releaser: update README.md and DEVELOPMENT.md 2021-12-21 02:05:21 +01:00
umarcor
615aafc0b4 releaser: add DEVELOPMENT.md 2021-12-21 02:05:21 +01:00
umarcor
1f3d12ef95 README: add 'Container Step' to Context; add References 2021-12-21 01:39:02 +01:00
10 changed files with 214 additions and 41 deletions

View File

@@ -35,6 +35,16 @@ on:
required: false
default: '-r tests/requirements.txt'
type: string
unittest_directory:
description: 'Path to the directory containing unit tests.'
required: false
default: 'tests/unit'
type: string
coverage_config:
description: 'Path to the .coveragerc file. Use pyproject.toml by default.'
required: false
default: 'pyproject.toml'
type: string
artifact:
description: 'Name of the coverage artifact.'
required: true
@@ -62,27 +72,68 @@ jobs:
- name: 🗂 Install dependencies
run: |
python -m pip install -U pip
python -m pip install tomli
python -m pip install ${{ inputs.requirements }}
- name: 🔁 Extract configurations from pyproject.toml
id: getVariables
shell: python
run: |
from pathlib import Path
from tomli import load as tomli_load
htmlDirectory = 'htmlcov'
xmlFile = './coverage.xml'
coverageRC = "${{ inputs.coverage_config }}".strip()
# Read output paths from 'pyproject.toml' file
if coverageRC == "pyproject.toml":
pyProjectFile = Path("pyproject.toml")
if pyProjectFile.exists():
with pyProjectFile.open("rb") as file:
pyProjectSettings = tomli_load(file)
htmlDirectory = pyProjectSettings["tool"]["coverage"]["html"]["directory"]
xmlFile = pyProjectSettings["tool"]["coverage"]["xml"]["output"]
else:
print(f"File '{pyProjectFile}' not found and no ' .coveragerc' file specified.")
# Read output paths from '.coveragerc' file
elif len(coverageRC) > 0:
coverageRCFile = Path(coverageRC)
if coverageRCFile.exists():
with coverageRCFile.open("rb") as file:
coverageRCSettings = tomli_load(file)
htmlDirectory = coverageRCSettings["html"]["directory"]
xmlFile = coverageRCSettings["xml"]["output"]
else:
print(f"File '{coverageRCFile}' not found.")
print(f"::set-output name=coverage_report_html_directory::{htmlDirectory}")
print(f"::set-output name=coverage_report_xml::{xmlFile}")
print(f"DEBUG:\n html={htmlDirectory}\n xml={xmlFile}")
- name: Collect coverage
continue-on-error: true
run: |
python -m pytest -rA --cov=.. --cov-config=tests/.coveragerc tests/unit --color=yes
[ 'x${{ inputs.coverage_config }}' != 'x' ] && PYCOV_ARGS='--cov-config=${{ inputs.coverage_config }}' || unset PYCOV_ARGS
python -m pytest -rA --cov=. $PYCOV_ARGS ${{ inputs.unittest_directory }} --color=yes
- name: Convert to cobertura format
run: coverage xml
- name: Convert to HTML format
run: |
coverage html
rm htmlcov/.gitignore
coverage html -d ${{ steps.getVariables.outputs.coverage_report_html_directory }}
rm ${{ steps.getVariables.outputs.coverage_report_html_directory }}/.gitignore
- name: 📤 Upload 'Coverage Report' artifact
continue-on-error: true
uses: actions/upload-artifact@v2
with:
name: ${{ inputs.artifact }}
path: htmlcov
path: ${{ steps.getVariables.outputs.coverage_report_html_directory }}
if-no-files-found: error
retention-days: 1
@@ -90,7 +141,7 @@ jobs:
continue-on-error: true
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
file: ${{ steps.getVariables.outputs.coverage_report_xml }}
flags: unittests
env_vars: PYTHON
@@ -99,4 +150,4 @@ jobs:
uses: codacy/codacy-coverage-reporter-action@master
with:
project-token: ${{ secrets.codacy_token }}
coverage-reports: ./coverage.xml
coverage-reports: ${{ steps.getVariables.outputs.coverage_report_xml }}

View File

@@ -33,7 +33,12 @@ on:
python_version_list:
description: 'Space separated list of Python versions to run tests with.'
required: false
default: '3.6 3.7 3.8 3.9 3.10'
default: '3.7 3.8 3.9 3.10'
type: string
system_list:
description: 'Space separated list of systems to run tests on.'
required: false
default: 'ubuntu windows msys2 macos'
type: string
name:
description: 'Name of the tool.'
@@ -75,16 +80,39 @@ jobs:
print("Parameters:")
print(params)
systems = '${{ inputs.system_list }}'.split(' ')
versions = '${{ inputs.python_version_list }}'.split(' ')
if '3.6' in versions:
print("::warning title=Deprecated::Support for Python 3.6 ended in 2021.12.23.")
if '3.11' in versions:
print(f"::notice title=Experimental::Python 3.11 (3.11.0-alpha3) is a pre-release.")
data = {
'3.6': { 'icon': '🔴', 'until': '23.12.2021' },
'3.7': { 'icon': '🟠', 'until': '27.06.2023' },
'3.8': { 'icon': '🟡', 'until': 'Oct. 2024' },
'3.9': { 'icon': '🟢', 'until': 'Oct. 2025' },
'3.10': { 'icon': '🟢', 'until': 'Oct. 2026' },
'python': {
'3.6': { 'icon': '', 'until': '2021.12.23' },
'3.7': { 'icon': '🔴', 'until': '2023.06.27' },
'3.8': { 'icon': '🟠', 'until': '2024.10' },
'3.9': { 'icon': '🟡', 'until': '2025.10' },
'3.10': { 'icon': '🟢', 'until': '2026.10' },
'3.11': { 'icon': '🟣', 'until': '2027.10' },
},
'sys': {
'ubuntu': { 'icon': '🐧', 'runs-on': 'ubuntu-latest', 'shell': 'bash' },
'windows': { 'icon': '🧊', 'runs-on': 'windows-latest', 'shell': 'pwsh' },
'msys2': { 'icon': '🟦', 'runs-on': 'windows-latest', 'shell': 'msys2 {0}' },
'macos': { 'icon': '🍎', 'runs-on': 'macos-latest', 'shell': 'bash' }
}
}
jobs = [
{'python': version, 'icon': data[version]['icon']}
for version in '${{ inputs.python_version_list }}'.split(' ')
{
'sysicon': data['sys'][system]['icon'],
'system': system,
'runs-on': data['sys'][system]['runs-on'],
'shell': data['sys'][system]['shell'],
'pyicon': data['python'][version]['icon'],
'python': '3.11.0-alpha.3' if version == '3.11' else version
}
for system in systems
for version in (versions if system != 'msys2' else ['3.9'])
]
print(f'::set-output name=python_jobs::{jobs!s}')
print("Python jobs:")

View File

@@ -26,7 +26,7 @@ on:
workflow_call:
inputs:
jobs:
description: 'JSON list with field <python>, telling the versions to run tests with.'
description: 'JSON list with environment fields, telling the system and Python versions to run tests with.'
required: true
type: string
requirements:
@@ -34,6 +34,11 @@ on:
required: false
default: '-r tests/requirements.txt'
type: string
unittest_directory:
description: 'Path to the directory containing unit tests.'
required: false
default: 'tests/unit'
type: string
artifact:
description: "Generate unit test report with junitxml and upload results as an artifact."
required: false
@@ -43,38 +48,68 @@ on:
jobs:
UnitTesting:
name: ${{ matrix.icon }} Unit Tests using Python ${{ matrix.python }}
runs-on: ubuntu-latest
name: ${{ matrix.sysicon }} ${{ matrix.pyicon }} Unit Tests using Python ${{ matrix.python }}
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
include: ${{ fromJson(inputs.jobs) }}
defaults:
run:
shell: ${{ matrix.shell }}
steps:
- name: ⏬ Checkout repository
uses: actions/checkout@v2
- name: '🟦 Setup MSYS2'
if: matrix.system == 'msys2'
uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
update: true
pacboy: >-
python-pip:p
python-wheel:p
python-coverage:p
python-lxml:p
- name: 🐍 Setup Python ${{ matrix.python }}
if: matrix.system != 'msys2'
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: 🔧 Install dependencies
- name: ⚙️ Update pip
run: python -m pip install -U pip
- name: ⚙️ Install wheel
if: matrix.system != 'msys2'
run: |
python -m pip install -U pip
python -m pip install ${{ inputs.requirements }}
python -m pip install -U wheel
- name: 🔧 Install dependencies
run: python -m pip install ${{ inputs.requirements }}
- name: ☑ Run unit tests
if: matrix.system == 'windows'
run: |
$PYTEST_ARGS = if ("${{ inputs.artifact }}".length -gt 0) { "--junitxml=TestReport.xml" } else { "" }
python -m pytest -rA ${{ inputs.unittest_directory }} $PYTEST_ARGS --color=yes
- name: ☑ Run unit tests
if: matrix.system != 'windows'
run: |
[ 'x${{ inputs.artifact }}' != 'x' ] && PYTEST_ARGS='--junitxml=TestReport.xml' || unset PYTEST_ARGS
python -m pytest -rA tests/unit $PYTEST_ARGS --color=yes
python -m pytest -rA ${{ inputs.unittest_directory }} $PYTEST_ARGS --color=yes
- name: 📤 Upload 'TestReport.xml' artifact
if: inputs.artifact != ''
uses: actions/upload-artifact@v2
with:
name: ${{ inputs.artifact }}-${{ matrix.python }}
name: ${{ inputs.artifact }}-${{ matrix.system }}-${{ matrix.python }}
path: TestReport.xml
if-no-files-found: error
retention-days: 1

8
.idea/Actions.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Actions.iml" filepath="$PROJECT_DIR$/.idea/Actions.iml" />
</modules>
</component>
</project>

View File

@@ -34,6 +34,7 @@ jobs:
with:
name: ToolName
# Optional
system_list: 'ubuntu windows msys2 macos'
python_version: '3.10'
python_version_list: '3.8 3.9 3.10'
@@ -148,7 +149,7 @@ jobs:
uses: pyTooling/Actions/.github/workflows/ArtifactCleanUp.yml@main
needs:
- Params
- UnitTesting
- PublishTestResults
- Coverage
- StaticTypeCheck
- BuildTheDocs

View File

@@ -7,24 +7,30 @@ language for writing reusable CI code.
However, Python being equally popular and capable, usage of JS/TS might be bypassed, with some caveats.
This repository gathers reusable CI tooling for testing, packaging and distributing Python projects and documentation.
## Context
GitHub Actions supports four types of reusable code:
GitHub Actions supports five procedures to reuse code:
- JavaScript Action.
- JavaScript Action:
- [docs.github.com: actions/creating-actions/creating-a-javascript-action](https://docs.github.com/en/actions/creating-actions/creating-a-javascript-action)
- Container Action.
- Container Action:
- [docs.github.com: actions/creating-actions/creating-a-docker-container-action](https://docs.github.com/en/actions/creating-actions/creating-a-docker-container-action)
- Composite Action.
- Container Step:
- [docs.github.com: actions/learn-github-actions/workflow-syntax-for-github-actions#example-using-a-docker-public-registry-action](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#example-using-a-docker-public-registry-action)
- [docs.github.com: actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswithargs](https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswithargs)
- Composite Action:
- [docs.github.com: actions/creating-actions/creating-a-composite-action](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action)
- [github.blog/changelog: 2020-08-07-github-actions-composite-run-steps](https://github.blog/changelog/2020-08-07-github-actions-composite-run-steps/)
- [github.blog/changelog: 2021-08-25-github-actions-reduce-duplication-with-action-compositio](https://github.blog/changelog/2021-08-25-github-actions-reduce-duplication-with-action-composition/)
- Reusable Workflows.
- Reusable Workflow:
- [docs.github.com: actions/learn-github-actions/reusing-workflows](https://docs.github.com/en/actions/learn-github-actions/reusing-workflows)
- [github.blog/changelog: 2021-10-05-github-actions-dry-your-github-actions-configuration-by-reusing-workflows](https://github.blog/changelog/2021-10-05-github-actions-dry-your-github-actions-configuration-by-reusing-workflows/)
Leaving JavaScript and Container Actions aside, the main differences between Composite Actions and Reusable Workflows
are the following:
Container Actions and Container Steps are almost equivalent: Actions use a configuration file (`action.yml`), while
Steps do not.
Leaving JavaScript and Container Actions and Steps aside, the main differences between Composite Actions and Reusable
Workflows are the following:
- Composite Actions can be executed from a remote/external path or from the checked out branch, and from any location.
However, Reusable Workflows can only be used through a remote/external path (`{owner}/{repo}/{path}/{filename}@{ref}`),
@@ -74,6 +80,7 @@ It allows using the `post` feature with scripts written in bash, python or any o
the environment.
See: [actions/runner#1478](https://github.com/actions/runner/issues/1478).
## Reusable workflows
This repository provides 10+ Reusable Workflows based on the CI pipelines of the repos in this organisation,
@@ -88,13 +95,16 @@ As shown in the screenshot above, the expected order is:
- Global:
- [Parameters](.github/workflows/Parameters.yml): a workaround for the limitations to handle global variables in
GitHub Actions workflows (see [actions/runner#480](https://github.com/actions/runner/issues/480)).
It generates outputs with artifact names and job matrices to be used in other jobs.
It generates outputs with artifact names and job matrices to be used in later running jobs.
- Code testing/analysis:
- [UnitTesting](.github/workflows/UnitTesting.yml): run unit test with `pytest` using multiple versions of Python, and
optionally upload results as XML reports.
- [CoverageCollection](.github/workflows/CoverageCollection.yml): collect coverage data with `pytest` using a single
version of Python, generate HTML and Cobertura (XML) reports, upload the HTML report as an artifact, and upload the
results to Codecov and Codacy.
optionally upload results as XML reports. Configuration options to `pytest` should be given via section
`[tool.pytest.ini_options]` in a `pyproject.toml` file.
- [CoverageCollection](.github/workflows/CoverageCollection.yml): collect code coverage data (incl. branch coverage)
with `pytest`/`pytest-cov`/`coverage.py` using a single version of Python (latest). It generates HTML and Cobertura
(XML)reports, upload the HTML report as an artifact, and upload the test results to Codecov and Codacy. Configuration
options to `pytest` and `coverage.py` should be given via section `[tool.pytest.ini_options]` and `[tool.coverage.*]`
in a `pyproject.toml` file.
- [StaticTypeCheck](.github/workflows/StaticTypeCheck.yml): collect static type check result with `mypy`, and
optionally upload results as an HTML report.
Example `commands`:
@@ -121,7 +131,7 @@ As shown in the screenshot above, the expected order is:
mypy --html-report ../htmlmypy -p ToolName
```
- [VerifyDocs](.github/workflows/VerifyDocs.yml): extract code examples from the README and test.
- [VerifyDocs](.github/workflows/VerifyDocs.yml): extract code examples from the README and test these code snippets.
- Packaging and releasing:
- [Release](.github/workflows/Release.yml): publish GitHub Release.
- [Package](.github/workflows/Package.yml): generate source and wheel packages, and upload them as an artifact.
@@ -150,19 +160,23 @@ Find further usage cases in the following list of projects:
- [VHDL/pyVHDLModel](https://github.com/VHDL/pyVHDLModel/tree/main/.github/workflows)
## References
- [hdl/containers#48](https://github.com/hdl/containers/issues/48)
## Contributors
* [Patrick Lehmann](https://GitHub.com/Paebbels)
* [Unai Martinez-Corral](https://GitHub.com/umarcor) (Maintainer)
* [and more...](https://GitHub.com/pyTooling/Actions/graphs/contributors)
* [and more...](https://GitHub.com/pyTooling/Actions/graphs/contributors)
## License
This Python package (source code) licensed under [Apache License 2.0](LICENSE.md).
This Python package (source code) licensed under [Apache License 2.0](LICENSE.md).
The accompanying documentation is licensed under [Creative Commons - Attribution 4.0 (CC-BY 4.0)](doc/Doc-License.rst).
---
-------------------------
SPDX-License-Identifier: Apache-2.0
SPDX-License-Identifier: Apache-2.0

8
releaser/DEVELOPMENT.md Normal file
View File

@@ -0,0 +1,8 @@
# Releaser Development
- [pyTooling/pyAttributes](https://github.com/pyTooling/pyAttributes) or
[willmcgugan/rich](https://github.com/willmcgugan/rich) might be used to enhance the UX.
- It might be desirable to have pyTooling.Version.SemVersion handle the regular expression from
[semver.org](https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string), and use
proper Python classes in **Releaser**.

View File

@@ -48,6 +48,20 @@ as assets.
In this context, one of the main use cases of **Releaser** is pushing artifacts as release assets.
Thus, the name of the Action.
GitHub provides an official CLI tool, written in golang: [cli/cli](https://github.com/cli/cli).
When the Python version of **Releaser** was written, `cli` was evaluated as an alternative to *PyGitHub*.
`gh release` was (and still is) not flexible enough to update the reference of a release, without deleting and
recreating it (see [cli.github.com: manual/gh_release_create](https://cli.github.com/manual/gh_release_create)).
Deletion and recreation is unfortunate, because it notifies all the watchers of a repository
(see [eine/tip#111](https://github.com/eine/tip/issues/111)).
However, [cli.github.com: manual/gh_release_upload](https://cli.github.com/manual/gh_release_upload) handles uploading
artifacts as assets faster and with better stability for larger files than *PyGitHub*
(see [msys2/msys2-installer#36](https://github.com/msys2/msys2-installer/pull/36)).
Furthermore, the GitHub CLI is installed on GitHub Actions' default virtual environments.
Although `gh` does not support login through SSH (see [cli/cli#3715](https://github.com/cli/cli/issues/3715)), on GitHub
Actions a token is available `${{ github.token }}`.
Therefore, **Releaser** uses `gh release upload` internally.
## Usage
The following block shows a minimal YAML workflow file: