Skip to content

ci: run the full smoke test once, and split the GNUmakefile - #5616

Merged
deadprogram merged 4 commits into
devfrom
ci-speedup-makefile-split
Aug 28, 2026
Merged

ci: run the full smoke test once, and split the GNUmakefile#5616
deadprogram merged 4 commits into
devfrom
ci-speedup-makefile-split

Conversation

@deadprogram

Copy link
Copy Markdown
Member

Why

The smoke test ran six times for each push: twice on Linux, twice on macOS, once on Windows, and once in the compatibility test. Measured on a recent run, that is about 97 minutes of the CI time.

Job Workflow Smoke test time
test-linux-build linux.yml 14.7 min
assert-test-linux linux.yml 13.9 min
build-macos (macos-15-intel) build-macos.yml 26.3 min
build-macos (macos-14) build-macos.yml 10.6 min
smoke-test-windows windows.yml 18.0 min
test-compat compat.yml 14.2 min

The smoke test checks that TinyGo can build a binary for each of 144 boards. That does not depend on the host OS. The only part that does is one build behind a Windows check. So five of the six runs add very little.

The GNUmakefile also had 1295 lines and mixed build configuration, LLVM bootstrapping, device generation, four test suites, the smoke test, release packaging, and lint tools. The smoke test alone was 500 lines.

What changed

Split the GNUmakefile into topic files in make/. GNUmakefile now holds the default goal and the include lines. config.mk must come first, because the other files use its variables in immediate assignments and conditionals.

Split the smoke test into 14 groups. The split follows the group boundaries that were already in the file, with no lines moved. Each group writes to its own name in build/smoke/, because all builds wrote to test.hex before and would overwrite each other in a parallel build. make -j smoketest now works.

Added smoketest-quick, which builds one board for each processor architecture. It covers all 13 LLVM triples that the full smoke test uses.

Changed the workflows. A new smoketest-linux job runs the full set, split across four runners, using the tarball from build-linux. The groups are balanced with the measured build time of each group. The other four jobs use smoketest-quick.

Effect

Expected: about 55 to 39 minutes for the slowest workflow, and about 97 to 35 minutes of total smoke test time.

The slowest job then becomes assert-test-linux at about 39 minutes. Splitting it into a build step and parallel test jobs, as windows.yml does, is the next step and is not in this PR.

Tests

  • All 226 checksums are identical for the original makefile, the new one, and make -j16.
  • The set of build commands is unchanged for the default flags and for XTENSA=0, STM32=0, WASM=0, and all three together.
  • The parsed make database is identical for the default build and for ASSERT=1, STATIC=1, XTENSA=0, STM32=0, WASM=0, and CROSS=aarch64-linux-gnu. Only MAKEFILE_LIST and the .PHONY list differ. The .PHONY list now also declares targets that were phony but not declared.
  • make help prints the same output.
  • Each of the 14 groups and smoketest-quick were built from a cold cache.
  • make lint, make spell, and make fmt-check pass.
  • A full smoke test goes from 325 to 91 seconds with -j16 on a 32 core machine. smoketest-quick takes 85 seconds from a cold cache.

Notes

The Dockerfile copies GNUmakefile on its own before it builds LLVM, to keep that layer independent of the source tree. It must copy make/ too, or the tinygo-llvm stage fails.

The comment above the esp32c3 group had 4 spaces of indentation instead of a tab. That was harmless in the middle of a recipe, but it is now the first line of a group, where it would stop the recipe from starting.

The ARM builds in build-linux-cross are unchanged. They take 3 to 4 minutes, are not on the critical path, and are a pure cross compile. qemu-user is only used as LLVM_CONFIG_PREFIX while LLVM builds, which is cached and skipped on almost every run. There is also no 32 bit armhf runner.

The GNUmakefile had 1295 lines and mixed build configuration, LLVM
bootstrapping, device generation, four test suites, the smoke tests,
release packaging, and lint tools. The smoke tests alone were 500 lines.

Move each part into its own file in make/ and include them from
GNUmakefile. config.mk must be included first because the other files
use its variables in immediate assignments and conditionals.

