|
|
|
@@ -0,0 +1,568 @@
|
|
|
|
|
# Workflow Authoring Reference
|
|
|
|
|
|
|
|
|
|
> Reference document for AI agents and developers writing Gitea Actions workflows
|
|
|
|
|
> for the Local CI/CD System.
|
|
|
|
|
> Keep this document in sync with `runner/config.yaml` and `scripts/Invoke-CIJob.ps1`.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## Table of Contents
|
|
|
|
|
|
|
|
|
|
1. [System Overview](#1-system-overview)
|
|
|
|
|
2. [Runner Labels](#2-runner-labels)
|
|
|
|
|
3. [Shell Requirement](#3-shell-requirement)
|
|
|
|
|
4. [Environment Variables (Injected by Runner)](#4-environment-variables-injected-by-runner)
|
|
|
|
|
5. [Invoke-CIJob.ps1 Parameter Reference](#5-invoke-cijobps1-parameter-reference)
|
|
|
|
|
6. [URL Conventions](#6-url-conventions)
|
|
|
|
|
7. [Artifact Path Convention](#7-artifact-path-convention)
|
|
|
|
|
8. [Workflow Patterns](#8-workflow-patterns)
|
|
|
|
|
- [8.1 Windows build (host-clone mode)](#81-windows-build-host-clone-mode)
|
|
|
|
|
- [8.2 Linux build (host-clone mode)](#82-linux-build-host-clone-mode)
|
|
|
|
|
- [8.3 Windows build (UseGitClone mode)](#83-windows-build-usegitclone-mode)
|
|
|
|
|
- [8.4 Linux build (UseGitClone mode)](#84-linux-build-usegitclone-mode)
|
|
|
|
|
- [8.5 PSScriptAnalyzer lint](#85-psscriptanalyzer-lint)
|
|
|
|
|
- [8.6 Matrix build (multiple configs)](#86-matrix-build-multiple-configs)
|
|
|
|
|
9. [Checkout Step](#9-checkout-step)
|
|
|
|
|
10. [Timeouts](#10-timeouts)
|
|
|
|
|
11. [Common Mistakes](#11-common-mistakes)
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 1. System Overview
|
|
|
|
|
|
|
|
|
|
The CI system runs entirely on a local Windows 11 host. Gitea triggers jobs; act_runner
|
|
|
|
|
executes them. Every build step runs inside an ephemeral VMware VM — the runner host
|
|
|
|
|
itself never invokes compilers or build tools. The entire VM lifecycle is managed by
|
|
|
|
|
`Invoke-CIJob.ps1`.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
git push → Gitea → act_runner (host) → Invoke-CIJob.ps1 → VM lifecycle
|
|
|
|
|
├── New-BuildVM
|
|
|
|
|
├── Wait-VMReady
|
|
|
|
|
├── Invoke-RemoteBuild
|
|
|
|
|
├── Get-BuildArtifacts
|
|
|
|
|
└── Remove-BuildVM (always)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Supported guest OS: Windows Server 2025 / 2022 (WinRM transport), Ubuntu 24.04 (SSH transport).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 2. Runner Labels
|
|
|
|
|
|
|
|
|
|
| Label | Configured as | Use for |
|
|
|
|
|
| ---------------- | ---------------- | ----------------------------------- |
|
|
|
|
|
| `windows-build` | `windows-build:host` | Windows VM builds, Windows-only tools |
|
|
|
|
|
| `linux-build` | `linux-build:host` | Linux VM builds (cross-compile, GCC/Clang) |
|
|
|
|
|
| `dotnet` | `dotnet:host` | Alias for windows-build (legacy) |
|
|
|
|
|
| `msbuild` | `msbuild:host` | Alias for windows-build (legacy) |
|
|
|
|
|
|
|
|
|
|
Both `windows-build` and `linux-build` run on the same physical host. Only the
|
|
|
|
|
template VM used differs. Use `windows-build` unless you specifically need a Linux
|
|
|
|
|
guest (cross-compile, GCC, CMake, Python/Linux toolchains).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 3. Shell Requirement
|
|
|
|
|
|
|
|
|
|
**Always use `shell: powershell`** (Windows PowerShell 5.1).
|
|
|
|
|
|
|
|
|
|
Never use `shell: pwsh` — that invokes PowerShell Core 7+ which is not the
|
|
|
|
|
required runtime and may not be present on the host.
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# CORRECT
|
|
|
|
|
- name: Build
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
...
|
|
|
|
|
|
|
|
|
|
# WRONG — do not use
|
|
|
|
|
- name: Build
|
|
|
|
|
shell: pwsh # PowerShell 7+ — not supported
|
|
|
|
|
run: |
|
|
|
|
|
...
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 4. Environment Variables (Injected by Runner)
|
|
|
|
|
|
|
|
|
|
These variables are automatically available in every job step. Do not redeclare them
|
|
|
|
|
in the workflow unless you need to override the default.
|
|
|
|
|
|
|
|
|
|
| Variable | Value (default) | Description |
|
|
|
|
|
| ---------------------------- | ---------------------------------------------------- | --------------------------------------- |
|
|
|
|
|
| `GITEA_CI_TEMPLATE_PATH` | `F:\CI\Templates\WinBuild2025\WinBuild2025.vmx` | Windows 2025 template VMX path |
|
|
|
|
|
| `GITEA_CI_LINUX_TEMPLATE_PATH` | `F:\CI\Templates\LinuxBuild2404\LinuxBuild2404.vmx` | Ubuntu 24.04 template VMX path |
|
|
|
|
|
| `GITEA_CI_CLONE_BASE_DIR` | `F:\CI\BuildVMs` | Directory for ephemeral VM clones |
|
|
|
|
|
| `GITEA_CI_ARTIFACT_DIR` | `F:\CI\Artifacts` | Root directory for job artifacts |
|
|
|
|
|
| `GITEA_CI_GUEST_CRED_TARGET` | `BuildVMGuest` | Credential Manager target for guest VM |
|
|
|
|
|
| `GITEA_CI_SSH_KEY_PATH` | `F:\CI\keys\ci_linux` | SSH private key for Linux guest access |
|
|
|
|
|
|
|
|
|
|
Invoke-CIJob.ps1 reads `GITEA_CI_TEMPLATE_PATH` and `GITEA_CI_CLONE_BASE_DIR`
|
|
|
|
|
automatically if `-TemplatePath` and `-CloneBaseDir` are not passed explicitly.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 5. Invoke-CIJob.ps1 Parameter Reference
|
|
|
|
|
|
|
|
|
|
Script location (fixed on host): `N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1`
|
|
|
|
|
|
|
|
|
|
### Mandatory parameters
|
|
|
|
|
|
|
|
|
|
| Parameter | Type | Description |
|
|
|
|
|
| ----------- | ------ | --------------------------------------------------------------- |
|
|
|
|
|
| `-JobId` | string | Unique job ID. Use `${{ github.run_id }}` or `run_id-run_attempt`. |
|
|
|
|
|
| `-RepoUrl` | string | Git URL of the repository to clone/build. See Section 6. |
|
|
|
|
|
| `-Branch` | string | Branch to build. Use `${{ github.ref_name }}`. |
|
|
|
|
|
|
|
|
|
|
### Optional parameters
|
|
|
|
|
|
|
|
|
|
| Parameter | Type | Default | Description |
|
|
|
|
|
| ------------------------ | ------ | --------------------------- | -------------------------------------------------------------- |
|
|
|
|
|
| `-Commit` | string | `''` | Specific commit SHA. Use `${{ github.sha }}`. Empty = HEAD. |
|
|
|
|
|
| `-TemplatePath` | string | `$env:GITEA_CI_TEMPLATE_PATH` | Full path to template VMX. |
|
|
|
|
|
| `-SnapshotName` | string | `BaseClean` | Template snapshot name to clone from. |
|
|
|
|
|
| `-GuestOS` | string | `Auto` | `Windows`, `Linux`, or `Auto` (detected from VMX guestOS field). |
|
|
|
|
|
| `-BuildCommand` | string | `''` | Shell command to run inside VM. Empty = `dotnet build`. |
|
|
|
|
|
| `-GuestArtifactSource` | string | `dist` | Path inside VM guest from which artifacts are collected. |
|
|
|
|
|
| `-Submodules` | switch | off | Pass to clone with `--recurse-submodules`. |
|
|
|
|
|
| `-UseGitClone` | switch | off | Skip host-side clone; let the guest VM clone directly via HTTPS. |
|
|
|
|
|
| `-GiteaCredentialTarget` | string | `GiteaPAT` | Credential Manager target for Gitea PAT (UseGitClone only). |
|
|
|
|
|
| `-GuestCredentialTarget` | string | `BuildVMGuest` | Credential Manager target for Windows guest VM login. |
|
|
|
|
|
| `-SshKeyPath` | string | `F:\CI\keys\ci_linux` | SSH key for Linux guest (ignored for Windows). |
|
|
|
|
|
| `-SshUser` | string | `ci_build` | SSH username for Linux guest (ignored for Windows). |
|
|
|
|
|
| `-CloneBaseDir` | string | `$env:GITEA_CI_CLONE_BASE_DIR` | Directory for VM clone files. |
|
|
|
|
|
| `-ArtifactBaseDir` | string | `F:\CI\Artifacts` | Root artifact directory on host. |
|
|
|
|
|
| `-VMIPAddress` | string | `''` | Override guest IP. Empty = auto-detected via vmrun. |
|
|
|
|
|
| `-LogDir` | string | `F:\CI\Logs` | Directory for per-job transcripts and JSONL logs. |
|
|
|
|
|
| `-LogRetentionDays` | int | `30` | Days before log directories are purged. `0` = disabled. |
|
|
|
|
|
|
|
|
|
|
Exit codes: `0` = success, `1` = failure (VM is always destroyed regardless).
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 6. URL Conventions
|
|
|
|
|
|
|
|
|
|
The `-RepoUrl` value depends on the build mode.
|
|
|
|
|
|
|
|
|
|
### Host-clone mode (default, no `-UseGitClone`)
|
|
|
|
|
|
|
|
|
|
The host runner clones the repo using the **SSH alias** defined in `~/.ssh/config`:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
ssh://gitea-ci/<owner>/<repo>.git
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- `gitea-ci` is a Host alias in the runner's SSH config pointing to Gitea's SSH port.
|
|
|
|
|
- This URL is reachable **only from the host**, not from inside guest VMs.
|
|
|
|
|
- After clone, source is transferred to the guest via WinRM (Windows) or tar+scp (Linux).
|
|
|
|
|
|
|
|
|
|
### UseGitClone mode (`-UseGitClone`)
|
|
|
|
|
|
|
|
|
|
The guest VM clones the repo directly via **HTTPS**:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
https://gitea.emulab.it/<owner>/<repo>.git
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- This URL must be reachable from inside the VM's NAT network.
|
|
|
|
|
- For private repos, the Gitea PAT is injected from Credential Manager (`GiteaPAT` target).
|
|
|
|
|
- Do not use the SSH alias URL with `-UseGitClone` — it is not accessible inside VMs.
|
|
|
|
|
|
|
|
|
|
### Quick reference
|
|
|
|
|
|
|
|
|
|
| Mode | RepoUrl to use |
|
|
|
|
|
| ----------------- | ---------------------------------------------------------- |
|
|
|
|
|
| Default (Windows) | `ssh://gitea-ci/<owner>/<repo>.git` |
|
|
|
|
|
| Default (Linux) | `ssh://gitea-ci/<owner>/<repo>.git` |
|
|
|
|
|
| UseGitClone | `https://gitea.emulab.it/<owner>/<repo>.git` |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 7. Artifact Path Convention
|
|
|
|
|
|
|
|
|
|
Artifacts are written to:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
F:\CI\Artifacts\<JobId>\
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Where `<JobId>` is the value passed to `-JobId`.
|
|
|
|
|
|
|
|
|
|
In `upload-artifact` steps use:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}\
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Or for single-file artifact:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
`Invoke-CIJob.ps1` always produces an `artifacts.zip` at the job artifact root,
|
|
|
|
|
plus the raw files extracted from the guest under the same directory.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 8. Workflow Patterns
|
|
|
|
|
|
|
|
|
|
Place workflow files in `.gitea/workflows/` in the project repository.
|
|
|
|
|
|
|
|
|
|
### 8.1 Windows build (host-clone mode)
|
|
|
|
|
|
|
|
|
|
Standard Windows build. Host clones the repo and transfers source to the Windows VM
|
|
|
|
|
via WinRM/zip transfer.
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
name: Build (Windows)
|
|
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
push:
|
|
|
|
|
tags:
|
|
|
|
|
- 'v*'
|
|
|
|
|
workflow_dispatch:
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
build:
|
|
|
|
|
runs-on: windows-build
|
|
|
|
|
timeout-minutes: 90
|
|
|
|
|
|
|
|
|
|
steps:
|
|
|
|
|
- name: Run CI Job
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
|
|
|
|
|
-JobId '${{ github.run_id }}' `
|
|
|
|
|
-RepoUrl 'ssh://gitea-ci/<owner>/<repo>.git' `
|
|
|
|
|
-Branch '${{ github.ref_name }}' `
|
|
|
|
|
-Commit '${{ github.sha }}' `
|
|
|
|
|
-Submodules `
|
|
|
|
|
-BuildCommand 'python build_plugin.py --final --dist-dir dist' `
|
|
|
|
|
-GuestArtifactSource 'dist'
|
|
|
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
|
|
|
|
|
|
- name: Upload artifacts
|
|
|
|
|
if: success()
|
|
|
|
|
uses: actions/upload-artifact@v4
|
|
|
|
|
with:
|
|
|
|
|
name: build-${{ github.ref_name }}
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip
|
|
|
|
|
if-no-files-found: error
|
|
|
|
|
|
|
|
|
|
- name: Upload logs on failure
|
|
|
|
|
if: failure()
|
|
|
|
|
uses: actions/upload-artifact@v4
|
|
|
|
|
with:
|
|
|
|
|
name: build-logs-${{ github.run_id }}
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}\*.log
|
|
|
|
|
if-no-files-found: ignore
|
|
|
|
|
retention-days: 3
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 8.2 Linux build (host-clone mode)
|
|
|
|
|
|
|
|
|
|
Host clones the repo, compresses it with tar, and transfers it to the Ubuntu 24.04
|
|
|
|
|
VM via scp. Build runs under `ci_build` user.
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
name: Build (Linux)
|
|
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
push:
|
|
|
|
|
tags:
|
|
|
|
|
- 'v*'
|
|
|
|
|
workflow_dispatch:
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
build:
|
|
|
|
|
runs-on: linux-build
|
|
|
|
|
timeout-minutes: 60
|
|
|
|
|
|
|
|
|
|
steps:
|
|
|
|
|
- name: Run CI Job (Linux)
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
|
|
|
|
|
-JobId '${{ github.run_id }}' `
|
|
|
|
|
-RepoUrl 'ssh://gitea-ci/<owner>/<repo>.git' `
|
|
|
|
|
-Branch '${{ github.ref_name }}' `
|
|
|
|
|
-Commit '${{ github.sha }}' `
|
|
|
|
|
-Submodules `
|
|
|
|
|
-GuestOS 'Linux' `
|
|
|
|
|
-TemplatePath $env:GITEA_CI_LINUX_TEMPLATE_PATH `
|
|
|
|
|
-SshKeyPath $env:GITEA_CI_SSH_KEY_PATH `
|
|
|
|
|
-BuildCommand 'python3 build_plugin.py --host linux --verbose' `
|
|
|
|
|
-GuestArtifactSource 'dist'
|
|
|
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
|
|
|
|
|
|
- name: Upload artifacts
|
|
|
|
|
if: success()
|
|
|
|
|
uses: actions/upload-artifact@v4
|
|
|
|
|
with:
|
|
|
|
|
name: build-linux-${{ github.ref_name }}
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip
|
|
|
|
|
if-no-files-found: error
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Notes:
|
|
|
|
|
- `-GuestOS 'Linux'` can be omitted if `Auto` detection is sufficient (reads VMX `guestOS` field).
|
|
|
|
|
- `-TemplatePath $env:GITEA_CI_LINUX_TEMPLATE_PATH` selects the Ubuntu template.
|
|
|
|
|
- `-SshKeyPath $env:GITEA_CI_SSH_KEY_PATH` uses the injected key path.
|
|
|
|
|
- The default snapshot name for Linux templates is `BaseClean-Linux`. If you rely on
|
|
|
|
|
`Auto`, add `-SnapshotName 'BaseClean-Linux'` explicitly.
|
|
|
|
|
|
|
|
|
|
### 8.3 Windows build (UseGitClone mode)
|
|
|
|
|
|
|
|
|
|
The Windows guest VM clones the repository directly via HTTPS. Requires Git for Windows
|
|
|
|
|
in the template and the `GiteaPAT` Credential Manager entry on the host (for private repos).
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
name: Build (Windows, in-VM clone)
|
|
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
push:
|
|
|
|
|
branches:
|
|
|
|
|
- main
|
|
|
|
|
workflow_dispatch:
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
build:
|
|
|
|
|
runs-on: windows-build
|
|
|
|
|
timeout-minutes: 90
|
|
|
|
|
|
|
|
|
|
steps:
|
|
|
|
|
- name: Run CI Job
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
|
|
|
|
|
-JobId '${{ github.run_id }}' `
|
|
|
|
|
-RepoUrl 'https://gitea.emulab.it/<owner>/<repo>.git' `
|
|
|
|
|
-Branch '${{ github.ref_name }}' `
|
|
|
|
|
-Commit '${{ github.sha }}' `
|
|
|
|
|
-Submodules `
|
|
|
|
|
-UseGitClone `
|
|
|
|
|
-GiteaCredentialTarget 'GiteaPAT' `
|
|
|
|
|
-BuildCommand 'python build_plugin.py --final --dist-dir dist' `
|
|
|
|
|
-GuestArtifactSource 'dist'
|
|
|
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
|
|
|
|
|
|
- name: Upload artifacts
|
|
|
|
|
if: success()
|
|
|
|
|
uses: actions/upload-artifact@v4
|
|
|
|
|
with:
|
|
|
|
|
name: build-${{ github.ref_name }}
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip
|
|
|
|
|
if-no-files-found: error
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 8.4 Linux build (UseGitClone mode)
|
|
|
|
|
|
|
|
|
|
Linux guest clones directly. Useful when the host SSH agent is unavailable or for
|
|
|
|
|
testing the full HTTPS clone path.
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
name: Build (Linux, in-VM clone)
|
|
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
workflow_dispatch:
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
build:
|
|
|
|
|
runs-on: linux-build
|
|
|
|
|
timeout-minutes: 60
|
|
|
|
|
|
|
|
|
|
steps:
|
|
|
|
|
- name: Run CI Job (Linux, UseGitClone)
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
|
|
|
|
|
-JobId '${{ github.run_id }}' `
|
|
|
|
|
-RepoUrl 'https://gitea.emulab.it/<owner>/<repo>.git' `
|
|
|
|
|
-Branch '${{ github.ref_name }}' `
|
|
|
|
|
-Commit '${{ github.sha }}' `
|
|
|
|
|
-Submodules `
|
|
|
|
|
-UseGitClone `
|
|
|
|
|
-GiteaCredentialTarget 'GiteaPAT' `
|
|
|
|
|
-GuestOS 'Linux' `
|
|
|
|
|
-TemplatePath $env:GITEA_CI_LINUX_TEMPLATE_PATH `
|
|
|
|
|
-SshKeyPath $env:GITEA_CI_SSH_KEY_PATH `
|
|
|
|
|
-SnapshotName 'BaseClean-Linux' `
|
|
|
|
|
-BuildCommand 'python3 build_plugin.py --host linux --verbose' `
|
|
|
|
|
-GuestArtifactSource 'dist'
|
|
|
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
|
|
|
|
|
|
- name: Upload artifacts
|
|
|
|
|
if: success()
|
|
|
|
|
uses: actions/upload-artifact@v4
|
|
|
|
|
with:
|
|
|
|
|
name: build-linux-${{ github.ref_name }}
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}\artifacts.zip
|
|
|
|
|
if-no-files-found: error
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 8.5 PSScriptAnalyzer lint
|
|
|
|
|
|
|
|
|
|
Runs on every push or PR that touches `.ps1` / `.psm1` files.
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
name: Lint (PSScriptAnalyzer)
|
|
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
push:
|
|
|
|
|
paths:
|
|
|
|
|
- '**.ps1'
|
|
|
|
|
- '**.psm1'
|
|
|
|
|
pull_request:
|
|
|
|
|
paths:
|
|
|
|
|
- '**.ps1'
|
|
|
|
|
- '**.psm1'
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
lint:
|
|
|
|
|
runs-on: windows-build
|
|
|
|
|
timeout-minutes: 10
|
|
|
|
|
|
|
|
|
|
steps:
|
|
|
|
|
- name: Checkout
|
|
|
|
|
uses: actions/checkout@v4
|
|
|
|
|
with:
|
|
|
|
|
fetch-depth: 1
|
|
|
|
|
|
|
|
|
|
- name: Run PSScriptAnalyzer
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
|
|
|
|
|
|
if (-not (Get-Module -ListAvailable PSScriptAnalyzer)) {
|
|
|
|
|
Install-Module PSScriptAnalyzer -Scope CurrentUser -Force -AllowClobber
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$paths = @('scripts', 'template', 'runner', 'Setup-Host.ps1')
|
|
|
|
|
$results = $paths | ForEach-Object {
|
|
|
|
|
if (Test-Path $_) {
|
|
|
|
|
Invoke-ScriptAnalyzer -Path $_ -Recurse -Severity Error,Warning
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($results) {
|
|
|
|
|
$results | Format-Table -AutoSize
|
|
|
|
|
Write-Error "PSScriptAnalyzer found $($results.Count) issue(s)."
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
Write-Host "PSScriptAnalyzer: no issues found."
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Note: The lint job uses `actions/checkout@v4` because it analyzes files inside the
|
|
|
|
|
project repo (the checked-out workspace), not the CI scripts repo.
|
|
|
|
|
|
|
|
|
|
### 8.6 Matrix build (multiple configs)
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
name: Build (matrix)
|
|
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
push:
|
|
|
|
|
tags:
|
|
|
|
|
- 'v*'
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
build:
|
|
|
|
|
runs-on: windows-build
|
|
|
|
|
timeout-minutes: 90
|
|
|
|
|
|
|
|
|
|
strategy:
|
|
|
|
|
matrix:
|
|
|
|
|
config: [x86-ansi, x86-unicode, x64-unicode]
|
|
|
|
|
fail-fast: false
|
|
|
|
|
|
|
|
|
|
steps:
|
|
|
|
|
- name: Run CI Job
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' `
|
|
|
|
|
-JobId '${{ github.run_id }}-${{ matrix.config }}' `
|
|
|
|
|
-RepoUrl 'ssh://gitea-ci/<owner>/<repo>.git' `
|
|
|
|
|
-Branch '${{ github.ref_name }}' `
|
|
|
|
|
-Commit '${{ github.sha }}' `
|
|
|
|
|
-Submodules `
|
|
|
|
|
-BuildCommand "python build_plugin.py --config ${{ matrix.config }}" `
|
|
|
|
|
-GuestArtifactSource 'dist'
|
|
|
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
|
|
|
|
|
|
- name: Upload artifacts
|
|
|
|
|
if: success()
|
|
|
|
|
uses: actions/upload-artifact@v4
|
|
|
|
|
with:
|
|
|
|
|
name: build-${{ matrix.config }}-${{ github.ref_name }}
|
|
|
|
|
path: F:\CI\Artifacts\${{ github.run_id }}-${{ matrix.config }}\artifacts.zip
|
|
|
|
|
if-no-files-found: error
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Note: With `runner.capacity: 4`, up to 4 matrix jobs run in parallel. Each gets its
|
|
|
|
|
own ephemeral VM. Ensure `-JobId` is unique per matrix cell.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 9. Checkout Step
|
|
|
|
|
|
|
|
|
|
**Do not add `actions/checkout@v4` to build steps** unless the workflow itself needs
|
|
|
|
|
access to repository files on the host (e.g. lint, static analysis).
|
|
|
|
|
|
|
|
|
|
For build jobs, `Invoke-CIJob.ps1` handles all source acquisition — either by cloning
|
|
|
|
|
on the host and transferring to the VM, or by having the VM clone directly.
|
|
|
|
|
|
|
|
|
|
The CI scripts are at a fixed path on the runner host and do not need a checkout:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
# CORRECT — direct path to CI scripts, no checkout needed
|
|
|
|
|
- name: Run CI Job
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
& 'N:\Code\Workspace\Local-CI-CD-System\scripts\Invoke-CIJob.ps1' ...
|
|
|
|
|
|
|
|
|
|
# WRONG — CI scripts are not in the project repo
|
|
|
|
|
- name: Checkout
|
|
|
|
|
uses: actions/checkout@v4 # this checks out the PROJECT repo, not CI scripts
|
|
|
|
|
- name: Run CI Job
|
|
|
|
|
shell: powershell
|
|
|
|
|
run: |
|
|
|
|
|
& "$env:GITHUB_WORKSPACE\scripts\Invoke-CIJob.ps1" ... # path doesn't exist
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 10. Timeouts
|
|
|
|
|
|
|
|
|
|
| Build type | Recommended `timeout-minutes` |
|
|
|
|
|
| --------------------------- | ----------------------------- |
|
|
|
|
|
| Windows build (typical) | 90 |
|
|
|
|
|
| Windows build (large/slow) | 120 |
|
|
|
|
|
| Linux build (typical) | 60 |
|
|
|
|
|
| Lint / static analysis | 10 |
|
|
|
|
|
| Template provisioning | 180 |
|
|
|
|
|
|
|
|
|
|
The runner-level timeout is `2h` (from `runner/config.yaml`). Keep job timeouts
|
|
|
|
|
below that value. If a job timeout fires, the VM may not be cleanly destroyed —
|
|
|
|
|
the cleanup task `Cleanup-OrphanedBuildVMs.ps1` handles these cases.
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 11. Common Mistakes
|
|
|
|
|
|
|
|
|
|
| Mistake | Correct approach |
|
|
|
|
|
| ------- | ---------------- |
|
|
|
|
|
| `shell: pwsh` | Use `shell: powershell` (PS 5.1, not Core) |
|
|
|
|
|
| SSH alias URL with `-UseGitClone` | Use `https://gitea.emulab.it/...` for UseGitClone; SSH alias is host-only |
|
|
|
|
|
| HTTPS URL without `-UseGitClone` | SSH alias `ssh://gitea-ci/...` for host-clone mode |
|
|
|
|
|
| `$env:GITHUB_WORKSPACE` to find CI scripts | Use hardcoded `N:\Code\Workspace\Local-CI-CD-System\scripts\` |
|
|
|
|
|
| `actions/checkout@v4` in build jobs | Omit it; source is handled by Invoke-CIJob.ps1 |
|
|
|
|
|
| `runs-on: ubuntu-latest` or `windows-latest` | Use `windows-build` or `linux-build` — cloud runners are not available |
|
|
|
|
|
| `&&` in PowerShell run blocks | Not valid in PS 5.1; use `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` |
|
|
|
|
|
| Hardcoded IP in `-VMIPAddress` | Omit it; auto-detected via `vmrun getGuestIPAddress` |
|
|
|
|
|
| `-TemplatePath` for Linux without `GITEA_CI_LINUX_TEMPLATE_PATH` | Pass `$env:GITEA_CI_LINUX_TEMPLATE_PATH` or the full path explicitly |
|
|
|
|
|
| Forgetting `-SnapshotName 'BaseClean-Linux'` for Linux | Linux template uses a different snapshot name than Windows (`BaseClean`) |
|
|
|
|
|
| Same `-JobId` for matrix jobs | Append matrix key: `${{ github.run_id }}-${{ matrix.config }}` |
|