Skip to content

Commit b81c627

Browse files
authored
ci: build the compiler image on a prebuilt LLVM image (#5671)
* ci: build the compiler image on a prebuilt LLVM image The Docker workflow built LLVM as the first stages of the compiler image. The only thing that prevented a rebuild was the BuildKit registry layer cache. A layer cache is a best-effort optimisation, so CI sometimes built LLVM again although llvm-version.txt did not change. Move the LLVM stages to Dockerfile.llvm and make the compiler image start from that image through an LLVM_IMAGE build argument. The compiler build now holds no LLVM build step, so it cannot build LLVM again. Tag the LLVM image with the LLVM revision and a hash of the files that set the content of the image: llvm-version.txt, Dockerfile.llvm, GNUmakefile, and the files in make/. tools/llvm-image-tag.sh prints the tag, and CI and developers use the same script. The workflow builds and pushes the LLVM image only when the registry does not hold that tag. Remove the object files and the git history from the LLVM image in the same layer as the build. The CI caches already link against that subset, see .github/actions/setup-llvm/action.yml. Delete llvm.yml. It made an image that nothing used, and it needed a push to a special branch. The Docker workflow now does the same work when it is necessary, and the force-llvm input of the manual trigger makes the image again. The LLVM image goes to GHCR only, because only CI uses it. The compiler image continues to go to Docker Hub and GHCR. * ci: remove the LLVM git history in the layer that makes it The removal was in the tinygo-llvm-build stage. This stage is below the layer that makes the shallow clone. A later layer only hides files from a parent layer. Thus the pack files stayed in the published image. * ci: link the LLVM image to the repository The label puts the package in the repository package list, so the package settings are easy to find. BUILDING.md now tells you to build LLVM locally if you are not able to pull the image.
1 parent 013a081 commit b81c627

7 files changed

Lines changed: 167 additions & 91 deletions

File tree

.github/workflows/docker.yml

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,85 @@
22
# If you are looking for the tinygo/tinygo "release" Docker image please see
33
# https://github.com/tinygo-org/docker
44
#
5+
# The compiler image is built on top of an LLVM image. The LLVM image is built
6+
# only when the registry holds no image for the current LLVM version and build
7+
# recipe, so a change to TinyGo alone does not build LLVM again. An LLVM build
8+
# takes at least 1-2 hours.
9+
#
10+
# Use the manual trigger with force-llvm to build the LLVM image again.
511
name: Docker
612
on:
713
push:
8-
branches: [ dev, fix-docker-llvm-build ]
14+
branches: [ dev ]
15+
workflow_dispatch:
16+
inputs:
17+
force-llvm:
18+
description: Build the LLVM image again
19+
type: boolean
20+
default: false
921

1022
concurrency:
1123
group: ${{ github.workflow }}-${{ github.ref }}
1224
cancel-in-progress: true
1325

1426
jobs:
27+
llvm:
28+
name: build-push-llvm
29+
runs-on: ubuntu-latest
30+
permissions:
31+
packages: write
32+
contents: read
33+
outputs:
34+
image: ${{ steps.tag.outputs.image }}
35+
steps:
36+
- name: Free Disk space
37+
shell: bash
38+
run: |
39+
df -h
40+
sudo rm -rf /opt/hostedtoolcache
41+
sudo rm -rf /usr/local/lib/android
42+
sudo rm -rf /usr/share/dotnet
43+
sudo rm -rf /opt/ghc
44+
sudo rm -rf /usr/local/graalvm
45+
sudo rm -rf /usr/local/share/boost
46+
df -h
47+
- name: Check out the repo
48+
uses: actions/checkout@v6
49+
- name: Set up Docker Buildx
50+
uses: docker/setup-buildx-action@v4
51+
- name: Log in to Github Container Registry
52+
uses: docker/login-action@v4
53+
with:
54+
registry: ghcr.io
55+
username: ${{ github.actor }}
56+
password: ${{ secrets.GITHUB_TOKEN }}
57+
- name: Get the image tag
58+
id: tag
59+
shell: bash
60+
run: |
61+
tag=$(sh tools/llvm-image-tag.sh)
62+
echo "image=ghcr.io/${{ github.repository_owner }}/llvm-22:$tag" >> "$GITHUB_OUTPUT"
63+
- name: Look for the image
64+
id: check
65+
shell: bash
66+
run: |
67+
if docker buildx imagetools inspect "${{ steps.tag.outputs.image }}" > /dev/null 2>&1; then
68+
echo "exists=true" >> "$GITHUB_OUTPUT"
69+
else
70+
echo "exists=false" >> "$GITHUB_OUTPUT"
71+
fi
72+
- name: Build and push
73+
if: steps.check.outputs.exists != 'true' || inputs.force-llvm
74+
uses: docker/build-push-action@v7
75+
with:
76+
context: .
77+
file: Dockerfile.llvm
78+
push: true
79+
tags: ${{ steps.tag.outputs.image }}
80+
1581
push_to_registry:
1682
name: build-push-dev
83+
needs: llvm
1784
runs-on: ubuntu-latest
1885
permissions:
1986
packages: write
@@ -62,7 +129,9 @@ jobs:
62129
with:
63130
context: .
64131
push: true
132+
build-args: |
133+
LLVM_IMAGE=${{ needs.llvm.outputs.image }}
65134
tags: ${{ steps.meta.outputs.tags }}
66135
labels: ${{ steps.meta.outputs.labels }}
67136
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/tinygo-dev:buildcache
68-
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/tinygo-dev:buildcache,mode=max
137+
cache-to: type=registry,ref=ghcr.io/${{ github.repository_owner }}/tinygo-dev:buildcache,mode=max,ignore-error=true

.github/workflows/llvm.yml

Lines changed: 0 additions & 63 deletions
This file was deleted.

BUILDING.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ It provides a help target for quick reference:
4545
tinygo Build the TinyGo compiler
4646
lint Lint source tree
4747
spell Spellcheck source tree
48+
llvm-image-tag Print the tag of the prebuilt LLVM Docker image
49+
docker-llvm Build the LLVM base image (slow)
50+
docker-tinygo Build the TinyGo compiler image
4851

4952
## Download the source
5053

@@ -54,8 +57,8 @@ the git repository). Then, inside the directory, download the LLVM source:
5457
make llvm-source
5558

