Skip to content

Commit 8efd8ac

Browse files
authored
Merge pull request #125 from fwcd/docker/cross-compile-image
Create multiarch image by cross-compiling Swift package to `arm64`
2 parents 0ef6c30 + e59ede9 commit 8efd8ac

9 files changed

+229
-27
lines changed

.github/workflows/deploy.yml

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ on:
1111
jobs:
1212
deploy:
1313
runs-on: ubuntu-latest
14+
if: github.ref == 'refs/heads/main'
1415
steps:
1516
- uses: actions/checkout@v3
1617
- name: Set up Kubernetes CLI

.github/workflows/docker.yml

+4-7
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- main
7+
pull_request:
78
workflow_dispatch:
89

910
jobs:
@@ -25,11 +26,7 @@ jobs:
2526
uses: docker/build-push-action@v4
2627
with:
2728
context: .
28-
# TODO: Ideally we could just build an aarch64 image too (under QEMU user-mode emulation) by using
29-
#
30-
# platforms: linux/amd64,linux/arm64/v8
31-
#
32-
# See https://github.com/fwcd/d2/issues/124
33-
platforms: linux/amd64
34-
push: true
29+
platforms: linux/amd64,linux/arm64/v8
30+
push: true # TODO: Only push on main
3531
tags: ghcr.io/fwcd/d2:latest
32+

Dockerfile

+43-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,46 @@
1-
FROM swift:5.7 as builder
1+
ARG SWIFTVERSION=5.8.1
2+
ARG UBUNTUDISTRO=jammy
23

