One does not simply use GHCup on macOS M1
Info
Summary | Using Haskell through Nix or Docker might be easier paths on macOS, but this article should help if those options aren't available. |
---|---|
Shared | 2023-03-20 |
Revised | 2023-03-22 @ 11:51 UTC |
Intro
TheGHCup tool is the official installer for coreHaskell tools:cabal,stack,haskell-language-server, andghc.
I usually use Haskell throughNix (I’m likingdevenv.sh, too), and I’ve also used it through Docker, but I was frustrated with build times and wanted to try the official Haskell way.
Unfortunately, I had a rough time trying to use GHCup on a macOS M1 (Ventura 13.2.1), so I documented trying to build a small Haskell project of mine,slugger, with it.
A note about Homebrew
I useHomebrew for installing all sorts of CLI tools and apps for macOS (here’s my personalBrewfile).
While I will use it for something else later in this guide, I could not getghcup
to work properly when installed via Homebrew, and trying to upgrade GHCup through its interface conflicted with the Homebrew install. Instead, I will use the installer found onthe GHCup page.
The library I tried to build
The example library I tried building was my URI slug library,slugger.
Installing GHCup
I like to keep my$HOME
directory clean by having tools adhere to theXDG spec. I read that if I wanted GHCup to useXDG
, I needed to export this variable in the shell where the installer was going to run:
exportGHCUP_USE_XDG_DIRS="true"
Here are my XDG environment variables.
Since I always want this to be true,I include that in my.zshenv
dotfile just in case.
Next, I installed GHCup:
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
This is an interactive installer, so there was a bit of output and questions.
Tip: if you run this installer, make sure you read the messages.
Including the GHCup environment
The script asked if it could append something to the end of my.zshrc
file. I prefer to own my environment setup, so I let it do its thing, inspected the file to make sure it looked good, then I changed the sourcing code into a style I prefer:
ghcup_script_path="${XDG_DATA_HOME}/ghcup/env"[[ -f"${ghcup_script_path}"]]&&source"${ghcup_script_path}"
This adds some Haskell bin-related directories to$PATH
if they aren’t already there.
Running the GHCup terminal user interface
Once this was all done, I opened a new shell window and ran
ghcup tui
TUI is an acronym for “terminal user interface”.
I used the interface to install the recommended tool versions, and this was really easy! Well done, GHCup crew.
Then I went to go see if I could buildslugger
.
Building slugger: failure #1 (LLVM)
When I went to theslugger
project directory, I rancabal v2-build
, and some LLVM errors printed to the screen.
Notably:
Warning: Couldn’t figure out LLVM version! Make sure you have installed LLVM between [9 and 13]
Remember how I said to make sure you read the installer messages? Yeah. I didn’t.
On Darwin M1 you might also need a working llvm installed (e.g. via brew) and have the toolchain exposed in the PATH.
Update: User bgamari on lobste.rs had a valuable insight intowhy installing LLVM is recommended by GHCup.
Building slugger: failures #2-4 (also LLVM)
As suggested by the warnings above, I addedbrew llvm@9
to myBrewfile
, installed it, and tried tocabal v2-build
theslugger
project.
That didn’t work (same sort of issue).
I triedllvm@10
,llvm@11
, andllvm@12
.
None of those worked, either! Wouldllvm@13
work? Maybe, maybe, maybe…
Building slugger: failure #5 (GHC and LLVM)
Update: This section may not be necessary. I went back, disabled this option, and I’m able still able to build the library. I don’t recall this being my experience the first time around, though.
It seems none of these will work ifghc
doesn’t know to use LLVM.
I keep acabal config file in my dotfiles and it had a section,program-default-options
, that contained aghc-options
key for passing flags to ghc.
Here’s how I told GHC about LLVM:
program-default-options ghc-options: -fllvm
There’s more information about that on theHaskell GHC Backends doc.
Did that make a difference? Yep!
Building slugger: failure #6 (missing foreign libraries)
Aha! A different error.
cabal-3.6.2.0 Missing dependencies on foreign libraries:
Missing (or bad) C libraries: icuuc, icui18n, icudata
This one stemmed from trying to build a dependency,text-icu, and it seemed I was missing some libraries it expected to find on the OS.
I saw some references on GitHub issues to theicu4c
tool, but I was luckily able to findthis archived “Missing dependency on a foreign library” guide that simply told me what to do:
brew install icu4c
If you’re using stack, add this to
~/.stack/config.yaml
:extra-include-dirs:- /usr/local/opt/icu4c/includeextra-lib-dirs:- /usr/local/opt/icu4c/lib
Unfortunately, none of this worked out of the box for me for two reasons:
- I’m not using
stack
- Homebrew uses
/opt/homebrew/
for Apple Silicon—not/usr/local/
But those config options lookedexactly the same as the recommendation from the build warning above, and that gave me some things to try:
If the libraries are already installed but in a non-standard location then you can use the flags
--extra-include-dirs=
and--extra-lib-dirs=
to specify where they are.
Fixing the missing foreign libraries issue
It turns out that mycabal.conf
file hadextra-include-dirs
andextra-lib-dirs
in it, so I didn’t need to pass paths every time I tried to build with cabal.
I don’t regularly edit cabal config files, so I took thestack
YAML config above and tried it:
extra-include-dirs:- /opt/homebrew/opt/icu4c/includeextra-lib-dirs:- /opt/homebrew/opt/icu4c/lib
Nope, that didn’t work. I tried indenting the-
to see if the config file liked that.
Nope.
While this config file might, at a glance, resemble YAML, it isn’t—it seems to resemble (or even be) a.cabal
file (email me if you know, please!). Here was a correct way to write them:
extra-include-dirs: /opt/homebrew/opt/icu4c/includeextra-lib-dirs: /opt/homebrew/opt/icu4c/lib
Sweet success
With high hopes, I rancabal v2-build
again, and it worked!
I was successfully able to build my little library and test it out withcabal
.
Personal retrospective on the experience
There are a number of places here where, if I’d have paid closer attention to (admittedly helpful) walls of text, I’d have been led to solutions faster. That is unquestionably my fault!
That said, the errors don’t cover everything you have to do (like the-fllvm
GHC flag), and this overall experience on macOS was rough for me.
I am grateful for all the effort put into GHCup, and I know it takes time and money to make things simple.
For now, even though Nix’s story isn’t one of simplicity, either, I’m going to mostly stick with building Haskell projects that way. However, I’ll keep my options open and periodically try things the GHCup way, as well.
Thanks for reading!
— Robert