5659
The LLVM commit to use is pinned in `llvm-version.txt`. A change to that file
57-
makes CI build LLVM again, because the file is part of the LLVM cache key. All
58-
other changes reuse the cached LLVM build.
60+
makes CI build LLVM again. The file is part of the LLVM cache key and of the
61+
Docker image tag. All other changes reuse the cached LLVM build.
5962

6063
You can also store LLVM outside of the TinyGo root directory by setting the
6164
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
@@ -105,6 +108,29 @@ On macOS, use otool -L:
105108

106109
The result should not contain libclang or libLLVM.
107110

111+
## Build with Docker
112+
113+
The Docker build uses two images. The LLVM image holds the LLVM build, and the
114+
compiler image holds TinyGo. The LLVM image changes only when
115+
`llvm-version.txt`, `Dockerfile.llvm`, or the make files change, so the slow
116+
LLVM build does not run again for each change to TinyGo.
117+
118+
To build both images:
119+
120+
make docker-llvm
121+
make docker-tinygo
122+
123+
The first command takes 1-2 hours. To use the LLVM image that CI published
124+
instead of a local build:
125+
126+
docker build -t tinygo-dev \
127+
--build-arg LLVM_IMAGE=ghcr.io/tinygo-org/llvm-22:$(sh tools/llvm-image-tag.sh) .
128+
129+
`tools/llvm-image-tag.sh` prints the tag of the LLVM image for the current
130+
source tree, and `make llvm-image-tag` does the same. CI uses that script, so
131+
the tag agrees. If the registry does not hold that tag, or you are not able to
132+
pull it, build the LLVM image with `make docker-llvm`.
133+
108134
## Make a release tarball
109135

110136
Now that we have a working static build, it's time to make a release tarball:

Dockerfile

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,12 @@
1-
# tinygo-llvm stage obtains the llvm source for TinyGo
2-
FROM golang:1.27 AS tinygo-llvm
3-
4-
RUN apt-get update && \
5-
apt-get install -y apt-utils make cmake clang-17 ninja-build && \
6-
rm -rf \
7-
/var/lib/apt/lists/* \
8-
/var/log/* \
9-
/var/tmp/* \
10-
/tmp/*
11-
12-
COPY ./GNUmakefile /tinygo/GNUmakefile
13-
COPY ./make /tinygo/make
14-
COPY ./llvm-version.txt /tinygo/llvm-version.txt
15-
16-
RUN cd /tinygo/ && \
17-
make llvm-source
18-
19-
# tinygo-llvm-build stage build the custom llvm with xtensa support
20-
FROM tinygo-llvm AS tinygo-llvm-build
21-
22-
RUN cd /tinygo/ && \
23-
make llvm-build
1+
# Build the TinyGo compiler on top of a prebuilt LLVM image.
2+
# Build the base image first with:
3+
# docker build -t tinygo-llvm-build -f Dockerfile.llvm .
4+
# Or use the image that CI published:
5+
# docker build --build-arg LLVM_IMAGE=ghcr.io/tinygo-org/llvm-22:<tag> .
6+
ARG LLVM_IMAGE=tinygo-llvm-build
247

258
# tinygo-compiler-build stage builds the compiler itself
26-
FROM tinygo-llvm-build AS tinygo-compiler-build
9+
FROM ${LLVM_IMAGE} AS tinygo-compiler-build
2710

2811
COPY . /tinygo
2912

Dockerfile.llvm

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Build the LLVM base image for the TinyGo compiler image.
2+
# It is built again only when llvm-version.txt or the build recipe changes.
3+
# tools/llvm-image-tag.sh prints the tag and lists the files that set it.
4+
5+
# tinygo-llvm stage obtains the llvm source for TinyGo
6+
FROM golang:1.27 AS tinygo-llvm
7+
8+
RUN apt-get update && \
9+
apt-get install -y apt-utils make cmake clang-17 ninja-build && \
10+
rm -rf \
11+
/var/lib/apt/lists/* \
12+
/var/log/* \
13+
/var/tmp/* \
14+
/tmp/*
15+
16+
COPY ./GNUmakefile /tinygo/GNUmakefile
17+
COPY ./make /tinygo/make
18+
COPY ./llvm-version.txt /tinygo/llvm-version.txt
19+
20+
# Remove the git history in the layer that makes it. A later layer only hides
21+
# files from a parent layer.
22+
RUN cd /tinygo/ && \
23+
make llvm-source && \
24+
rm -rf llvm-project/.git
25+
26+
# tinygo-llvm-build stage build the custom llvm with xtensa support.
27+
# The object files are removed in the same layer as the build to keep the image
28+
# small. The static libraries and the headers stay, because TinyGo links to
29+
# them. The CI caches keep the same subset, see
30+
# .github/actions/setup-llvm/action.yml.
31+
FROM tinygo-llvm AS tinygo-llvm-build
32+
33+
# Link the package to the repository. See
34+
# https://docs.github.com/en/packages/learn-github-packages/connecting-a-repository-to-a-package
35+
LABEL org.opencontainers.image.source=https://github.com/tinygo-org/tinygo
36+
37+
RUN cd /tinygo/ && \
38+
make llvm-build && \
39+
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;

make/tools.mk

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ spell: tools ## Spellcheck source tree
2626
spellfix: tools ## Same as spell, but fixes what it finds
2727
misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md
2828

29+
.PHONY: llvm-image-tag
30+
llvm-image-tag: ## Print the tag of the prebuilt LLVM Docker image
31+
@sh tools/llvm-image-tag.sh
32+
33+
.PHONY: docker-llvm
34+
docker-llvm: ## Build the LLVM base image (slow)
35+
docker build -t tinygo-llvm-build -f Dockerfile.llvm .
36+
37+
.PHONY: docker-tinygo
38+
docker-tinygo: ## Build the TinyGo compiler image
39+
docker build -t tinygo-dev .
40+
2941
# https://www.client9.com/self-documenting-makefiles/
3042
.PHONY: help
3143
help:

tools/llvm-image-tag.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/sh
2+
# Print the tag of the prebuilt LLVM Docker image for this source tree.
3+
# The tag changes when the LLVM revision or the build recipe changes.
4+
# Keep the file list in agreement with the COPY lines in Dockerfile.llvm.
5+
set -e
6+
cd "$(dirname "$0")/.."
7+
rev=$(cut -c1-12 llvm-version.txt)
8+
hash=$(cat llvm-version.txt Dockerfile.llvm GNUmakefile make/*.mk \
9+
| git hash-object --stdin | cut -c1-12)
10+
echo "$rev-$hash"

0 commit comments

Comments
 (0)