WILT: Universal Binaries and DMG for OSX

I've been building an app I call Helm that combines a bunch of regular activities into one place. Alongsized a daily Zettlekasten status prompt it links together all the damn places that work assigns me jobs. The incident system, the planner system, the backlog system, and more. This could be useful for more than just me, so I wanted to have it compile to multiple OS-es.



2    set GOOS=windows&&\
3    set GOARCH=amd64&&\
4    set CGO_ENABLED=1&&\
5    set CC="x86_64-w64-mingw32-gcc"&&\
6    cd src&&\
7    go build -ldflags "-w -s  -H=windowsgui" -o ../bin/helm.exe -mod=readonly

Set the target architecture, enable CGO and a mingw32 compiler for building a windows binary on a mac, and then the build.


Intel and M1 versions

With OS X there's a wrinkle - the new M1 Macs have a different compile image to the Intel Macs. First off, I had to build two versions

2    cd src && \
3    GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=10.12" CGO_LDFLAGS="-mmacosx-version-min=10.12" go build -mod=readonly -o ../bin/macold
6     cd src && \
7     GOOS=darwin GOARCH=arm64 CGO_ENABLED=1 CGO_CFLAGS="-mmacosx-version-min=10.14" CGO_LDFLAGS="-mmacosx-version-min=10.14" go build -mod=readonly -o ../bin/macnew

Just like windows - set the target architecture and C compile flags and build. For OS X there's two target architectures, amd64 for Intel and arm64 for the new M1s. As I'm building a Fyne app in Golang, fyne comes with a nice .app maker

2    cd src && \
3    fyne package -os darwin && \
4    defaults write Helm.app/Contents/Info LSUIElement 1

Universal Binary

Now I have a Helm.app that by default is built for M1. For this to work I need to replace the executable in there with a Universal binary. The Universal binary is built with the following:

1build-osxu: build-oldosx build-newosx build-osx
2    cd bin && \
3    lipo -create -output helm macold macnew && \
4    cp helm ../src/Helm.app/Contents/MacOS && \
5    codesign -f -s - ../src/Helm.app

With this command it requires the above Intel and M1 versions are built and there's a Helm.app. lipo is a command that creates universal binaries! I run that and then copy it to the right subfolder in Helm.app and then I sign it.


To send this to other people, it's nice to have an installer as a .dmg file. Looking online there's many ways to do it in a GUI, but here's the way I found to create the .dmg via the command line (thus part of my Makefile).

 1installer: build-osxu
 2    [[ -f Helm-Installer.dmg ]] && rm Helm-Installer.dmg
 3    cp -r src/Helm.app src/dmgsource
 4    create-dmg \           
 5    --volname "Helm Installer" \
 6    --background "../assets/big-seal-arkark.png" \
 7    --window-pos 200 120 \
 8    --window-size 800 400 \
 9    --icon-size 100 \
10    --icon "dmgsource/Helm.app" 200 190 \
11    --hide-extension "Helm.app" \
12    --app-drop-link 600 185 \
13    "Helm-Installer.dmg" \
14    "dmgsource/"

This command requiers the universal binary to be all in place in the Helm.app directory. This script doesn't delete any existing .dmg files, so the first thing the command does is remove the Helm-Installer.dmg if it exists. It then puts the Helm.app in an empty directory - this directory has everything that will be in the .dmg - at the moment all I want is that Helm.app file.

create-dmg is a script I got from Brew, I send to that all the required command flags to build the Helm-Installer.dmg. When you run this tool it pops up a folder view prompting the user to copy the file into the /Applications/ folder on their Mac. Installed!