5371ffe48a
Gitea act_runner reports as GHES; @actions/artifact v2 (upload/ download-artifact@v4) uses the Twirp API Gitea does not implement, failing with GHESNotSupportedError. v3 uses the artifact protocol Gitea supports. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
301 lines
12 KiB
YAML
301 lines
12 KiB
YAML
# .gitea/actions/local-ci-build/action.yml
|
|
#
|
|
# Reusable composite action — Local CI/CD System
|
|
#
|
|
# Wraps Invoke-CIJob.ps1 (ephemeral VMware VM pipeline) into a single callable
|
|
# step, usable from any Gitea repository without duplicating the orchestration
|
|
# logic.
|
|
#
|
|
# Stored in: Simone/local-ci-cd-system / .gitea/actions/local-ci-build/action.yml
|
|
#
|
|
# Usage from a calling repo's workflow:
|
|
#
|
|
# jobs:
|
|
# build:
|
|
# runs-on: windows-build # or linux-build
|
|
# steps:
|
|
# - uses: actions/checkout@v4
|
|
# - uses: Simone/local-ci-cd-system/.gitea/actions/local-ci-build@main
|
|
# with:
|
|
# build-command: 'python build_plugin.py --final --dist-dir dist'
|
|
# artifact-source: 'dist'
|
|
# submodules: 'true'
|
|
#
|
|
# Runner label determines the guest OS template:
|
|
# windows-build -> WinBuild2025 (WinRM/HTTPS transport)
|
|
# linux-build -> LinuxBuild2404 (SSH transport)
|
|
|
|
name: Local CI Build
|
|
description: >
|
|
Builds a project using an ephemeral VMware VM managed by Invoke-CIJob.ps1.
|
|
Handles the full VM lifecycle: clone template -> start VM -> run build ->
|
|
collect artifacts -> destroy VM.
|
|
Compatible with windows-build (WinRM) and linux-build (SSH) runners.
|
|
|
|
inputs:
|
|
build-command:
|
|
description: >
|
|
Shell command to execute inside the build VM.
|
|
Example: 'python build_plugin.py --final --dist-dir dist'
|
|
required: true
|
|
|
|
artifact-source:
|
|
description: >
|
|
Path inside VM where build outputs are located, relative to the CI work
|
|
directory. Collected to F:\CI\Artifacts\{JobId}\ on the host.
|
|
required: false
|
|
default: 'dist'
|
|
|
|
submodules:
|
|
description: >
|
|
Set to "true" to clone the repository with --recurse-submodules.
|
|
required: false
|
|
default: 'false'
|
|
|
|
guest-os:
|
|
description: >
|
|
Guest OS type: Windows, Linux, or Auto.
|
|
Auto (default) detects the OS from the VMX guestOS field after cloning.
|
|
required: false
|
|
default: 'Auto'
|
|
|
|
use-git-clone:
|
|
description: >
|
|
Set to "true" to clone the repository inside the VM rather than
|
|
transferring a zip from the host. Requires Git in the template (see
|
|
Install-CIToolchain-WinBuild2025.ps1 Step 12 / Install-CIToolchain-Linux2404.sh).
|
|
Preferred for repositories larger than ~200 MB.
|
|
required: false
|
|
default: 'false'
|
|
|
|
configuration:
|
|
description: >
|
|
Build configuration string forwarded to the build command (Release/Debug).
|
|
required: false
|
|
default: 'Release'
|
|
|
|
template-path:
|
|
description: >
|
|
Override the VMX template path. Leave empty (default) to use the value
|
|
from the GITEA_CI_TEMPLATE_PATH runner environment variable set in
|
|
runner/config.yaml.
|
|
required: false
|
|
default: ''
|
|
|
|
snapshot-name:
|
|
description: >
|
|
Override the snapshot name to clone from. Leave empty (default) to use
|
|
GITEA_CI_SNAPSHOT_NAME runner env var, or "BaseClean" if that is also
|
|
unset.
|
|
required: false
|
|
default: ''
|
|
|
|
artifact-name:
|
|
description: >
|
|
Name for the artifact uploaded to Gitea. Leave empty to use the default:
|
|
build-{run_id}-{sha}.
|
|
required: false
|
|
default: ''
|
|
|
|
artifact-retention-days:
|
|
description: 'Days to retain uploaded artifacts in Gitea.'
|
|
required: false
|
|
default: '14'
|
|
|
|
job-id-suffix:
|
|
description: >
|
|
Optional suffix appended to the job ID: run_id-run_attempt-SUFFIX.
|
|
Use to disambiguate parallel matrix legs so each leg writes to its own
|
|
artifact directory (e.g. set to matrix.target: windows, linux).
|
|
Leave empty (default) for single-job workflows.
|
|
required: false
|
|
default: ''
|
|
|
|
repo-url:
|
|
description: >
|
|
Override the repository URL passed to Invoke-CIJob.ps1.
|
|
Defaults to github.server_url/repository.git (HTTPS).
|
|
Set to an SSH URL (e.g. ssh://gitea-ci/Org/repo.git) when the host
|
|
runner uses an SSH alias for Gitea.
|
|
required: false
|
|
default: ''
|
|
|
|
extra-guest-env-json:
|
|
description: >
|
|
JSON object of extra environment variables to inject into the build VM
|
|
guest before the build command runs. Use for signing keys and secrets.
|
|
Example: '{"SIGN_PASS":"${{ secrets.SIGN_PASS }}","GPG_KEY_ID":"0xABCD1234"}'
|
|
Values are passed via ExtraGuestEnv hashtable in Invoke-CIJob.ps1 and
|
|
are never echoed to the log.
|
|
required: false
|
|
default: '{}'
|
|
|
|
use-shared-cache:
|
|
description: >
|
|
Set to "true" to redirect NuGet and pip caches to VMware shared folders
|
|
on the host (F:\CI\Cache\NuGet and F:\CI\Cache\pip).
|
|
Requires Set-TemplateSharedFolders.ps1 to have been run on the template
|
|
and VMware Tools HGFS driver present in the guest.
|
|
required: false
|
|
default: 'false'
|
|
|
|
skip-artifact:
|
|
description: >
|
|
Set to "true" to skip artifact packaging and collection entirely.
|
|
Use for jobs that intentionally produce no output (lint, static analysis,
|
|
test-only). When absent (default), the build MUST produce artifact-source
|
|
or the job fails.
|
|
required: false
|
|
default: 'false'
|
|
|
|
outputs:
|
|
artifact-path:
|
|
description: >
|
|
Host-side directory where Invoke-CIJob.ps1 placed collected artifacts.
|
|
Format: F:\CI\Artifacts\{run_id}-{run_attempt}
|
|
value: ${{ steps.invoke-ci.outputs.artifact-path }}
|
|
|
|
artifact-name:
|
|
description: 'Name used when uploading the artifact to Gitea.'
|
|
value: ${{ steps.invoke-ci.outputs.artifact-name }}
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
|
|
# ── Invoke the CI orchestrator ─────────────────────────────────────────────
|
|
- name: Invoke CI Job
|
|
id: invoke-ci
|
|
shell: powershell
|
|
# Inputs are forwarded via environment variables to avoid shell injection
|
|
# from user-supplied build-command or path strings.
|
|
env:
|
|
INPUT_BUILD_COMMAND: ${{ inputs.build-command }}
|
|
INPUT_ARTIFACT_SOURCE: ${{ inputs.artifact-source }}
|
|
INPUT_SUBMODULES: ${{ inputs.submodules }}
|
|
INPUT_GUEST_OS: ${{ inputs.guest-os }}
|
|
INPUT_USE_GIT_CLONE: ${{ inputs.use-git-clone }}
|
|
INPUT_CONFIGURATION: ${{ inputs.configuration }}
|
|
INPUT_TEMPLATE_PATH: ${{ inputs.template-path }}
|
|
INPUT_SNAPSHOT_NAME: ${{ inputs.snapshot-name }}
|
|
INPUT_ARTIFACT_NAME: ${{ inputs.artifact-name }}
|
|
INPUT_JOB_ID_SUFFIX: ${{ inputs.job-id-suffix }}
|
|
INPUT_REPO_URL: ${{ inputs.repo-url }}
|
|
INPUT_EXTRA_GUEST_ENV_JSON: ${{ inputs.extra-guest-env-json }}
|
|
INPUT_USE_SHARED_CACHE: ${{ inputs.use-shared-cache }}
|
|
INPUT_SKIP_ARTIFACT: ${{ inputs.skip-artifact }}
|
|
run: |
|
|
#Requires -Version 5.1
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# Resolve Python interpreter from venv (Phase A4 — Python orchestrator).
|
|
# Override with CI_VENV_PYTHON env var; default matches Setup-Host.ps1 layout.
|
|
$venvPython = $env:CI_VENV_PYTHON
|
|
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
|
if (-not (Test-Path -LiteralPath $venvPython)) {
|
|
throw "Python interpreter not found at '$venvPython'. Set CI_VENV_PYTHON or run Setup-Host.ps1."
|
|
}
|
|
|
|
# Build job ID — append suffix when provided (matrix disambiguation)
|
|
$jobId = '${{ github.run_id }}-${{ github.run_attempt }}'
|
|
if ($env:INPUT_JOB_ID_SUFFIX) { $jobId = "$jobId-$($env:INPUT_JOB_ID_SUFFIX)" }
|
|
$artifactPath = "F:\CI\Artifacts\$jobId"
|
|
|
|
# Resolve artifact name: caller-supplied or default to build-{run_id}-{sha}
|
|
if ($env:INPUT_ARTIFACT_NAME) {
|
|
$artifactName = $env:INPUT_ARTIFACT_NAME
|
|
}
|
|
else {
|
|
$artifactName = "build-${{ github.run_id }}-${{ github.sha }}"
|
|
}
|
|
|
|
# Resolve repository URL: explicit override or construct from GitHub context
|
|
if ($env:INPUT_REPO_URL) {
|
|
$repoUrl = $env:INPUT_REPO_URL
|
|
}
|
|
else {
|
|
$repoUrl = '${{ github.server_url }}/${{ github.repository }}.git'
|
|
}
|
|
|
|
# Resolve guest OS: honor explicit input; for Auto, infer from RUNNER_LABELS
|
|
$resolvedGuestOS = $env:INPUT_GUEST_OS
|
|
if ($resolvedGuestOS -eq 'Auto') {
|
|
if ($env:RUNNER_LABELS -match 'linux') {
|
|
$resolvedGuestOS = 'Linux'
|
|
} else {
|
|
$resolvedGuestOS = 'Windows'
|
|
}
|
|
}
|
|
|
|
# Select template path: explicit input overrides OS-based runner env var
|
|
$resolvedTemplatePath = $env:INPUT_TEMPLATE_PATH
|
|
if (-not $resolvedTemplatePath) {
|
|
if ($resolvedGuestOS -eq 'Linux') {
|
|
$resolvedTemplatePath = $env:GITEA_CI_LINUX_TEMPLATE_PATH
|
|
} else {
|
|
$resolvedTemplatePath = $env:GITEA_CI_TEMPLATE_PATH
|
|
}
|
|
}
|
|
|
|
# Select snapshot name: explicit input > OS-based default
|
|
# GITEA_CI_SNAPSHOT_NAME applies to Windows (template-refresh override)
|
|
$resolvedSnapshotName = $env:INPUT_SNAPSHOT_NAME
|
|
if (-not $resolvedSnapshotName) {
|
|
if ($resolvedGuestOS -eq 'Linux') {
|
|
$resolvedSnapshotName = 'BaseClean-Linux'
|
|
} else {
|
|
$resolvedSnapshotName = if ($env:GITEA_CI_SNAPSHOT_NAME) { $env:GITEA_CI_SNAPSHOT_NAME } else { 'BaseClean' }
|
|
}
|
|
}
|
|
|
|
# Build argument list for the Python CLI. Long-form kebab-case flags only.
|
|
$pyArgs = @(
|
|
'-m', 'ci_orchestrator', 'job',
|
|
'--job-id', $jobId,
|
|
'--repo-url', $repoUrl,
|
|
'--branch', '${{ github.ref_name }}',
|
|
'--commit', '${{ github.sha }}',
|
|
'--build-command', $env:INPUT_BUILD_COMMAND,
|
|
'--guest-artifact-source', $env:INPUT_ARTIFACT_SOURCE,
|
|
'--guest-os', $resolvedGuestOS,
|
|
'--configuration', $env:INPUT_CONFIGURATION,
|
|
'--template-path', $resolvedTemplatePath,
|
|
'--snapshot-name', $resolvedSnapshotName
|
|
)
|
|
|
|
if ($env:INPUT_SUBMODULES -eq 'true') { $pyArgs += '--submodules' }
|
|
if ($env:INPUT_USE_GIT_CLONE -eq 'true') { $pyArgs += '--use-git-clone' }
|
|
if ($env:INPUT_USE_SHARED_CACHE -eq 'true') { $pyArgs += '--use-shared-cache' }
|
|
if ($env:INPUT_SKIP_ARTIFACT -eq 'true') { $pyArgs += '--skip-artifact' }
|
|
if ($env:INPUT_EXTRA_GUEST_ENV_JSON -and $env:INPUT_EXTRA_GUEST_ENV_JSON -ne '{}') {
|
|
$pyArgs += @('--extra-env-json', $env:INPUT_EXTRA_GUEST_ENV_JSON)
|
|
}
|
|
|
|
& $venvPython @pyArgs
|
|
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
|
|
|
# Emit outputs consumed by subsequent steps
|
|
"artifact-path=$artifactPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
|
"artifact-name=$artifactName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
|
|
|
# ── Upload build artifacts on success ─────────────────────────────────────
|
|
- name: Upload artifacts
|
|
if: success() && inputs.skip-artifact != 'true'
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: ${{ steps.invoke-ci.outputs.artifact-name }}
|
|
path: ${{ steps.invoke-ci.outputs.artifact-path }}\
|
|
retention-days: ${{ inputs.artifact-retention-days }}
|
|
if-no-files-found: error
|
|
|
|
# ── Upload diagnostic logs on failure ─────────────────────────────────────
|
|
- name: Upload diagnostic logs on failure
|
|
if: failure()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: build-logs-${{ github.sha }}
|
|
path: ${{ steps.invoke-ci.outputs.artifact-path }}\*.log
|
|
retention-days: 3
|
|
if-no-files-found: ignore
|