【2023】Cabal 簡易指南
本文使用的 Cabal 版本為 3.10.1.0。本文只包含它的簡易使用指南、簡易機(jī)制解惑和一些作者自己使用時遇到過的疑惑的簡單解答,不包括安裝教程。
作者推薦使用 GHCUP 進(jìn)行安裝與后續(xù)的版本管理。
Cabal 是一個應(yīng)用于 Haskell 開發(fā)的包管理器。它所安裝的包的默認(rèn)來源為 Haskell 社區(qū)中的包倉庫 Hackage (hackage.haskell.org)。因此,所有在Hackage上能夠被搜索到的包都可以使用 Cabal 進(jìn)行安裝。
1.0 簡易起步
要使用 Cabal 作為包管理器進(jìn)行 Haskell 程序開發(fā),首先你要使用 Cabal 創(chuàng)建對應(yīng)的工程目錄:
cabal init foldername
這條命令將會在指定目錄初始化相應(yīng)的工程項(xiàng)目,也就是一個 Cabal Package,一個包。 當(dāng)然,你也可以直接在當(dāng)前目錄下新建項(xiàng)目:
cd test cabal init
默認(rèn)情況下,cabal init
會默認(rèn)使用 --interactive
參數(shù)在交互模式下啟動。這一模式下,cabal
會和 nodejs
社區(qū)的包管理器 npm
一樣,詳細(xì)詢問你相應(yīng)的配置項(xiàng):
-- 首先會詢問你想構(gòu)建的項(xiàng)目的類型
What does the package build:
? 1) Library -- 如果你是想創(chuàng)建其他 Haskell 項(xiàng)目可以復(fù)用的庫,選取此項(xiàng)
* 2) Executable -- 如果你只是想創(chuàng)建一個 Haskell 程序,選取此項(xiàng)
? 3) Library and Executable -- 如果你構(gòu)建的項(xiàng)目同時包括程序與庫,選取此項(xiàng)
? 4) Test suite -- 測試單元
Your choice? [default: Executable] -- 默認(rèn)情況會是 Excutable,構(gòu)筑 Haskell 程序
-- 是否覆寫指定目錄現(xiàn)有的文件
Do you wish to overwrite existing files (backups will be created) (y/n)? [default: n]
-- 選擇projectname.cabal文件的詮釋版本
Please choose version of the Cabal specification to use:
? 1) 1.24 ?(legacy)
? 2) 2.0 ? (+ support for Backpack, internal sub-libs, '^>=' operator)
? 3) 2.2 ? (+ support for 'common', 'elif', redundant commas, SPDX)
? 4) 2.4 ? (+ support for '**' globbing)
* 5) 3.0 ? (+ set notation for ==, common stanzas in ifs, more redundant commas, better pkgconfig-depends)
? 6) 3.4 ? (+ sublibraries in 'mixins', optional 'default-language')
Your choice? [default: 3.0]
-- 包的名稱,或者說項(xiàng)目的名稱
Package name? [default: test]
-- 包的版本
Package version? [default: 0.1.0.0]
-- 選擇對應(yīng)的開源許可證
Please choose a license:
? 1) BSD-2-Clause
? 2) BSD-3-Clause
? 3) Apache-2.0
? 4) MIT
? 5) MPL-2.0
? 6) ISC
? 7) GPL-2.0-only
? 8) GPL-3.0-only
? 9) LGPL-2.1-only
?10) LGPL-3.0-only
?11) AGPL-3.0-only
?12) GPL-2.0-or-later
?13) GPL-3.0-or-later
?14) LGPL-2.1-or-later
?15) LGPL-3.0-or-later
?16) AGPL-3.0-or-later
?17) Other (specify)
Your choice?
-- 包的作者名
Author name? [default: Wendaolee]
-- 維護(hù)者郵箱
Maintainer email? [default: leewendao@outlook.com]
-- 項(xiàng)目主頁地址
Project homepage URL? [optional]
-- 項(xiàng)目的簡介
Project synopsis? [optional]
-- 項(xiàng)目的類別
Project category:
? 1) Codec
? 2) Concurrency
? 3) Control
? 4) Data
? 5) Database
? 6) Development
? 7) Distribution
? 8) Game
? 9) Graphics
?10) Language
?11) Math
?12) Network
?13) Sound
?14) System
?15) Testing
?16) Text
?17) Web
?18) Other (specify)
Your choice? [default: (none)]
-- 入口文件
What is the main module of the executable:
* 1) Main.hs
? 2) Main.lhs
? 3) Other (specify)
Your choice? [default: Main.hs]
-- 源代碼目錄
Application directory:
* 1) app
? 2) exe
? 3) src-exe
? 4) Other (specify)
Your choice? [default: app]
-- 在哪種 Haskell 標(biāo)準(zhǔn)上編寫程序
Choose a language for your executable:
* 1) Haskell2010
? 2) Haskell98
? 3) GHC2021 (requires at least GHC 9.2)
? 4) Other (specify)
Your choice? [default: Haskell2010]
-- 是否在project.cabal文件中添加各個配置項(xiàng)的注釋(個人建議為y)
Add informative comments to each field in the cabal file. (y/n)? [default: y]
如果你只是單純想寫一個 Haskell 應(yīng)用,可以使用 cabal init --n
?(cabal init --non-interactive
)。它會直接生成一個 Haskell 應(yīng)用的包目錄。
根據(jù)上面的步驟生成的目錄結(jié)構(gòu)如下:
│- ?CHANGELOG.md --應(yīng)用更改的更新日志模板
│- projectname.cabal -- cabal配置文件
│
├─app ?-- 源代碼目錄,根據(jù)你在init時的值的設(shè)置的不同,源代碼目錄名也會不同
│ ? ? ?Main.hs
│
└─dist-newstyle -- 編譯后出現(xiàn)的輸出目錄
如果您想編譯您的項(xiàng)目,只需在當(dāng)前目錄下執(zhí)行:
cabal build
它會編譯您的項(xiàng)目,將可執(zhí)行文件生成于同目錄的 dist-newstyles/build/compile-info-paths...
之中。
如果您想運(yùn)行您的項(xiàng)目,則是執(zhí)行:
cabal run
它會編譯并運(yùn)行您的項(xiàng)目。
在通過cabal init
生成的目錄里,唯一需要你注意的是 projectname.cabal
文件。它是 Cabal 管理你的包的重要憑證,就像是 package.json
之于 npm
,同時你可以在其中修改你在交互模式中填入的一些信息。
里面的內(nèi)容應(yīng)該初中生就能讀懂,在此不做詳細(xì)翻譯。唯一需要你注意的是這幾項(xiàng)配置:other-modules
,build-depends
:
other-modules
用于指定在程序中使用的模塊。也就是說,想要對代碼進(jìn)行模塊化,那么在使用 Cabal 的工程項(xiàng)目中必需把每個模塊在projectname.cabal
文件中的other-modules
項(xiàng)闡明。例如你要寫一個DataLayer.Type
模塊,那么便需要在該項(xiàng)添加DataLayer.Type
。build-depends
用于指定程序需要使用的第三方庫。cabal
和npm
是不一樣的。如果你要使用第三方庫,你需要手動在這一項(xiàng)中指定該庫。
build-depends
這一配置項(xiàng)與cabal install
是令人迷惑的。習(xí)慣于使用類似npm
的包管理器可能會通過npm install package-name
為當(dāng)前項(xiàng)目安裝依賴,然而在 cabal 中您只能通過更改 projectname.cabal
文件的 build-depends
項(xiàng)來在自己的項(xiàng)目中使用第三方庫。
關(guān)于這一點(diǎn)在下面一節(jié)中會提供詳細(xì)的解釋。在這里,我只為您闡述清楚在自己的項(xiàng)目中如何引入第三方庫:
在終端里,運(yùn)行:
cabal install --lib libname ?
而后在 projectname.cabal
中添加該庫:
-- Other library packages from which modules are imported.
-- 引入多個庫時,使用英文逗號分割。您同樣可以使用 = 、 ^>= 等符號指定庫的版本 ? ?
build-depends: ? ?base ^>=4.14.3.0,libname
之后運(yùn)行 cabal build
或 cabal run
時,cabal會自動引入相關(guān)依賴庫完成編譯與程序的運(yùn)行。
當(dāng)然,您也可以直接在 projectname.cabal
文件的 build-depends
聲明該庫,cabal 會在執(zhí)行 build
或 run
時自動安裝相關(guān)依賴庫完成編譯與程序的運(yùn)行。但是我個人推薦預(yù)先通過cabal install --lib libname
安裝庫,因?yàn)檫@樣你可以通過 LHS
在編寫程序時獲取相應(yīng)的庫的語法與類型支持。
2.0 簡易 cabal install
解惑:cabal 的包管理機(jī)制
Haskell是一門編譯型語言,這一特性間接使得 Cabal
不會和 npm
一樣,存在著存放在工程目錄的本地包文件夾 (如 npm
的 node_modules
)。Cabal 的包永遠(yuǎn)是全局管理的。因此,在運(yùn)行 cabal install package
時,它實(shí)質(zhì)上是在運(yùn)行cabal install --global package
(就像是 npm install -g package
,請注意這里只是“釋例”,cabal實(shí)際上并沒有--global
參數(shù))。
一般來講,當(dāng)你鍵入 cabal install remote-package
時,會發(fā)生這些事:
從遠(yuǎn)程倉庫中下載對應(yīng)包的源碼,將源碼存放至
~/cabal/packages
目錄中 。而后將會編譯這些源碼為 Haskell 的
.hi
中間文件,存放到~/cabal/store
目錄中。.hi
類似于 C 的.o
文件,能夠提高后續(xù)的編譯速度。如果你沒有鍵入
cabal install --lib remote-package
,即沒有在cabal install
后補(bǔ)上--lib
參數(shù),cabal默認(rèn)會將下載下來的源碼包視為一個 Haskell 程序包,而不是一個第三方的Haskell庫。因此它會嘗試將該包編譯為一個可執(zhí)行程序,而后將輸出的程序放到 cabal 的配置目錄中(老版本的cabal
會將文件放至~/cabal/bin
,更為現(xiàn)代化的cabal
會將文件放至~/.local/bin
,根據(jù)當(dāng)前cabal
的配置而定)。
對于第三點(diǎn),你可能會有些困惑?,F(xiàn)在,讓我們把目光回到開頭。前邊我們說過,如果使用交互模式運(yùn)行cabal init
,我們首先會得到初始化的包的類型:
What does the package build:
? 1) Library -- 如果你是想創(chuàng)建其他 Haskell 項(xiàng)目可以復(fù)用的庫,選取此項(xiàng)
* 2) Executable -- 如果你只是想創(chuàng)建一個 Haskell 程序,選取此項(xiàng)
? 3) Library and Executable -- 如果你構(gòu)建的項(xiàng)目同時包括程序與庫,選取此項(xiàng)
? 4) Test suite -- 測試單元
也就是說,cabal的包主要是這三種類型:
Haskell庫 (library)
Haskell程序 (executable)
測試單元
默認(rèn)情況下,cabal install
實(shí)際上執(zhí)行的是 cabal install --executable
(這也只是一個“釋例”!cabal install
實(shí)際上并沒有--executable
參數(shù))。它總是會嘗試將目標(biāo)的cabal包進(jìn)行編譯,而后嘗試生成一個可執(zhí)行程序。
這也是為什么當(dāng)你嘗試通過 cabal install lib
時,會出現(xiàn)這樣的報錯:
cabal install sqlite-simple
...
Error: cabal-3.10.1.0.exe: Cannot build the executables in the package
sqlite-simple because it does not contain any executables. Check the .cabal
file for the package and make sure that it properly declares the components
that you expect.
但實(shí)際上這并沒有什么影響。事實(shí)上在出現(xiàn)這一報錯時,相應(yīng)的庫已經(jīng)安裝到你的設(shè)備上了。
同理,當(dāng)你在一個 cabal 工程的根目錄下運(yùn)行 cabal install
時,它實(shí)際上會嘗試將該 cabal 工程生成為一個可執(zhí)行程序安裝到你的設(shè)備上。因此會發(fā)生這些事:
讀取分析
projectname.cabal
文件,嘗試引入build-depends
的庫,而后進(jìn)行編譯。對于沒有安裝到本地的庫,它會自動下載安裝。(相當(dāng)于cabal build
,這也是為什么cabal install
也可以)嘗試將編譯完的工程生成可執(zhí)行程序,輸出到相應(yīng)的配置目錄中。
這也同樣是 cabal install
和 npm install
的不同之處: cabal install
是直接編譯相應(yīng) cabal 包生成程序,這一過程中會自動下載安裝相應(yīng)的依賴庫;而 npm install
僅僅只是下載相應(yīng)的依賴庫。
總結(jié):
如果你想要安裝第三方的 Haskell 庫,可以使用
cabal install --lib libname
如果你要安裝其他的,使用
cabal install xxxx
。它也可以完成安裝庫的任務(wù),但它的語義并非是安裝庫。安裝相應(yīng)的依賴庫只是它附帶的。如果你不確定你要安裝的是什么,無腦
cabal install
即可。
事實(shí)上,cabal已經(jīng)有計劃將安裝庫與安裝程序的命令進(jìn)行區(qū)分,詳情可見 Github Issue#6481。只是三年了依然沒有推進(jìn)......
3.0 參考 Reference
[1] What?cabal install
?did when type this command.https://github.com/haskell/cabal/discussions/9210
[2]Cabal docs.https://cabal.readthedocs.io/en/latest/getting-started.html