오늘은 하스켈 개발 환경 세팅에 관련해서 기록해보자. 지금 업무용으로 사용하고 있는 랩탑은 Apple Macbook Pro(M1 Pro)이다. 올 초만 해도 하스켈 생태계에서 M1을 잘 지원하지 않아 조금 고생했지만 지금은 자주 쓰는 툴은 모두 M1 버전을 지원해서 크게 문제는 없다. 다만 아직 하스켈 Docker 공식 이미지에는 몇 가지 문제로 linux/arm64/v8
아키텍처에서 stack
이 설치되어 있지 않다. 하지만 cabal
은 설치되어 있기 때문에 만약 같은 Dockerfile로 linux/arm64/v8
과 linux/amd64
에서 모두 빌드하는 것이 문제없다. stack
프로젝트라면 stack
프로젝트를 cabal
로 바꿔주는 stack2cabal 바이너리로 stack.yaml
을 cabal
프로젝트로 바꿔주면 된다.
하지만 우리 프로젝트는 Github Action CI에서 haskell/actions/setup@v2
를 설치하고 stack
으로 빌드해 실행 바이너리를 Docker로 복사하는 방식으로 배포하고 있어 지금은 특별히 문제가 되지 않는다.
아무튼 M1 맥북에서 stack
을 사용하는 두 가지 방법에 대해 적어보려고 한다. 먼저 가장 깔끔한 방법은 brew install haskell-stack
으로 설치하는 방법이다. 필요한 의존성과 몇 가지 문제를 해결한 버전이기 때문에 가장 쉽게 쓸 수 있다.
두 번째 방법은 GHCup으로 설치하는 방법이다. GHCup은 하스켈 개발할 때 필요한 GHC, Stack, HLS(Haskell Language Server), Cabal을 쉽게 설치하고 버전 관리할 수 있는 툴이기 때문에 유용하다. 최근 VSCode Haskell 확장도 GHCUp에서 설치한 HLS를 쉽게 사용할 수 있도록 통합이 되어 여러 가지로 GHCup을 쓰는 것이 좋다.
GHCup으로 설치한 현재 Stack 최신 버전은 몇 가지 차이점이 있는 것을 알았다. 특히 hpack 버전이 달랐다. GHCup으로 설치한 Stack-2.7.5 버전에는 hpack이 0.34.4 버전이고 Brew로 설치한 Stack-2.7.5 버전은 hpack이 0.34.6이다. hpack 버전이 다르면 협업을 할 때 조금 귀찮은 점이 있다. 상위 버전에서 생성된 cabal 파일을 하위 버전 hpack에서 고치려고 할 때 This file has been generated from package.yaml by hpack version 0.34.6.
라는 에러와 함께 package.yaml
을 바꿔도 cabal 파일이 바뀌지 않는다. 그래서 우리 팀은 모두 GHCup으로 설치한 Stack을 사용하기로 했다.
앞에서 GHCup으로 설치한 Stack에 살짝 문제가 있다고 했는데 터미널에서 stack build
를 할 때 Segmentation Fault가 발생하는 문제가 있다. 이 문제는 llvm
의존성 문제로 알려져 있는데 Brew에서 설치한 버전은 llvm@12
의존성을 설치하고 이 버전을 사용하도록 LDFLAGS
, CPPFLAGS
가 빌드할 때 설정되는 것 같다. GHCup으로 설치한 버전(현재 단순히 미리 빌드된https://downloads.haskell.org/ghcup/unofficial-bindists/stack/2.7.5/stack-2.7.5-osx-aarch64.tar.gz 파일을 받아서 적절한 위치에 압축을 풀어주는 것이 전부이다)은 따로 llvm@12 버전의 공유 라이브러리를 사용하도록 설정해줘야 하는 번거로움이 있다. Segmentation Fault가 발생하지 않는 경우도 있는 것 같은데 아마 로컬 llvm@12 버전과 관련 있는 것 같다. 현재 나의 llvm 버전은 13인 것 같다.
$ llvm-gcc --version
Apple clang version 13.1.6 (clang-1316.0.21.2)
Target: arm64-apple-darwin21.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
그래서 brew install llvm@12
를 따로 설치하고 GHCup에서 설치한 stack
파일을 실행할 때 LDFLAGS
와 CPPFLAGS
를 llvm@12
로 설정하는 스크립트로 감싸서 stack
을 사용하고 있다.
#!/bin/bash
export PATH="/opt/homebrew/opt/llvm@12/bin:$PATH"
export LDFLAGS="-L/opt/homebrew/opt/llvm@12/lib"
export CPPFLAGS="-I/opt/homebrew/opt/llvm@12/include"
~/.ghcup/bin/stack "$@"
이렇게 사용하면 Segmentation Fault 문제없이 잘 사용할 수 있다. 그래도 어서 이 문제가 해결되어 새로 설정하는 사람들이 쉽게 M1에서 stack
을 사용할 수 있으면 좋겠다.
hpack 버전이 다르면 협업을 할 때 조금 귀찮은 점이 있다. 상위 버전에서 생성된 cabal 파일을 하위 버전 hpack에서 고치려고 할 때
This file has been generated from package.yaml by hpack version 0.34.6.
라는 에러와 함께package.yaml
을 바꿔도 cabal 파일이 바뀌지 않는다.
협업 시에 모두 stack을 쓰는 환경이라면 .cabal file을 version manager에 올리지 않는 방법도 있습니다. 그러면 0.34.6든 0.34.5든 자기가 만든 .cabal을 쓸테고, 굳이 따지자면 저 둘의 호환성 문제만 남게 되겠죠.
맥에서 여러 언어의 개발환경을 관리하기 위해 asdf를 유용하게 사용하는데, 하스켈용 플러그인도 있더군요. 이것도 도움이 될 수 있어보입니다.
https://github.com/asdf-community/asdf-haskell