3-
# Install native dependencies
4+
FROM swift:${SWIFTVERSION}-${UBUNTUDISTRO} AS sysroot
5+
6+
WORKDIR /opt/d2
7+
8+
# Install build dependencies into target sysroot
49
COPY Scripts/install-build-dependencies-apt Scripts/
510
RUN Scripts/install-build-dependencies-apt && rm -rf /var/lib/apt/lists/*
611

7-
# Build
12+
FROM --platform=$BUILDPLATFORM swift:${SWIFTVERSION}-${UBUNTUDISTRO} AS builder
13+
14+
ARG SWIFTVERSION
15+
ARG UBUNTUDISTRO
16+
ARG BUILDARCH
17+
ARG TARGETARCH
18+
19+
ARG TARGETSYSROOT=/usr/local/${TARGETARCH}-ubuntu-${UBUNTUDISTRO}
20+
821
WORKDIR /opt/d2
22+
23+
# Copy target sysroot into builder
24+
# TODO: Only copy stuff that we need for compilation (/usr/lib, /usr/include etc.)
25+
COPY --from=sysroot / ${TARGETSYSROOT}
26+
27+
# Install (cross-)GCC and patch some paths
28+
COPY Scripts/prepare-docker-buildroot Scripts/get-linux-arch-name Scripts/
29+
RUN Scripts/prepare-docker-buildroot
30+
31+
# (Cross-)compile D2
932
COPY Sources Sources
1033
COPY Tests Tests
1134
COPY Package.swift Package.resolved ./
12-
RUN swift build -c release
35+
COPY Scripts/build-release Scripts/
36+
RUN Scripts/build-release
37+
38+
FROM swift:${SWIFTVERSION}-${UBUNTUDISTRO}-slim AS runner
1339

14-
FROM swift:5.7-slim as runner
40+
ARG TARGETARCH
41+
ARG UBUNTUDISTRO
42+
43+
ARG TARGETSYSROOT=/usr/local/${TARGETARCH}-ubuntu-${UBUNTUDISTRO}
1544

1645
# Install Curl, add-apt-repository and node package repository
1746
RUN apt-get update && apt-get install -y curl software-properties-common && rm -rf /var/lib/apt/lists/*
@@ -21,6 +50,10 @@ RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
2150
COPY Scripts/install-runtime-dependencies-apt Scripts/
2251
RUN Scripts/install-runtime-dependencies-apt && rm -rf /var/lib/apt/lists/*
2352

53+
# Link 'sysroot' to / to make sure D2 can find the Swift stdlibs
54+
# (the runpath within the D2 executable still points to its /usr/lib/swift)
55+
RUN ln -s / ${TARGETSYSROOT}
56+
2457
WORKDIR /opt/d2
2558

2659
# Install Node dependencies
@@ -32,9 +65,13 @@ RUN Scripts/install-node-dependencies
3265
COPY Resources Resources
3366
COPY LICENSE README.md ./
3467

68+
ARG TARGETOS
69+
ARG TARGETARCH
70+
3571
# Set up .build folder in runner
3672
WORKDIR /opt/d2/.build
37-
RUN mkdir -p x86_64-unknown-linux-gnu/release && ln -s x86_64-unknown-linux-gnu/release release
73+
COPY Scripts/setup-dotbuild-tree Scripts/
74+
RUN Scripts/setup-dotbuild-tree
3875

3976
# Copy font used by swiftplot to the correct path
4077
COPY --from=builder \

Scripts/build-release

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
3+
# (Cross-)compile D2
4+
5+
set -e
6+
cd "$(dirname $0)/.."
7+
8+
extra_flags=()
9+
10+
if [ -n "$TARGETARCH" ] && [ -n "$TARGETSYSROOT" ]; then
11+
echo "==> Setting flags for target arch '$TARGETARCH'"
12+
arch_name="$(Scripts/get-linux-arch-name $TARGETARCH)"
13+
14+
extra_flags+=(
15+
--arch "$arch_name"
16+
--sdk "$TARGETSYSROOT"
17+
-Xswiftc -use-ld=lld
18+
-Xswiftc -resource-dir -Xswiftc "$TARGETSYSROOT/usr/lib/swift"
19+
-Xswiftc -tools-directory -Xswiftc "/usr/$arch_name-linux-gnu/bin"
20+
-Xcc -I"$TARGETSYSROOT/usr/include"
21+
-Xcc -I"$TARGETSYSROOT/usr/include/freetype2"
22+
-Xcc -I"$TARGETSYSROOT/usr/include/$arch_name-linux-gnu"
23+
-Xcc -I"$TARGETSYSROOT/usr/include/cairo"
24+
-Xlinker -L"$TARGETSYSROOT/usr/lib"
25+
-Xlinker -L"$TARGETSYSROOT/usr/lib/$arch_name-linux-gnu" # libtesseract installs there
26+
)
27+
fi
28+
29+
echo "==> Building D2 with flags ${extra_flags[@]}"
30+
exec swift build -c release "${extra_flags[@]}"

Scripts/get-linux-arch-name

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
3+
# Unfortunately, Docker uses a different name for some CPU architectures than some
4+
# Linux tools (arm64 vs aarch64, amd64 vs x86_64, ...), therefore we use this script
5+
# to perform this mapping once and for all.
6+
7+
set -e
8+
9+
if [ $# -lt 1 ]; then
10+
echo "Usage: $0 [arch name]"
11+
exit 1
12+
fi
13+
14+
case "$1" in
15+
amd64) echo "x86_64";;
16+
arm64) echo "aarch64";;
17+
*) echo "$1";;
18+
esac

Scripts/install-build-dependencies-apt

+20-7
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,25 @@
55
set -e
66
cd "$(dirname $0)/.."
77

8-
apt-get update
9-
apt-get install -y \
10-
libcairo2-dev \
11-
libsqlite3-dev \
12-
libleptonica-dev \
13-
libtesseract-dev \
8+
dependencies=(
9+
libcairo2-dev
10+
libsqlite3-dev
11+
libleptonica-dev
12+
libtesseract-dev
1413
libgraphviz-dev
14+
libstdc++-12-dev
15+
# TODO: poppler-utils and libssl1.1 needed?
16+
)
17+
18+
echo "==> Installing add-apt-repository"
19+
apt-get update
20+
apt-get install -y software-properties-common
1521

16-
# TODO: poppler-utils and libssl1.1 needed?
22+
echo "==> Enabling all repositories"
23+
for repo in main universe multiverse; do
24+
add-apt-repository $repo
25+
done
26+
27+
echo "==> Installing ${dependencies[@]}"
28+
apt-get update
29+
apt-get install -y "${dependencies[@]}"

Scripts/install-runtime-dependencies-apt

+9-7
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
set -e
66
cd "$(dirname $0)/.."
77

8-
apt-get update
9-
apt-get install -y \
10-
libcairo2 \
11-
libsqlite3-0 \
12-
tesseract-ocr \
13-
graphviz \
8+
dependencies=(
9+
libcairo2
10+
libsqlite3-0
11+
tesseract-ocr
12+
graphviz
1413
nodejs
14+
# TODO: poppler-utils and libssl1.1 needed?
15+
)
1516

16-
# TODO: poppler-utils and libssl1.1 needed?
17+
apt-get update
18+
apt-get install -y "${dependencies[@]}"

Scripts/prepare-docker-buildroot

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/bin/bash
2+
3+
# Prepares the build stage during the Docker image build. This assumes
4+
# that the target sysroot has already been installed to $TARGETSYSROOT.
5+
6+
set -e
7+
cd "$(dirname $0)/.."
8+
9+
if [ -z "$BUILDARCH" ]; then
10+
echo "Please make sure to specify BUILDARCH!"
11+
exit 1
12+
fi
13+
14+
if [ -z "$TARGETARCH" ]; then
15+
echo "Please make sure to specify TARGETARCH!"
16+
exit 1
17+
fi
18+
19+
if [ -z "$TARGETSYSROOT" ]; then
20+
echo "Please make sure to specify TARGETSYSROOT!"
21+
exit 1
22+
fi
23+
24+
arch_name="$(Scripts/get-linux-arch-name $TARGETARCH)"
25+
26+
echo "==> Updating apt..."
27+
apt-get update
28+
29+
if [ "$BUILDARCH" == "$TARGETARCH" ]; then
30+
echo "==> Installing build essentials"
31+
apt-get install -y build-essential
32+
else
33+
# Apparently, we need to symlink libstdc++ manually
34+
if [ ! -f "$TARGETSYSROOT/usr/lib/$arch_name-linux-gnu/libstdc++.so" ]; then
35+
echo "==> Symlinking libstdc++"
36+
ln -s "$TARGETSYSROOT/usr/lib/$arch_name-linux-gnu/libstdc++.so"{.6,}
37+
fi
38+
39+
# Symlinking the sysroot libs into the cross-GCC libs
40+
# fixes a very strange linking issue where the linker would
41+
# try resolving libc in /lib/$arch_name-linux-gnu. This also
42+
# needs to happen before installing the cross-GCC package.
43+
echo "==> Symlinking sysroot libs into cross-GCC libs"
44+
mkdir -p "/usr/$arch_name-linux-gnu"
45+
ln -s "$TARGETSYSROOT/usr/lib/$arch_name-linux-gnu" "/usr/$arch_name-linux-gnu/lib"
46+
47+
echo "==> Installing cross-GCC"
48+
apt-get install -y gcc-$arch_name-linux-gnu
49+
fi
50+
51+
# Workaround for https://github.com/stephencelis/CSQLite/pull/1
52+
if [ ! -f /usr/include/sqlite3.h ]; then
53+
echo "==> Patching sqlite3.h"
54+
ln -s {"$TARGETSYSROOT",}/usr/include/sqlite3.h
55+
fi
56+
57+
# There seem to be some strange errors with <tgmath.h> (glibc)
58+
# on host + target x86_64. Since this header seems to be provided
59+
# elsewhere, we can remove it from the target sysroot as a workaround.
60+
if [ "$BUILDARCH" = amd64 ] && [ "$TARGETARCH" = amd64 ] && [ -f "$TARGETSYSROOT/usr/include/tgmath.h" ]; then
61+
echo "==> Patching tgmath.h"
62+
mv "$TARGETSYSROOT/usr/include/tgmath.h"{,.backup}
63+
fi

Scripts/setup-dotbuild-tree

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#!/bin/bash
2+
3+
set -e
4+
cd "$(dirname $0)/.."
5+
6+
if [ -n "$TARGETOS" ]; then
7+
case "$TARGETOS" in
8+
linux)
9+
vendor=unknown
10+
os=linux-gnu
11+
;;
12+
*)
13+
echo "Unsupported target os '$TARGETOS'"
14+
exit 1
15+
;;
16+
esac
17+
else
18+
echo "Please specify the target OS by setting the TARGETOS variable!"
19+
exit 1
20+
fi
21+
22+
if [ -n "$TARGETARCH" ]; then
23+
case "$TARGETARCH" in
24+
amd64) arch=x86_64;;
25+
arm64) arch=arm64;;
26+
*)
27+
echo "Unsupported target arch '${D2TARGETARCH[@]}'"
28+
exit 1
29+
;;
30+
esac
31+
else
32+
echo "Please specify the target arch by setting the TARGETARCH variable!"
33+
exit 1
34+
fi
35+
36+
config=release
37+
38+
mkdir -p .build/"$arch-$vendor-$os"/"$config"
39+
40+
cd .build
41+
ln -s "$config" "$arch-$vendor-$os"/"$config"

0 commit comments

Comments
 (0)