Skip to content

Commit c5de6f3

Browse files
committed
ci: migrate Linux workflow to RunsOn self-hosted runners
Move the Linux build and debug-validation jobs from GitHub-hosted runners to RunsOn ephemeral EC2 runners (qgc-ci-runs-on stack in us-west-2, RunsOn v3.0.6). x86_64 builds now run on c8i.2xlarge, ARM64 on c8g.2xlarge, the Debug+coverage test job on m8i.2xlarge — all 8 vCPU, On-Demand, ubuntu24-full-* images. Runner labels are inline in the workflow so this PR is self-contained (named runner profiles in .github/runs-on.yml require the config on the default branch first); the named profiles are kept in the tree for the next workflow migration to reuse. Caching: extras=s3-cache + runs-on/action@v2 transparently redirect all existing actions/cache@v5 calls (ccache, Qt SDK, GStreamer, pipx, apt, CPM) to the S3 bucket provisioned by the stack. runs-on/action is a no-op on GitHub-hosted runners so the workflow stays portable. Matrix cleanup: dropped the dual-purpose `matrix.os` field on the build job (it was both a runner selector and a discriminator for two size-analysis steps). matrix.arch is now the single discriminator; the previous `matrix.os == 'ubuntu-22.04'` conditions on lines 128 and 135 now correctly read `matrix.arch == 'linux_gcc_64'`. Both architectures build on Ubuntu 24.04 (was 22.04 for x64, 24.04 for ARM). This bumps the AppImage glibc baseline from 2.35 to 2.39; older distros (RHEL 8, Ubuntu 20.04, Debian 11) won't run binaries produced here. Test execution restructured by label. cmake/QGCTest.cmake:164 auto-attaches RESOURCE_LOCK "MockLink" to every Integration test because MockLink shares a LinkManager singleton and static _nextVehicleSystemId counter; a single CTest invocation over both labels with --parallel auto silently serialized everything on that lock. Split into two passes: - Run Unit Tests (parallel): -L Unit, --parallel auto. 151 Unit tests with no shared state. - Run Integration Tests (serial): -L Integration, --parallel 1. 37 Integration tests serialize on shared MockLink state. Each pass writes its own junit + ctest output; downstream Analyze / Report / Upload steps run once per pass. Coverage path picks up .gcda from both passes via the existing find . -name '*.gcda'. Tester runner uses volume=60gb (40GB default left ~1-2GB headroom at peak with the Debug build + Qt SDK + caches + .gcda + scratch, which silently killed the agent before any diagnostic could run). Signed-off-by: Ramon Roche <mrpollo@gmail.com>
1 parent 5d3af65 commit c5de6f3

2 files changed

Lines changed: 97 additions & 29 deletions

File tree

.github/runs-on.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
runners:
2+
linux-x64-builder:
3+
family: ["c8i.2xlarge"]
4+
spot: false
5+
image: ubuntu24-full-x64
6+
extras: s3-cache
7+
linux-arm64-builder:
8+
family: ["c8g.2xlarge"]
9+
spot: false
10+
image: ubuntu24-full-arm64
11+
extras: s3-cache
12+
linux-x64-tester:
13+
family: ["m8i.2xlarge"]
14+
spot: false
15+
image: ubuntu24-full-x64
16+
extras: s3-cache
17+
volume: 60gb

.github/workflows/linux.yml

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,31 @@ jobs:
3939
name: Release ${{ matrix.arch }}
4040
needs: changes
4141
if: always() && !cancelled() && (needs.changes.outputs.should_build == 'true' || needs.changes.result == 'skipped')
42-
runs-on: ${{ matrix.os }}
42+
runs-on: ${{ matrix.runs_on }}
4343
timeout-minutes: 120
4444

4545
strategy:
4646
fail-fast: false
4747
matrix:
48-
os: [ubuntu-24.04-arm, ubuntu-22.04]
4948
include:
50-
- os: ubuntu-24.04-arm
49+
- arch: linux_gcc_arm64
50+
runs_on: runs-on=${{ github.run_id }}/family=c8g.2xlarge/spot=false/image=ubuntu24-full-arm64/extras=s3-cache
5151
package: QGroundControl-aarch64
5252
host: linux_arm64
53-
arch: linux_gcc_arm64
5453

55-
- os: ubuntu-22.04
54+
- arch: linux_gcc_64
55+
runs_on: runs-on=${{ github.run_id }}/family=c8i.2xlarge/spot=false/image=ubuntu24-full-x64/extras=s3-cache
5656
package: QGroundControl-x86_64
5757
host: linux
58-
arch: linux_gcc_64
5958

6059
defaults:
6160
run:
6261
shell: bash
6362

