From 3af7f31a0ad5c4c7fe6df51220b3ec3e1d62a643 Mon Sep 17 00:00:00 2001 From: Mario Rogic Date: Sat, 12 Nov 2022 14:14:54 +0000 Subject: [PATCH] MacOS + Linux arm64 build support, consolidated build scripts --- .gitignore | 1 + distribution/build-linux-aarch64-glibc.sh | 20 ++++++ distribution/build-linux-aarch64-musl.sh | 20 ++++++ distribution/build-linux-x86_64-musl.sh | 20 ++++++ distribution/build-macos-arm64.sh | 26 ++++++++ distribution/build-macos-x86_64.sh | 26 ++++++++ distribution/docker/aarch64-musl.dockerfile | 26 ++++++++ distribution/docker/x86_64-musl.dockerfile | 58 +++++++++++++++++ distribution/readme.md | 69 +++++++++++++++++++++ installers/linux/Dockerfile | 39 ------------ 10 files changed, 266 insertions(+), 39 deletions(-) create mode 100755 distribution/build-linux-aarch64-glibc.sh create mode 100755 distribution/build-linux-aarch64-musl.sh create mode 100755 distribution/build-linux-x86_64-musl.sh create mode 100755 distribution/build-macos-arm64.sh create mode 100755 distribution/build-macos-x86_64.sh create mode 100644 distribution/docker/aarch64-musl.dockerfile create mode 100644 distribution/docker/x86_64-musl.dockerfile create mode 100644 distribution/readme.md delete mode 100644 installers/linux/Dockerfile diff --git a/.gitignore b/.gitignore index e91bb3276..fd6eb38b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ elm-stuff dist +dist-newstyle cabal-dev .cabal-sandbox/ cabal.sandbox.config diff --git a/distribution/build-linux-aarch64-glibc.sh b/distribution/build-linux-aarch64-glibc.sh new file mode 100755 index 000000000..db190953c --- /dev/null +++ b/distribution/build-linux-aarch64-glibc.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -ex # Be verbose and exit immediately on error instead of trying to continue + +buildTag="elm-linux-aarch64-glibc" + +scriptDir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd "$scriptDir/.." # Move into the project root + + # Build in Docker +docker build --platform linux/arm64 \ + -t $buildTag \ + -f distribution/docker/aarch64-glibc.dockerfile . + +mkdir -p distribution/dist # Ensure the dist directory is present + + +bin=distribution/dist/$buildTag # Copy built binary to dist +docker run --rm --entrypoint cat $buildTag /elm/elm > $bin +chmod a+x $bin diff --git a/distribution/build-linux-aarch64-musl.sh b/distribution/build-linux-aarch64-musl.sh new file mode 100755 index 000000000..49eb43e5f --- /dev/null +++ b/distribution/build-linux-aarch64-musl.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -ex # Be verbose and exit immediately on error instead of trying to continue + +buildTag="elm-linux-aarch64-musl" + +scriptDir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd "$scriptDir/.." # Move into the project root + + # Build in Docker +docker build --platform linux/arm64 \ + -t $buildTag \ + -f distribution/docker/aarch64-musl.dockerfile . + +mkdir -p distribution/dist # Ensure the dist directory is present + + +bin=distribution/dist/$buildTag # Copy built binary to dist +docker run --rm --entrypoint cat $buildTag /elm/elm > $bin +chmod a+x $bin diff --git a/distribution/build-linux-x86_64-musl.sh b/distribution/build-linux-x86_64-musl.sh new file mode 100755 index 000000000..6d172dca7 --- /dev/null +++ b/distribution/build-linux-x86_64-musl.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -ex # Be verbose and exit immediately on error instead of trying to continue + +buildTag="elm-linux-x86_64-musl" + +scriptDir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd "$scriptDir/.." # Move into the project root + + # Build in Docker +docker build --platform linux/amd64 \ + -t $buildTag \ + -f distribution/docker/x86_64-musl.dockerfile . + +mkdir -p distribution/dist # Ensure the dist directory is present + + +bin=distribution/dist/$buildTag # Copy built binary to dist +docker run --rm --entrypoint cat $buildTag /elm/elm > $bin +chmod a+x $bin diff --git a/distribution/build-macos-arm64.sh b/distribution/build-macos-arm64.sh new file mode 100755 index 000000000..8da3e2af2 --- /dev/null +++ b/distribution/build-macos-arm64.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -ex # Be verbose and exit immediately on error instead of trying to continue + +buildTag="elm-macos-arm64" + +ghcup install ghc 9.0.2 --set +ghcup install cabal 3.6.2.0 --set + +opt --version # The arm64 build currently requires llvm until we get to GHC 9.4+ + +scriptDir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd "$scriptDir/.." # Move into the project root + +ffiLibs="$(xcrun --show-sdk-path)/usr/include/ffi" # Workaround for GHC9.0.2 bug until we can use GHC9.2.3+ +export C_INCLUDE_PATH=$ffiLibs # https://gitlab.haskell.org/ghc/ghc/-/issues/20592#note_436353 + + +cabal update +cabal build -j4 # Build with concurrency 4 + +mkdir -p distribution/dist # Ensure the dist directory is present + +bin=distribution/dist/$buildTag +cp "$(cabal list-bin .)" $bin # Copy built binary to dist +strip $bin # Strip symbols to reduce binary size (90M -> 56M) diff --git a/distribution/build-macos-x86_64.sh b/distribution/build-macos-x86_64.sh new file mode 100755 index 000000000..771d5f067 --- /dev/null +++ b/distribution/build-macos-x86_64.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -ex # Be verbose and exit immediately on error instead of trying to continue + +buildTag="elm-macos-x86_64" + +ghcup install ghc 9.0.2 --set +ghcup install cabal 3.6.2.0 --set + + + +scriptDir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +cd "$scriptDir/.." # Move into the project root + + + + + +cabal update +cabal build -j4 # Build with concurrency 4 + +mkdir -p distribution/dist # Ensure the dist directory is present + +bin=distribution/dist/$buildTag +cp "$(cabal list-bin .)" $bin # Copy built binary to dist +strip $bin # Strip symbols to reduce binary size (90M -> 56M) diff --git a/distribution/docker/aarch64-musl.dockerfile b/distribution/docker/aarch64-musl.dockerfile new file mode 100644 index 000000000..fd808724b --- /dev/null +++ b/distribution/docker/aarch64-musl.dockerfile @@ -0,0 +1,26 @@ +FROM registry.gitlab.b-data.ch/ghc/ghc4pandoc:8.10.7 as bootstrap + +# @TODO having issues on subsequent versions of GHC, retry in future +# FROM registry.gitlab.b-data.ch/ghc/ghc4pandoc:9.0.2 as bootstrap + +RUN cabal update + +# Install packages +WORKDIR /elm +COPY elm.cabal ./ + +ENV CABALOPTS="-f-export-dynamic -fembed_data_files --enable-executable-static -j4" +ENV GHCOPTS="-j4 +RTS -A256m -RTS -split-sections -optc-Os -optl=-pthread" + +RUN cabal build $CABALOPTS --ghc-options="$GHCOPTS" --only-dependencies + +# Import source code +COPY builder builder +COPY compiler compiler +COPY reactor reactor +COPY terminal terminal +COPY LICENSE ./ + +RUN cabal build $CABALOPTS --ghc-options="$GHCOPTS" +RUN cp `cabal list-bin .` ./elm +RUN strip elm diff --git a/distribution/docker/x86_64-musl.dockerfile b/distribution/docker/x86_64-musl.dockerfile new file mode 100644 index 000000000..54c83f71d --- /dev/null +++ b/distribution/docker/x86_64-musl.dockerfile @@ -0,0 +1,58 @@ +FROM alpine:3.15 as build + +RUN apk add --no-cache \ + alpine-sdk \ + autoconf \ + gcc \ + gmp \ + gmp-dev \ + libffi \ + libffi-dev \ + llvm10 \ + make \ + musl-dev \ + ncurses-dev \ + ncurses-static \ + tree \ + wget \ + zlib-dev \ + zlib-static \ + curl + +RUN curl https://downloads.haskell.org/~ghcup/0.1.18.0/x86_64-linux-ghcup-0.1.18.0 -o /usr/local/bin/ghcup && chmod a+x /usr/local/bin/ghcup + +# Setup GHC +RUN ghcup install ghc 9.0.2 --set +RUN ghcup install cabal 3.6.2.0 --set + +ENV PATH="${PATH}:/root/.ghcup/bin" + + +# FIX https://bugs.launchpad.net/ubuntu/+source/gcc-4.4/+bug/640734 +# Use the next line to debug the right file source if this area starts failing in future +# RUN tree /usr/lib/gcc/x86_64-alpine-linux-musl +# @TODO is there a sure-fire way of getting this path? +WORKDIR /usr/lib/gcc/x86_64-alpine-linux-musl/10.3.1/ +RUN cp crtbeginT.o crtbeginT.o.orig +RUN cp crtbeginS.o crtbeginT.o +RUN cp crtend.o crtend.o.orig +RUN cp crtendS.o crtend.o + +RUN cabal update + +# Install packages +WORKDIR /elm +COPY elm.cabal ./ +RUN cabal build --ghc-option=-optl=-static --ghc-option=-split-sections -O2 --only-dependencies + + +# Import source code +COPY builder builder +COPY compiler compiler +COPY reactor reactor +COPY terminal terminal +COPY LICENSE ./ + +RUN cabal build --ghc-option=-optl=-static --ghc-option=-split-sections -O2 +RUN cp `cabal list-bin .` ./elm +RUN strip elm diff --git a/distribution/readme.md b/distribution/readme.md new file mode 100644 index 000000000..a27e48b43 --- /dev/null +++ b/distribution/readme.md @@ -0,0 +1,69 @@ + +## Supported architectures + +Distributions for the following architectures are currently supported: + +| Build | Linking | Supported build hosts | +| ----------------------- | ------- | --------------------------------------- | +| `elm-macos-x86_64` | Dynamic | `macos-x86_64`, `macos-arm64` (Rosetta) | +| `elm-macos-arm64` | Dynamic | `macos-arm64` + llvm | +| `elm-linux-x86_64-musl` | Static | `linux-x86_64`, `macos-x86_64` (Docker) | +| `elm-linux-arm64-glibc` | Dynamic | `linux-arm64`, `macos-arm64` (Docker) | +| `elm-win-x86_64` | Dynamic | `win-x86_64` | + + +## Building an Elm compiler binary + +### Pre-requisites + +- [ghcup](https://www.haskell.org/ghcup/) (scripts will attempt to install GHC 9.0.2 + Cabal 3.6.2.0) +- LLVM v13+ (MacOS arm64 only, suggest [homebrew](https://brew.sh/) `brew install llvm@13`) + +How to install these varies depending on your build host. [GHCup](https://www.haskell.org/ghcup/) is a convenient option for trying out multiple versions. + +Once you have these dependencies, running the relevant `build--.sh` should result in an Elm binary. + + +## Macos cross-compiling + +Elm should compile on both `x86_64` (Intel) and `arm64` (M-series) chipset macs. + +If you have an `x86_64` mac, you can only build `x86_64` binaries. + +If you have an `arm64` mac and [Rosetta 2](https://support.apple.com/en-gb/HT211861), you can also cross-compile the `x86_64` binary, however it will require the prerequisite toolchain in the respective CPU flavour, i.e. + +- llvm-arm64 + GHC-arm64 + cabal-arm64 => elm-macos-arm64 +- GHC-x86_64 + cabal-x86_64 => elm-macos-x86-64 + +If you use `ghcup`, you can force install the x86 tools to replace the arm64 ones like follows: + +``` +ghcup install ghc 9.0.2 --force -p x86_64-apple-darwin --set +ghcup install cabal 3.6.2.0 --force -p x86_64-apple-darwin --set +ghcup set ghc 9.0.2 +``` + +It seems ghcup doesn't currently support multi-arch installs. Check the arch of your currently installed binaries as follows: + +``` +file ~/.ghcup/bin/cabal +file ~/.ghcup/ghc/9.0.2/lib/ghc-9.0.2/bin/ghc +``` + + +## Linux builds with Docker + +Docker provides us with the convenience of being able to run encapsulated Linux builds on both Linux and MacOS. + +The Docker philosophy of immutable layers is great for reproducibility, but a pain for debugging. To get back some of the benefits of tool-level caching, break up expensive operations whenever possible. + +I.e. where `cabal install` run directly will resume based on prior progress (i.e. expensive package compilation), to get similar behaviour via Docker we need to: + +```bash +COPY elm.cabal ./ # only add elm.cabal +RUN cabal build --only-dependencies # single layer for building deps based on elm.cabal only +COPY ... # add all remaining project files afterward +RUN cabal build # run the actual elm build +``` + +Without this, even changing the readme would mean Docker decides all the packages need recompiling from scratch. diff --git a/installers/linux/Dockerfile b/installers/linux/Dockerfile deleted file mode 100644 index 3b6df3f61..000000000 --- a/installers/linux/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -# Based initially on https://gist.github.com/rlefevre/1523f47e75310e28eee243c9c5651ac9 -# -# Build Linux x64 binary from elm compiler top-level directory: -# $ docker build -t elm -f installers/linux/Dockerfile . -# -# Retrieve elm Linux binary: -# $ docker cp $(docker create elm):/usr/local/bin/elm DESTINATION_DIRECTORY -# -# Delete docker elm image: -# $ docker rmi elm -# -# Display all images: -# $ docker images -a -# -# Delete all unused docker images: -# $ docker system prune -a - -# Use Alpine 3.11 with GHC 8.6.5 -FROM alpine:3.11 - -# Install packages required to build elm -RUN apk add --no-cache ghc cabal wget musl-dev zlib-dev zlib-static ncurses-dev ncurses-static - -WORKDIR /elm - -# Import source code -COPY builder builder -COPY compiler compiler -COPY reactor reactor -COPY terminal terminal -COPY cabal.config elm.cabal LICENSE ./ - -# Build statically linked elm binary -RUN cabal new-update -RUN cabal new-build --ghc-option=-optl=-static --ghc-option=-split-sections -RUN cp ./dist-newstyle/build/x86_64-linux/ghc-*/elm-*/x/elm/build/elm/elm /usr/local/bin/elm - -# Remove debug symbols to optimize the binary size -RUN strip -s /usr/local/bin/elm