-
-
Notifications
You must be signed in to change notification settings - Fork 0
183 lines (171 loc) · 9.66 KB
/
Copy pathci.yml
File metadata and controls
183 lines (171 loc) · 9.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
name: ci
on:
push:
# `develop` is where work lands and `main` is the released line; both are watched. `master`
# used to be listed and has never existed in this repository — a branch filter naming a branch
# that is not there is indistinguishable, from the outside, from a workflow that is running.
branches: [ main, develop ]
pull_request:
workflow_dispatch:
# Least privilege. Nothing here writes to the repository, so nothing here needs a token that can.
permissions:
contents: read
# One run per ref. A push that lands while an earlier run is still going cancels it rather than
# paying for a result nobody will read — except on the released line, where every commit's own
# verdict is worth keeping.
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
linux:
name: linux-${{ matrix.cc }}
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
cc: [gcc, clang]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Nothing in this workflow pushes, so the checkout has no reason to leave a
# credential in .git/config for every later step to inherit.
persist-credentials: false
# apt on a hosted runner, bounded three ways.
#
# Measured twice on this workflow, and the first diagnosis was wrong. The step produced
# ZERO output between the command echo and the cancellation twenty minutes later -- not one
# `Hit:` or `Get:` line -- so it was never fetching: it was waiting on the dpkg/apt lock
# that unattended-upgrades holds while a fresh runner settles, which apt waits for FOREVER
# by default. Acquire::http::Timeout, which was the first fix, bounds network fetches and
# so changed nothing; the second run timed out at 20m11s exactly like the first.
#
# So: DPkg::Lock::Timeout makes apt give up on the lock instead of waiting, `timeout` caps
# the call whatever the reason it hangs, and the retry loop gets a turn because the two
# above guarantee there IS one. The cheapest guard is first, though -- if every tool is
# already on the image, which is the usual case, apt is never invoked at all.
- name: Install toolchain
run: |
set -e
cc_pkgs='${{ matrix.cc == 'gcc' && 'gcc g++' || 'clang' }}'
cc_probe='${{ matrix.cc == 'gcc' && 'gcc g++' || 'clang' }}'
missing=""
for c in cmake make pkg-config autoreconf automake libtoolize m4 perl $cc_probe; do
command -v "$c" >/dev/null 2>&1 || missing="$missing $c"
done
if [ -z "$missing" ]; then
echo "toolchain already present on the image; not touching apt"
else
echo "missing:$missing"
# `sudo timeout`, not `timeout sudo`: the latter signals sudo, which need not pass
# it on to the apt-get it spawned -- the call would look bounded and not be.
APT="sudo timeout 300 apt-get -o DPkg::Lock::Timeout=120 -o Acquire::Retries=3"
for attempt in 1 2 3; do
if $APT update -qq; then break; fi
[ "$attempt" = 3 ] && { echo "::error::apt-get update failed three times"; exit 1; }
echo "::warning::apt-get update failed or timed out (attempt $attempt/3); retrying"
sleep $((attempt * 15))
done
for attempt in 1 2 3; do
if sudo timeout 420 apt-get -o DPkg::Lock::Timeout=120 -o Acquire::Retries=3 \
install -y --no-install-recommends \
cmake make pkg-config autoconf automake libtool m4 perl $cc_pkgs; then break; fi
[ "$attempt" = 3 ] && { echo "::error::apt-get install failed three times"; exit 1; }
echo "::warning::apt-get install failed or timed out (attempt $attempt/3); retrying"
sleep $((attempt * 15))
done
fi
for c in cmake make autoreconf $cc_probe; do
command -v "$c" >/dev/null || { echo "::error::$c still missing after install"; exit 1; }
done
- name: Build matrix (CMake static/shared · autotools · sanitizers · backends · census)
env:
STRICT: 1
CC: ${{ matrix.cc == 'gcc' && 'gcc' || 'clang' }}
CXX: ${{ matrix.cc == 'gcc' && 'g++' || 'clang++' }}
run: bash scripts/test-build-matrix.sh native
macos:
name: macos
runs-on: macos-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Nothing in this workflow pushes, so the checkout has no reason to leave a
# credential in .git/config for every later step to inherit.
persist-credentials: false
- name: Install autotools
run: brew install autoconf automake libtool
- name: Build matrix
env:
STRICT: 1
run: bash scripts/test-build-matrix.sh native
windows:
name: windows-msvc
runs-on: windows-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Nothing in this workflow pushes, so the checkout has no reason to leave a
# credential in .git/config for every later step to inherit.
persist-credentials: false
- name: Configure (CMake, wepoll/epoll backend)
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build --config Release
# This job used to stop at the line above: it compiled, on the one platform this library
# exists for -- a real epoll via wepoll, where libev only ever offered select -- and never
# ran a single watcher. A Windows keep-alive defect in `ev::io::set(int)` lived through two
# months of green runs of exactly that shape.
- name: Test (the watcher suite must actually run)
run: ctest --test-dir build -C Release --output-on-failure
- name: Assert the suite was not empty
shell: bash
run: |
n=$(ctest --test-dir build -C Release -N | sed -n 's/^Total Tests: //p')
echo "registered tests: ${n:-0}"
[ "${n:-0}" -ge 1 ] || { echo "::error::ctest registered no tests - the suite did not build"; exit 1; }
# The invariant the artefact naming exists for. It needs an ARCHIVE, so it cannot live in a
# source-level check.
# ev_* EXPECTED - the library keeps libev's API names, so a migrating consumer
# compiles unchanged; only the header path and the artefact name are ours.
# qev_* FORBIDDEN - a spelling tried and abandoned; source says ev, artefact says qev.
# event_* FORBIDDEN unless QB_EV_LIBEVENT_COMPAT is ON: those 24 unprefixed names collide
# with a real libevent, which is far more widely deployed than libev.
#
# `dumpbin` is NOT on PATH in a plain runner shell -- it lives in the MSVC toolchain, and a
# step that simply called it would fail for a reason having nothing to do with the symbols.
# vswhere ships at a fixed path on every windows runner, so the tool is located rather than
# assumed, and its absence is reported as itself.
- name: Exported-symbol census
shell: pwsh
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (-not (Test-Path $vswhere)) { Write-Error "vswhere not found - cannot locate the MSVC tools"; exit 1 }
$vs = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $vs) { Write-Error "no MSVC toolchain reported by vswhere"; exit 1 }
$dumpbin = Get-ChildItem -Path "$vs\VC\Tools\MSVC" -Recurse -Filter dumpbin.exe -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match 'Hostx64\\x64' } | Select-Object -First 1
if (-not $dumpbin) { Write-Error "dumpbin.exe not found under $vs"; exit 1 }
Write-Host "using $($dumpbin.FullName)"
$lib = Get-ChildItem -Recurse -Filter qev.lib build | Select-Object -First 1
if (-not $lib) { Write-Error "qev.lib not found"; exit 1 }
$syms = & $dumpbin.FullName /SYMBOLS $lib.FullName |
Select-String -Pattern 'External\s+\|\s+(\w+)' -AllMatches |
ForEach-Object { $_.Matches.Groups[1].Value } | Sort-Object -Unique
$ev = @($syms | Where-Object { $_ -match '^_?ev_' })
$qev = @($syms | Where-Object { $_ -match '^_?qev_' })
$event = @($syms | Where-Object { $_ -match '^_?event' })
Write-Host "ev_* = $($ev.Count); qev_* = $($qev.Count); event_* = $($event.Count)"
if ($ev.Count -lt 40) { Write-Error "only $($ev.Count) ev_* symbols - the archive is not what it should be"; exit 1 }
if ($qev.Count -gt 0) { Write-Error "abandoned qev_* spelling is back: $($qev -join ' ')"; exit 1 }
if ($event.Count -gt 0) { Write-Error "unprefixed event_*, the libevent shim must stay OFF: $($event -join ' ')"; exit 1 }
# The census is the one invariant here that a reader might want to see without
# opening a log, so it is also written to the run summary.
$rows = "| symbol family | count | expected |", "|---|---|---|",
"| ``ev_*`` | $($ev.Count) | >= 40 |",
"| ``qev_*`` | $($qev.Count) | 0 |",
"| unprefixed ``event_*`` | $($event.Count) | 0 |"
"### Exported-symbol census (MSVC)", "" + ($rows -join "`n") |
Add-Content -Path $env:GITHUB_STEP_SUMMARY