6463
steps:
64+
- name: Enable RunsOn magic cache
65+
uses: runs-on/action@v2
66+
6567
- name: Harden Runner
6668
if: runner.arch != 'ARM64'
6769
uses: step-security/harden-runner@v2
@@ -123,14 +125,14 @@ jobs:
123125
binary-path: ${{ runner.temp }}/build/Release/QGroundControl
124126

125127
- name: Analyze binary size
126-
if: matrix.os == 'ubuntu-22.04'
128+
if: matrix.arch == 'linux_gcc_64'
127129
uses: ./.github/actions/size-analysis
128130
with:
129131
binary-path: ${{ runner.temp }}/build/Release/QGroundControl
130132
output-file: ${{ runner.temp }}/build/metrics.json
131133

132134
- name: Upload metrics artifact
133-
if: matrix.os == 'ubuntu-22.04'
135+
if: matrix.arch == 'linux_gcc_64'
134136
uses: actions/upload-artifact@v7
135137
with:
136138
name: size-metrics
@@ -185,7 +187,11 @@ jobs:
185187
name: ${{ matrix.job_name }}
186188
needs: [changes, debug-matrix]
187189
if: ${{ !cancelled() && (needs.changes.outputs.should_build == 'true' || needs.changes.result == 'skipped') }}
188-
runs-on: ubuntu-22.04
190+
# 60GB volume so the Debug build + Qt SDK + caches + Integration test
191+
# artifacts fit comfortably; the 40GB default left only ~1-2GB headroom
192+
# at peak and killed the runner agent before any diagnostic step could
193+
# run.
194+
runs-on: runs-on=${{ github.run_id }}/family=m8i.2xlarge/spot=false/image=ubuntu24-full-x64/extras=s3-cache/volume=60gb
189195
timeout-minutes: ${{ matrix.timeout_minutes }}
190196

191197
strategy:
@@ -198,6 +204,9 @@ jobs:
198204
shell: bash
199205

200206
steps:
207+
- name: Enable RunsOn magic cache
208+
uses: runs-on/action@v2
209+
201210
- name: Harden Runner
202211
uses: step-security/harden-runner@v2
203212
with:
@@ -240,53 +249,95 @@ jobs:
240249
build-dir: ${{ runner.temp }}/build
241250
build-type: Debug
242251

243-
- name: Run Unit Tests
244-
id: tests
252+
# Split test execution by label: Unit tests parallelize cleanly (no
253+
# MockLink/Vehicle shared state), Integration tests share LinkManager
254+
# singletons and per-test RESOURCE_LOCK so they serialize naturally on
255+
# one CTest invocation. Run them in two passes so the Unit pass can use
256+
# all cores while the Integration pass stays correct.
257+
- name: Run Unit Tests (parallel)
258+
id: unit_tests
259+
uses: ./.github/actions/run-unit-tests
260+
env:
261+
ASAN_OPTIONS: ${{ matrix.mode == 'sanitizers' && 'detect_leaks=1:halt_on_error=1:check_initialization_order=1' || '' }}
262+
LSAN_OPTIONS: ${{ matrix.mode == 'sanitizers' && format('suppressions={0}/build/asan_suppressions.txt', runner.temp) || '' }}
263+
UBSAN_OPTIONS: ${{ matrix.mode == 'sanitizers' && format('print_stacktrace=1:halt_on_error=1:suppressions={0}/build/ubsan_suppressions.txt', runner.temp) || '' }}
264+
with:
265+
build-dir: ${{ runner.temp }}/build
266+
junit-output: junit-results-linux-${{ matrix.mode }}-unit.xml
267+
ctest-output: test-output-linux-${{ matrix.mode }}-unit.txt
268+
include-labels: 'Unit'
269+
exclude-labels: ${{ matrix.exclude_labels }}
270+
parallel: auto
271+
272+
- name: Run Integration Tests (serial)
273+
id: integration_tests
274+
if: always() && !cancelled() && steps.unit_tests.conclusion != 'cancelled'
245275
uses: ./.github/actions/run-unit-tests
246276
env:
247277
ASAN_OPTIONS: ${{ matrix.mode == 'sanitizers' && 'detect_leaks=1:halt_on_error=1:check_initialization_order=1' || '' }}
248278
LSAN_OPTIONS: ${{ matrix.mode == 'sanitizers' && format('suppressions={0}/build/asan_suppressions.txt', runner.temp) || '' }}
249279
UBSAN_OPTIONS: ${{ matrix.mode == 'sanitizers' && format('print_stacktrace=1:halt_on_error=1:suppressions={0}/build/ubsan_suppressions.txt', runner.temp) || '' }}
250280
with:
251281
build-dir: ${{ runner.temp }}/build
252-
junit-output: junit-results-linux-${{ matrix.mode }}.xml
253-
ctest-output: test-output-linux-${{ matrix.mode }}.txt
254-
include-labels: 'Unit|Integration'
282+
junit-output: junit-results-linux-${{ matrix.mode }}-integration.xml
283+
ctest-output: test-output-linux-${{ matrix.mode }}-integration.txt
284+
include-labels: 'Integration'
255285
exclude-labels: ${{ matrix.exclude_labels }}
256-
# Coverage requires serial execution (gcov writes race); sanitizers don't.
257-
parallel: ${{ matrix.mode == 'coverage' && '1' || 'auto' }}
286+
parallel: '1'
258287

