Running Dart on Arm Servers

Running Dart on Arm Servers

·

4 min read

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.

Arm adoption at AWS

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.