Skip to content

Commit

Permalink
MacOS + Linux arm64 build support, consolidated build scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
supermario committed Nov 17, 2022
1 parent 047d502 commit 3af7f31
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 39 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,5 +1,6 @@
elm-stuff
dist
dist-newstyle
cabal-dev
.cabal-sandbox/
cabal.sandbox.config
Expand Down
20 changes: 20 additions & 0 deletions 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
20 changes: 20 additions & 0 deletions 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
20 changes: 20 additions & 0 deletions 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
26 changes: 26 additions & 0 deletions 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)
26 changes: 26 additions & 0 deletions 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)
26 changes: 26 additions & 0 deletions 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
58 changes: 58 additions & 0 deletions 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
69 changes: 69 additions & 0 deletions 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-<os>-<aarch>.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.
39 changes: 0 additions & 39 deletions installers/linux/Dockerfile

This file was deleted.

0 comments on commit 3af7f31

Please sign in to comment.