259-
- name: Analyze Unit Test Durations
260-
if: always() && !cancelled() && steps.tests.conclusion != 'skipped'
288+
- name: Analyze Unit Test Durations (unit)
289+
if: always() && !cancelled() && steps.unit_tests.conclusion != 'skipped'
261290
uses: ./.github/actions/test-duration-report
262291
with:
263-
junit-path: ${{ runner.temp }}/build/junit-results-linux-${{ matrix.mode }}.xml
264-
report-json-path: ${{ runner.temp }}/build/test-duration-linux-${{ matrix.mode }}.json
292+
junit-path: ${{ runner.temp }}/build/junit-results-linux-${{ matrix.mode }}-unit.xml
293+
report-json-path: ${{ runner.temp }}/build/test-duration-linux-${{ matrix.mode }}-unit.json
265294
top-n: '20'
266295
slow-threshold-seconds: '60'
267296

268-
- name: Report Test Results
269-
if: always() && !cancelled() && steps.tests.conclusion != 'skipped'
297+
- name: Analyze Unit Test Durations (integration)
298+
if: always() && !cancelled() && steps.integration_tests.conclusion != 'skipped'
299+
uses: ./.github/actions/test-duration-report
300+
with:
301+
junit-path: ${{ runner.temp }}/build/junit-results-linux-${{ matrix.mode }}-integration.xml
302+
report-json-path: ${{ runner.temp }}/build/test-duration-linux-${{ matrix.mode }}-integration.json
303+
top-n: '20'
304+
slow-threshold-seconds: '60'
305+
306+
- name: Report Test Results (unit)
307+
if: always() && !cancelled() && steps.unit_tests.conclusion != 'skipped'
270308
uses: ./.github/actions/test-report
271309
with:
272310
name: Unit Tests (${{ matrix.mode }})
273311
build-dir: ${{ runner.temp }}/build
274-
junit-file: junit-results-linux-${{ matrix.mode }}.xml
275-
output-file: test-output-linux-${{ matrix.mode }}.txt
276-
artifact-name: test-results-linux-${{ matrix.mode }}
312+
junit-file: junit-results-linux-${{ matrix.mode }}-unit.xml
313+
output-file: test-output-linux-${{ matrix.mode }}-unit.txt
314+
artifact-name: test-results-linux-${{ matrix.mode }}-unit
315+
retention-days: 7
316+
trunk-org-slug: ${{ vars.TRUNK_ORG_SLUG }}
317+
trunk-token: ${{ secrets.TRUNK_TOKEN }}
318+
319+
- name: Report Test Results (integration)
320+
if: always() && !cancelled() && steps.integration_tests.conclusion != 'skipped'
321+
uses: ./.github/actions/test-report
322+
with:
323+
name: Integration Tests (${{ matrix.mode }})
324+
build-dir: ${{ runner.temp }}/build
325+
junit-file: junit-results-linux-${{ matrix.mode }}-integration.xml
326+
output-file: test-output-linux-${{ matrix.mode }}-integration.txt
327+
artifact-name: test-results-linux-${{ matrix.mode }}-integration
277328
retention-days: 7
278329
trunk-org-slug: ${{ vars.TRUNK_ORG_SLUG }}
279330
trunk-token: ${{ secrets.TRUNK_TOKEN }}
280331

281332
- name: Upload Test Artifacts
282-
if: always() && !cancelled() && steps.tests.conclusion != 'skipped'
333+
if: always() && !cancelled() && (steps.unit_tests.conclusion != 'skipped' || steps.integration_tests.conclusion != 'skipped')
283334
uses: actions/upload-artifact@v7
284335
with:
285336
name: test-artifacts-linux-${{ matrix.mode }}
286337
path: |
287-
${{ runner.temp }}/build/test-output-linux-${{ matrix.mode }}.txt
288-
${{ runner.temp }}/build/junit-results-linux-${{ matrix.mode }}.xml
289-
${{ runner.temp }}/build/test-duration-linux-${{ matrix.mode }}.json
338+
${{ runner.temp }}/build/test-output-linux-${{ matrix.mode }}-*.txt
339+
${{ runner.temp }}/build/junit-results-linux-${{ matrix.mode }}-*.xml
340+
${{ runner.temp }}/build/test-duration-linux-${{ matrix.mode }}-*.json
290341
retention-days: 7
291342

292343
- name: Verify coverage data files exist

0 commit comments

Comments
 (0)