-
Notifications
You must be signed in to change notification settings - Fork 2
178 lines (161 loc) · 8.5 KB
/
Copy pathbuild.yml
File metadata and controls
178 lines (161 loc) · 8.5 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
# Build + cheap invariant gate for TinyOS.
#
# SCOPE: 58 of the 63 harnesses in verify/ boot a QEMU guest and drive it with
# a scripted typist. They are kept out of this workflow because a job that
# boots 58 guests is slow and needs its own timeout/retry design, NOT because
# the typist is unreliable -- that claim used to stand here and it was wrong.
#
# It said keystrokes "drop under TCG load", citing CLAUDE.md. Measured: across
# ~45 boots (auto-verify-exec 20/20, plus six harnesses run 2-5x each) ZERO
# keystrokes were dropped. Every failure was deterministic and every one was a
# defect in the harness, not the guest: six standing failures across five files
# that had never been run end-to-end (PR #115). "Flaky QEMU tests" was the
# story; unrun tests was the fact. Anyone adding the QEMU suite here should
# design for runtime, and should not expect to need retries.
#
# So this workflow runs what is both deterministic AND cheap:
#
# 1. the build, warning-clean under -Werror -- twice, since
# -DTINYOS_FAULT_INJECT compiles 15 blocks the default build never sees
# 2. the four harnesses that never boot a guest
#
# The -Werror gate is the point. A clean build takes under a second locally,
# so nothing but habit was enforcing it, and the flags are load-bearing:
# -Werror=implicit-function-declaration and -Werror=uninitialized have each
# caught real kernel bugs in this tree.
#
# TOOLCHAIN: the tree builds with any i686 ELF-targeting gcc. It includes only
# genuine freestanding headers (stdarg/stdbool/stddef/stdint/limits -- the
# lone <stdio.h> is in src/test_offset.c, which is not in OBJ), links with
# -nostdlib against elf32-i386, and pulls only libgcc. So Ubuntu's packaged
# gcc-i686-linux-gnu works and no source-built cross-compiler is needed.
name: build
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc-i686-linux-gnu binutils-i686-linux-gnu nasm
- name: Build (must be warning-clean under -Werror)
run: |
GCCINC="$(i686-linux-gnu-gcc -print-file-name=include)"
make -j"$(nproc)" CROSS=i686-linux-gnu- \
EXTRA_CFLAGS="-nostdinc -isystem $GCCINC" kernel.elf
- name: Confirm the artifact is a 32-bit ELF
run: |
file kernel.elf
i686-linux-gnu-readelf -h kernel.elf | grep -E 'Class:|Machine:'
# Each header must compile on its own. A header that names bool or
# uint32_t without including <stdbool.h>/<stdint.h> builds fine as long
# as every .c that includes it happens to pull those in FIRST -- so the
# breakage is invisible until an include order changes or the toolchain
# does. That is exactly how this workflow's first run failed: dns.h had
# declared bool since the initial release, and six of its eight
# includers were getting the type transitively.
# -DTINYOS_FAULT_INJECT guards 15 blocks across 8 files and is required by
# six harnesses -- and until now NOTHING in CI compiled any of it. The flag
# is deliberately absent from the make dependency graph (that is why
# run-all.sh `make clean`s after each fault-inject harness), so these blocks
# are invisible to every default build: a type error or a stale symbol
# inside one ships green and only surfaces when someone runs the harness
# locally, where it reads as that harness breaking rather than as a build
# error introduced weeks earlier.
#
# Compile-only, deliberately. Running the fault-inject harnesses needs a
# guest; proving the code still COMPILES needs only the toolchain, and
# that is where the whole failure mode lives.
- name: Fault-inject build is warning-clean too
run: |
make clean
GCCINC="$(i686-linux-gnu-gcc -print-file-name=include)"
make -j"$(nproc)" CROSS=i686-linux-gnu- \
EXTRA_CFLAGS="-nostdinc -isystem $GCCINC -DTINYOS_FAULT_INJECT" \
kernel.elf
make clean
- name: Every header compiles standalone
run: |
echo 'int ci_translation_unit;' > /tmp/probe.c
rc=0
for h in src/*.h userspace/*.h; do
i686-linux-gnu-gcc -m32 -ffreestanding -Isrc -Iuserspace \
-nostdinc -isystem "$(i686-linux-gnu-gcc -print-file-name=include)" \
-c -include "$h" /tmp/probe.c -o /tmp/probe.o 2>/tmp/err.txt \
|| { echo "NOT SELF-SUFFICIENT: $h"; sed -n '1,6p' /tmp/err.txt; rc=1; }
done
exit $rc
source-guards:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# None of these boots a guest, so all are deterministic in CI.
#
# verify-arch-svg.sh : the architecture diagram still matches
# the tree (17 checks)
# verify-shell-path-overflow.sh : the path-reserve arithmetic does not
# overflow; runs its own pre-fix negative
# control, so a vacuous pass is visible
# verify-entropy-pool-stir.sh : pool_counter is incremented
# unconditionally (source-level)
# edr-rejoin-test.sh : the shared EDR splice helper behaves on
# all 9 cases. Pure text -- it feeds
# fixtures through the awk and diffs the
# output, so no guest and no toolchain.
# Two of the nine are negative controls.
# It gates the helper that 20 harnesses
# now source: a regression there turns
# correct kernels into FAILs across the
# whole suite, and only this catches it
# without booting anything.
# preserve-serial-test.sh : the shared preserve-on-failure helper
# keeps a failing run's serial log and
# leaves nothing on a passing one. Pure
# shell, no guest. Test 5 is a negative
# control asserting the trap $? clobber
# is real -- without it, test 1 could
# silently stop testing anything.
# verify-elf-enforce-report.sh: secstatus reports the REAL ELF gate.
# This one BUILDS -- twice, default and
# -DELF_PERMISSIVE_SIGNATURES -- and
# asserts the two disagree, so it carries
# its own negative control. It needs the
# toolchain, hence the install step and
# the CROSS/OBJDUMP/EXTRA_CFLAGS it is
# handed below.
#
# firstexec-trial.sh is the one non-QEMU script NOT here: it wraps the
# interactive GUI verify-exec.sh and blocks forever when batched.
- name: Architecture diagram matches the tree
run: bash verify/verify-arch-svg.sh
- name: Shell path-reserve overflow guard
run: bash verify/verify-shell-path-overflow.sh
- name: Entropy pool is actually stirred
run: bash verify/verify-entropy-pool-stir.sh
- name: EDR splice helper unit tests
run: bash verify/edr-rejoin-test.sh
- name: Serial-log preservation helper unit tests
run: bash verify/preserve-serial-test.sh
- name: Install toolchain
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
gcc-i686-linux-gnu binutils-i686-linux-gnu nasm
# It cleans and rebuilds twice, so it must not race the build job's tree;
# it runs in this separate job on its own checkout.
- name: secstatus reports the real ELF signature gate
run: |
GCCINC="$(i686-linux-gnu-gcc -print-file-name=include)"
CROSS=i686-linux-gnu- \
OBJDUMP=i686-linux-gnu-objdump \
EXTRA_CFLAGS="-nostdinc -isystem $GCCINC" \
bash verify/verify-elf-enforce-report.sh