Run the quality checks from a script file #90

Merged
Tirsvad merged 3 commits from quality-checks-script-file into main 2026-10-11 05:15:53 +02:00
16 changed files with 279 additions and 119 deletions
Showing only changes of commit 670f0ac469 - Show all commits
+16 -95
View File
@@ -35,6 +35,10 @@ on:
description: "Version of the shfmt-py package (shfmt 3.11.0)" description: "Version of the shfmt-py package (shfmt 3.11.0)"
type: string type: string
default: "3.11.0.2" default: "3.11.0.2"
tools-ref:
description: "Tag or branch of TirSystem/github-action whose tools/quality-checks.sh is run; pin it to the exact tag together with the workflow"
type: string
default: "v1"
jobs: jobs:
quality-checks: quality-checks:
@@ -50,104 +54,21 @@ jobs:
SHFMT_PY_VERSION: ${{ inputs.shfmt-py-version }} SHFMT_PY_VERSION: ${{ inputs.shfmt-py-version }}
QC_VENV: /tmp/quality-tools QC_VENV: /tmp/quality-tools
steps: steps:
- name: Validate the inputs
run: |
set -u
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
if [ "${#langs[@]}" -eq 0 ]; then
echo "::error::[configuration] languages is empty; use python, shell or both, comma separated."
exit 1
fi
for lang in "${langs[@]}"; do
case "$lang" in
python | shell) ;;
*)
echo "::error::[configuration] Unknown language '$lang'; use python and shell, comma separated."
exit 1
;;
esac
done
case "$WORKING_DIRECTORY" in
"" | /* | .. | ../* | */.. | */../*)
echo "::error::[configuration] working-directory must be a relative path inside the repository."
exit 1
;;
esac
echo "Checking ${LANGUAGES} in ${WORKING_DIRECTORY}"
- name: Check out the repository - name: Check out the repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Fetch the shared script
uses: actions/checkout@v4
with:
repository: TirSystem/github-action
ref: ${{ inputs.tools-ref }}
path: shared-scripts
- name: Validate the inputs
run: bash shared-scripts/tools/quality-checks.sh validate
- name: Install the tools - name: Install the tools
run: | run: bash shared-scripts/tools/quality-checks.sh install
set -eu
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
packages=()
for lang in "${langs[@]}"; do
case "$lang" in
python) packages+=("ruff==${RUFF_VERSION}" "mypy==${MYPY_VERSION}") ;;
shell) packages+=("shellcheck-py==${SHELLCHECK_PY_VERSION}" "shfmt-py==${SHFMT_PY_VERSION}") ;;
esac
done
python3 -m venv "$QC_VENV"
"$QC_VENV/bin/pip" install --quiet "${packages[@]}"
- name: Run the checks - name: Run the checks
run: | run: bash shared-scripts/tools/quality-checks.sh run
set -u
QC_BIN="${QC_BIN:-$QC_VENV/bin}"
if [ ! -d "$WORKING_DIRECTORY" ]; then
echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist."
exit 1
fi
cd "$WORKING_DIRECTORY"
failed=()
run_tool() {
local name="$1"
shift
echo "::group::$name"
if "$@"; then
echo "::endgroup::"
echo "PASS $name"
else
echo "::endgroup::"
echo "FAIL $name"
failed+=("$name")
fi
}
wanted() {
local lang
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
for lang in "${langs[@]}"; do
[ "$lang" = "$1" ] && return 0
done
return 1
}
if wanted python; then
mapfile -t files < <(git ls-files -- '*.py')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No python files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "ruff check" "$QC_BIN/ruff" check "${files[@]}"
run_tool "ruff format --check" "$QC_BIN/ruff" format --check "${files[@]}"
run_tool "mypy --strict" "$QC_BIN/mypy" --strict "${files[@]}"
fi
fi
if wanted shell; then
mapfile -t files < <(git ls-files -- '*.sh')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No shell files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "shellcheck" "$QC_BIN/shellcheck" "${files[@]}"
run_tool "shfmt -d" "$QC_BIN/shfmt" -d "${files[@]}"
fi
fi
if [ "${#failed[@]}" -gt 0 ]; then
echo "::error::Quality checks failed: ${failed[*]}"
exit 1
fi
echo "All selected checks passed."
+11
View File
@@ -20,6 +20,17 @@ Each release lists its changes in these groups, in this order:
## [Unreleased] ## [Unreleased]
### Added
- Input `tools-ref` of `quality-checks.yml` (default `v1`): the tag or branch from
which the workflow fetches its script `tools/quality-checks.sh`.
### Changed
- `quality-checks.yml` runs the script file `tools/quality-checks.sh` instead of
embedding about 100 lines of shell; the checks, messages and results are the
same.
## [1.0.1] - 2026-10-11 ## [1.0.1] - 2026-10-11
### Changed ### Changed
+8 -5
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [385d2fb] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Reviewed (RC-038): the security impact of installing the tools from PyPI named | [385d2fb] |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Reviewed (RC-038): the security impact of installing the tools from PyPI named | [385d2fb] | | 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added the input tools-ref: the shell steps moved to a script file fetched at that ref (RC-051) | pending |
Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`. Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
@@ -55,9 +55,12 @@ Options for the shape of the workflow:
The shared workflow `quality-checks.yml` is one reusable workflow with the The shared workflow `quality-checks.yml` is one reusable workflow with the
inputs `languages` (a comma list of `python` and `shell`, default both), inputs `languages` (a comma list of `python` and `shell`, default both),
`working-directory` (default `.`) and one version input per tool whose `working-directory` (default `.`), one version input per tool whose
defaults are the pinned versions above, and it installs the tools from PyPI defaults are the pinned versions above, and `tools-ref` (default `v1`, the
into a virtual environment. A run passes when every selected tool exits 0 and tag or branch from which the workflow fetches its script
`tools/quality-checks.sh`), and it installs the tools from PyPI into a virtual
environment. A call at an exact tag is pinned completely only when `tools-ref`
names the same tag; with the default the script follows the major tag. A run passes when every selected tool exits 0 and
fails when any exits non-zero, and a language with no files is skipped with a fails when any exits non-zero, and a language with no files is skipped with a
notice and counts as a pass. notice and counts as a pass.
+1 -1
View File
@@ -11,7 +11,7 @@ each document lives in *this* project and the next version to use.
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
| PP | Project Plan | docs/project-plan.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 |
| MIL | Milestone / Gateway | docs/milestones/*.md | 007 | | MIL | Milestone / Gateway | docs/milestones/*.md | 007 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 051 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 052 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0008 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0008 |
| UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 |
@@ -0,0 +1,50 @@
# Review Record: ADR-0005 (tools-ref)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-051 |
| CrossReference | [ADR-0005], [QC-ADR-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | pending |
---
## Artifact Under Review
- Instance reviewed: [ADR-0005] after the addition of the input `tools-ref`
- Checklist used: [QC-ADR-001]
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The ID (`ADR-NNNN`, 4-digit) matches the filename number and is unique and sequential | Pass | Unchanged. |
| 2 | Context states the problem, the forces (cost, risk, constraints) and the options evaluated | Pass | Unchanged; the move of the shell steps to a script file is an implementation of the decided contract, not a new option. |
| 3 | At least two options were considered, or the absence of alternatives is justified | Pass | Unchanged. |
| 4 | Decision is stated in one or two unhedged sentences and matches one of the evaluated options | Pass | The decision sentence names the new optional input. |
| 5 | Consequences list both Positive and Negative outcomes | Pass | Finding noted for the next revision, not a blocker: a reusable workflow cannot learn the ref it was called at (the context fields `workflow_ref`, `workflow_sha`, `job_workflow_sha` and `action_ref` are empty on this instance, verified on 2026-10-11), so the script ref is an input and a call at an exact tag is pinned completely only with `tools-ref` set to the same tag. |
| 6 | Quality-attribute impacts (performance, security, portability, etc.) are named where the decision affects them | Pass | Maintainability gains (a script of this repository, linted and tested as a file); the pin limitation above is the reliability cost. |
| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | Unchanged. |
| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | The earlier row is kept with status Deprecated and a new row added. |
| 9 | A superseded or deprecated ADR names its successor, and the successor links back | N-A | Nothing is superseded. |
| 10 | Written in business or technical language of the domain, with no unexplained abbreviations | Pass | Unchanged. |
| 11 | Status is one of `Proposed`, `Accepted`, `Rejected`, `Deprecated` or `Superseded by ADR-NNNN`, and every Version History row uses only these | Pass | Deprecated and Accepted. |
## Overall Verdict
Go - all applicable criteria pass. Adding an optional input is a minor change under ADR-0006. S01 gave the Go in chat on 2026-10-11, standing in for the other reviewers as ADR-0002 records; there is no independent reader.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Run a pinned consumer (`@v1.0.1` with `tools-ref: v1.0.1`) after the next release to confirm the complete pin | S05 | next release |
---
[ADR-0005]: ../../adr/adr-0005-quality-check-contract.md
[QC-ADR-001]: ../../../framework/qc/qc-adr.md
+6 -5
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | TM-001 | | ID | TM-001 |
| CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [ADR-0007], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] | | CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [RC-051], [ADR-0007], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added RC-049 (MIL-006 pilot results) | [d575f27] | | 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added RC-050 (MIL-005 translations accepted) | [ead3d98] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added RC-050 (MIL-005 translations accepted) | [ead3d98] | | 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added RC-051 (ADR-0005 tools-ref) | pending |
--- ---
@@ -62,7 +62,7 @@ updated whenever an artifact instance is created or reviewed.
| [UC-003] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-023] | | [UC-003] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-023] |
| [UC-004] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-024] | | [UC-004] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-024] |
| [UC-005] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0005] | [RC-037] | | [UC-005] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0005] | [RC-037] |
| [ADR-0005] | ADR | [MIL-003], [UC-005], [US-001], [ADR-0004] | - | [RC-038] | | [ADR-0005] | ADR | [MIL-003], [UC-005], [US-001], [ADR-0004] | - | [RC-038], [RC-051] |
| [UC-006] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0006] | [RC-042] | | [UC-006] | UC | [UCD-001], [US-001], [SA-001] | [ADR-0006] | [RC-042] |
| [ADR-0006] | ADR | [MIL-004], [UC-006], [US-001], [ADR-0004], [ADR-0005] | - | [RC-043] | | [ADR-0006] | ADR | [MIL-004], [UC-006], [US-001], [ADR-0004], [ADR-0005] | - | [RC-043] |
| [ADR-0007] | ADR | [MIL-004], [UC-006], [ADR-0006] | - | [RC-047] | | [ADR-0007] | ADR | [MIL-004], [UC-006], [ADR-0006] | - | [RC-047] |
@@ -99,6 +99,7 @@ updated whenever an artifact instance is created or reviewed.
| [RC-048] | RC | [MIL-004] | - | - | | [RC-048] | RC | [MIL-004] | - | - |
| [RC-049] | RC | [MIL-006] | - | - | | [RC-049] | RC | [MIL-006] | - | - |
| [RC-050] | RC | [MIL-005] | - | - | | [RC-050] | RC | [MIL-005] | - | - |
| [RC-051] | RC | [ADR-0005] | - | - |
## Coverage Notes ## Coverage Notes
@@ -178,11 +179,11 @@ updated whenever an artifact instance is created or reviewed.
[RC-048]: ./reviews/rc-048-mil-004-outcome.md [RC-048]: ./reviews/rc-048-mil-004-outcome.md
[RC-049]: ./reviews/rc-049-mil-006-outcome.md [RC-049]: ./reviews/rc-049-mil-006-outcome.md
[RC-050]: ./reviews/rc-050-mil-005-outcome.md [RC-050]: ./reviews/rc-050-mil-005-outcome.md
[RC-051]: ./reviews/rc-051-adr-0005-tools-ref.md
[ADR-0007]: ../adr/adr-0007-release-publish-destination.md [ADR-0007]: ../adr/adr-0007-release-publish-destination.md
[UC-007]: ../uc-007/uc.md [UC-007]: ../uc-007/uc.md
[UC-006]: ../uc-006/uc.md [UC-006]: ../uc-006/uc.md
[ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md [ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md
[UC-005]: ../uc-005/uc.md [UC-005]: ../uc-005/uc.md
[ADR-0005]: ../adr/adr-0005-quality-check-contract.md [ADR-0005]: ../adr/adr-0005-quality-check-contract.md
[d575f27]: https://git.tirsystem.com/TirSystem/github-action/commit/d575f272fcb044e3165951013464177a6e659ea6
[ead3d98]: https://git.tirsystem.com/TirSystem/github-action/commit/ead3d98d73fcab95fd57b0eb005a02aff9983256 [ead3d98]: https://git.tirsystem.com/TirSystem/github-action/commit/ead3d98d73fcab95fd57b0eb005a02aff9983256
+6
View File
@@ -32,12 +32,18 @@ jobs:
| `mypy-version` | `1.11.2` | Version of mypy | | `mypy-version` | `1.11.2` | Version of mypy |
| `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag Reference the workflow at the major tag (`@v1`) or at an exact tag
(`@v1.0.0`), never at `main` and never at the branch of a pull request: that (`@v1.0.0`), never at `main` and never at the branch of a pull request: that
branch is deleted when the pull request is merged and the reference stops branch is deleted when the pull request is merged and the reference stops
working. See the upgrade guide for what each choice receives. working. See the upgrade guide for what each choice receives.
The workflow file calls a script of this repository, fetched at `tools-ref`. A
call at an exact tag is pinned completely only when `tools-ref` names the same
tag, for example `@v1.0.1` with `tools-ref: v1.0.1`; with the default the
script follows `v1`.
## What is checked ## What is checked
| Language | Files | Tools | | Language | Files | Tools |
+4
View File
@@ -16,6 +16,10 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`.
A breaking release gets a new major version (`v2`), so a pipeline on `@v1` A breaking release gets a new major version (`v2`), so a pipeline on `@v1`
never receives it until you change the line yourself. never receives it until you change the line yourself.
The quality-checks workflow fetches its script at its input `tools-ref`, which
defaults to `v1`. To pin it completely, call it at an exact tag and set
`tools-ref` to the same tag; otherwise the script follows the major tag.
The two scoped workflows (**Sync GitHub mirror metadata** and **Publish The two scoped workflows (**Sync GitHub mirror metadata** and **Publish
wiki**) are delivered by the instance and are not in your `uses:` lines; you wiki**) are delivered by the instance and are not in your `uses:` lines; you
cannot pin them, but they fetch their scripts at the major tag. cannot pin them, but they fetch their scripts at the major tag.
+31 -9
View File
@@ -15,6 +15,8 @@ import yaml
from workflow_source import REPO_ROOT from workflow_source import REPO_ROOT
WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks.yml" WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks.yml"
SCRIPT = REPO_ROOT / "tools" / "quality-checks.sh"
COMMANDS = {"Validate the inputs": "validate", "Install the tools": "install", "Run the checks": "run"}
BASH = shutil.which("bash") BASH = shutil.which("bash")
pytestmark = pytest.mark.skipif(BASH is None, reason="bash is needed to run the workflow steps") pytestmark = pytest.mark.skipif(BASH is None, reason="bash is needed to run the workflow steps")
@@ -25,7 +27,7 @@ def document() -> dict:
return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8")) return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))
def step_script(document: dict, name: str) -> str: def step_command(document: dict, name: str) -> str:
steps = document["jobs"]["quality-checks"]["steps"] steps = document["jobs"]["quality-checks"]["steps"]
return next(step["run"] for step in steps if step.get("name") == name) return next(step["run"] for step in steps if step.get("name") == name)
@@ -40,9 +42,11 @@ class TestDefinition:
"mypy-version", "mypy-version",
"shellcheck-py-version", "shellcheck-py-version",
"shfmt-py-version", "shfmt-py-version",
"tools-ref",
} }
assert inputs["languages"]["default"] == "python,shell" assert inputs["languages"]["default"] == "python,shell"
assert inputs["working-directory"]["default"] == "." assert inputs["working-directory"]["default"] == "."
assert inputs["tools-ref"]["default"] == "v1"
def test_tool_versions_are_pinned_to_the_adr(self, document): def test_tool_versions_are_pinned_to_the_adr(self, document):
inputs = document[True]["workflow_call"]["inputs"] inputs = document[True]["workflow_call"]["inputs"]
@@ -60,14 +64,32 @@ class TestDefinition:
for step in document["jobs"]["quality-checks"]["steps"]: for step in document["jobs"]["quality-checks"]["steps"]:
assert "${{" not in step.get("run", ""), f"{step.get('name')} interpolates an expression" assert "${{" not in step.get("run", ""), f"{step.get('name')} interpolates an expression"
@pytest.mark.parametrize( def test_the_script_parses(self):
"name", ["Validate the inputs", "Install the tools", "Run the checks"] result = subprocess.run([BASH, "-n", str(SCRIPT)], text=True, capture_output=True)
)
def test_every_script_parses(self, document, name):
script = step_script(document, name)
result = subprocess.run([BASH, "-n"], input=script, text=True, capture_output=True)
assert result.returncode == 0, result.stderr assert result.returncode == 0, result.stderr
def test_the_workflow_embeds_no_script_and_runs_the_file(self, document):
steps = document["jobs"]["quality-checks"]["steps"]
commands = [step["run"] for step in steps if "run" in step]
assert commands == [
"bash shared-scripts/tools/quality-checks.sh validate",
"bash shared-scripts/tools/quality-checks.sh install",
"bash shared-scripts/tools/quality-checks.sh run",
]
def test_the_script_is_fetched_at_the_tools_ref_input(self, document):
steps = document["jobs"]["quality-checks"]["steps"]
fetch = next(step for step in steps if step.get("name") == "Fetch the shared script")
assert fetch["with"] == {
"repository": "TirSystem/github-action",
"ref": "${{ inputs.tools-ref }}",
"path": "shared-scripts",
}
def test_an_unknown_command_is_refused(self):
result = subprocess.run([BASH, str(SCRIPT), "bogus"], text=True, capture_output=True)
assert result.returncode == 2
STUB = """#!/usr/bin/env bash STUB = """#!/usr/bin/env bash
name="$(basename "$0")" name="$(basename "$0")"
@@ -111,7 +133,7 @@ def add_files(root: Path, *relative: str) -> None:
def run_step(document, name, repo, bin_dir, tmp_path, **env): def run_step(document, name, repo, bin_dir, tmp_path, **env):
script = step_script(document, name) command = COMMANDS[name]
log = tmp_path / "tools.log" log = tmp_path / "tools.log"
environment = dict(os.environ) environment = dict(os.environ)
environment.update( environment.update(
@@ -126,7 +148,7 @@ def run_step(document, name, repo, bin_dir, tmp_path, **env):
) )
environment.update(env) environment.update(env)
result = subprocess.run( result = subprocess.run(
[BASH, "-c", script], cwd=repo, env=environment, text=True, capture_output=True [BASH, str(SCRIPT), command], cwd=repo, env=environment, text=True, capture_output=True
) )
calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else [] calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else []
return result, calls return result, calls
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env bash
# Steps of the reusable workflow .gitea/workflows/quality-checks.yml (ADR-0005).
# The workflow checks out the calling repository, fetches this file into
# shared-scripts/ and runs one command per step:
#
# quality-checks.sh validate check the inputs, before anything is installed
# quality-checks.sh install install the pinned tools into a virtual environment
# quality-checks.sh run run every selected tool on the tracked files
#
# Settings come from the environment, never from interpolated expressions:
# LANGUAGES, WORKING_DIRECTORY, RUFF_VERSION, MYPY_VERSION,
# SHELLCHECK_PY_VERSION, SHFMT_PY_VERSION, QC_VENV (and QC_BIN for tests).
# Every failure is printed as ::error::[category] message.
validate_inputs() {
set -u
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
if [ "${#langs[@]}" -eq 0 ]; then
echo "::error::[configuration] languages is empty; use python, shell or both, comma separated."
exit 1
fi
for lang in "${langs[@]}"; do
case "$lang" in
python | shell) ;;
*)
echo "::error::[configuration] Unknown language '$lang'; use python and shell, comma separated."
exit 1
;;
esac
done
case "$WORKING_DIRECTORY" in
"" | /* | .. | ../* | */.. | */../*)
echo "::error::[configuration] working-directory must be a relative path inside the repository."
exit 1
;;
esac
echo "Checking ${LANGUAGES} in ${WORKING_DIRECTORY}"
}
install_tools() {
set -eu
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
packages=()
for lang in "${langs[@]}"; do
case "$lang" in
python) packages+=("ruff==${RUFF_VERSION}" "mypy==${MYPY_VERSION}") ;;
shell) packages+=("shellcheck-py==${SHELLCHECK_PY_VERSION}" "shfmt-py==${SHFMT_PY_VERSION}") ;;
esac
done
python3 -m venv "$QC_VENV"
"$QC_VENV/bin/pip" install --quiet "${packages[@]}"
}
run_checks() {
set -u
QC_BIN="${QC_BIN:-$QC_VENV/bin}"
if [ ! -d "$WORKING_DIRECTORY" ]; then
echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist."
exit 1
fi
cd "$WORKING_DIRECTORY"
failed=()
run_tool() {
local name="$1"
shift
echo "::group::$name"
if "$@"; then
echo "::endgroup::"
echo "PASS $name"
else
echo "::endgroup::"
echo "FAIL $name"
failed+=("$name")
fi
}
wanted() {
local lang
IFS=',' read -ra langs <<< "${LANGUAGES// /}"
for lang in "${langs[@]}"; do
[ "$lang" = "$1" ] && return 0
done
return 1
}
if wanted python; then
mapfile -t files < <(git ls-files -- '*.py')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No python files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "ruff check" "$QC_BIN/ruff" check "${files[@]}"
run_tool "ruff format --check" "$QC_BIN/ruff" format --check "${files[@]}"
run_tool "mypy --strict" "$QC_BIN/mypy" --strict "${files[@]}"
fi
fi
if wanted shell; then
mapfile -t files < <(git ls-files -- '*.sh')
if [ "${#files[@]}" -eq 0 ]; then
echo "::notice::No shell files in '$WORKING_DIRECTORY'; nothing was checked."
else
run_tool "shellcheck" "$QC_BIN/shellcheck" "${files[@]}"
run_tool "shfmt -d" "$QC_BIN/shfmt" -d "${files[@]}"
fi
fi
if [ "${#failed[@]}" -gt 0 ]; then
echo "::error::Quality checks failed: ${failed[*]}"
exit 1
fi
echo "All selected checks passed."
}
case "${1:-}" in
validate) validate_inputs ;;
install) install_tools ;;
run) run_checks ;;
*)
echo "::error::[configuration] usage: quality-checks.sh validate|install|run" >&2
exit 2
;;
esac
+4 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of QualityChecksPythonShell.md, source sha256 9b63f6eb0c8c --> <!-- translation of QualityChecksPythonShell.md, source sha256 2a0c9d43c77d -->
> Dette er en oversættelse af [`QualityChecksPythonShell`](QualityChecksPythonShell). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`QualityChecksPythonShell`](QualityChecksPythonShell). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -35,9 +35,12 @@ jobs:
| `mypy-version` | `1.11.2` | Version af mypy | | `mypy-version` | `1.11.2` | Version af mypy |
| `shellcheck-py-version` | `0.10.0.1` | Version af pakken shellcheck-py (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Version af pakken shellcheck-py (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Version af pakken shfmt-py (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Version af pakken shfmt-py (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag eller gren i `TirSystem/github-action`, hvorfra scriptet `tools/quality-checks.sh` hentes |
Henvis til workflowet ved major-tagget (`@v1`) eller et præcist tag (`@v1.0.0`), aldrig ved `main` og aldrig ved en pull requests gren: den gren slettes, når pull requesten flettes, og henvisningen holder op med at virke. Se opgraderingsvejledningen for, hvad hvert valg modtager. Henvis til workflowet ved major-tagget (`@v1`) eller et præcist tag (`@v1.0.0`), aldrig ved `main` og aldrig ved en pull requests gren: den gren slettes, når pull requesten flettes, og henvisningen holder op med at virke. Se opgraderingsvejledningen for, hvad hvert valg modtager.
Workflowfilen kalder et script i dette repository, hentet ved `tools-ref`. Et kald ved et præcist tag er kun fuldt fastlåst, når `tools-ref` angiver samme tag, for eksempel `@v1.0.1` med `tools-ref: v1.0.1`; med standardværdien følger scriptet `v1`.
## Hvad der kontrolleres ## Hvad der kontrolleres
| Sprog | Filer | Værktøjer | | Sprog | Filer | Værktøjer |
+6
View File
@@ -38,12 +38,18 @@ jobs:
| `mypy-version` | `1.11.2` | Version of mypy | | `mypy-version` | `1.11.2` | Version of mypy |
| `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Version of the shellcheck-py package (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Version of the shfmt-py package (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag Reference the workflow at the major tag (`@v1`) or at an exact tag
(`@v1.0.0`), never at `main` and never at the branch of a pull request: that (`@v1.0.0`), never at `main` and never at the branch of a pull request: that
branch is deleted when the pull request is merged and the reference stops branch is deleted when the pull request is merged and the reference stops
working. See the upgrade guide for what each choice receives. working. See the upgrade guide for what each choice receives.
The workflow file calls a script of this repository, fetched at `tools-ref`. A
call at an exact tag is pinned completely only when `tools-ref` names the same
tag, for example `@v1.0.1` with `tools-ref: v1.0.1`; with the default the
script follows `v1`.
## What is checked ## What is checked
| Language | Files | Tools | | Language | Files | Tools |
+4 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of QualityChecksPythonShell.md, source sha256 9b63f6eb0c8c --> <!-- translation of QualityChecksPythonShell.md, source sha256 2a0c9d43c77d -->
> Ini ialah terjemahan [`QualityChecksPythonShell`](QualityChecksPythonShell). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`QualityChecksPythonShell`](QualityChecksPythonShell). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -35,9 +35,12 @@ jobs:
| `mypy-version` | `1.11.2` | Versi mypy | | `mypy-version` | `1.11.2` | Versi mypy |
| `shellcheck-py-version` | `0.10.0.1` | Versi pakej shellcheck-py (ShellCheck 0.10.0) | | `shellcheck-py-version` | `0.10.0.1` | Versi pakej shellcheck-py (ShellCheck 0.10.0) |
| `shfmt-py-version` | `3.11.0.2` | Versi pakej shfmt-py (shfmt 3.11.0) | | `shfmt-py-version` | `3.11.0.2` | Versi pakej shfmt-py (shfmt 3.11.0) |
| `tools-ref` | `v1` | Tag atau cabang `TirSystem/github-action` tempat skrip `tools/quality-checks.sh` diambil |
Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat (`@v1.0.0`), jangan sekali-kali pada `main` dan jangan pada cabang pull request: cabang itu dipadam apabila pull request digabungkan dan rujukan itu berhenti berfungsi. Lihat panduan naik taraf untuk apa yang diterima oleh setiap pilihan. Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat (`@v1.0.0`), jangan sekali-kali pada `main` dan jangan pada cabang pull request: cabang itu dipadam apabila pull request digabungkan dan rujukan itu berhenti berfungsi. Lihat panduan naik taraf untuk apa yang diterima oleh setiap pilihan.
Fail aliran kerja memanggil skrip dalam repositori ini, yang diambil pada `tools-ref`. Panggilan pada tag tepat hanya disematkan sepenuhnya apabila `tools-ref` menamakan tag yang sama, contohnya `@v1.0.1` dengan `tools-ref: v1.0.1`; dengan nilai lalai skrip mengikuti `v1`.
## Perkara yang disemak ## Perkara yang disemak
| Bahasa | Fail | Alat | | Bahasa | Fail | Alat |
+3 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of Upgrading.md, source sha256 ff8a06c4a03f --> <!-- translation of Upgrading.md, source sha256 3f119a1ab12d -->
> Dette er en oversættelse af [`Upgrading`](Upgrading). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Upgrading`](Upgrading). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -18,6 +18,8 @@ Sådan tager en softwareudvikler en ny udgivelse af de fælles workflows i brug
En udgivelse, der bryder kompatibiliteten, får en ny major-version (`v2`), så en pipeline på `@v1` aldrig modtager den, før du selv ændrer linjen. En udgivelse, der bryder kompatibiliteten, får en ny major-version (`v2`), så en pipeline på `@v1` aldrig modtager den, før du selv ændrer linjen.
Kvalitetskontrol-workflowet henter sit script ved sit input `tools-ref`, som som standard er `v1`. For at fastlåse det helt skal du kalde det ved et præcist tag og sætte `tools-ref` til samme tag; ellers følger scriptet major-tagget.
De to scoped workflows (**Sync GitHub mirror metadata** og **Publish wiki**) leveres af instansen og står ikke i dine `uses:`-linjer; du kan ikke fastlåse dem, men de henter deres scripts ved major-tagget. De to scoped workflows (**Sync GitHub mirror metadata** og **Publish wiki**) leveres af instansen og står ikke i dine `uses:`-linjer; du kan ikke fastlåse dem, men de henter deres scripts ved major-tagget.
## Aflæsning af en changelog-post ## Aflæsning af en changelog-post
+4
View File
@@ -22,6 +22,10 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`.
A breaking release gets a new major version (`v2`), so a pipeline on `@v1` A breaking release gets a new major version (`v2`), so a pipeline on `@v1`
never receives it until you change the line yourself. never receives it until you change the line yourself.
The quality-checks workflow fetches its script at its input `tools-ref`, which
defaults to `v1`. To pin it completely, call it at an exact tag and set
`tools-ref` to the same tag; otherwise the script follows the major tag.
The two scoped workflows (**Sync GitHub mirror metadata** and **Publish The two scoped workflows (**Sync GitHub mirror metadata** and **Publish
wiki**) are delivered by the instance and are not in your `uses:` lines; you wiki**) are delivered by the instance and are not in your `uses:` lines; you
cannot pin them, but they fetch their scripts at the major tag. cannot pin them, but they fetch their scripts at the major tag.
+3 -1
View File
@@ -1,4 +1,4 @@
<!-- translation of Upgrading.md, source sha256 ff8a06c4a03f --> <!-- translation of Upgrading.md, source sha256 3f119a1ab12d -->
> Ini ialah terjemahan [`Upgrading`](Upgrading). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Upgrading`](Upgrading). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -18,6 +18,8 @@ Cara seorang jurutera perisian menerima keluaran baharu aliran kerja bersama den
Keluaran yang memecahkan keserasian mendapat versi major baharu (`v2`), jadi saluran paip pada `@v1` tidak menerimanya sehingga anda sendiri menukar baris itu. Keluaran yang memecahkan keserasian mendapat versi major baharu (`v2`), jadi saluran paip pada `@v1` tidak menerimanya sehingga anda sendiri menukar baris itu.
Aliran kerja semakan kualiti mengambil skripnya pada inputnya `tools-ref`, yang lalai ialah `v1`. Untuk menyematkannya sepenuhnya, panggil pada tag tepat dan tetapkan `tools-ref` kepada tag yang sama; jika tidak, skrip mengikuti tag major.
Dua aliran kerja scoped (**Sync GitHub mirror metadata** dan **Publish wiki**) dihantar oleh instans dan tidak terdapat dalam baris `uses:` anda; anda tidak boleh menyematkannya, tetapi ia mengambil skripnya pada tag major. Dua aliran kerja scoped (**Sync GitHub mirror metadata** dan **Publish wiki**) dihantar oleh instans dan tidak terdapat dalam baris `uses:` anda; anda tidak boleh menyematkannya, tetapi ia mengambil skripnya pada tag major.
## Membaca satu catatan changelog ## Membaca satu catatan changelog