There is no change in behavior. The parsed make database is identical
for the default build and for ASSERT=1, STATIC=1, XTENSA=0, STM32=0,
WASM=0, and CROSS=aarch64-linux-gnu, except for MAKEFILE_LIST and the
.PHONY list, which now also includes targets that were phony but not
declared.

The Dockerfile copies GNUmakefile alone before it builds LLVM, to keep
that layer independent of the source tree. It must copy make/ too.
The smoke test was one recipe of about 500 lines with 236 builds that
always ran in sequence. Split it at the group boundaries that were
already there, so that:

- `make -j smoketest` builds the groups in parallel. A full run goes
  from 325 to 91 seconds on a 32 core machine.
- CI can shard the groups across runners.

Each group writes to its own name in build/smoke/, because all builds
wrote to test.hex before and would overwrite each other in a parallel
build. The output extension selects the format, so it stays per line.

Add smoketest-quick, which builds one board for each processor
architecture. The full smoke test answers "can TinyGo build for every
board", which does not depend on the host OS, so it only needs to run
on one OS. The other jobs use smoketest-quick.

The comment above the esp32c3 group had 4 spaces of indentation instead
of a tab. That was harmless in the middle of a recipe, but it is now the
first line of a group, where it would stop the recipe from starting.

The set of build commands is unchanged for the default flags and for
XTENSA=0, STM32=0, and WASM=0. All 226 checksums are the same as before,
for a sequential build and for `make -j16`.
The smoke test ran six times for each push: twice on Linux, twice on
macOS, once on Windows, and once in the compatibility test. Together
that was about 97 minutes of the CI time.

The smoke test checks that TinyGo can build a binary for each board.
That does not depend on the host OS. The only part that does is one
build behind a Windows check.

Add a smoketest-linux job that runs the full set, split across four
runners that use the tarball from the build-linux job. The groups are
balanced with the measured build time of each group. Remove the full
smoke test from test-linux-build, which the new job replaces, and use
smoketest-quick for the other four jobs.

Expected result: about 55 to 39 minutes for the slowest workflow, and
about 97 to 35 minutes of total smoke test time.
It is the only target that uses the aarch64 LLVM triple. With it,
smoketest-quick covers all 13 triples that the full smoke test uses.
@github-actions

Copy link
Copy Markdown

Size difference with the dev branch:

Binary size difference
 flash                          ram
 before   after   diff          before   after   diff
  19684   19684      0   0.00%    7052    7052      0   0.00% tinygo build -size short -o ./build/test.hex -target=feather-rp2040 ./examples/adafruit4650
  63228   63228      0   0.00%    6788    6788      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adt7410/main.go
  10024   10024      0   0.00%    5348    5348      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/adxl345/main.go
  14628   14628      0   0.00%    7396    7396      0   0.00% tinygo build -size short -o ./build/test.hex -target=pybadge ./examples/amg88xx
  10184   10184      0   0.00%    5348    5348      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/main.go
  12592   12592      0   0.00%    7172    7172      0   0.00% tinygo build -size short -o ./build/test.hex -target=nano-33-ble ./examples/apds9960/proximity/main.go
  11136   11136      0   0.00%    5360    5360      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/apa102/itsybitsy-m0/main.go
   7588    7588      0   0.00%    2312    2312      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/at24cx/main.go
   9272    9272      0   0.00%    5340    5340      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bh1750/main.go
   8660    8660      0   0.00%    5340    5340      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/blinkm/main.go
  71408   71408      0   0.00%    3656    3656      0   0.00% tinygo build -size short -o ./build/test.hex -target=pinetime     ./examples/bma42x/main.go
  67120   67120      0   0.00%    6804    6804      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmi160/main.go
  28952   28952      0   0.00%    5380    5380      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp180/main.go
  65720   65720      0   0.00%    6828    6828      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/bmp280/main.go
  13080   13080      0   0.00%    5412    5412      0   0.00% tinygo build -size short -o ./build/test.hex -target=trinket-m0 ./examples/bmp388/main.go
  24024   24024      0   0.00%    6220    6220      0   0.00% tinygo build -size short -o ./build/test.hex -target=metro-rp2350 ./examples/bno08x/i2c/main.go
   8008    8008      0   0.00%    3344    3344      0   0.00% tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/sram/main.go
  22116   22116      0   0.00%    3548    3548      0   0.00% tinygo build -size short -o ./build/test.hex -target=bluepill ./examples/ds1307/time/main.go
  30556   30556      0   0.00%    5576    5576      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/alarms/main.go
  44492   44492      0   0.00%    5576    5576      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/ds3231/basic/main.go
   4592    4592      0   0.00%    2272    2272      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/easystepper/main.go
  71884   71884      0   0.00%    7588    7588      0   0.00% tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi
  67996   67996      0   0.00%    9628    9628      0   0.00% tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi
   7128    7128      0   0.00%    2276    2276      0   0.00% tinygo build -size short -o ./build/test.hex -target=microbit ./examples/gc9a01/main.go
 694072  694072      0   0.00%  131564  131564      0   0.00%

