native: shared Android, Windows and macOS workflows #7
No files matched your search
@@ -0,0 +1,169 @@
|
||||
# Shared Android CI: run the repository's Gradle gates in the pinned Android image (cimg/android plus the
|
||||
# emulator, JDK 8/17/21/25) on the Lore android-build runners, which pass /dev/kvm through for the emulator.
|
||||
#
|
||||
# any push -> commands, then each Gradle line in tasks; emulator-tasks with a booted emulator
|
||||
# tag vX.Y.Z -> + publish-tasks with -Pversion=X.Y.Z to the owner's Gitea Maven registry
|
||||
#
|
||||
# tasks: one ./gradlew invocation per line, for example
|
||||
# testDebugUnitTest lintDebug assembleDebug
|
||||
# -PscannerTestBuildType=qa :scanner-host:assembleQa
|
||||
name: android
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tasks:
|
||||
description: Gradle invocations, one per line (arguments to ./gradlew); every line must pass
|
||||
type: string
|
||||
default: build
|
||||
java-version:
|
||||
description: JDK major version from the image (8, 17, 21 or 25)
|
||||
type: string
|
||||
default: '21'
|
||||
runner:
|
||||
type: string
|
||||
default: android-build
|
||||
commands:
|
||||
description: Shell commands run before Gradle, one per line (for example source checks)
|
||||
type: string
|
||||
default: ''
|
||||
node-version:
|
||||
description: Install this Node.js version (official tarball, checksum-verified) for commands; empty = none
|
||||
type: string
|
||||
default: ''
|
||||
emulator-tasks:
|
||||
description: Gradle invocations, one per line, run with a booted emulator (connected tests); empty = no emulator
|
||||
type: string
|
||||
default: ''
|
||||
emulator-image:
|
||||
description: sdkmanager system image for the emulator
|
||||
type: string
|
||||
default: system-images;android-30;google_apis;x86
|
||||
publish-tasks:
|
||||
description: Gradle tasks run on vX.Y.Z tags to publish to the Gitea registry (repository named gitea); empty = none
|
||||
type: string
|
||||
default: ''
|
||||
secrets:
|
||||
PACKAGES_TOKEN:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
android:
|
||||
runs-on: ${{ inputs.runner }}
|
||||
timeout-minutes: 90
|
||||
container:
|
||||
# Praetor ops/gitea/fleet-ci/platform-ci/android-image/Dockerfile: cimg/android 2026.10.1 + emulator, uid 999.
|
||||
image: git.kof22.com/koftwentytwo/ci-android-runtime@sha256:838a1a494794a7c0705930e9a13d744498c6e01dc481a29bdda5ecae85f1a466
|
||||
volumes:
|
||||
- /var/lib/gitea-job-workspace:${{ github.workspace }}
|
||||
env:
|
||||
HOME: /tmp
|
||||
CI: 'true'
|
||||
GRADLE_USER_HOME: /tmp/.gradle
|
||||
GRADLE_OPTS: -Dorg.gradle.daemon=false
|
||||
steps:
|
||||
- uses: builtin:checkout
|
||||
|
||||
- name: Set up
|
||||
shell: bash
|
||||
env:
|
||||
JAVA_VERSION: ${{ inputs.java-version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
[[ -x ./gradlew ]] || { echo "./gradlew is missing or not executable"; exit 1; }
|
||||
jdk=$(ls -d /usr/lib/jvm/*-"$JAVA_VERSION"-* /usr/lib/jvm/*-"$JAVA_VERSION" /usr/lib/jvm/*"$JAVA_VERSION"* 2>/dev/null | head -1 || true)
|
||||
[[ -n $jdk && -x $jdk/bin/java ]] || { echo "No JDK $JAVA_VERSION in the image:"; ls /usr/lib/jvm; exit 1; }
|
||||
echo "JAVA_HOME=$jdk" >> "$GITHUB_ENV"
|
||||
echo "$jdk/bin" >> "$GITHUB_PATH"
|
||||
"$jdk/bin/java" -version
|
||||
|
||||
- name: Install Node.js
|
||||
if: inputs.node-version != ''
|
||||
shell: bash
|
||||
env:
|
||||
NODE_VERSION: ${{ inputs.node-version }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base=https://nodejs.org/dist/v$NODE_VERSION
|
||||
file=node-v$NODE_VERSION-linux-x64.tar.gz
|
||||
mkdir -p /tmp/node && cd /tmp/node
|
||||
curl -fsSLO "$base/$file" && curl -fsSLO "$base/SHASUMS256.txt"
|
||||
grep " $file\$" SHASUMS256.txt | sha256sum -c -
|
||||
tar -xzf "$file" --strip-components=1 && rm "$file"
|
||||
echo /tmp/node/bin >> "$GITHUB_PATH"
|
||||
|
||||
- name: Run commands
|
||||
if: inputs.commands != ''
|
||||
shell: bash
|
||||
env:
|
||||
COMMANDS: ${{ inputs.commands }}
|
||||
run: |
|
||||
printf '%s\n' "$COMMANDS" > /tmp/commands.sh
|
||||
bash -exo pipefail /tmp/commands.sh
|
||||
|
||||
- name: Gradle
|
||||
shell: bash
|
||||
env:
|
||||
TASKS: ${{ inputs.tasks }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
while IFS= read -r line; do
|
||||
[[ -z ${line// } ]] && continue
|
||||
echo "::group::./gradlew $line"
|
||||
# Word splitting is intended: each line is a list of Gradle arguments.
|
||||
# shellcheck disable=SC2086
|
||||
./gradlew --console=plain $line
|
||||
echo "::endgroup::"
|
||||
done <<< "$TASKS"
|
||||
|
||||
- name: Emulator tests
|
||||
if: inputs.emulator-tasks != ''
|
||||
shell: bash
|
||||
env:
|
||||
TASKS: ${{ inputs.emulator-tasks }}
|
||||
IMAGE: ${{ inputs.emulator-image }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ -r /dev/kvm && -w /dev/kvm ]] || { echo "/dev/kvm is not usable by uid $(id -u): this runner cannot host the emulator"; exit 1; }
|
||||
sdkmanager --install "$IMAGE" > /dev/null
|
||||
echo no | avdmanager create avd --force -n ci -k "$IMAGE"
|
||||
emulator -avd ci -no-window -no-audio -no-boot-anim -no-snapshot -gpu swiftshader_indirect > /tmp/emulator.log 2>&1 &
|
||||
trap 'adb emu kill > /dev/null 2>&1 || true' EXIT
|
||||
adb wait-for-device
|
||||
deadline=$((SECONDS + 600))
|
||||
until [[ $(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') == 1 ]]; do
|
||||
(( SECONDS < deadline )) || { echo "emulator did not boot in 10 minutes"; tail -50 /tmp/emulator.log; exit 1; }
|
||||
sleep 2
|
||||
done
|
||||
for s in window_animation_scale transition_animation_scale animator_duration_scale; do
|
||||
adb shell settings put global "$s" 0
|
||||
done
|
||||
while IFS= read -r line; do
|
||||
[[ -z ${line// } ]] && continue
|
||||
echo "::group::./gradlew $line"
|
||||
# shellcheck disable=SC2086
|
||||
./gradlew --console=plain $line
|
||||
echo "::endgroup::"
|
||||
done <<< "$TASKS"
|
||||
|
||||
- name: Publish
|
||||
if: inputs.publish-tasks != '' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
|
||||
shell: bash
|
||||
env:
|
||||
TASKS: ${{ inputs.publish-tasks }}
|
||||
REF_NAME: ${{ github.ref_name }}
|
||||
ORG_GRADLE_PROJECT_giteaUsername: ${{ github.actor }}
|
||||
ORG_GRADLE_PROJECT_giteaPassword: ${{ secrets.PACKAGES_TOKEN || github.token }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[[ $REF_NAME =~ ^v([0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?)$ ]] || { echo "Tag $REF_NAME is not vX.Y.Z[-suffix]"; exit 1; }
|
||||
version=${BASH_REMATCH[1]}
|
||||
export ORG_GRADLE_PROJECT_giteaMavenUrl="https://git.kof22.com/api/packages/$GITHUB_REPOSITORY_OWNER/maven"
|
||||
# shellcheck disable=SC2086
|
||||
./gradlew --console=plain -Pversion="$version" -PVERSION_NAME="$version" $TASKS
|
||||
echo "published $version to $ORG_GRADLE_PROJECT_giteaMavenUrl"
|
||||
@@ -0,0 +1,134 @@
|
||||
# Shared macOS CI: run the repository's own gates with bash directly on the macOS host runner
|
||||
# (grogu, no container). Xcode and other tools come from the host; nothing is installed here.
|
||||
#
|
||||
# any push -> commands (bash -eo pipefail)
|
||||
# tag v* -> + upload the artifacts files and a SHA256SUMS to the tag's Gitea release
|
||||
#
|
||||
# Host runners execute repository code on the machine: callers trigger on push only (never on
|
||||
# pull_request), so only branches of the repository itself, never forks, reach the host.
|
||||
name: macos
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
commands:
|
||||
description: Shell commands, one per line, run with bash -exo pipefail; the first failure fails the build
|
||||
type: string
|
||||
required: true
|
||||
runner:
|
||||
description: macOS runner label (KofTwentyTwo kof-macos-arm64, QRun-IO qqq-macos-arm64)
|
||||
type: string
|
||||
default: kof-macos-arm64
|
||||
xcode:
|
||||
description: Xcode to select through DEVELOPER_DIR, a version (/Applications/Xcode_<v>.app) or an app path; empty = host default
|
||||
type: string
|
||||
default: ''
|
||||
lfs:
|
||||
description: Fetch Git LFS objects in the checkout (needs git-lfs on the host)
|
||||
type: boolean
|
||||
default: false
|
||||
artifacts:
|
||||
description: Files or globs, one per line, attached to the Gitea release on v* tags; empty = no release
|
||||
type: string
|
||||
default: ''
|
||||
checksums:
|
||||
description: Name of the checksum asset (use a per-platform name when several jobs release the same tag)
|
||||
type: string
|
||||
default: SHA256SUMS
|
||||
secrets:
|
||||
PACKAGES_TOKEN:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
macos:
|
||||
runs-on: ${{ inputs.runner }}
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
steps:
|
||||
- uses: builtin:checkout
|
||||
with:
|
||||
lfs: ${{ inputs.lfs }}
|
||||
|
||||
- name: Toolchain
|
||||
shell: bash
|
||||
env:
|
||||
XCODE: ${{ inputs.xcode }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n $XCODE ]]; then
|
||||
case $XCODE in
|
||||
/*) app=$XCODE ;;
|
||||
*) app=/Applications/Xcode_$XCODE.app; [[ -d $app ]] || app=/Applications/Xcode-$XCODE.app ;;
|
||||
esac
|
||||
[[ -d $app/Contents/Developer ]] || { echo "Xcode $XCODE not found; installed:"; ls -d /Applications/Xcode*.app; exit 1; }
|
||||
echo "DEVELOPER_DIR=$app/Contents/Developer" >> "$GITHUB_ENV"
|
||||
export DEVELOPER_DIR=$app/Contents/Developer
|
||||
fi
|
||||
sw_vers
|
||||
xcodebuild -version
|
||||
swift --version 2>&1 | head -1
|
||||
|
||||
- name: Run commands
|
||||
shell: bash
|
||||
env:
|
||||
COMMANDS: ${{ inputs.commands }}
|
||||
run: |
|
||||
script=$(mktemp)
|
||||
printf '%s\n' "$COMMANDS" > "$script"
|
||||
status=0
|
||||
bash -exo pipefail "$script" || status=$?
|
||||
rm -f "$script"
|
||||
exit $status
|
||||
|
||||
- name: Release
|
||||
if: inputs.artifacts != '' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
|
||||
shell: bash
|
||||
env:
|
||||
ARTIFACTS: ${{ inputs.artifacts }}
|
||||
TOKEN: ${{ secrets.PACKAGES_TOKEN || github.token }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
CHECKSUMS: ${{ inputs.checksums }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
files=()
|
||||
while IFS= read -r pattern; do
|
||||
[[ -z ${pattern// } ]] && continue
|
||||
# Glob expansion is intended.
|
||||
# shellcheck disable=SC2206
|
||||
matches=($pattern)
|
||||
[[ -f ${matches[0]} ]] || { echo "No file matches $pattern"; exit 1; }
|
||||
files+=("${matches[@]}")
|
||||
done <<< "$ARTIFACTS"
|
||||
sums=$(mktemp)
|
||||
for f in "${files[@]}"; do (cd "$(dirname "$f")" && shasum -a 256 "$(basename "$f")"); done > "$sums"
|
||||
cat "$sums"
|
||||
|
||||
api="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY"
|
||||
auth=(-H "Authorization: token $TOKEN")
|
||||
code=$(curl -sS -o /tmp/release-$$.json -w '%{http_code}' "${auth[@]}" "$api/releases/tags/$TAG")
|
||||
if [[ $code == 404 ]]; then
|
||||
prerelease=false; [[ $TAG == *-* ]] && prerelease=true
|
||||
# Another job may create the same release concurrently: fall back to reading it.
|
||||
curl -fsS "${auth[@]}" -H 'Content-Type: application/json' -o /tmp/release-$$.json \
|
||||
-d "{\"tag_name\":\"$TAG\",\"name\":\"$TAG\",\"prerelease\":$prerelease}" "$api/releases" \
|
||||
|| curl -fsS "${auth[@]}" -o /tmp/release-$$.json "$api/releases/tags/$TAG"
|
||||
elif [[ $code != 200 ]]; then
|
||||
echo "release lookup for $TAG returned HTTP $code"; exit 1
|
||||
fi
|
||||
release=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["id"])' /tmp/release-$$.json)
|
||||
upload() {
|
||||
# Re-runs replace an asset of the same name instead of failing.
|
||||
python3 -c 'import json,sys; [print(a["id"]) for a in json.load(open(sys.argv[1])).get("assets") or [] if a["name"] == sys.argv[2]]' \
|
||||
/tmp/release-$$.json "$2" | while read -r old; do
|
||||
curl -fsS -X DELETE "${auth[@]}" "$api/releases/$release/assets/$old"
|
||||
done
|
||||
curl -fsS -o /dev/null "${auth[@]}" -F "attachment=@$1" "$api/releases/$release/assets?name=$2"
|
||||
echo "attached $2 to release $TAG"
|
||||
}
|
||||
for f in "${files[@]}"; do upload "$f" "$(basename "$f")"; done
|
||||
upload "$sums" "$CHECKSUMS"
|
||||
rm -f "$sums" /tmp/release-$$.json
|
||||
@@ -0,0 +1,129 @@
|
||||
# Shared Windows CI: run the repository's own gates with PowerShell 7 directly on a Windows host runner
|
||||
# (no container). Tools come from the host; nothing is installed here.
|
||||
#
|
||||
# any push -> commands (fail fast)
|
||||
# tag v* -> + upload the artifacts files and a SHA256SUMS (checksums) to the tag's Gitea release
|
||||
#
|
||||
# A .ps1 called in-process with & that ends in `exit 1` does not stop the script: PowerShell only
|
||||
# turns native exit codes into errors. Call such scripts as native processes: pwsh -NoProfile -File x.ps1.
|
||||
#
|
||||
# Host runners execute repository code on the machine: callers trigger on push only (never on
|
||||
# pull_request), so only branches of the repository itself, never forks, reach the host.
|
||||
name: windows
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
commands:
|
||||
description: PowerShell commands run as one script; any error or non-zero native exit code fails the build (run .ps1 files that report failure with exit codes as pwsh -File)
|
||||
type: string
|
||||
required: true
|
||||
runner:
|
||||
description: Windows runner label (KofTwentyTwo kof-windows-amd64, QRun-IO qqq-windows-vm6218)
|
||||
type: string
|
||||
default: kof-windows-amd64
|
||||
dotnet-version:
|
||||
description: Require a host .NET SDK whose version starts with this (for example 10.0.4); empty = no check
|
||||
type: string
|
||||
default: ''
|
||||
artifacts:
|
||||
description: Files or wildcards, one per line, attached to the Gitea release on v* tags; empty = no release
|
||||
type: string
|
||||
default: ''
|
||||
checksums:
|
||||
description: Name of the checksum asset (use a per-platform name when several jobs release the same tag)
|
||||
type: string
|
||||
default: SHA256SUMS
|
||||
secrets:
|
||||
PACKAGES_TOKEN:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
windows:
|
||||
runs-on: ${{ inputs.runner }}
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CI: 'true'
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: '1'
|
||||
DOTNET_NOLOGO: '1'
|
||||
steps:
|
||||
- uses: builtin:checkout
|
||||
|
||||
- name: Toolchain
|
||||
shell: pwsh
|
||||
env:
|
||||
DOTNET_VERSION: ${{ inputs.dotnet-version }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Write-Output "PowerShell $($PSVersionTable.PSVersion) on $([Environment]::OSVersion.VersionString)"
|
||||
if (Get-Command dotnet -ErrorAction SilentlyContinue) {
|
||||
$sdks = @(dotnet --list-sdks)
|
||||
Write-Output ".NET SDKs:"; $sdks | ForEach-Object { " $_" }
|
||||
if ($env:DOTNET_VERSION -and -not ($sdks | Where-Object { $_.StartsWith($env:DOTNET_VERSION) })) {
|
||||
throw ".NET SDK $env:DOTNET_VERSION is not installed on this runner"
|
||||
}
|
||||
} elseif ($env:DOTNET_VERSION) {
|
||||
throw 'dotnet is not installed on this runner'
|
||||
}
|
||||
|
||||
- name: Run commands
|
||||
shell: pwsh
|
||||
env:
|
||||
COMMANDS: ${{ inputs.commands }}
|
||||
run: |
|
||||
$temp = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { [IO.Path]::GetTempPath() }
|
||||
$script = Join-Path $temp "commands-$env:GITHUB_RUN_ID.ps1"
|
||||
# Stop on the first error, including a non-zero exit code from a native command.
|
||||
"`$ErrorActionPreference = 'Stop'`n`$PSNativeCommandUseErrorActionPreference = `$true`n$env:COMMANDS" |
|
||||
Set-Content -LiteralPath $script -Encoding utf8
|
||||
Write-Output $env:COMMANDS
|
||||
try { & $script } finally { Remove-Item -LiteralPath $script -ErrorAction SilentlyContinue }
|
||||
if ($LASTEXITCODE) { exit $LASTEXITCODE }
|
||||
|
||||
- name: Release
|
||||
if: inputs.artifacts != '' && github.ref_type == 'tag' && startsWith(github.ref_name, 'v')
|
||||
shell: pwsh
|
||||
env:
|
||||
ARTIFACTS: ${{ inputs.artifacts }}
|
||||
TOKEN: ${{ secrets.PACKAGES_TOKEN || github.token }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
CHECKSUMS: ${{ inputs.checksums }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$files = foreach ($pattern in ($env:ARTIFACTS -split "`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ })) {
|
||||
$found = @(Get-ChildItem -Path $pattern -File)
|
||||
if ($found.Count -eq 0) { throw "No file matches $pattern" }
|
||||
$found
|
||||
}
|
||||
$sums = Join-Path ([IO.Path]::GetTempPath()) "SHA256SUMS-$env:GITHUB_RUN_ID"
|
||||
$files | ForEach-Object { '{0} {1}' -f (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(), $_.Name } |
|
||||
Set-Content -LiteralPath $sums -Encoding ascii
|
||||
Get-Content -LiteralPath $sums
|
||||
|
||||
$api = "$env:GITHUB_SERVER_URL/api/v1/repos/$env:GITHUB_REPOSITORY"
|
||||
$headers = @{ Authorization = "token $env:TOKEN" }
|
||||
function Get-Release {
|
||||
try { Invoke-RestMethod -Headers $headers "$api/releases/tags/$env:TAG" }
|
||||
catch { if ($_.Exception.Response.StatusCode.value__ -ne 404) { throw }; $null }
|
||||
}
|
||||
$release = Get-Release
|
||||
if (-not $release) {
|
||||
$body = @{ tag_name = $env:TAG; name = $env:TAG; prerelease = $env:TAG.Contains('-') } | ConvertTo-Json
|
||||
# Another job may create the same release concurrently: fall back to reading it.
|
||||
try { $release = Invoke-RestMethod -Method Post -Headers $headers -ContentType 'application/json' -Body $body "$api/releases" }
|
||||
catch { $release = Get-Release; if (-not $release) { throw } }
|
||||
}
|
||||
$uploads = @($files | ForEach-Object { @{ Path = $_.FullName; Name = $_.Name } }) + @{ Path = $sums; Name = $env:CHECKSUMS }
|
||||
foreach ($upload in $uploads) {
|
||||
# Re-runs replace an asset of the same name instead of failing.
|
||||
foreach ($old in @($release.assets | Where-Object name -eq $upload.Name)) {
|
||||
Invoke-RestMethod -Method Delete -Headers $headers "$api/releases/$($release.id)/assets/$($old.id)" | Out-Null
|
||||
}
|
||||
$name = [Uri]::EscapeDataString($upload.Name)
|
||||
Invoke-RestMethod -Method Post -Headers $headers -Form @{ attachment = Get-Item -LiteralPath $upload.Path } "$api/releases/$($release.id)/assets?name=$name" | Out-Null
|
||||
Write-Output "attached $($upload.Name) to release $env:TAG"
|
||||
}
|
||||
Remove-Item -LiteralPath $sums
|
||||
@@ -120,6 +120,96 @@ To consume a package:
|
||||
Binaries for macOS or Windows need native runners; repositories that ship
|
||||
them (for example `QRun-IO/qqq-cli`) keep their own release workflow.
|
||||
|
||||
## Android
|
||||
|
||||
`.gitea/workflows/build.yaml` (see `QRun-IO/qqq-android`):
|
||||
|
||||
```yaml
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
tags: ['v*']
|
||||
jobs:
|
||||
android:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/android.yaml@v1
|
||||
secrets: inherit
|
||||
with:
|
||||
tasks: |
|
||||
testDebugUnitTest lintDebug assembleDebug
|
||||
emulator-tasks: connectedDebugAndroidTest
|
||||
```
|
||||
|
||||
| Input | Default | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `tasks` | `build` | Gradle invocations, one per line (arguments to `./gradlew`) |
|
||||
| `java-version` | `'21'` | JDK from the image (8, 17, 21, 25) |
|
||||
| `commands` | `''` | Shell commands run before Gradle |
|
||||
| `node-version` | `''` | Install this Node.js (official tarball, checksum-verified) for `commands` |
|
||||
| `emulator-tasks` | `''` | Gradle invocations run with a booted emulator (needs `/dev/kvm`) |
|
||||
| `emulator-image` | `system-images;android-30;google_apis;x86` | AVD system image |
|
||||
| `publish-tasks` | `''` | Run on `vX.Y.Z` tags with `-Pversion=X.Y.Z` |
|
||||
| `runner` | `android-build` | Runner label (Lore builders) |
|
||||
|
||||
Runs in the pinned `ci-android-runtime` image (cimg/android plus the emulator).
|
||||
To publish an AAR, declare a `maven { name = "gitea" ... credentials(PasswordCredentials::class) }`
|
||||
repository whose URL comes from the `giteaMavenUrl` Gradle property; the workflow
|
||||
sets it to the owner's Gitea Maven registry with `PACKAGES_TOKEN` (or the job token).
|
||||
|
||||
## Windows and macOS
|
||||
|
||||
Host jobs (no container) on the native runners. The repository's own commands
|
||||
run with the tools the host provides; nothing is installed.
|
||||
|
||||
```yaml
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
branches: ['**']
|
||||
tags: ['v*']
|
||||
jobs:
|
||||
windows:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/windows.yaml@v1
|
||||
secrets: inherit
|
||||
with:
|
||||
commands: |
|
||||
dotnet restore App.slnx --locked-mode
|
||||
dotnet build App.slnx -warnaserror --no-restore
|
||||
dotnet test tests/App.Tests --no-build
|
||||
artifacts: artifacts/release/*.msi
|
||||
macos:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/macos.yaml@v1
|
||||
secrets: inherit
|
||||
with:
|
||||
commands: |
|
||||
swift build
|
||||
swift test
|
||||
```
|
||||
|
||||
| Input | Windows | macOS | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `commands` | PowerShell 7, one script | bash `-exo pipefail` | Required. The first failure fails the build |
|
||||
| `runner` | `kof-windows-amd64` | `kof-macos-arm64` | QRun-IO repositories use `qqq-windows-vm6218` / `qqq-macos-arm64` |
|
||||
| `dotnet-version` | `''` | | Fail unless a host SDK starts with this version |
|
||||
| `xcode` | | `''` | Select Xcode by version (`/Applications/Xcode_<v>.app`) or path via `DEVELOPER_DIR` |
|
||||
| `lfs` | | `false` | Fetch Git LFS objects (needs `git-lfs` on the host) |
|
||||
| `artifacts` | `''` | `''` | Files or globs, one per line; on `v*` tags they and a `SHA256SUMS` are attached to the tag's Gitea release |
|
||||
| `checksums` | `SHA256SUMS` | `SHA256SUMS` | Checksum asset name; give each platform its own when several jobs release one tag |
|
||||
|
||||
- Trigger on `push` only. Host runners run repository code on the machine, and
|
||||
push events come only from branches of the repository itself, never forks.
|
||||
- Windows: native commands with a non-zero exit fail the build. A `.ps1` that
|
||||
reports failure with `exit 1` must run as `pwsh -NoProfile -File x.ps1`; called
|
||||
with `&` it would not stop the script. Test tools that assert on child exit
|
||||
codes themselves (Pester suites) need `$PSNativeCommandUseErrorActionPreference = $false` first.
|
||||
- Releases use `PACKAGES_TOKEN` (or the job token). Re-running a tag replaces
|
||||
assets of the same name. A Homebrew or Scoop update can read the release's
|
||||
`SHA256SUMS`.
|
||||
- Host limits today: the Windows runner is a batch-logon task with no interactive
|
||||
desktop (no FlaUI UI tests); the macOS runner is a launchd daemon with no GUI
|
||||
session, so `xcodebuild test` cannot reach `testmanagerd` (SwiftPM `swift test`
|
||||
works), and the host has no `git-lfs`.
|
||||
|
||||
## Releasing
|
||||
|
||||
| To publish | Do this |
|
||||
|
||||
Reference in new issue
Block a user