Running Dart on Arm Servers
Last week I wrote about easier automation using GitHub Actions to build multi architecture Docker images. That led me to another question - how to create the binaries to go into those builds? If I'm making an Arm image, then I need an Arm binary to go into that image.
At the moment dart compile only compiles self-contained executable files for the architecture it's being run on. This is a shame, as the underlying tooling is perfectly capable of doing cross compilation as J-P Nurmi explains in Cross-compiling Dart apps.
Without cross compilation the only alternative is to run
dart compile from the Dart SDK on each of the target platforms. Google handily packages up the SDK into a Docker image google/dart but unfortunately that's only available for x86_64 (aka amd64). The Dockerfile for that image also makes use of the SDK apt package, but that too is only available for x86_64.
So to run
dart compile on multiple architectures, I first needed a multi arch build image:
FROM debian:stable ARG DART_VERSION="2.12.4" WORKDIR /tmp/ RUN \ apt-get -q update && apt-get install --no-install-recommends -y -q \ gnupg2 curl git ca-certificates unzip openssh-client && \ case "$(uname -m)" in armv7l | armv7) ARCH="arm";; aarch64) ARCH="arm64";; *) ARCH="x64";; esac && \ curl -O https://storage.googleapis.com/dart-archive/channels/stable/release/$DART_VERSION/sdk/dartsdk-linux-$ARCH-release.zip && \ unzip dartsdk-linux-$ARCH-release.zip -d /usr/lib/ && \ rm dartsdk-linux-$ARCH-release.zip && \ mv /usr/lib/dart-sdk /usr/lib/dart ENV DART_SDK /usr/lib/dart ENV PATH $DART_SDK/bin:/root/.pub-cache/bin:$PATH
This is installing some key dependencies (the same as google/dart) and then downloading and installing the Dart SDK according to the output from
uname -m to establish the underlying architecture.
With the build image it's then possible to create a lightweight run image based on the work by Google engineer Tony Pujals with his subfuzion/dart-docker-slim:
# Originally based on subfuzion/dart-docker-slim # Using our buildimage as it supports x64|arm|arm64 FROM atsigncompany/buildimage AS dart # Do all the copying to an output dir here first # as the buildimage has a shell that can be used # for multi-arch conditionals RUN \ mkdir -p /output/lib && mkdir -p /output/etc && \ mkdir -p /output/etc/ssl/certs && \ mkdir -p /output/usr/share/ca-certificates && \ case "$(uname -m)" in \ armv7l | armv7) ARCH="arm-linux-gnueabihf" ; \ mkdir -p /output/lib/$ARCH ; \ cp /lib/ld-linux-armhf.so.3 /output/lib/ld-linux-armhf.so.3 ; \ cp /lib/arm-linux-gnueabihf/ld-linux-armhf.so.3 \ /output/lib/arm-linux-gnueabihf/ld-linux-armhf.so.3 ;; \ aarch64) ARCH="aarch64-linux-gnu" ; \ mkdir -p /output/lib/$ARCH ; \ cp /lib/ld-linux-aarch64.so.1 /output/lib/ld-linux-aarch64.so.1 ; \ cp /lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 \ /output/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 ;; \ *) ARCH="x86_64-linux-gnu" ; \ mkdir -p /output/lib/$ARCH ; mkdir -p /output/lib64/ ; \ cp /lib64/ld-linux-x86-64.so.2 /output/lib64/ld-linux-x86-64.so.2 ;; \ esac && \ cp /lib/$ARCH/libdl.so.2 /output/lib/$ARCH/libdl.so.2 && \ cp /lib/$ARCH/libc.so.6 /output/lib/$ARCH/libc.so.6 && \ cp /lib/$ARCH/libm.so.6 /output/lib/$ARCH/libm.so.6 && \ cp /lib/$ARCH/librt.so.1 /output/lib/$ARCH/librt.so.1 && \ cp /lib/$ARCH/libpthread.so.0 /output/lib/$ARCH/libpthread.so.0 && \ cp /lib/$ARCH/libnss_dns.so.2 /output/lib/$ARCH/libnss_dns.so.2 && \ cp /lib/$ARCH/libresolv.so.2 /output/lib/$ARCH/libresolv.so.2 && \ cp /etc/nsswitch.conf /output/etc/nsswitch.conf && \ cp /etc/resolv.conf /output/etc/resolv.conf && \ cp -R /usr/share/ca-certificates /output/usr/share/ca-certificates && \ cp -R /etc/ssl/certs /output/etc/ssl/certs FROM scratch COPY --from=dart /output / # Is this even really needed, as Docker uses host hosts? COPY ./at-runimage/etc-hosts /etc/hosts
Once again this is using the output of
uname -m to determine the correct dependencies to copy in from the build image.
The build image and run image can then be brought together with a Dart application. I put together a trivial one
dartshowplatform that prints out version and platform information like:
$ sudo docker run -it atsigncompany/dartshowplatform 2.12.4 (stable) (Thu Apr 15 12:26:53 2021 +0200) on "linux_arm64"
Here's the Dockerfile:
FROM atsigncompany/buildimage WORKDIR /app COPY ./dartshowplatform/showplatform.dart . RUN dart compile exe /app/showplatform.dart -o /app/dartshowplatform FROM atsigncompany/runimage COPY --from=0 /app/dartshowplatform /app/dartshowplatform ENTRYPOINT ["/app/dartshowplatform"]
The build image is used to run
dart compile and then the binary created from that is copied into the run image.
The full source etc. for the stuff above is available on GitHub atsign-company/at_dockerfiles.
If you're wondering why we're doing this, it's because people are asking us to support Arm and Arm64, as there's huge growth in the Arm footprint as illustrated above. We can expect it won't be long before we're having to add RISC-V support too, as that Instruction Set Architecture (ISA) gains traction and wider adoption. The days of WinTel hegemony are over, which means dealing with a lot more heterogeneity, and that demands greater automation to avoid being drowned by the complexity of it all.