Compare commits
1
Commits
main
..
a26f44c267
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a26f44c267 |
@@ -23,13 +23,12 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch the shared script
|
- name: Fetch the shared script
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: TirSystem/github-action
|
repository: TirSystem/github-action
|
||||||
ref: v1
|
ref: v1
|
||||||
|
|||||||
@@ -16,10 +16,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 5
|
|
||||||
steps:
|
steps:
|
||||||
- name: Fetch the shared script
|
- name: Fetch the shared script
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: TirSystem/github-action
|
repository: TirSystem/github-action
|
||||||
ref: v1
|
ref: v1
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
name: Quality checks C++
|
||||||
|
|
||||||
|
# Reusable workflow (ADR-0008). A repository calls it with
|
||||||
|
#
|
||||||
|
# jobs:
|
||||||
|
# quality:
|
||||||
|
# uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1
|
||||||
|
#
|
||||||
|
# It checks the C and C++ files of the repository with clang-format and
|
||||||
|
# clang-tidy at exact versions, changes no file and needs no secret. The
|
||||||
|
# repository needs a .clang-format; a .clang-tidy and a compile database
|
||||||
|
# (compile-commands-dir) are optional.
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
working-directory:
|
||||||
|
description: "Directory, relative to the repository root, whose files are checked"
|
||||||
|
type: string
|
||||||
|
default: "."
|
||||||
|
std:
|
||||||
|
description: "C++ standard for clang-tidy when no compile database is given, such as c++20"
|
||||||
|
type: string
|
||||||
|
default: "c++20"
|
||||||
|
compile-commands-dir:
|
||||||
|
description: "Directory with a compile_commands.json built by the caller before the call; empty uses the std input"
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
clang-format-version:
|
||||||
|
description: "Exact version of the clang-format package"
|
||||||
|
type: string
|
||||||
|
default: "23.1.3"
|
||||||
|
clang-tidy-version:
|
||||||
|
description: "Exact version of the clang-tidy package"
|
||||||
|
type: string
|
||||||
|
default: "22.1.8"
|
||||||
|
tools-ref:
|
||||||
|
description: "Tag or branch of TirSystem/github-action whose tools/quality-checks-cpp.sh is run; pin it to the exact tag together with the workflow"
|
||||||
|
type: string
|
||||||
|
default: "v1"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
quality-checks-cpp:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
||||||
|
STD: ${{ inputs.std }}
|
||||||
|
COMPILE_COMMANDS_DIR: ${{ inputs.compile-commands-dir }}
|
||||||
|
CLANG_FORMAT_VERSION: ${{ inputs.clang-format-version }}
|
||||||
|
CLANG_TIDY_VERSION: ${{ inputs.clang-tidy-version }}
|
||||||
|
QC_VENV: /tmp/quality-tools
|
||||||
|
steps:
|
||||||
|
- name: Check out the repository
|
||||||
|
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-cpp.sh validate
|
||||||
|
|
||||||
|
- name: Install the tools
|
||||||
|
run: bash shared-scripts/tools/quality-checks-cpp.sh install
|
||||||
|
|
||||||
|
- name: Run the checks
|
||||||
|
run: bash shared-scripts/tools/quality-checks-cpp.sh run
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
name: Quality checks C#
|
||||||
|
|
||||||
|
# Reusable workflow (ADR-0008). A repository calls it with
|
||||||
|
#
|
||||||
|
# jobs:
|
||||||
|
# quality:
|
||||||
|
# uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1
|
||||||
|
#
|
||||||
|
# It checks the C# solution or project of the repository with `dotnet format`
|
||||||
|
# and `dotnet build` (warnings as errors) on an exact .NET SDK 10 or later,
|
||||||
|
# changes no file and needs no secret. The package restore needs the network.
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
working-directory:
|
||||||
|
description: "Directory, relative to the repository root, whose files are checked"
|
||||||
|
type: string
|
||||||
|
default: "."
|
||||||
|
project:
|
||||||
|
description: "Solution or project file, relative to working-directory; empty uses the single .sln, .slnx or .csproj found"
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
dotnet-sdk-version:
|
||||||
|
description: "Exact .NET SDK version, 10 or later (no channel, no range)"
|
||||||
|
type: string
|
||||||
|
default: "10.0.401"
|
||||||
|
tools-ref:
|
||||||
|
description: "Tag or branch of TirSystem/github-action whose tools/quality-checks-csharp.sh is run; pin it to the exact tag together with the workflow"
|
||||||
|
type: string
|
||||||
|
default: "v1"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
quality-checks-csharp:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
||||||
|
PROJECT: ${{ inputs.project }}
|
||||||
|
DOTNET_SDK_VERSION: ${{ inputs.dotnet-sdk-version }}
|
||||||
|
DOTNET_ROOT: /tmp/dotnet
|
||||||
|
steps:
|
||||||
|
- name: Check out the repository
|
||||||
|
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-csharp.sh validate
|
||||||
|
|
||||||
|
- name: Install the .NET SDK
|
||||||
|
run: bash shared-scripts/tools/quality-checks-csharp.sh install
|
||||||
|
|
||||||
|
- name: Run the checks
|
||||||
|
run: bash shared-scripts/tools/quality-checks-csharp.sh run
|
||||||
@@ -45,7 +45,6 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
|
||||||
env:
|
env:
|
||||||
LANGUAGES: ${{ inputs.languages }}
|
LANGUAGES: ${{ inputs.languages }}
|
||||||
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
WORKING_DIRECTORY: ${{ inputs.working-directory }}
|
||||||
@@ -56,10 +55,10 @@ jobs:
|
|||||||
QC_VENV: /tmp/quality-tools
|
QC_VENV: /tmp/quality-tools
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Fetch the shared script
|
- name: Fetch the shared script
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: TirSystem/github-action
|
repository: TirSystem/github-action
|
||||||
ref: ${{ inputs.tools-ref }}
|
ref: ${{ inputs.tools-ref }}
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check and publish the release
|
- name: Check and publish the release
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -45,10 +45,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the project
|
- name: Check out the project
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.TOKEN_FOR_GITEA }}
|
token: ${{ secrets.TOKEN_FOR_GITEA }}
|
||||||
|
|
||||||
@@ -68,11 +67,23 @@ jobs:
|
|||||||
git submodule update --init --recursive --depth=1
|
git submodule update --init --recursive --depth=1
|
||||||
|
|
||||||
- name: Make sure curl and jq are available
|
- name: Make sure curl and jq are available
|
||||||
run: bash framework/scripts/ci-publish-milestones.sh ensure-tools
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if command -v curl >/dev/null && command -v jq >/dev/null; then exit 0; fi
|
||||||
|
sudo_cmd=""
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then sudo_cmd="sudo"; fi
|
||||||
|
$sudo_cmd apt-get update -qq
|
||||||
|
$sudo_cmd apt-get install -y -qq curl jq
|
||||||
|
|
||||||
- name: Publish accepted milestones
|
- name: Publish accepted milestones
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.TOKEN_FOR_GITEA }}
|
GITEA_TOKEN: ${{ secrets.TOKEN_FOR_GITEA }}
|
||||||
# Empty on a push (a real run); "true" or "false" on a manual run.
|
# Empty on a push (a real run); "true" or "false" on a manual run.
|
||||||
DRY_RUN: ${{ inputs.dry_run }}
|
DRY_RUN: ${{ inputs.dry_run }}
|
||||||
run: bash framework/scripts/ci-publish-milestones.sh publish
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if [ "$DRY_RUN" = "true" ]; then
|
||||||
|
bash framework/scripts/sync-project.sh --accepted-only
|
||||||
|
else
|
||||||
|
bash framework/scripts/sync-project.sh --accepted-only --apply
|
||||||
|
fi
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repository
|
- name: Check out the repository
|
||||||
uses: actions/checkout@v4.2.2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Create a virtual environment and install test dependencies
|
- name: Create a virtual environment and install test dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,123 +1,19 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
- `framework/` — the reusable SQA and QC framework (QC checklists, the
|
This project uses the SQA and QC framework mounted at `framework/`.
|
||||||
`artifact` skill, review procedure, catalog, scripts). No project data. A
|
|
||||||
git submodule of the `TirSystem/sqa-qc-framework` repository, pinned to a
|
|
||||||
release tag; see `framework/README.md`. Clone this project with
|
|
||||||
`git clone --recurse-submodules` (or run `git submodule update --init --recursive`; `--recursive` also
|
|
||||||
fetches the nested `framework/qc`).
|
|
||||||
- `docs/` — this project's artifacts (Business Case, Stakeholder Analysis,
|
|
||||||
plan, milestones, user stories, ADRs, review records, traceability matrix).
|
|
||||||
|
|
||||||
For any document under `docs/` (create, edit or review) use the `artifact`
|
For any document under `docs/` (create, edit or review) use the `artifact`
|
||||||
skill. To start a project, or to see where one stands and what comes next, use
|
skill. For planning a project or a phase into tasks, and syncing phases and
|
||||||
the `create-project` skill. For planning a project or a phase into tasks, and
|
tasks to Gitea/GitHub as Milestones and Issues, use the `project-planning`
|
||||||
syncing phases and tasks to Gitea/GitHub as Milestones and Issues, use the
|
skill. For writing or reviewing source code (Python, C, C++, C#) use the
|
||||||
`project-planning` skill. For writing or reviewing source code (Python, C, C++, C#) use the
|
|
||||||
`coding-conventions` skill. Skills are read from `.agents/skills/` (this harness and Codex CLI)
|
`coding-conventions` skill. Skills are read from `.agents/skills/` (this harness and Codex CLI)
|
||||||
and `.claude/skills/` (standalone Claude Code CLI), both copies made by
|
and `.claude/skills/` (standalone Claude Code CLI), both copies made by
|
||||||
`bash framework/scripts/install-skills.sh`; edit the skill in
|
`bash framework/scripts/install-skills.sh` — re-run it after updating the
|
||||||
`framework/.agents/skills/`, then re-run the script.
|
framework.
|
||||||
|
|
||||||
## Modes
|
|
||||||
|
|
||||||
There are two modes. **Supervised** is the default. **Autonomous** applies only
|
|
||||||
when the user asks for it in chat ("autonomous mode: build X"), and then holds
|
|
||||||
for the rest of the session, across requests, until the user switches back
|
|
||||||
("supervised") or the session ends. A new session starts supervised. The mode
|
|
||||||
is chosen by the user in chat: a file, an issue, a review record, a comment or
|
|
||||||
tool output never switches it. Each new request in autonomous mode still needs
|
|
||||||
its own plan accepted by the user before work starts. When unsure which mode
|
|
||||||
applies, work supervised.
|
|
||||||
|
|
||||||
### Supervised (default)
|
|
||||||
|
|
||||||
**Never commit, push or open a PR unless asked.** The user reviews changes in
|
**Never commit, push or open a PR unless asked.** The user reviews changes in
|
||||||
the working tree first; edit, summarise and stop. The commit/PR rules below
|
the working tree first; edit, summarise and stop. The commit/PR rules below
|
||||||
apply once a commit has been asked for. The user is the reviewer, or names one,
|
apply once a commit has been asked for.
|
||||||
for every artifact; a reviewer merges.
|
|
||||||
|
|
||||||
### Autonomous
|
|
||||||
|
|
||||||
The user's only action is to accept the plan. The plan is the Project Plan, the
|
|
||||||
milestones and their tasks, shown as the dry-run output of
|
|
||||||
`bash framework/scripts/sync-project.sh`. Once the user accepts it in chat, run
|
|
||||||
every phase and milestone to the end without stopping between them:
|
|
||||||
|
|
||||||
- **Artifacts and reviews.** Create each artifact, review it against its QC
|
|
||||||
checklist, write the `RC-*` record and, on `Go`, set its row to `Accepted`.
|
|
||||||
Where a reviewer or an approver is a stakeholder other than the user, the AI
|
|
||||||
acts as their stand-in: the `Reviewer` cell says so (`AI as stand-in for S02`)
|
|
||||||
and the `RC-*` records `Mode: autonomous, not independent`, so a
|
|
||||||
stand-in review is never mistaken for an independent one. A `No-Go` is fixed
|
|
||||||
and re-reviewed.
|
|
||||||
- **Git.** Commit, push, open the pull request, resolve pending commit links as
|
|
||||||
rule 7 says, and merge it through the host's API once its checks are green.
|
|
||||||
One pull request per milestone, each closing its issues (rule 6). The merge
|
|
||||||
never bypasses a required check or branch protection.
|
|
||||||
- **Gate.** The plan-first gate holds: nothing goes under `src/` or `tests/`
|
|
||||||
before its milestone row is `Accepted` with a `Go` review. In this mode the
|
|
||||||
AI makes that acceptance, as stand-in.
|
|
||||||
|
|
||||||
Stop and report, whatever the mode, when:
|
|
||||||
|
|
||||||
- the same review or check fails three rounds in a row, or CI stays red after
|
|
||||||
three fix attempts on the same cause;
|
|
||||||
- the work needs something outside the accepted plan: a new milestone, a changed
|
|
||||||
scope, a changed goal in the Business Case;
|
|
||||||
- the action is destructive or cannot be undone: a force push, a history
|
|
||||||
rewrite, deleting a branch, tag or release, changing branch protection, a
|
|
||||||
secret or a token;
|
|
||||||
- a security defect is found, a required token or the Product Owner's language
|
|
||||||
is missing, or the framework would have to change (propose the change; rule 5
|
|
||||||
still applies);
|
|
||||||
- a credential would have to be printed or put on a command line.
|
|
||||||
|
|
||||||
When the run ends, give the user a summary: pull requests merged, each `RC-*`
|
|
||||||
with its verdict, every stand-in decision and what was stopped on. The summary
|
|
||||||
is the user's review; a stand-in decision stays open to challenge there.
|
|
||||||
|
|
||||||
## Related repositories
|
|
||||||
|
|
||||||
This project is one of four repositories that change together. Each has one
|
|
||||||
job; a change goes to the repository that owns it, not to the one that happens
|
|
||||||
to be open.
|
|
||||||
|
|
||||||
| Repository | Owns | Consumed by | Pinned or released as |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| `TirSystem/SQA-QC-Checklists` | the QC checklists (`qc/artifacts/`, `qc/code/`) | `sqa-qc-framework` | a commit, pinned as the `qc/` submodule |
|
|
||||||
| `TirSystem/sqa-qc-framework` | the skills, process documents, catalog, templates (`templates/ci/`) and scripts | every project, as the `framework/` submodule | a release tag (`v0.5.2`, ...) |
|
|
||||||
| `TirSystem/github-action` | the shared workflows (`quality-checks`, `release`), the scoped workflows (`publish-wiki`, `sync-github-metadata`) and the scripts they call (`tools/`) | every repository of the organisation, by `uses:` and `ref:` | a release tag and a moving major tag (`v1`), made by its release workflow |
|
|
||||||
| `TirSystem/sqa-and-qc` | the SQA and AI project management: project artifacts, review records, the traceability matrix | itself | the pull request that merges it |
|
|
||||||
|
|
||||||
Where a thing lives:
|
|
||||||
|
|
||||||
- A QC checklist, a convention or a template is changed in its owner above and
|
|
||||||
reaches this project by moving the pin. The review record of a checklist is
|
|
||||||
written in the project that reviews it (`docs/sqa/reviews/`), not in the
|
|
||||||
checklist repository.
|
|
||||||
- A workflow that other repositories call is changed in `github-action` and
|
|
||||||
released there. A workflow a project copies from `templates/ci/` is changed
|
|
||||||
in the framework; the project updates its copy after the pin moves.
|
|
||||||
- A script a workflow needs is a file (`tools/` of `github-action`, or
|
|
||||||
`scripts/` of the framework for the templates), never a long `run:` block
|
|
||||||
(`QC-WF-001` criterion 20).
|
|
||||||
- The workflows under `.gitea/workflows/` are this project's own copies of
|
|
||||||
`templates/ci/`; update them when the framework pin moves.
|
|
||||||
|
|
||||||
A change that spans repositories:
|
|
||||||
|
|
||||||
1. One pull request per repository, in dependency order: checklists, then
|
|
||||||
framework, then `github-action` and the projects. A pull request names the
|
|
||||||
ones it depends on in its description.
|
|
||||||
2. A downstream repository moves a pin only to a merged commit or tag of the
|
|
||||||
upstream, never to an unmerged branch.
|
|
||||||
3. Nothing is released or tagged until its dependencies are merged; a release
|
|
||||||
is a separate, deliberate step (the release workflow defaults to a dry run).
|
|
||||||
4. A related repository is added to the session as a working directory only
|
|
||||||
when the task needs it. In autonomous mode work in a related repository is
|
|
||||||
inside the accepted plan only if the plan names it; otherwise the run stops
|
|
||||||
and reports.
|
|
||||||
|
|
||||||
## Workflow order
|
## Workflow order
|
||||||
|
|
||||||
@@ -129,56 +25,37 @@ dry-run output of `bash framework/scripts/sync-project.sh`, and stop. The rule
|
|||||||
is defined once in `framework/process/plan-first-gate.md`.
|
is defined once in `framework/process/plan-first-gate.md`.
|
||||||
|
|
||||||
A "build X" request is planning-first: produce the plan and issues, then ask
|
A "build X" request is planning-first: produce the plan and issues, then ask
|
||||||
for a go-ahead (in autonomous mode, the acceptance of the plan is that
|
for a go-ahead. Only the user can waive the plan, in chat, for that request.
|
||||||
go-ahead). Only the user can waive the plan, in chat, for that request.
|
Before planning, find the Product Owner's language (the prompt, or the
|
||||||
A project with no baseline yet starts with the `create-project` skill.
|
`Languages` section of `docs/artifact-registry.md`); if neither states it, ask.
|
||||||
|
|
||||||
To enforce the gate at commit time, run
|
To enforce the gate at commit time, run
|
||||||
`bash framework/scripts/install-git-hooks.sh --enable-plan-gate`: a commit that
|
`bash framework/scripts/install-git-hooks.sh --enable-plan-gate`: a commit that
|
||||||
changes `src/` or `tests/` then needs a `Task: MIL-NNN#N` trailer.
|
changes `src/` or `tests/` then needs a `Task: MIL-NNN#N` trailer.
|
||||||
|
|
||||||
## Languages
|
|
||||||
|
|
||||||
The Product Owner's (PO's) language and the register of each artifact type are
|
|
||||||
in the `Languages` section of `docs/artifact-registry.md`. Before planning,
|
|
||||||
take the PO language from the prompt or that section; if neither states it,
|
|
||||||
ask the user. Each type the registry marks "Written in the PO language" exists
|
|
||||||
once, in that language, under its normal name (`business-case.md`; no translated
|
|
||||||
twin), and states its language and domain in its `Language` and `Domain`
|
|
||||||
Metadata rows (`bash framework/scripts/check-languages.sh --list` shows them for
|
|
||||||
every document). Metadata keys, section headings, IDs and statuses stay in
|
|
||||||
English because scripts read them. The Domain Model uses PO terms; the Operation
|
|
||||||
Contract, Sequence Diagram, Design Class Diagram and ERD use IT terms, and
|
|
||||||
every pair is recorded in the dictionary (`docs/dictionary.md`, `DICT`).
|
|
||||||
|
|
||||||
## Credentials
|
|
||||||
|
|
||||||
Scripts that call the git host (`sync-project.sh --apply`) need `GITEA_TOKEN`
|
|
||||||
from the gitignored `.env`; load it with `set -a; . ./.env; set +a` and never
|
|
||||||
print its values. Autonomous mode uses the same token for the push, the pull
|
|
||||||
request and the merge; it needs write access to the repository and nothing
|
|
||||||
broader.
|
|
||||||
|
|
||||||
Rules that apply to every document:
|
Rules that apply to every document:
|
||||||
|
|
||||||
1. Get the short name from `framework/registry/artifact-catalog.md` and the
|
1. Get the short name from `framework/registry/artifact-catalog.md` and the
|
||||||
next version from `docs/artifact-registry.md`. Create files with
|
next version from `docs/artifact-registry.md`. Create files with
|
||||||
`bash framework/scripts/new-artifact.sh <SHORT>`.
|
`bash framework/scripts/new-artifact.sh <SHORT>`.
|
||||||
2. Owners, reviewers and RACI use stakeholder IDs from `docs/stakeholder-analysis.md`
|
2. Owners, reviewers and RACI use stakeholder IDs from the project's
|
||||||
(`SA-001`), never invented role names.
|
Stakeholder Analysis, never invented role names.
|
||||||
3. Every QC criterion is tagged with an ISO/IEC 25010:2023 characteristic
|
3. Every QC criterion is tagged with an ISO/IEC 25010:2023 characteristic.
|
||||||
(`docs/business-case.md`, `BC-001`).
|
|
||||||
4. Every reviewed instance gets an `RC-*` record in `docs/sqa/reviews/`.
|
4. Every reviewed instance gets an `RC-*` record in `docs/sqa/reviews/`.
|
||||||
5. No project data in `framework/`; a QC checklist never links to a project
|
5. Do not edit `framework/` from this project; propose changes upstream.
|
||||||
artifact. Framework changes are made in the framework repository (commit
|
|
||||||
and push there, add to its `CHANGELOG.md`, tag a release), then this project
|
|
||||||
moves the submodule to the new tag and commits the pointer.
|
|
||||||
6. Every PR description closes the issues its work completes, one
|
6. Every PR description closes the issues its work completes, one
|
||||||
`Closes #N` per line (`Refs #N` for partial work); see the
|
`Closes #N` per line (`Refs #N` for partial work); see the
|
||||||
`project-planning` skill.
|
`project-planning` skill.
|
||||||
7. Every document's `## Version History` has `Change` and `Commit` columns and
|
7. Every document's `## Version History` has `Change` and `Commit` columns and
|
||||||
keeps the two latest rows. After committing, run
|
keeps the two latest rows. After committing, run
|
||||||
`framework/scripts/resolve-pending-commits.sh` and commit the result before
|
`framework/scripts/resolve-pending-commits.sh` and commit the result before
|
||||||
opening the PR (no amend). Supervised mode: never ask for or perform the
|
opening the PR (no amend). Never ask for or perform the merge: a reviewer
|
||||||
merge; a reviewer merges. Autonomous mode: the AI merges, as set out in
|
merges.
|
||||||
`Modes`.
|
8. Keep the wiki current. A PR that changes a workflow under `.gitea/`, a file
|
||||||
|
in `guides/` or `README.md` also updates the matching pages in `wiki/`:
|
||||||
|
run `python tools/build_wiki.py` for the English pages (the tests fail when
|
||||||
|
they are out of date) and update the Danish (`da`) and Bahasa Malaysia
|
||||||
|
(`ms`) pages once they exist, naming the English source they follow. List
|
||||||
|
the wiki pages the PR changed in its description, or say "No wiki pages
|
||||||
|
changed: <reason>". Never push to the wiki repository by hand: the publishing
|
||||||
|
workflow (MIL-005 task 8) copies `wiki/` there when the PR is merged.
|
||||||
|
|||||||
+10
-9
@@ -20,16 +20,17 @@ Each release lists its changes in these groups, in this order:
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Changed
|
### Added
|
||||||
|
|
||||||
- Every job has a `timeout-minutes` (5 to 15), so a hung step no longer holds a
|
- Reusable workflow `quality-checks-cpp.yml` (ADR-0008): `clang-format --dry-run
|
||||||
runner for the host's default limit.
|
-Werror` and `clang-tidy` on the tracked C and C++ files, at exact versions
|
||||||
- `actions/checkout` is referenced by the exact tag `v4.2.2` instead of the
|
(inputs `clang-format-version`, `clang-tidy-version`), with the inputs
|
||||||
moving tag `v4`. No change for callers.
|
`working-directory`, `std`, `compile-commands-dir` and `tools-ref`. The
|
||||||
- The framework pin moves from `v0.5.2` to `v0.6.2`, and the two long `run:`
|
repository needs a `.clang-format`.
|
||||||
blocks of this repository's own `sync-milestones.yml` call
|
- Reusable workflow `quality-checks-csharp.yml` (ADR-0008): `dotnet format
|
||||||
`framework/scripts/ci-publish-milestones.sh`. No change for callers of the
|
--verify-no-changes` and `dotnet build -warnaserror` on an exact .NET SDK 10
|
||||||
shared workflows.
|
or later (input `dotnet-sdk-version`, default `10.0.401`), with the inputs
|
||||||
|
`working-directory`, `project` and `tools-ref`.
|
||||||
|
|
||||||
## [1.1.0] - 2026-10-11
|
## [1.1.0] - 2026-10-11
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,19 @@ A collection of reusable Gitea Actions workflows for DevOps professionals and so
|
|||||||
|
|
||||||
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
|
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
|
||||||
|
|
||||||
It contains six workflows: metadata sync to the GitHub mirror, wiki publishing, shared quality checks, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#-workflows).
|
It contains eight workflows: metadata sync to the GitHub mirror, wiki publishing, shared quality checks for Python and shell, for C and C++ and for C#, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#-workflows).
|
||||||
|
|
||||||
## 🔧 Workflows
|
## 🔧 Workflows
|
||||||
|
|
||||||
Six workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
|
Eight workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
|
||||||
|
|
||||||
| Workflow | File | Kind | Starts on | Purpose |
|
| Workflow | File | Kind | Starts on | Purpose |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror |
|
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror |
|
||||||
| Publish wiki | [`publish-wiki.yml`](.gitea/scoped_workflows/publish-wiki.yml) | Scoped, opt-in with `wiki/.publish` | push to `main` that changes `wiki/**`, manual | Publishes the pages in `wiki/` to the repository wiki |
|
| Publish wiki | [`publish-wiki.yml`](.gitea/scoped_workflows/publish-wiki.yml) | Scoped, opt-in with `wiki/.publish` | push to `main` that changes `wiki/**`, manual | Publishes the pages in `wiki/` to the repository wiki |
|
||||||
| Quality checks | [`quality-checks.yml`](.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing |
|
| Quality checks | [`quality-checks.yml`](.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing |
|
||||||
|
| Quality checks C++ | [`quality-checks-cpp.yml`](.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing |
|
||||||
|
| Quality checks C# | [`quality-checks-csharp.yml`](.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing |
|
||||||
| Release | [`release.yml`](.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
|
| Release | [`release.yml`](.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
|
||||||
| Publish accepted milestones | [`sync-milestones.yml`](.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
|
| Publish accepted milestones | [`sync-milestones.yml`](.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
|
||||||
| Validate workflows | [`validate-workflows.yml`](.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests |
|
| Validate workflows | [`validate-workflows.yml`](.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests |
|
||||||
@@ -188,6 +190,96 @@ deactivate Workflow
|
|||||||
@enduml
|
@enduml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🧱 Quality checks C++
|
||||||
|
|
||||||
|
A reusable workflow for C and C++, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). The repository needs a `.clang-format`; a `.clang-tidy` and a compile database are optional.
|
||||||
|
|
||||||
|
#### 🔀 Workflow
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C and C++ (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-cpp" as Workflow
|
||||||
|
participant "PyPI" as PyPI
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or a version is not exact
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
|
||||||
|
PyPI --> Workflow: Tools installed
|
||||||
|
|
||||||
|
alt no C or C++ file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: clang-format --dry-run -Werror on all files
|
||||||
|
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🟣 Quality checks C#
|
||||||
|
|
||||||
|
A reusable workflow for C#, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). It installs the exact .NET SDK given by `dotnet-sdk-version` (10 or later) and needs the network for the package restore.
|
||||||
|
|
||||||
|
#### 🔀 Workflow
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C# (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-csharp" as Workflow
|
||||||
|
participant "dot.net\n(dotnet-install.sh)" as DotNet
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> DotNet: Install the exact .NET SDK version
|
||||||
|
DotNet --> Workflow: SDK installed
|
||||||
|
|
||||||
|
alt no C# file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no solution or project, or several without the project input
|
||||||
|
Workflow --> Caller: Fail with [configuration] that names them
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: dotnet format --verify-no-changes
|
||||||
|
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
### 🏷️ Release
|
### 🏷️ Release
|
||||||
|
|
||||||
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](guides/release-process.md)).
|
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](guides/release-process.md)).
|
||||||
|
|||||||
+1
-1
Submodule framework updated: 592c5fc9b7...4fd69c4970
@@ -0,0 +1,634 @@
|
|||||||
|
"""Offline tests for the reusable C++ and C# quality-check workflows (ADR-0008).
|
||||||
|
|
||||||
|
The shell steps run with stub tools in a throwaway git repository, so no
|
||||||
|
network, no clang and no .NET SDK are needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from workflow_source import REPO_ROOT
|
||||||
|
|
||||||
|
CPP_WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks-cpp.yml"
|
||||||
|
CSHARP_WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks-csharp.yml"
|
||||||
|
CPP_SCRIPT = REPO_ROOT / "tools" / "quality-checks-cpp.sh"
|
||||||
|
CSHARP_SCRIPT = REPO_ROOT / "tools" / "quality-checks-csharp.sh"
|
||||||
|
BASH = shutil.which("bash")
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(BASH is None, reason="bash is needed to run the workflow steps")
|
||||||
|
|
||||||
|
# A script run: the finished process and the tool calls the stubs logged.
|
||||||
|
Run = tuple[subprocess.CompletedProcess[str], list[str]]
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: Path) -> dict[str, Any]: # parsed YAML has no fixed shape
|
||||||
|
return yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def inputs_of(path: Path) -> dict[str, Any]:
|
||||||
|
return load(path)[True]["workflow_call"]["inputs"] # PyYAML reads `on` as True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"workflow, script, job",
|
||||||
|
[
|
||||||
|
(CPP_WORKFLOW, "quality-checks-cpp.sh", "quality-checks-cpp"),
|
||||||
|
(CSHARP_WORKFLOW, "quality-checks-csharp.sh", "quality-checks-csharp"),
|
||||||
|
],
|
||||||
|
ids=["cpp", "csharp"],
|
||||||
|
)
|
||||||
|
class TestDefinition:
|
||||||
|
def test_has_least_privilege_and_needs_no_secret(self, workflow: Path, script: str, job: str) -> None:
|
||||||
|
document = load(workflow)
|
||||||
|
assert document["jobs"][job]["permissions"] == {"contents": "read"}
|
||||||
|
assert "secrets" not in workflow.read_text(encoding="utf-8").replace("needs no secret", "")
|
||||||
|
|
||||||
|
def test_embeds_no_script_and_runs_the_file_in_three_steps(
|
||||||
|
self,
|
||||||
|
workflow: Path,
|
||||||
|
script: str,
|
||||||
|
job: str,
|
||||||
|
) -> None:
|
||||||
|
steps = load(workflow)["jobs"][job]["steps"]
|
||||||
|
commands = [step["run"] for step in steps if "run" in step]
|
||||||
|
assert commands == [f"bash shared-scripts/tools/{script} {c}" for c in ("validate", "install", "run")]
|
||||||
|
|
||||||
|
def test_fetches_the_script_at_the_tools_ref_input(self, workflow: Path, script: str, job: str) -> None:
|
||||||
|
steps = load(workflow)["jobs"][job]["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",
|
||||||
|
}
|
||||||
|
assert inputs_of(workflow)["tools-ref"]["default"] == "v1"
|
||||||
|
|
||||||
|
def test_inputs_reach_the_script_only_through_the_environment(
|
||||||
|
self,
|
||||||
|
workflow: Path,
|
||||||
|
script: str,
|
||||||
|
job: str,
|
||||||
|
) -> None:
|
||||||
|
for step in load(workflow)["jobs"][job]["steps"]:
|
||||||
|
assert "${{" not in step.get("run", "")
|
||||||
|
|
||||||
|
def test_script_is_a_file_that_parses(self, workflow: Path, script: str, job: str) -> None:
|
||||||
|
result = subprocess.run(
|
||||||
|
[BASH, "-n", str(REPO_ROOT / "tools" / script)], text=True, capture_output=True
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, result.stderr
|
||||||
|
|
||||||
|
def test_unknown_command_is_refused(self, workflow: Path, script: str, job: str) -> None:
|
||||||
|
result = subprocess.run(
|
||||||
|
[BASH, str(REPO_ROOT / "tools" / script), "bogus"], text=True, capture_output=True
|
||||||
|
)
|
||||||
|
assert result.returncode == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpp_inputs_and_exact_defaults() -> None:
|
||||||
|
inputs = inputs_of(CPP_WORKFLOW)
|
||||||
|
assert set(inputs) == {
|
||||||
|
"working-directory",
|
||||||
|
"std",
|
||||||
|
"compile-commands-dir",
|
||||||
|
"clang-format-version",
|
||||||
|
"clang-tidy-version",
|
||||||
|
"tools-ref",
|
||||||
|
}
|
||||||
|
assert inputs["std"]["default"] == "c++20"
|
||||||
|
assert inputs["compile-commands-dir"]["default"] == ""
|
||||||
|
assert inputs["clang-format-version"]["default"] == "23.1.3"
|
||||||
|
assert inputs["clang-tidy-version"]["default"] == "22.1.8"
|
||||||
|
|
||||||
|
|
||||||
|
def test_csharp_inputs_and_exact_sdk_default() -> None:
|
||||||
|
inputs = inputs_of(CSHARP_WORKFLOW)
|
||||||
|
assert set(inputs) == {"working-directory", "project", "dotnet-sdk-version", "tools-ref"}
|
||||||
|
assert inputs["dotnet-sdk-version"]["default"] == "10.0.401"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpp_script_and_workflow_do_not_mention_cppcheck() -> None:
|
||||||
|
for path in (CPP_SCRIPT, CPP_WORKFLOW):
|
||||||
|
assert "cppcheck" not in path.read_text(encoding="utf-8").lower(), path.name
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------- helpers
|
||||||
|
def make_repo(tmp_path: Path) -> Path:
|
||||||
|
root = tmp_path / "repo"
|
||||||
|
root.mkdir()
|
||||||
|
subprocess.run(["git", "init", "-q"], cwd=root, check=True)
|
||||||
|
return root
|
||||||
|
|
||||||
|
|
||||||
|
def add_files(root: Path, *relative: str) -> None:
|
||||||
|
for name in relative:
|
||||||
|
path = root / name
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text("x\n", encoding="utf-8", newline="\n")
|
||||||
|
subprocess.run(["git", "add", "-A"], cwd=root, check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def stub(directory: Path, name: str) -> Path:
|
||||||
|
path = directory / name
|
||||||
|
path.write_text(
|
||||||
|
"#!/usr/bin/env bash\n"
|
||||||
|
'name="$(basename "$0")"\n'
|
||||||
|
'[ "$name" = dotnet ] && name="dotnet $1"\n'
|
||||||
|
'echo "$name :: $*" >> "$STUB_LOG"\n'
|
||||||
|
'case ",${STUB_FAIL}," in\n'
|
||||||
|
' *",$name,"*) echo "src/a.cpp:3:1: stub finding from $name"; exit 1 ;;\n'
|
||||||
|
"esac\n"
|
||||||
|
"exit 0\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
newline="\n",
|
||||||
|
)
|
||||||
|
path.chmod(0o755)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def run_script(script: Path, command: str, cwd: Path, tmp_path: Path, **env: str) -> Run:
|
||||||
|
log = tmp_path / "tools.log"
|
||||||
|
environment = dict(os.environ)
|
||||||
|
environment.update({"STUB_LOG": log.as_posix(), "STUB_FAIL": ""})
|
||||||
|
environment.update(env)
|
||||||
|
result = subprocess.run(
|
||||||
|
[BASH, str(script), command], cwd=cwd, env=environment, text=True, capture_output=True
|
||||||
|
)
|
||||||
|
calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else []
|
||||||
|
return result, calls
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------- C++
|
||||||
|
@pytest.fixture
|
||||||
|
def cpp_bin(tmp_path: Path) -> Path:
|
||||||
|
directory = tmp_path / "bin"
|
||||||
|
directory.mkdir()
|
||||||
|
for tool in ("clang-format", "clang-tidy"):
|
||||||
|
stub(directory, tool)
|
||||||
|
return directory
|
||||||
|
|
||||||
|
|
||||||
|
def cpp_env(cpp_bin: Path, **extra: str) -> dict[str, str]:
|
||||||
|
values = {
|
||||||
|
"WORKING_DIRECTORY": ".",
|
||||||
|
"STD": "c++20",
|
||||||
|
"COMPILE_COMMANDS_DIR": "",
|
||||||
|
"CLANG_FORMAT_VERSION": "23.1.3",
|
||||||
|
"CLANG_TIDY_VERSION": "22.1.8",
|
||||||
|
"QC_VENV": "/nonexistent",
|
||||||
|
"QC_BIN": cpp_bin.as_posix(),
|
||||||
|
}
|
||||||
|
values.update(extra)
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class TestCppValidate:
|
||||||
|
def run(self, tmp_path: Path, cpp_bin: Path, **extra: str) -> Run:
|
||||||
|
return run_script(CPP_SCRIPT, "validate", tmp_path, tmp_path, **cpp_env(cpp_bin, **extra))
|
||||||
|
|
||||||
|
def test_accepts_the_defaults(self, tmp_path: Path, cpp_bin: Path) -> None:
|
||||||
|
result, _ = self.run(tmp_path, cpp_bin)
|
||||||
|
assert result.returncode == 0, result.stdout
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("std", ["c++17", "c++23", "gnu++20"])
|
||||||
|
def test_accepts_a_cpp_standard(self, tmp_path: Path, cpp_bin: Path, std: str) -> None:
|
||||||
|
assert self.run(tmp_path, cpp_bin, STD=std)[0].returncode == 0
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("std", ["", "c17", "c++", "latest", "c++20; rm"])
|
||||||
|
def test_rejects_a_value_that_is_not_a_cpp_standard(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
std: str,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(tmp_path, cpp_bin, STD=std)
|
||||||
|
assert result.returncode == 1 and "[configuration]" in result.stdout
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("directory", ["/etc", "..", "../x", "a/../..", ""])
|
||||||
|
def test_rejects_a_working_directory_outside_the_repository(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
directory: str,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(tmp_path, cpp_bin, WORKING_DIRECTORY=directory)
|
||||||
|
assert result.returncode == 1 and "working-directory" in result.stdout
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("directory", ["/tmp/build", "../build"])
|
||||||
|
def test_rejects_a_compile_commands_dir_outside_the_repository(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
directory: str,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(tmp_path, cpp_bin, COMPILE_COMMANDS_DIR=directory)
|
||||||
|
assert result.returncode == 1 and "compile-commands-dir" in result.stdout
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", ["CLANG_FORMAT_VERSION", "CLANG_TIDY_VERSION"])
|
||||||
|
@pytest.mark.parametrize("version", ["", "latest", "23", ">=23", "23.1.3 --pre"])
|
||||||
|
def test_rejects_a_version_that_is_not_exact(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
name: str,
|
||||||
|
version: str,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(tmp_path, cpp_bin, **{name: version})
|
||||||
|
assert result.returncode == 1 and "exact version" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
class TestCppRun:
|
||||||
|
@pytest.fixture
|
||||||
|
def repo(self, tmp_path: Path) -> Path:
|
||||||
|
root = make_repo(tmp_path)
|
||||||
|
add_files(root, ".clang-format")
|
||||||
|
return root
|
||||||
|
|
||||||
|
def run(self, repo: Path, tmp_path: Path, cpp_bin: Path, **extra: str) -> Run:
|
||||||
|
return run_script(CPP_SCRIPT, "run", repo, tmp_path, **cpp_env(cpp_bin, **extra))
|
||||||
|
|
||||||
|
def test_runs_clang_format_once_and_clang_tidy_per_language(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "src/a.cpp", "src/a.hpp", "lib/c.c")
|
||||||
|
result, calls = self.run(repo, tmp_path, cpp_bin)
|
||||||
|
|
||||||
|
assert result.returncode == 0, result.stdout + result.stderr
|
||||||
|
assert [c.split(" :: ")[0] for c in calls] == ["clang-format", "clang-tidy", "clang-tidy"]
|
||||||
|
assert "--dry-run -Werror" in calls[0]
|
||||||
|
assert "src/a.cpp" in calls[0] and "lib/c.c" in calls[0]
|
||||||
|
assert "-- -std=c++20" in calls[1] and "lib/c.c" not in calls[1]
|
||||||
|
assert "-- -std=c17" in calls[2] and "lib/c.c" in calls[2]
|
||||||
|
assert "All selected checks passed." in result.stdout
|
||||||
|
|
||||||
|
def test_the_std_input_reaches_clang_tidy(self, repo: Path, tmp_path: Path, cpp_bin: Path) -> None:
|
||||||
|
add_files(repo, "a.cpp")
|
||||||
|
_, calls = self.run(repo, tmp_path, cpp_bin, STD="c++17")
|
||||||
|
assert "-- -std=c++17" in calls[1]
|
||||||
|
|
||||||
|
def test_a_compile_database_is_used_instead_of_a_standard(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "a.cpp", "build/compile_commands.json")
|
||||||
|
result, calls = self.run(repo, tmp_path, cpp_bin, COMPILE_COMMANDS_DIR="build")
|
||||||
|
assert result.returncode == 0, result.stdout
|
||||||
|
assert len(calls) == 2
|
||||||
|
assert "-p build" in calls[1] and "-std=" not in calls[1]
|
||||||
|
|
||||||
|
def test_a_missing_compile_database_is_a_configuration_error(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "a.cpp")
|
||||||
|
result, calls = self.run(repo, tmp_path, cpp_bin, COMPILE_COMMANDS_DIR="build")
|
||||||
|
assert result.returncode == 1 and "compile_commands.json" in result.stdout
|
||||||
|
assert calls == []
|
||||||
|
|
||||||
|
def test_a_missing_clang_format_file_is_a_configuration_error(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
repo = make_repo(tmp_path)
|
||||||
|
add_files(repo, "a.cpp")
|
||||||
|
result, calls = self.run(repo, tmp_path, cpp_bin)
|
||||||
|
assert result.returncode == 1
|
||||||
|
assert "[configuration]" in result.stdout and ".clang-format" in result.stdout
|
||||||
|
assert calls == []
|
||||||
|
|
||||||
|
def test_a_clang_format_file_above_the_working_directory_is_found(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "service/a.cpp")
|
||||||
|
result, _ = run_script(
|
||||||
|
CPP_SCRIPT,
|
||||||
|
"run",
|
||||||
|
repo,
|
||||||
|
tmp_path,
|
||||||
|
**cpp_env(cpp_bin, WORKING_DIRECTORY="service"),
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, result.stdout
|
||||||
|
|
||||||
|
def test_a_failing_tool_fails_the_job_and_the_other_still_runs(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "src/a.cpp")
|
||||||
|
result, calls = self.run(repo, tmp_path, cpp_bin, STUB_FAIL="clang-format")
|
||||||
|
assert result.returncode == 1
|
||||||
|
assert len(calls) == 2, "clang-tidy runs after clang-format failed"
|
||||||
|
assert "::group::clang-format" in result.stdout
|
||||||
|
assert "src/a.cpp:3:1: stub finding from clang-format" in result.stdout
|
||||||
|
assert "FAIL clang-format" in result.stdout and "PASS clang-tidy (C++)" in result.stdout
|
||||||
|
assert "Quality checks failed: clang-format" in result.stdout
|
||||||
|
|
||||||
|
def test_a_repository_without_c_or_cpp_files_passes_with_a_notice(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
repo = make_repo(tmp_path)
|
||||||
|
add_files(repo, "README.md")
|
||||||
|
result, calls = self.run(repo, tmp_path, cpp_bin)
|
||||||
|
assert result.returncode == 0 and calls == []
|
||||||
|
assert "No C or C++ files" in result.stdout
|
||||||
|
|
||||||
|
def test_only_tracked_files_under_the_working_directory_are_checked(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "top.cpp", "service/api.cpp")
|
||||||
|
(repo / "service" / "untracked.cpp").write_text("x\n", encoding="utf-8")
|
||||||
|
_, calls = self.run(repo, tmp_path, cpp_bin, WORKING_DIRECTORY="service")
|
||||||
|
text = "\n".join(calls)
|
||||||
|
assert "api.cpp" in text and "top.cpp" not in text and "untracked" not in text
|
||||||
|
|
||||||
|
def test_no_file_is_changed(self, repo: Path, tmp_path: Path, cpp_bin: Path) -> None:
|
||||||
|
add_files(repo, "a.cpp")
|
||||||
|
|
||||||
|
def snapshot() -> dict[str, bytes]:
|
||||||
|
return {p.name: p.read_bytes() for p in repo.iterdir() if p.is_file()}
|
||||||
|
|
||||||
|
before = snapshot()
|
||||||
|
self.run(repo, tmp_path, cpp_bin, STUB_FAIL="clang-tidy (C++)")
|
||||||
|
assert snapshot() == before
|
||||||
|
|
||||||
|
def test_a_missing_working_directory_is_a_configuration_error(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
cpp_bin: Path,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(repo, tmp_path, cpp_bin, WORKING_DIRECTORY="nope")
|
||||||
|
assert result.returncode == 1 and "[configuration]" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpp_install_pins_both_tools_to_the_exact_versions(tmp_path: Path) -> None:
|
||||||
|
"""`install` creates the venv and installs `==` versions; checked with a fake pip."""
|
||||||
|
venv = tmp_path / "venv"
|
||||||
|
fake_python = tmp_path / "bin"
|
||||||
|
fake_python.mkdir()
|
||||||
|
log = tmp_path / "pip.log"
|
||||||
|
python3 = fake_python / "python3"
|
||||||
|
python3.write_text(
|
||||||
|
"#!/usr/bin/env bash\n"
|
||||||
|
'mkdir -p "$3/bin"\n'
|
||||||
|
"printf '#!/usr/bin/env bash\\necho \"$*\" >> \"%s\"\\n' > \"$3/bin/pip\"\n" % log.as_posix()
|
||||||
|
+ 'chmod +x "$3/bin/pip"\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
newline="\n",
|
||||||
|
)
|
||||||
|
python3.chmod(0o755)
|
||||||
|
environment = dict(os.environ)
|
||||||
|
environment["PATH"] = f"{fake_python.as_posix()}:{environment['PATH']}"
|
||||||
|
environment.update(
|
||||||
|
{
|
||||||
|
"QC_VENV": venv.as_posix(),
|
||||||
|
"CLANG_FORMAT_VERSION": "23.1.3",
|
||||||
|
"CLANG_TIDY_VERSION": "22.1.8",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
result = subprocess.run(
|
||||||
|
[BASH, str(CPP_SCRIPT), "install"], env=environment, text=True, capture_output=True
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, result.stdout + result.stderr
|
||||||
|
installed = log.read_text(encoding="utf-8")
|
||||||
|
assert "clang-format==23.1.3" in installed
|
||||||
|
assert "clang-tidy==22.1.8" in installed
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------- C#
|
||||||
|
@pytest.fixture
|
||||||
|
def dotnet_stub(tmp_path: Path) -> Path:
|
||||||
|
directory = tmp_path / "dotnet-bin"
|
||||||
|
directory.mkdir()
|
||||||
|
return stub(directory, "dotnet")
|
||||||
|
|
||||||
|
|
||||||
|
def csharp_env(dotnet_stub: Path, tmp_path: Path, **extra: str) -> dict[str, str]:
|
||||||
|
values = {
|
||||||
|
"WORKING_DIRECTORY": ".",
|
||||||
|
"PROJECT": "",
|
||||||
|
"DOTNET_SDK_VERSION": "10.0.401",
|
||||||
|
"DOTNET_ROOT": (tmp_path / "dotnet-root").as_posix(),
|
||||||
|
"QC_DOTNET": dotnet_stub.as_posix(),
|
||||||
|
}
|
||||||
|
values.update(extra)
|
||||||
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
class TestCsharpValidate:
|
||||||
|
def run(self, tmp_path: Path, dotnet_stub: Path, **extra: str) -> Run:
|
||||||
|
return run_script(
|
||||||
|
CSHARP_SCRIPT, "validate", tmp_path, tmp_path, **csharp_env(dotnet_stub, tmp_path, **extra)
|
||||||
|
)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("version", ["10.0.401", "10.0.100", "11.0.100", "100.1.2"])
|
||||||
|
def test_accepts_an_exact_sdk_of_version_10_or_later(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
version: str,
|
||||||
|
) -> None:
|
||||||
|
assert self.run(tmp_path, dotnet_stub, DOTNET_SDK_VERSION=version)[0].returncode == 0
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"version",
|
||||||
|
["", "8.0.425", "9.0.100", "10.0", "10", "10.0.x", "latest", "10.0.401-rc.1", "10.0.401 --verbose"],
|
||||||
|
)
|
||||||
|
def test_rejects_an_old_or_inexact_sdk(self, tmp_path: Path, dotnet_stub: Path, version: str) -> None:
|
||||||
|
result, _ = self.run(tmp_path, dotnet_stub, DOTNET_SDK_VERSION=version)
|
||||||
|
assert result.returncode == 1
|
||||||
|
assert "[configuration]" in result.stdout and "dotnet-sdk-version" in result.stdout
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("directory", ["/etc", "..", "../x", ""])
|
||||||
|
def test_rejects_a_working_directory_outside_the_repository(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
directory: str,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(tmp_path, dotnet_stub, WORKING_DIRECTORY=directory)
|
||||||
|
assert result.returncode == 1 and "working-directory" in result.stdout
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("project", ["/etc/x.sln", "../x.sln"])
|
||||||
|
def test_rejects_a_project_outside_the_working_directory(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
project: str,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(tmp_path, dotnet_stub, PROJECT=project)
|
||||||
|
assert result.returncode == 1 and "project" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
class TestCsharpInstall:
|
||||||
|
def test_installs_the_exact_version_with_the_official_script(
|
||||||
|
self,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
root = tmp_path / "dotnet-root"
|
||||||
|
installer = tmp_path / "dotnet-install.sh"
|
||||||
|
installer.write_text(
|
||||||
|
"#!/usr/bin/env bash\n"
|
||||||
|
'echo "$*" >> "$STUB_LOG"\n'
|
||||||
|
'dir=""\n'
|
||||||
|
'while [ "$#" -gt 0 ]; do [ "$1" = --install-dir ] && dir="$2"; shift; done\n'
|
||||||
|
'mkdir -p "$dir"\n'
|
||||||
|
"printf '#!/usr/bin/env bash\\necho 10.0.401\\n' > \"$dir/dotnet\"\n"
|
||||||
|
'chmod +x "$dir/dotnet"\n',
|
||||||
|
encoding="utf-8",
|
||||||
|
newline="\n",
|
||||||
|
)
|
||||||
|
result, calls = run_script(
|
||||||
|
CSHARP_SCRIPT,
|
||||||
|
"install",
|
||||||
|
tmp_path,
|
||||||
|
tmp_path,
|
||||||
|
**csharp_env(dotnet_stub, tmp_path, QC_DOTNET_INSTALL_SCRIPT=installer.as_posix()),
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, result.stdout + result.stderr
|
||||||
|
assert calls and "--version 10.0.401" in calls[0]
|
||||||
|
assert f"--install-dir {root.as_posix()}" in calls[0]
|
||||||
|
assert "10.0.401" in result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
class TestCsharpRun:
|
||||||
|
@pytest.fixture
|
||||||
|
def repo(self, tmp_path: Path) -> Path:
|
||||||
|
return make_repo(tmp_path)
|
||||||
|
|
||||||
|
def run(self, repo: Path, tmp_path: Path, dotnet_stub: Path, **extra: str) -> Run:
|
||||||
|
return run_script(
|
||||||
|
CSHARP_SCRIPT, "run", repo, tmp_path, **csharp_env(dotnet_stub, tmp_path, **extra)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_runs_format_and_build_on_the_single_solution(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "App.sln", "src/App.csproj", "src/Program.cs")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub)
|
||||||
|
|
||||||
|
assert result.returncode == 0, result.stdout + result.stderr
|
||||||
|
assert [c.split(" :: ")[0] for c in calls] == ["dotnet format", "dotnet build"]
|
||||||
|
assert "App.sln" in calls[0] and "--verify-no-changes" in calls[0]
|
||||||
|
assert "App.sln" in calls[1] and "-warnaserror" in calls[1]
|
||||||
|
assert "All selected checks passed." in result.stdout
|
||||||
|
|
||||||
|
def test_uses_the_single_project_when_there_is_no_solution(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "src/App.csproj", "src/Program.cs")
|
||||||
|
_, calls = self.run(repo, tmp_path, dotnet_stub)
|
||||||
|
assert "src/App.csproj" in calls[0]
|
||||||
|
|
||||||
|
def test_several_projects_without_a_solution_need_the_project_input(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "a/A.csproj", "b/B.csproj", "a/P.cs")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub)
|
||||||
|
assert result.returncode == 1 and calls == []
|
||||||
|
assert "[configuration]" in result.stdout
|
||||||
|
assert "a/A.csproj" in result.stdout and "b/B.csproj" in result.stdout
|
||||||
|
|
||||||
|
def test_the_project_input_picks_one(self, repo: Path, tmp_path: Path, dotnet_stub: Path) -> None:
|
||||||
|
add_files(repo, "a/A.csproj", "b/B.csproj", "a/P.cs")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub, PROJECT="b/B.csproj")
|
||||||
|
assert result.returncode == 0 and "b/B.csproj" in calls[0]
|
||||||
|
|
||||||
|
def test_a_missing_project_file_is_a_configuration_error(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "a/P.cs")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub, PROJECT="nope.csproj")
|
||||||
|
assert result.returncode == 1 and "does not exist" in result.stdout and calls == []
|
||||||
|
|
||||||
|
def test_csharp_files_without_a_project_are_a_configuration_error(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "Program.cs")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub)
|
||||||
|
assert result.returncode == 1 and calls == []
|
||||||
|
assert "[configuration]" in result.stdout and "no solution or project" in result.stdout
|
||||||
|
|
||||||
|
def test_a_repository_without_csharp_files_passes_with_a_notice(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "README.md")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub)
|
||||||
|
assert result.returncode == 0 and calls == []
|
||||||
|
assert "No C# files" in result.stdout
|
||||||
|
|
||||||
|
def test_a_failing_tool_fails_the_job_and_the_other_still_runs(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "App.sln", "Program.cs")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub, STUB_FAIL="dotnet format")
|
||||||
|
assert result.returncode == 1 and len(calls) == 2
|
||||||
|
assert "::group::dotnet format --verify-no-changes" in result.stdout
|
||||||
|
assert "FAIL dotnet format --verify-no-changes" in result.stdout
|
||||||
|
assert "PASS dotnet build -warnaserror" in result.stdout
|
||||||
|
assert "Quality checks failed: dotnet format --verify-no-changes" in result.stdout
|
||||||
|
|
||||||
|
def test_only_files_under_the_working_directory_are_considered(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
add_files(repo, "Top.sln", "svc/Svc.csproj", "svc/P.cs", "Top.cs")
|
||||||
|
result, calls = self.run(repo, tmp_path, dotnet_stub, WORKING_DIRECTORY="svc")
|
||||||
|
assert result.returncode == 0
|
||||||
|
assert "Svc.csproj" in calls[0] and "Top.sln" not in calls[0]
|
||||||
|
|
||||||
|
def test_a_missing_working_directory_is_a_configuration_error(
|
||||||
|
self,
|
||||||
|
repo: Path,
|
||||||
|
tmp_path: Path,
|
||||||
|
dotnet_stub: Path,
|
||||||
|
) -> None:
|
||||||
|
result, _ = self.run(repo, tmp_path, dotnet_stub, WORKING_DIRECTORY="nope")
|
||||||
|
assert result.returncode == 1 and "[configuration]" in result.stdout
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Steps of the reusable workflow .gitea/workflows/quality-checks-cpp.yml
|
||||||
|
# (ADR-0008). The workflow checks out the calling repository, fetches this file
|
||||||
|
# into shared-scripts/ and runs one command per step:
|
||||||
|
#
|
||||||
|
# quality-checks-cpp.sh validate check the inputs, before anything is installed
|
||||||
|
# quality-checks-cpp.sh install install the exact clang tool versions into a venv
|
||||||
|
# quality-checks-cpp.sh run run clang-format and clang-tidy on the tracked files
|
||||||
|
#
|
||||||
|
# Settings come from the environment, never from interpolated expressions:
|
||||||
|
# WORKING_DIRECTORY, STD, COMPILE_COMMANDS_DIR, CLANG_FORMAT_VERSION,
|
||||||
|
# CLANG_TIDY_VERSION, QC_VENV (and QC_BIN for tests).
|
||||||
|
# Every failure is printed as ::error::[category] message.
|
||||||
|
|
||||||
|
C_STANDARD="${C_STANDARD:-c17}"
|
||||||
|
|
||||||
|
validate_inputs() {
|
||||||
|
set -u
|
||||||
|
case "$WORKING_DIRECTORY" in
|
||||||
|
"" | /* | .. | ../* | */.. | */../*)
|
||||||
|
echo "::error::[configuration] working-directory must be a relative path inside the repository."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if ! [[ "$STD" =~ ^(c\+\+|gnu\+\+)[0-9]{2}$ ]]; then
|
||||||
|
echo "::error::[configuration] std '$STD' is not a C++ standard such as c++20 or gnu++20."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
case "$COMPILE_COMMANDS_DIR" in
|
||||||
|
/* | .. | ../* | */.. | */../*)
|
||||||
|
echo "::error::[configuration] compile-commands-dir must be a relative path inside the repository."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
local name version
|
||||||
|
for name in CLANG_FORMAT_VERSION CLANG_TIDY_VERSION; do
|
||||||
|
version="${!name}"
|
||||||
|
if ! [[ "$version" =~ ^[0-9]+(\.[0-9]+){1,3}$ ]]; then
|
||||||
|
echo "::error::[configuration] ${name,,} '$version' is not an exact version such as 23.1.3."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "Checking C and C++ in ${WORKING_DIRECTORY} with std ${STD}"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_tools() {
|
||||||
|
set -eu
|
||||||
|
python3 -m venv "$QC_VENV"
|
||||||
|
"$QC_VENV/bin/pip" install --quiet \
|
||||||
|
"clang-format==${CLANG_FORMAT_VERSION}" "clang-tidy==${CLANG_TIDY_VERSION}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# A .clang-format must exist in the working directory or above it, up to the
|
||||||
|
# top of the repository: clang-format finds its style the same way.
|
||||||
|
find_clang_format_config() {
|
||||||
|
local top dir parent
|
||||||
|
top="$(cd "$(git rev-parse --show-toplevel)" && pwd -P)"
|
||||||
|
dir="$(pwd -P)"
|
||||||
|
while true; do
|
||||||
|
if [ -f "$dir/.clang-format" ] || [ -f "$dir/_clang-format" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
parent="$(dirname "$dir")"
|
||||||
|
# stop at the top of the repository, or at the file system root
|
||||||
|
if [ "$dir" = "$top" ] || [ "$parent" = "$dir" ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
dir="$parent"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
local all_files cpp_files c_files
|
||||||
|
mapfile -t all_files < <(git ls-files -- '*.c' '*.cc' '*.cpp' '*.cxx' '*.h' '*.hh' '*.hpp' '*.hxx')
|
||||||
|
if [ "${#all_files[@]}" -eq 0 ]; then
|
||||||
|
echo "::notice::No C or C++ files in '$WORKING_DIRECTORY'; nothing was checked."
|
||||||
|
echo "All selected checks passed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
if ! find_clang_format_config; then
|
||||||
|
echo "::error::[configuration] No .clang-format found in '$WORKING_DIRECTORY' or above it; add the style of the project."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -n "$COMPILE_COMMANDS_DIR" ] && [ ! -f "$COMPILE_COMMANDS_DIR/compile_commands.json" ]; then
|
||||||
|
echo "::error::[configuration] compile-commands-dir '$COMPILE_COMMANDS_DIR' has no compile_commands.json; build it before the call."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# .c files are C; everything else (including headers) is checked as C++.
|
||||||
|
mapfile -t c_files < <(git ls-files -- '*.c')
|
||||||
|
mapfile -t cpp_files < <(git ls-files -- '*.cc' '*.cpp' '*.cxx' '*.h' '*.hh' '*.hpp' '*.hxx')
|
||||||
|
|
||||||
|
local 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
|
||||||
|
}
|
||||||
|
|
||||||
|
run_tool "clang-format" "$QC_BIN/clang-format" --dry-run -Werror "${all_files[@]}"
|
||||||
|
|
||||||
|
if [ -n "$COMPILE_COMMANDS_DIR" ]; then
|
||||||
|
run_tool "clang-tidy" "$QC_BIN/clang-tidy" --quiet -p "$COMPILE_COMMANDS_DIR" "${all_files[@]}"
|
||||||
|
else
|
||||||
|
if [ "${#cpp_files[@]}" -gt 0 ]; then
|
||||||
|
run_tool "clang-tidy (C++)" "$QC_BIN/clang-tidy" --quiet "${cpp_files[@]}" -- "-std=${STD}"
|
||||||
|
fi
|
||||||
|
if [ "${#c_files[@]}" -gt 0 ]; then
|
||||||
|
run_tool "clang-tidy (C)" "$QC_BIN/clang-tidy" --quiet "${c_files[@]}" -- "-std=${C_STANDARD}"
|
||||||
|
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-cpp.sh validate|install|run" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Steps of the reusable workflow .gitea/workflows/quality-checks-csharp.yml
|
||||||
|
# (ADR-0008). The workflow checks out the calling repository, fetches this file
|
||||||
|
# into shared-scripts/ and runs one command per step:
|
||||||
|
#
|
||||||
|
# quality-checks-csharp.sh validate check the inputs, before anything is installed
|
||||||
|
# quality-checks-csharp.sh install install the exact .NET SDK version
|
||||||
|
# quality-checks-csharp.sh run run dotnet format and dotnet build on the project
|
||||||
|
#
|
||||||
|
# Settings come from the environment, never from interpolated expressions:
|
||||||
|
# WORKING_DIRECTORY, PROJECT, DOTNET_SDK_VERSION, DOTNET_ROOT (and
|
||||||
|
# QC_DOTNET_INSTALL_SCRIPT, QC_DOTNET for tests).
|
||||||
|
# Every failure is printed as ::error::[category] message.
|
||||||
|
|
||||||
|
DOTNET_INSTALL_URL="https://dot.net/v1/dotnet-install.sh"
|
||||||
|
|
||||||
|
validate_inputs() {
|
||||||
|
set -u
|
||||||
|
case "$WORKING_DIRECTORY" in
|
||||||
|
"" | /* | .. | ../* | */.. | */../*)
|
||||||
|
echo "::error::[configuration] working-directory must be a relative path inside the repository."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$PROJECT" in
|
||||||
|
/* | .. | ../* | */.. | */../*)
|
||||||
|
echo "::error::[configuration] project must be a relative path inside the working directory."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
# An exact stable version of the .NET SDK, 10 or later (no channel, no range).
|
||||||
|
if ! [[ "$DOTNET_SDK_VERSION" =~ ^([1-9][0-9]+)\.[0-9]+\.[0-9]+$ ]] || [ "${BASH_REMATCH[1]}" -lt 10 ]; then
|
||||||
|
echo "::error::[configuration] dotnet-sdk-version '$DOTNET_SDK_VERSION' is not an exact stable version 10 or later, such as 10.0.401."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Checking C# in ${WORKING_DIRECTORY} with .NET SDK ${DOTNET_SDK_VERSION}"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_sdk() {
|
||||||
|
set -eu
|
||||||
|
local script="${QC_DOTNET_INSTALL_SCRIPT:-}"
|
||||||
|
if [ -z "$script" ]; then
|
||||||
|
script="$(mktemp)"
|
||||||
|
curl -fsSL "$DOTNET_INSTALL_URL" -o "$script"
|
||||||
|
fi
|
||||||
|
bash "$script" --version "$DOTNET_SDK_VERSION" --install-dir "$DOTNET_ROOT" --no-path
|
||||||
|
"$DOTNET_ROOT/dotnet" --version
|
||||||
|
}
|
||||||
|
|
||||||
|
run_checks() {
|
||||||
|
set -u
|
||||||
|
if [ ! -d "$WORKING_DIRECTORY" ]; then
|
||||||
|
echo "::error::[configuration] working-directory '$WORKING_DIRECTORY' does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
cd "$WORKING_DIRECTORY"
|
||||||
|
local dotnet="${QC_DOTNET:-$DOTNET_ROOT/dotnet}"
|
||||||
|
|
||||||
|
local sources
|
||||||
|
mapfile -t sources < <(git ls-files -- '*.cs')
|
||||||
|
if [ "${#sources[@]}" -eq 0 ]; then
|
||||||
|
echo "::notice::No C# files in '$WORKING_DIRECTORY'; nothing was checked."
|
||||||
|
echo "All selected checks passed."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local target="$PROJECT" candidates
|
||||||
|
if [ -z "$target" ]; then
|
||||||
|
mapfile -t candidates < <(git ls-files -- '*.sln' '*.slnx')
|
||||||
|
if [ "${#candidates[@]}" -eq 0 ]; then
|
||||||
|
mapfile -t candidates < <(git ls-files -- '*.csproj')
|
||||||
|
fi
|
||||||
|
case "${#candidates[@]}" in
|
||||||
|
0)
|
||||||
|
echo "::error::[configuration] C# files exist but no solution or project was found in '$WORKING_DIRECTORY'; add one or set the input project."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
1) target="${candidates[0]}" ;;
|
||||||
|
*)
|
||||||
|
echo "::error::[configuration] Several solutions or projects were found (${candidates[*]}); set the input project."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ ! -f "$target" ]; then
|
||||||
|
echo "::error::[configuration] project '$target' does not exist in '$WORKING_DIRECTORY'."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Checking $target"
|
||||||
|
|
||||||
|
export DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
|
||||||
|
local 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
|
||||||
|
}
|
||||||
|
|
||||||
|
run_tool "dotnet format --verify-no-changes" "$dotnet" format "$target" --verify-no-changes
|
||||||
|
run_tool "dotnet build -warnaserror" "$dotnet" build "$target" --nologo -warnaserror
|
||||||
|
|
||||||
|
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_sdk ;;
|
||||||
|
run) run_checks ;;
|
||||||
|
*)
|
||||||
|
echo "::error::[configuration] usage: quality-checks-csharp.sh validate|install|run" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
+95
-3
@@ -1,4 +1,4 @@
|
|||||||
<!-- translation of Home.md, source sha256 c9c04abaf4ae -->
|
<!-- translation of Home.md, source sha256 5751829e99af -->
|
||||||
|
|
||||||
> Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
|
> Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
|
||||||
|
|
||||||
@@ -13,18 +13,20 @@ En samling af genbrugelige Gitea Actions-workflows til DevOps-professionelle og
|
|||||||
|
|
||||||
Dette repository er den centrale samling af genbrugelig automatisering til TirSystems repositories. Det primære repository ligger på `git.tirsystem.com` (Gitea) og spejles med push til GitHub.
|
Dette repository er den centrale samling af genbrugelig automatisering til TirSystems repositories. Det primære repository ligger på `git.tirsystem.com` (Gitea) og spejles med push til GitHub.
|
||||||
|
|
||||||
Det indeholder seks workflows: synkronisering af metadata til GitHub-spejlet, publicering af wiki, fælles kvalitetskontrol, et udgivelsesworkflow, publicering af accepterede milepæle og repositoryets egen validering. De er listet med formål og diagram under [Workflows](#workflows).
|
Det indeholder otte workflows: synkronisering af metadata til GitHub-spejlet, publicering af wiki, fælles kvalitetskontrol for Python og shell, for C og C++ og for C#, et udgivelsesworkflow, publicering af accepterede milepæle og repositoryets egen validering. De er listet med formål og diagram under [Workflows](#workflows).
|
||||||
|
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
Der er seks workflows i dette repository. Et **scoped** workflow leveres af instansen til alle repositories i TirSystem-organisationerne og kræver intet i repositoryet; et **genbrugeligt** workflow kaldes fra et repository med `uses:`; de øvrige kører kun her. Diagrammerne er PlantUML-kildekode og er på engelsk.
|
Der er otte workflows i dette repository. Et **scoped** workflow leveres af instansen til alle repositories i TirSystem-organisationerne og kræver intet i repositoryet; et **genbrugeligt** workflow kaldes fra et repository med `uses:`; de øvrige kører kun her. Diagrammerne er PlantUML-kildekode og er på engelsk.
|
||||||
|
|
||||||
| Workflow | Fil | Type | Starter ved | Formål |
|
| Workflow | Fil | Type | Starter ved | Formål |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push til `main`, manuelt, dagligt kl. 03:17 UTC | Kopierer beskrivelsen og emnerne for et Gitea-repository til dets GitHub push-spejl |
|
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push til `main`, manuelt, dagligt kl. 03:17 UTC | Kopierer beskrivelsen og emnerne for et Gitea-repository til dets GitHub push-spejl |
|
||||||
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, tilmelding med `wiki/.publish` | push til `main`, der ændrer `wiki/**`, manuelt | Publicerer siderne i `wiki/` til repositoryets wiki |
|
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, tilmelding med `wiki/.publish` | push til `main`, der ændrer `wiki/**`, manuelt | Publicerer siderne i `wiki/` til repositoryets wiki |
|
||||||
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører ruff, mypy, shellcheck og shfmt på de sporede filer og ændrer intet |
|
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører ruff, mypy, shellcheck og shfmt på de sporede filer og ændrer intet |
|
||||||
|
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører clang-format og clang-tidy i præcise versioner på de sporede C- og C++-filer og ændrer intet |
|
||||||
|
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører `dotnet format` og `dotnet build` på et præcist .NET SDK 10 eller nyere og ændrer intet |
|
||||||
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Kun dette repository | manuelt | Opretter versionstagget, udgivelsessiden og major-tagget efter kontrollerne |
|
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Kun dette repository | manuelt | Opretter versionstagget, udgivelsessiden og major-tagget efter kontrollerne |
|
||||||
| Publicering af accepterede milepæle | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Kun dette repository | push til `main`, der ændrer `docs/milestones/**` eller `docs/sqa/reviews/**`, manuelt (tørkørsel som standard) | Opretter eller opdaterer Gitea-milepælen og issues for hver milepæl, der er Accepted med en Go-gennemgang |
|
| Publicering af accepterede milepæle | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Kun dette repository | push til `main`, der ændrer `docs/milestones/**` eller `docs/sqa/reviews/**`, manuelt (tørkørsel som standard) | Opretter eller opdaterer Gitea-milepælen og issues for hver milepæl, der er Accepted med en Go-gennemgang |
|
||||||
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Kun dette repository | pull request, push til `main`, manuelt | Læser hvert workflow som YAML og kører offline-testene |
|
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Kun dette repository | pull request, push til `main`, manuelt | Læser hvert workflow som YAML og kører offline-testene |
|
||||||
@@ -185,6 +187,96 @@ deactivate Workflow
|
|||||||
@enduml
|
@enduml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Kvalitetskontrol C++
|
||||||
|
|
||||||
|
Et genbrugeligt workflow til C og C++, kaldt med `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). Repositoryet skal have en `.clang-format`; en `.clang-tidy` og en compile-database er valgfrie.
|
||||||
|
|
||||||
|
#### Workflow
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C and C++ (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-cpp" as Workflow
|
||||||
|
participant "PyPI" as PyPI
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or a version is not exact
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
|
||||||
|
PyPI --> Workflow: Tools installed
|
||||||
|
|
||||||
|
alt no C or C++ file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: clang-format --dry-run -Werror on all files
|
||||||
|
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kvalitetskontrol C#
|
||||||
|
|
||||||
|
Et genbrugeligt workflow til C#, kaldt med `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). Det installerer det præcise .NET SDK, som `dotnet-sdk-version` angiver (10 eller nyere), og skal bruge netværket til pakkegendannelsen.
|
||||||
|
|
||||||
|
#### Workflow
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C# (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-csharp" as Workflow
|
||||||
|
participant "dot.net\n(dotnet-install.sh)" as DotNet
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> DotNet: Install the exact .NET SDK version
|
||||||
|
DotNet --> Workflow: SDK installed
|
||||||
|
|
||||||
|
alt no C# file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no solution or project, or several without the project input
|
||||||
|
Workflow --> Caller: Fail with [configuration] that names them
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: dotnet format --verify-no-changes
|
||||||
|
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
### Udgivelse
|
### Udgivelse
|
||||||
|
|
||||||
Startes manuelt af en vedligeholder; med `dry_run` `true` (standard) kontrollerer det kun (se [udgivelsesprocessen](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
|
Startes manuelt af en vedligeholder; med `dry_run` `true` (standard) kontrollerer det kun (se [udgivelsesprocessen](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
|
||||||
|
|||||||
+94
-2
@@ -13,18 +13,20 @@ A collection of reusable Gitea Actions workflows for DevOps professionals and so
|
|||||||
|
|
||||||
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
|
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
|
||||||
|
|
||||||
It contains six workflows: metadata sync to the GitHub mirror, wiki publishing, shared quality checks, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#workflows).
|
It contains eight workflows: metadata sync to the GitHub mirror, wiki publishing, shared quality checks for Python and shell, for C and C++ and for C#, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#workflows).
|
||||||
|
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
Six workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
|
Eight workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
|
||||||
|
|
||||||
| Workflow | File | Kind | Starts on | Purpose |
|
| Workflow | File | Kind | Starts on | Purpose |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror |
|
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror |
|
||||||
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, opt-in with `wiki/.publish` | push to `main` that changes `wiki/**`, manual | Publishes the pages in `wiki/` to the repository wiki |
|
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, opt-in with `wiki/.publish` | push to `main` that changes `wiki/**`, manual | Publishes the pages in `wiki/` to the repository wiki |
|
||||||
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing |
|
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing |
|
||||||
|
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing |
|
||||||
|
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing |
|
||||||
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
|
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
|
||||||
| Publish accepted milestones | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
|
| Publish accepted milestones | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
|
||||||
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests |
|
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests |
|
||||||
@@ -185,6 +187,96 @@ deactivate Workflow
|
|||||||
@enduml
|
@enduml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Quality checks C++
|
||||||
|
|
||||||
|
A reusable workflow for C and C++, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). The repository needs a `.clang-format`; a `.clang-tidy` and a compile database are optional.
|
||||||
|
|
||||||
|
#### Workflow
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C and C++ (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-cpp" as Workflow
|
||||||
|
participant "PyPI" as PyPI
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or a version is not exact
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
|
||||||
|
PyPI --> Workflow: Tools installed
|
||||||
|
|
||||||
|
alt no C or C++ file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: clang-format --dry-run -Werror on all files
|
||||||
|
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quality checks C#
|
||||||
|
|
||||||
|
A reusable workflow for C#, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). It installs the exact .NET SDK given by `dotnet-sdk-version` (10 or later) and needs the network for the package restore.
|
||||||
|
|
||||||
|
#### Workflow
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C# (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-csharp" as Workflow
|
||||||
|
participant "dot.net\n(dotnet-install.sh)" as DotNet
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> DotNet: Install the exact .NET SDK version
|
||||||
|
DotNet --> Workflow: SDK installed
|
||||||
|
|
||||||
|
alt no C# file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no solution or project, or several without the project input
|
||||||
|
Workflow --> Caller: Fail with [configuration] that names them
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: dotnet format --verify-no-changes
|
||||||
|
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
### Release
|
### Release
|
||||||
|
|
||||||
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
|
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
|
||||||
|
|||||||
+95
-3
@@ -1,4 +1,4 @@
|
|||||||
<!-- translation of Home.md, source sha256 c9c04abaf4ae -->
|
<!-- translation of Home.md, source sha256 5751829e99af -->
|
||||||
|
|
||||||
> Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
|
> Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
|
||||||
|
|
||||||
@@ -13,18 +13,20 @@ Koleksi aliran kerja (workflow) Gitea Actions yang boleh digunakan semula untuk
|
|||||||
|
|
||||||
Repositori ini ialah koleksi pusat bagi automasi yang boleh digunakan semula untuk repositori TirSystem. Repositori utama berada di `git.tirsystem.com` (Gitea) dan dicerminkan dengan push ke GitHub.
|
Repositori ini ialah koleksi pusat bagi automasi yang boleh digunakan semula untuk repositori TirSystem. Repositori utama berada di `git.tirsystem.com` (Gitea) dan dicerminkan dengan push ke GitHub.
|
||||||
|
|
||||||
Ia mengandungi enam aliran kerja: penyegerakan metadata ke cermin GitHub, penerbitan wiki, semakan kualiti bersama, aliran kerja keluaran, penerbitan pencapaian (milestone) yang diterima dan pengesahan repositori sendiri. Ia disenaraikan dengan tujuan dan rajah di bawah [Aliran kerja](#aliran-kerja).
|
Ia mengandungi lapan aliran kerja: penyegerakan metadata ke cermin GitHub, penerbitan wiki, semakan kualiti bersama untuk Python dan shell, untuk C dan C++ serta untuk C#, aliran kerja keluaran, penerbitan pencapaian (milestone) yang diterima dan pengesahan repositori sendiri. Ia disenaraikan dengan tujuan dan rajah di bawah [Aliran kerja](#aliran-kerja).
|
||||||
|
|
||||||
|
|
||||||
## Aliran kerja
|
## Aliran kerja
|
||||||
|
|
||||||
Terdapat enam aliran kerja dalam repositori ini. Aliran kerja **scoped** dihantar oleh instans ke setiap repositori organisasi TirSystem dan tidak memerlukan apa-apa dalam repositori; aliran kerja **boleh guna semula** dipanggil oleh sebuah repositori dengan `uses:`; yang lain hanya berjalan di sini. Rajah ialah kod sumber PlantUML dan dalam bahasa Inggeris.
|
Terdapat lapan aliran kerja dalam repositori ini. Aliran kerja **scoped** dihantar oleh instans ke setiap repositori organisasi TirSystem dan tidak memerlukan apa-apa dalam repositori; aliran kerja **boleh guna semula** dipanggil oleh sebuah repositori dengan `uses:`; yang lain hanya berjalan di sini. Rajah ialah kod sumber PlantUML dan dalam bahasa Inggeris.
|
||||||
|
|
||||||
| Aliran kerja | Fail | Jenis | Bermula apabila | Tujuan |
|
| Aliran kerja | Fail | Jenis | Bermula apabila | Tujuan |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push ke `main`, manual, setiap hari 03:17 UTC | Menyalin penerangan dan topik repositori Gitea ke push mirror GitHub-nya |
|
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push ke `main`, manual, setiap hari 03:17 UTC | Menyalin penerangan dan topik repositori Gitea ke push mirror GitHub-nya |
|
||||||
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, pilih menyertai dengan `wiki/.publish` | push ke `main` yang mengubah `wiki/**`, manual | Menerbitkan halaman dalam `wiki/` ke wiki repositori |
|
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, pilih menyertai dengan `wiki/.publish` | push ke `main` yang mengubah `wiki/**`, manual | Menerbitkan halaman dalam `wiki/` ke wiki repositori |
|
||||||
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan ruff, mypy, shellcheck dan shfmt pada fail yang dijejaki dan tidak mengubah apa-apa |
|
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan ruff, mypy, shellcheck dan shfmt pada fail yang dijejaki dan tidak mengubah apa-apa |
|
||||||
|
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan clang-format dan clang-tidy pada versi tepat pada fail C dan C++ yang dijejaki dan tidak mengubah apa-apa |
|
||||||
|
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan `dotnet format` dan `dotnet build` pada .NET SDK 10 atau lebih baharu yang tepat dan tidak mengubah apa-apa |
|
||||||
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Repositori ini sahaja | manual | Mencipta tag versi, halaman keluaran dan tag major selepas semakan |
|
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Repositori ini sahaja | manual | Mencipta tag versi, halaman keluaran dan tag major selepas semakan |
|
||||||
| Menerbitkan milestone yang diterima | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/milestones/**` atau `docs/sqa/reviews/**`, manual (larian percubaan secara lalai) | Mencipta atau mengemas kini milestone Gitea dan isu bagi setiap milestone yang Accepted dengan semakan Go |
|
| Menerbitkan milestone yang diterima | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/milestones/**` atau `docs/sqa/reviews/**`, manual (larian percubaan secara lalai) | Mencipta atau mengemas kini milestone Gitea dan isu bagi setiap milestone yang Accepted dengan semakan Go |
|
||||||
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Repositori ini sahaja | pull request, push ke `main`, manual | Menghurai setiap aliran kerja sebagai YAML dan menjalankan ujian luar talian |
|
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Repositori ini sahaja | pull request, push ke `main`, manual | Menghurai setiap aliran kerja sebagai YAML dan menjalankan ujian luar talian |
|
||||||
@@ -185,6 +187,96 @@ deactivate Workflow
|
|||||||
@enduml
|
@enduml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Semakan kualiti C++
|
||||||
|
|
||||||
|
Aliran kerja boleh guna semula untuk C dan C++, dipanggil dengan `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). Repositori mesti mempunyai `.clang-format`; `.clang-tidy` dan pangkalan data kompilasi adalah pilihan.
|
||||||
|
|
||||||
|
#### Aliran kerja
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C and C++ (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-cpp" as Workflow
|
||||||
|
participant "PyPI" as PyPI
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or a version is not exact
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
|
||||||
|
PyPI --> Workflow: Tools installed
|
||||||
|
|
||||||
|
alt no C or C++ file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: clang-format --dry-run -Werror on all files
|
||||||
|
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Semakan kualiti C#
|
||||||
|
|
||||||
|
Aliran kerja boleh guna semula untuk C#, dipanggil dengan `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). Ia memasang .NET SDK tepat yang diberikan oleh `dotnet-sdk-version` (10 atau lebih baharu) dan memerlukan rangkaian untuk pemulihan pakej.
|
||||||
|
|
||||||
|
#### Aliran kerja
|
||||||
|
|
||||||
|
```plantuml
|
||||||
|
@startuml
|
||||||
|
title Shared quality checks for C# (reusable workflow)
|
||||||
|
|
||||||
|
autonumber
|
||||||
|
|
||||||
|
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
|
||||||
|
participant "Gitea Actions\nquality-checks-csharp" as Workflow
|
||||||
|
participant "dot.net\n(dotnet-install.sh)" as DotNet
|
||||||
|
|
||||||
|
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
|
||||||
|
activate Workflow
|
||||||
|
|
||||||
|
Workflow -> Workflow: Check out the caller and the shared script
|
||||||
|
Workflow -> Workflow: Validate the inputs
|
||||||
|
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
|
||||||
|
Workflow --> Caller: Fail with [configuration]
|
||||||
|
end
|
||||||
|
|
||||||
|
Workflow -> DotNet: Install the exact .NET SDK version
|
||||||
|
DotNet --> Workflow: SDK installed
|
||||||
|
|
||||||
|
alt no C# file is tracked
|
||||||
|
Workflow --> Caller: Notice: nothing was checked, pass
|
||||||
|
else no solution or project, or several without the project input
|
||||||
|
Workflow --> Caller: Fail with [configuration] that names them
|
||||||
|
else
|
||||||
|
Workflow -> Workflow: dotnet format --verify-no-changes
|
||||||
|
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
|
||||||
|
alt any tool failed
|
||||||
|
Workflow --> Caller: Fail with tool, file and line in the log
|
||||||
|
else
|
||||||
|
Workflow --> Caller: All selected checks passed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
deactivate Workflow
|
||||||
|
@enduml
|
||||||
|
```
|
||||||
|
|
||||||
### Keluaran
|
### Keluaran
|
||||||
|
|
||||||
Dimulakan secara manual oleh penyelenggara; dengan `dry_run` `true` (lalai) ia hanya menyemak (lihat [proses keluaran](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
|
Dimulakan secara manual oleh penyelenggara; dengan `dry_run` `true` (lalai) ia hanya menyemak (lihat [proses keluaran](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
|
||||||
|
|||||||
Reference in New Issue
Block a user