stack.yaml
###### Project-specific
snapshot: lts-23.14
# snapshot: ghc-9.8.4 스냅샷 없이 컴파일러와 같이 배포된 패키지 이용한다.
# resolver는 snapshot의 동의어, 둘 중 하나만 지정한다.
packages:
- my-package
- dir3/my-other-package
# 현재 프로젝트가 가진 패키지 목록
# packages: . 기본값으로 싱글 프로젝트 패키지를 의미
extra-deps:
# 스냅샷에 없는 패키지나 버전. 같은 이름의 스냅샷 패키지는 가려진다.
# @TODO 팬트리: (영단어 뜻은 찬장 같은 것) 스냅샷에 없는 것들을 Hackage, Git, 로컬등에서
# 가져와 써먹고 캐싱해 둔 걸 팬트리 패키지라 부르는 것 같다.
# ※ 특이사항: GHC boot 패키지는 여기다 써봐야 무시된다고 한다.
ghc-options:
flags:
package-name:
flag-name: true
# 명령 줄 옵션과 동일
# 스냅샷 패키지에 지정된 모든 Cabal 플래그 설정을 덮어씌운다.
drop-packages:
user-message:
# stack이 이 설정 파일을 읽어들일 때마다 출력
custom-preprocessor-extensions:
extra-package-dbs:
curator:
# 2025.3 아직은 실험적인 필드
###### Non-project specific
allow-newer: false
# cabal 파일에 있는 버전 제약 무시
allow-newer-deps: false
# 위 allow-newer 설정이 적용될 하위 패키지들 지정
arch:
# stack이 동작하는 머신 아키텍처
compiler:
# 스냅샷에 있는 컴파일러 버전을 덮어 씌운다.
extra-lib-dirs:
# 빌드 환경 구성을 위해 필요한 라이브러리를 찾을 경로
hpack-force:
# .cabal 파일이 수작업으로 수정됐을 때, hpack이 덮어 쓸지 여부
# hpack 0.20부터는 덮어 쓰기 거부가 기본값
install-ghc:
# GHC를 다운로드하고 설치할지 여부.
local-bin-path: ~/.local/bin
# stack build --copy-bins(stack install과 동일)이 패키지를 설치할 경로
nix:
enable: false
pure: true
package: []
shell-file:
nix-shell-options: []
path: []
add-gc-roots: false
system-ghc: true
# 시스템에 있는 (Path에 잡혀 있는) GHC를 쓸지 말지 여부
with-hpack: /usr/local/bin/hpack
# stack 빌트인 hpack이 아닌, 별도 hpack 실행 파일을 쓴다.
work-dir: .stack-work
stack new sample
sample/
.
├── app
│ └── Main.hs
├── CHANGELOG.md
├── LICENSE
├── package.yaml
├── README.md
├── sample.cabal
├── Setup.hs
├── src
│ └── Lib.hs
├── stack.yaml
└── test └── Spec.hs
특별히 옵션을 주지 않으며, 빌드 중에 필요한 파일들과, 최종 빌드된 바이너리 모두 이 아래 위치합니다. (한 눈에 살펴 본적이 없었는데, 많이도 생성됩니다. stack build
, stack install
후의 모습입니다.)
.stack-work/
.
├── dist
│ └── x86_64-linux-nix
│ └── ghc-9.6.4 ----* stack path --dist-dir로 확인
│ ├── build
│ │ ├── autogen
│ │ │ ├── cabal_macros.h
│ │ │ ├── PackageInfo_sample.hs
│ │ │ └── Paths_sample.hs -----------------* Paths_pkgname 모듈
│ │ ├── Lib.dyn_hi
│ │ ├── Lib.dyn_o
│ │ ├── Lib.hi
│ │ ├── libHSsample-0.1.0.0-2tRkpRkMrLYKl9DJwHDpTe.a
│ │ ├── libHSsample-0.1.0.0-2tRkpRkMrLYKl9DJwHDpTe-ghc9.6.4.so
│ │ ├── Lib.o
│ │ ├── Paths_sample.dyn_hi
│ │ ├── Paths_sample.dyn_o
│ │ ├── Paths_sample.hi
│ │ ├── Paths_sample.o
│ │ └── sample-exe
│ │ ├── autogen
│ │ │ ├── cabal_macros.h
│ │ │ ├── PackageInfo_sample.hs
│ │ │ └── Paths_sample.hs -----------------* Paths_pkgname 모듈
│ │ ├── sample-exe --------------* 실행 파일
│ │ └── sample-exe-tmp
│ │ ├── Main.hi
│ │ ├── Main.o
│ │ ├── Paths_sample.hi
│ │ └── Paths_sample.o
│ ├── build-lock
│ ├── package.conf.inplace
│ │ ├── package.cache
│ │ ├── package.cache.lock
│ │ └── sample-0.1.0.0-2tRkpRkMrLYKl9DJwHDpTe.conf
│ ├── setup-config
│ ├── stack-build-caches
│ │ └── d6bc1450919ab91c0c8fcf9d9e33fcc28df25df98110f462287e036fb400 2bc3
│ │ ├── exe-sample-exe
│ │ └── lib
│ ├── stack-cabal-mod ----* 수정 시각 확인 용도
│ ├── stack-project-root ----* 프로젝트 개발의 루트 디렉토리를 가진 txt 파일
│ └── stack-setup-config-mod
├── install
│ └── x86_64-linux-nix
│ ├── af9ab2fb7015c65f339c4bb931b57f680cb5ea6b6d26131227e597eb096d0623
│ │ └── 9.6.4
│ │ └── pkgdb
│ │ ├── package.cache
│ │ └── package.cache.lock
│ └── d6bc1450919ab91c0c8fcf9d9e33fcc28df25df98110f462287e036fb4002bc3
│ └── 9.6.4 ----* stack path --local-install-root로 확인
│ ├── bin
│ │ └── sample-exe --------------* 실행 파일
│ ├── doc ----* stack path --local-doc-root로 확인
│ │ └── sample-0.1.0.0
│ │ └── LICENSE
│ ├── lib
│ │ └── x86_64-linux-ghc-9.6.4
│ │ ├── libHSsample-0.1.0.0-2tRkpRkMrLYKl9DJwHDpTe-ghc9.6.4. so
│ │ └── sample-0.1.0.0-2tRkpRkMrLYKl9DJwHDpTe
│ │ ├── Lib.dyn_hi
│ │ ├── Lib.hi
│ │ ├── libHSsample-0.1.0.0-2tRkpRkMrLYKl9DJwHDpTe.a
│ │ ├── Paths_sample.dyn_hi
│ │ └── Paths_sample.hi
│ ├── pkgdb ----* stack path --local-pkg-db로 확인
│ │ ├── package.cache
│ │ ├── package.cache.lock
│ │ └── sample-0.1.0.0-2tRkpRkMrLYKl9DJwHDpTe.conf
│ └── share
│ └── x86_64-linux-ghc-9.6.4
│ └── sample-0.1.0.0
│ └── config.txt --------------* 데이터 파일
├── stack.sqlite3 └── stack.sqlite3.pantry-write-lock
Hpack 패키지들은 package.yaml
파일에서 기술합니다. cabal2nix
와 stack
은 네이티브로 hpack
을 가지고 있어, 이 파일을 해석할 수 있습니다. 단독 CLI 도구 hpack
으로도 이 파일을 읽어 .cabal
파일을 생성할 수 있습니다.
Cabal은 모든 모듈마다 Paths_
모듈을 각 각 생성합니다. 이 들 모듈들에 대해서 어떤 동작을 할지는 spec-version
필드 값에 따라 달라집니다.
모던 동작을 하려면 적어도 0.36.0
이상이어야 하고, 이보다 낮으면 레거시 동작을 합니다.
spec-version: 0.36.0
※ Executable, Libary, Test 같은 것들을 컴포넌트라 부릅니다.
컴포넌트를 위해 Paths_
모듈을 사용하길 원하면, 명시적으로 generated-other-modules
에 지정해야 합니다.
library:
source-dirs: src
generated-other-modules: Paths_name # name 부분을 패키지 이름으로 바꿉니다.
역사적인 이유로, Hpack은 .cabal
파일을 생성할 때 Paths_
모듈을 other-modules
에 추가합니다.
이러지 않게 하려면, package.yaml
에 다음과 같이 설정합니다.
library:
when:
- condition: false
other-modules: Paths_name
7.6 Accessing data files from package code
data-files
필드에 써 준 파일들의 위치는 시스템마다 다를 수 있습니다. 어떤 경우는 설치 후 파일을 이동할 수도 있습니다. 패키지가 이런 데이터 파일을 런타임에 찾을 수 있게 하기 위해 Cabal이 Paths_pkgname
이란 모듈을 생성합니다. 예를 들어 패키지가 data/words.txt
란 데이터 파일을 참조하는데, 이 파일을 패키지에 포함시키더라도, 패키지가 설치될 곳을 미리 알 수 없기 때문에 하드 코딩할 수 없습니다. 이럴 때 Paths_pkgname
모듈을 이용해 설치 경로를 동적으로 얻을 수 있습니다.
※ pkgname
에 있는 하이픈은 모두 언더스코어_
로 바뀝니다.
Paths_pkgname
은 아래 함수를 가지고 있습니다.
getDataFileName :: FilePath -> IO FilePath
Paths_pkgname
모듈을 사용하려면, other-modules
와 autogen-modules
에 적어 줘야 합니다.
Paths_pkgname
모듈은 다른 자동 생성 모듈처럼 플래폼 독립적이지 않기 때문에, sdist
가 생성한 소스 tarballs에 포함되지 않습니다.
※ stack sdist
Hackage에 등록 가능한 형태로, 패키지 아카이브 파일을 만듭니다.
config.txt
Data contents
package.yaml
의 탑레벨에 아래를 추가합니다.
data-files: config.txt
src/Lib.hs
module Lib
( someFuncwhere
)
someFunc :: IO ()
= putStrLn "someFunc" someFunc
app/Main.hs
module Main (main) where
import Paths_sample (getDataFileName, getDataDir)
import Lib
main :: IO ()
= do
main
someFunc<- getDataDir
dir <- getDataFileName "config.txt"
filePath <- readFile filePath
content putStrLn $ "Data dir: " ++ dir
putStrLn $ content ++ " in \n" ++ filePath
그리고 stack sdist
하면, .cabal
에 아래가 추가된 걸 확인할 수 있습니다.
data-files:
config.txt
...
library
...
other-modules:
Paths_sample
autogen-modules:
Paths_sample
...
executable
...
other-modules:
Paths_sample
autogen-modules:
Paths_sample
Cabal이 빌드 과정에서 자동으로 생성해서 바이너리에 포함시킵니다. 빌드에 임시로 생성한 폴더인 /home/lionhairdino/workroom/stack_sample/sample/.stack-work/dist/x86_64-linux-nix/ghc-9.6.4/build/sample-exe/autogen
폴더에서 Paths_sample.hs
파일을 볼 수 있습니다.
실제 데이터 파일의 위치는, 저의 경우는 다음과 같이 잡혔습니다.(stack은 nix가 활성화된 상태입니다.)
someFunc
Data dir: /home/lionhairdino/workroom/stack_sample/sample/.stack-work/install/x86_64-linux-nix/d6bc1450919ab91c0c8fcf9d9e33fcc28df25df98110f462287e036fb4002bc3/9.6.4/share/x86_64-linux-ghc-9.6.4/sample-0.1.0.0
Data contents
in /home/lionhairdino/workroom/stack_sample/sample/.stack-work/install/x86_64-linux-nix/d6bc1450919ab91c0c8fcf9d9e33fcc28df25df98110f462287e036fb4002bc3/9.6.4/share/x86_64-linux-ghc-9.6.4/sample-0.1.0.0/config.txt
실행 파일의 위치는 다음과 같습니다.
/home/lionhairdino/workroom/stack_sample/sample/.stack-work/dist/x86_64-linux-nix/ghc-9.6.4/build/sample-exe
실행 파일 위치와 데이터 파일의 위치를 나란히 보면 다음과 같습니다.
dist/x86_64-linux-nix/ghc-9.6.4/build/sample-exe/sample-exe install/x86_64-linux-nix/해시/9.6.4/share/x86_64-linux-ghc-9.6.4/sample-0.1.0.0/config.txt
자동 생성된 Paths_sample.hs
파일을 열어 보면,
= catchIO (getEnv "sample_bindir") (\_ -> return bindir)
getBinDir = catchIO (getEnv "sample_libdir") (\_ -> return libdir)
getLibDir = catchIO (getEnv "sample_dynlibdir") (\_ -> return dynlibdir)
getDynLibDir = catchIO (getEnv "sample_datadir") (\_ -> return datadir)
getDataDir = catchIO (getEnv "sample_libexecdir") (\_ -> return libexecdir)
getLibexecDir = catchIO (getEnv "sample_sysconfdir") (\_ -> return sysconfdir) getSysconfDir
런타임에 환경 변수를 읽어, 각 디렉토리들을 바꿀 수 있음을 알 수 있습니다.
stack_sample/sample/result> sample_datadir=. ./sample-exe
someFunc
Data dir: .
Data contents
in config.txt
@TODO
Paths_sample.hs
를 열어 보면, 위 디렉토리값들이 현재 개발 중인 상황에서 build했을 때 쓰인 폴더명들이 모두 하드 코딩되어 있다. 이러면, 최종 바이너리에 개발 환경 흔적(환경 변수가 설정되어 있지 않을 때 쓸 기본값)이 남는 것 아닐까?
몇 년 전에는, 생태계에서 꽤 중요한 요소인듯한데, (아마도 규모가 그리 크지 않은) 하나의 상업 회사가 이끄는 게 불안하지 않나 생각했었는데요. 지금은 Commercial Haskell 그룹에서 후원을 받아 개발되고 있다고 합니다.