# .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-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-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: '{}' 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 }} run: | #Requires -Version 5.1 Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $ciScriptsDir = 'N:\Code\Workspace\Local-CI-CD-System\scripts' # 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' } # Parse extra-guest-env-json into a hashtable (§6.5 secret injection) $extraGuestEnv = @{} if ($env:INPUT_EXTRA_GUEST_ENV_JSON -and $env:INPUT_EXTRA_GUEST_ENV_JSON -ne '{}') { $parsed = $env:INPUT_EXTRA_GUEST_ENV_JSON | ConvertFrom-Json foreach ($prop in $parsed.PSObject.Properties) { $extraGuestEnv[$prop.Name] = [string]$prop.Value } } # Build parameter hashtable for Invoke-CIJob.ps1 $params = @{ JobId = $jobId RepoUrl = $repoUrl Branch = '${{ github.ref_name }}' Commit = '${{ github.sha }}' BuildCommand = $env:INPUT_BUILD_COMMAND GuestArtifactSource = $env:INPUT_ARTIFACT_SOURCE GuestOS = $env:INPUT_GUEST_OS Configuration = $env:INPUT_CONFIGURATION } if ($env:INPUT_SUBMODULES -eq 'true') { $params['Submodules'] = $true } if ($env:INPUT_USE_GIT_CLONE -eq 'true') { $params['UseGitClone'] = $true } if ($env:INPUT_TEMPLATE_PATH) { $params['TemplatePath'] = $env:INPUT_TEMPLATE_PATH } if ($env:INPUT_SNAPSHOT_NAME) { $params['SnapshotName'] = $env:INPUT_SNAPSHOT_NAME } if ($extraGuestEnv.Count -gt 0) { $params['ExtraGuestEnv'] = $extraGuestEnv } & "$ciScriptsDir\Invoke-CIJob.ps1" @params 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() uses: actions/upload-artifact@v4 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@v4 with: name: build-logs-${{ github.sha }} path: ${{ steps.invoke-ci.outputs.artifact-path }}\*.log retention-days: 3 if-no-files-found: ignore