Files
local-ci-cd-system/.gitea/actions/local-ci-build/action.yml
T
Simone ecfe0f3006 feat(ci): use-git-clone/submodules default-on in action (main)
Runner fetches the composite action from @main. Propagates the
default-on transport/submodules inputs and explicit flag forwarding.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-17 17:00:53 +02:00

334 lines
14 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# .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: >
Recurse submodules when cloning. Default "true". Set to "false"
to skip submodule init/update.
required: false
default: 'true'
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: >
Transport for the repo source. Default "true": clone inside the
build VM (requires Git in the template — see
Install-CIToolchain-WinBuild2025.ps1 Step 12 /
Install-CIToolchain-Linux2404.sh). Set to "false" for the
host-side variant: the host clones the repo and transfers a zip
to the guest (useful when the guest cannot reach Gitea).
required: false
default: 'true'
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: >
Deprecated / no-op. Artifacts are no longer uploaded to the Gitea
Actions store (that needs the github.com-hosted upload-artifact
action). They stay on the host under F:\CI\Artifacts and are aged
out by scripts/Invoke-RetentionPolicy.ps1. Kept for caller
back-compat.
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
)
# Submodules and transport default to ON; pass the explicit
# negative flag when the caller opted out so the Python default
# (also ON) is overridden deterministically.
if ($env:INPUT_SUBMODULES -eq 'false') { $pyArgs += '--no-submodules' }
else { $pyArgs += '--submodules' }
if ($env:INPUT_USE_GIT_CLONE -eq 'false') { $pyArgs += '--host-clone' }
else { $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.
# PowerShell 5.1 `Out-File -Encoding utf8` writes a BOM; the act_runner
# GITHUB_OUTPUT parser then reads the key as "artifact-path" and
# the output resolves empty. An empty artifact-path made the upload
# step glob the entire F: drive. Write UTF-8 without BOM.
if (-not $artifactPath) { throw "artifactPath is empty; refusing to emit." }
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::AppendAllText(
$env:GITHUB_OUTPUT, "artifact-path=$artifactPath`n", $utf8NoBom)
[System.IO.File]::AppendAllText(
$env:GITHUB_OUTPUT, "artifact-name=$artifactName`n", $utf8NoBom)
# ── Report artifact location ──────────────────────────────────────────────
# No actions/upload-artifact: that action's code is fetched from
# github.com (and v4 is GHES-incompatible, v3 deprecated). The runner
# host is single and its filesystem is shared across jobs, so the
# orchestrator's host-side collect dir IS the artifact handoff.
# Downstream jobs read F:\CI\Artifacts\{run_id}-{run_attempt}[-suffix]
# directly (see build-nsInnoUnp.yml release job).
- name: Report artifact location
if: success() && inputs.skip-artifact != 'true'
shell: powershell
run: |
$p = '${{ steps.invoke-ci.outputs.artifact-path }}'
if (-not (Test-Path -LiteralPath $p) -or
-not (Get-ChildItem -Force -LiteralPath $p)) {
throw "no artifacts at $p"
}
Write-Host "Artifacts on host: $p"
Get-ChildItem -Recurse -File -LiteralPath $p |
ForEach-Object { Write-Host " $($_.FullName)" }
- name: Report diagnostic log location on failure
if: failure()
shell: powershell
run: |
$p = '${{ steps.invoke-ci.outputs.artifact-path }}'
if ($p -and (Test-Path -LiteralPath $p)) {
Write-Host "Diagnostic logs (if any) on host: $p"
Get-ChildItem -Recurse -File -Filter '*.log' -LiteralPath $p `
-ErrorAction SilentlyContinue |
ForEach-Object { Write-Host " $($_.FullName)" }
}