@deadprogram

Copy link
Copy Markdown
Member Author

CI results

All 20 checks pass. These are the times from this branch against the baseline run of dev (runs 33098633057, 33098633062, 33098633063, and 33098633003).

Smoke test jobs

Job or step Before After
Full 144 board set 14.7 min in 1 job, and 5 more partial runs 7 min in 4 shards
smoke-test-windows 19 min 6 min
test-compat 21 min 9 min
Smoke step, macos-15-intel 26.3 min 7.7 min
Smoke step, macos-14 10.6 min 3.3 min
test-linux-build 40 min 26 min
assert-test-linux 49 min 35 min

The four shards took 7:00, 7:14, 7:33, and 7:53. They agree to less than one minute, so the groups do not need a new balance.

Workflow times

Workflow Before After
Linux 50 min 35 min
Version compatibility test 21 min 9 min
Windows 34 min 33 min
macOS 55 min 49 min

Two results that need an explanation

The macOS and Windows workflow times are noisy for this run. The job times above are correct, but these two totals hide the change.

macos-15-intel gives 49 min, not the 36 min that the smoke test change predicts. The smoke step did drop by 18.6 min. However, each other step on that runner became slower by about 12.6 min in total. This PR does not change any of these steps.

Step on macos-15-intel Before After
Test TinyGo 4.5 min 10.0 min
Test stdlib packages 16.7 min 19.5 min
Checkout 2.5 min 3.4 min
make gen-device 1.3 min 2.2 min

macos-14 is the control. It behaved as expected and went from 31 min to 22 min, with all steps other than the smoke test unchanged.

The Windows workflow gives 33 min because build-windows took 19 min in this run, against 14 min in the baseline. This PR does not change that job and its LLVM cache key is the same. Windows now waits for build-windows and stdlib-wasi-test-windows, not for the smoke test.

A second run on this branch will show if these two numbers are runner variance.

Next step

The slowest job is now assert-test-linux at 35 min. It runs make ASSERT=1 test, make tinygo-test, the baremetal tests, and the lint checks one after the other. To split it into a build job and parallel test jobs, in the way that windows.yml does, is the next change. It is not part of this PR.

@jakebailey jakebailey left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems fine, though perhaps one more step when trying to find something and I don't remember where it is

@soypat soypat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of leaning more into make. Since this seems like a net addition and not a replacement/modification of existing tests I urge us to reconsider adding more make

Comment thread make/build.mk
@@ -0,0 +1,41 @@
# Build the TinyGo compiler, plus housekeeping and code generation helpers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just throwing it out there: are we really happy with Makefiles? Just a reminder there's precedent in TinyGo for moving off of the makefile build system towards Go based build systems

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we really happy with Makefiles

I have no problem with Makefiles.

@deadprogram

Copy link
Copy Markdown
Member Author

Thanks for reviews now squash/merging.

@deadprogram
deadprogram merged commit a213d9a into dev Aug 28, 2026
30 checks passed
@deadprogram
deadprogram deleted the ci-speedup-makefile-split branch August 28, 2026 07:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants