Haskell/打包
建立新的 Haskell 專案或程式的最佳實踐指南。
幾乎所有新的 Haskell 專案都使用以下工具。每個工具本身都很有用,但使用一組通用的工具也有助於提高每個人的生產力,而且您更有可能獲得補丁。
使用 Cabal。您應該閱讀 Cabal 使用者指南。特別是,第二部分 以及 第三部分 將非常有幫助。為了生成專案,Cabal 依賴於 Git,因此如果您還沒有安裝 Git,我們建議您安裝它。
對於庫,使用 Haddock。
純程式碼可以使用 QuickCheck(對於 tasty 整合,推薦使用 tasty-quickcheck),hedgehog 或 SmallCheck(儘管截至 2023 年,他們建議使用 falsify 以及 tasty),而雜程式碼可以使用 tasty-hunit 進行測試。
要入門,請嘗試 Haskell/測試。對於稍微高階一點的介紹,Haskell 中的簡單單元測試 是一篇關於使用一些模板 Haskell 為 QuickCheck 建立測試框架的部落格文章。
您可以使用 `cabal init` 生成新的 Haskell 專案。預設情況下,它會以互動方式詢問您一些問題,以幫助您設定專案。對於大多數問題,預設值都可以。
但是,如果您停用互動模式,則它不會詢問您問題。假設 `$DIR` 是當前目錄的名稱。它將生成一個具有以下形狀的專案
- app/Main.hs -- 主要 Haskell 原始碼檔案
- $DIR.cabal -- Cabal 構建描述
- CHANGELOG.md
您當然可以修改它,新增子目錄和多個模組。
以下是一段使用 Git 和 Cabal 構建、安裝和釋出的最小 Haskell 專案的建立過程。
命令工具 'cabal init' 會自動完成所有這些操作,但首先了解所有部分很重要。
我們現在將逐步介紹簡單 Haskell 可執行檔案基礎設施的建立過程。庫的相關建議將在後面給出。
為原始碼建立目錄
$ mkdir haq $ cd haq
首先,執行 `cabal update` 來更新 Cabal 的儲存庫。
如前所述,執行 `cabal init` 並根據您的需要回答問題。
What does the package build: 1) Library * 2) Executable 3) Library and Executable 4) Test suite Your choice? [default: Executable] 2 Do you wish to overwrite existing files (backups will be created) (y/n)? [default: n] y 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] 5 Package name? [default: haq] Package version? [default: 0.1.0.0] 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? 16 Author name? [default: John Doe] Maintainer email? [default: johndoe@example.com] Project homepage URL? [optional] Project synopsis? [optional] Haqify code 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] 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] 3 Add informative comments to each field in the cabal file. (y/n)? [default: y] [Log] Using cabal specification: 3.0 [Log] Creating fresh file LICENSE... [Log] Creating fresh file CHANGELOG.md... [Log] Creating fresh directory ./app... [Log] Creating fresh file app/Main.hs... [Log] Creating fresh file haq.cabal... [Warning] No synopsis given. You should edit the .cabal file and add one. [Info] You may want to edit the .cabal file and add a Description field.
對於其中大多數問題,您只需選擇自己的答案,但正如我們之前指出的,預設值是可以的。對於選擇語言的問題,您應儘可能選擇最新存在的語言。
如果您的包使用其他包,例如array,則需要將它們新增到Build-DependsCabal 檔案中的欄位。
編寫您的程式
$ cat > app/Main.hs -- -- Copyright (c) 2006 Don Stewart - http://www.cse.unsw.edu.au/~dons -- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html) -- import System.Environment -- 'main' runs the main program main :: IO () main = getArgs >>= print . haqify . head haqify s = "Haq! " ++ s
$ git init $ git add --all $ git commit -m 'Import haq source' $ ls -A .git app CHANGELOG.md LICENSE haq.cabal
現在構建它!
$ cabal build
現在您可以執行您的酷炫專案了
$ cabal run exe:haq -- me "Haq! me"
將一些 API 文件生成到 dist/doc/* 中
$ cabal haddock --haddock-all
它將告訴您文件的生成位置。您可以透過以下方式檢視它
$ w3m -dump dist-newstyle/build/$ARCH/ghc-x.x.x/haq-x.x.x.x/doc/html/package/Main.html # this is only to show what the path will vaguely look like haq Contents Index Main Synopsis main :: IO () Documentation main :: IO () main runs the main program Produced by Haddock version 0.7
沒有輸出?確保您已經安裝了 Haddock。
我們將使用 QuickCheck 來指定 Haq.hs 程式碼的簡單屬性。建立一個測試模組 Tests.hs,其中包含一些 QuickCheck 樣板程式碼
$ cat > Tests.hs
import Char
import List
import Test.QuickCheck
import Text.Printf
main = mapM_ (\(s,a) -> printf "%-25s: " s >> a) tests
instance Arbitrary Char where
arbitrary = choose ('\0', '\128')
coarbitrary c = variant (ord c `rem` 4)
現在讓我們編寫一個簡單的屬性
$ cat >> Tests.hs
-- reversing twice a finite list, is the same as identity
prop_reversereverse s = (reverse . reverse) s == id s
where _ = s :: [Int]
-- and add this to the tests list
tests = [("reverse.reverse/id", test prop_reversereverse)]
現在我們可以執行此測試,並讓 QuickCheck 生成測試資料
$ runhaskell Tests.hs reverse.reverse/id : OK, passed 100 tests.
讓我們為 'haqify' 函式新增一個測試
-- Dropping the "Haq! " string is the same as identity
prop_haq s = drop (length "Haq! ") (haqify s) == id s
where haqify s = "Haq! " ++ s
tests = [("reverse.reverse/id", test prop_reversereverse)
,("drop.haq/id", test prop_haq)]
並且讓我們測試
$ runhaskell Tests.hs reverse.reverse/id : OK, passed 100 tests. drop.haq/id : OK, passed 100 tests.
太棒了!
我們可以安排 darcs 在每次提交時執行測試套件。
$ darcs setpref test "runhaskell Tests.hs" Changing value of test from '' to 'runhaskell Tests.hs'
將執行完整的 QuickChecks 集。(如果您的測試需要,您可能需要確保其他內容也已構建,例如darcs setpref test "alex Tokens.x;happy Grammar.y;runhaskell Tests.hs").
讓我們提交一個新的補丁。
$ darcs add Tests.hs $ darcs record --all What is the patch name? Add testsuite Do you want to add a long comment? [yn]n Running test... reverse.reverse/id : OK, passed 100 tests. drop.haq/id : OK, passed 100 tests. Test ran successfully. Looks like a good patch. Finished recording patch 'Add testsuite'
太好了,現在補丁必須透過測試套件才能被提交。
標記穩定版本
$ darcs tag What is the version name? 0.0 Finished tagging patch 'TAG 0.0'
隨著您的儲存庫積累補丁,新使用者可能會對完成初始操作所需的時間感到厭煩darcs get。(一些專案,如 yi 或 GHC,可能包含數千個補丁。)Darcs 速度足夠快,但下載數千個單獨的補丁仍然需要一段時間。有沒有什麼方法可以提高效率?
Darcs 提供了--lazy選項以darcs get。這使得能夠只下載儲存庫的最新版本。如果需要,補丁將在以後按需下載。
在分發您的 Haskell 程式時,您大致有三個選擇
- 透過 Darcs 儲存庫分發
- 分發 tarball
- 一個 Darcs tarball
- 一個 Cabal tarball
對於 Darcs 儲存庫,如果它是公開的,那麼您就完成了。但是:也許您沒有使用 Darcs 的伺服器,或者也許您的計算機沒有為人們設定darcs pull從中獲取。在這種情況下,您需要透過 tarball 分發原始碼。
Darcs 提供了一個命令,它將製作一個壓縮的 tarball,並將它管理的所有檔案副本放入其中。(注意,_darcs 中的任何內容都不會被包含在內 - 它只包含您的原始檔,沒有修訂歷史記錄。)
$ darcs dist -d haq-0.0 Created dist as haq-0.0.tar.gz
一切都準備好了!
由於我們的程式碼是 cabalised 的,我們可以直接使用 Cabal 建立 tarball
$ runhaskell Setup.lhs sdist Building source dist for haq-0.0... Source tarball created: dist/haq-0.0.tar.gz
與 Darcs 生成的 tarball 相比,這有優點和缺點。主要優點是 Cabal 將對我們的儲存庫進行更多檢查,更重要的是,它將確保 tarball 具有 HackageDB 和 cabal-install 所需的結構。
但是,它確實有一個缺點:它只打包構建專案所需的那些檔案。它會故意不包含儲存庫中的其他檔案,即使它們在某個時候被證明是必要的[1]。要包含其他檔案(例如Test.hs在上例中),我們需要在 cabal 檔案中新增類似的程式碼行
extra-source-files: Tests.hs
如果我們有它們,我們可以確保 AUTHORS 或 README 等檔案也被包含在內
data-files: AUTHORS, README
建立了以下檔案
$ ls Haq.hs Tests.hs dist haq.cabal Setup.lhs _darcs haq-0.0.tar.gz
建立 Haskell 庫的過程幾乎相同。對於假設的“ltree”庫,區別如下
原始碼應該位於適合現有 模組佈局指南 的目錄路徑下。因此,對於模組 Data.LTree,我們將建立以下目錄結構
$ mkdir Data $ cat > Data/LTree.hs module Data.LTree where
因此,我們的 Data.LTree 模組位於 Data/LTree.hs 中
庫的 Cabal 檔案列出了公開可見的模組,並且沒有可執行部分
$ cat ltree.cabal Name: ltree Version: 0.1 Description: Lambda tree implementation License: BSD3 License-file: LICENSE Author: Don Stewart Maintainer: dons@cse.unsw.edu.au Build-Depends: base Exposed-modules: Data.LTree
因此,我們可以構建我們的庫
$ runhaskell Setup.lhs configure --prefix=$HOME --user $ runhaskell Setup.lhs build Preprocessing library ltree-0.1... Building ltree-0.1... [1 of 1] Compiling Data.LTree ( Data/LTree.hs, dist/build/Data/LTree.o ) /usr/bin/ar: creating dist/build/libHSltree-0.1.a
我們的庫已建立為物件存檔。在 *nix 系統上,您可能需要在配置步驟中新增 --user 標誌(這意味著您希望在安裝過程中更新本地軟體包資料庫)。現在安裝它
$ runhaskell Setup.lhs install Installing: /home/dons/lib/ltree-0.1/ghc-6.6 & /home/dons/bin ltree-0.1... Registering ltree-0.1... Reading package info from ".installed-pkg-config" ... done. Saving old package config file... done. Writing new package config file... done.
我們完成了!您可以從例如 ghci 中使用您的新庫
$ ghci -package ltree Prelude> :m + Data.LTree Prelude Data.LTree>
新庫已在範圍內,隨時可以使用。
對於大型專案,將原始碼樹儲存在子目錄中很有用。這可以透過簡單地建立一個目錄來完成,例如,“src”,您將在其中放置您的 src 樹。
要讓 Cabal 找到此程式碼,您需要在 Cabal 檔案中新增以下程式碼行
hs-source-dirs: src
Cabal 可以設定成除了執行配置指令碼之外,還可以執行一系列其他功能。有關更多資訊,請參閱 Cabal 文件。
如果您的庫使用未公開的內部模組,請不要忘記在other-modules欄位中列出它們
other-modules: My.Own.Module
如果未這樣做(截至 GHC 6.8.3),可能會導致您的庫在沒有錯誤的情況下構建,但實際上無法從應用程式中使用,這將在構建時出現連結器錯誤。
一個名為 cabal-install 的 Haskell 包管理工具提供了一個命令列工具來幫助開發人員建立一個簡單的 cabal 專案。只需執行並回答所有問題。每個問題都提供了預設值。
$ cabal init Package name [default "test"]? Package version [default "0.1"]? Please choose a license: ...
mkcabal 是一個在 cabal init 之前存在的工具,它也會自動填充一個新的 cabal 專案
darcs get http://code.haskell.org/~dons/code/mkcabal
注意:此工具在 Windows 中不起作用。Windows 版本的 GHC 不包含此工具所需的 readline 軟體包。
使用方法是
$ mkcabal Project name: haq What license ["GPL","LGPL","BSD3","BSD4","PublicDomain","AllRightsReserved"] ["BSD3"]: What kind of project [Executable,Library] [Executable]: Is this your name? - "Don Stewart " [Y/n]: Is this your email address? - "<dons@cse.unsw.edu.au>" [Y/n]: Created Setup.lhs and haq.cabal $ ls Haq.hs LICENSE Setup.lhs _darcs dist haq.cabal
這將為專案“haq”填寫一些存根 Cabal 檔案。
要建立一個全新的專案樹
$ mkcabal --init-project Project name: haq What license ["GPL","LGPL","BSD3","BSD4","PublicDomain","AllRightsReserved"] ["BSD3"]: What kind of project [Executable,Library] [Executable]: Is this your name? - "Don Stewart " [Y/n]: Is this your email address? - "<dons@cse.unsw.edu.au>" [Y/n]: Created new project directory: haq $ cd haq $ ls Haq.hs LICENSE README Setup.lhs haq.cabal
用於公共基礎庫包的程式碼必須是 BSD 許可的或更免費/開放的。否則,完全取決於您作為作者。
選擇一個許可證(靈感來自 this)。檢查您使用的東西的許可證,包括其他 Haskell 軟體包和 C 庫,因為這些可能強加您必須遵守的條件。
儘可能使用與相關專案相同的許可證。Haskell 社群大致分為兩派,一派將所有內容釋出在 BSD 或公共領域下,另一派是 GPL/LGPLers(這種劃分大致反映了自由軟體社群中的複製許可/非複製許可劃分)。一些 Haskellers 特別建議避免使用 LGPL,因為存在跨模組最佳化問題。與許多許可問題一樣,這條建議是有爭議的。一些 Haskell 專案(wxHaskell、HaXml 等)使用 LGPL,並附加了一個更寬鬆的條款,以避免跨模組最佳化問題。
將您的程式碼釋出為穩定的、帶標籤的 tarball 很重要。不要只 依靠 darcs 進行分發。
- darcs dist 直接從 darcs 儲存庫生成 tarball
例如
$ cd fps $ ls Data LICENSE README Setup.hs TODO _darcs cbits dist fps.cabal tests $ darcs dist -d fps-0.8 Created dist as fps-0.8.tar.gz
您現在只需釋出您的 fps-0.8.tar.gz
您還可以透過使用後掛鉤讓 darcs 為您完成等同於“每日快照”的操作。
將以下內容放入 _darcs/prefs/defaults 中
apply posthook darcs dist apply run-posthook
建議
- 使用 darcs tag 標記每個版本。例如
$ darcs tag 0.8 Finished tagging patch 'TAG 0.8'
然後人們就可以darcs get --lazy --tag 0.8,以獲取僅標記的版本(而不是整個歷史記錄)。
您可以在 http://patch-tag.com/ 免費託管公共和私有 Darcs 倉庫。 否則,只需將 Darcs 倉庫從網頁上提供即可釋出。 另一種選擇是在 Haskell 社群伺服器 http://code.haskell.org/ 上託管。 您可以透過 http://community.haskell.org/admin/ 申請帳戶。 您也可以使用 https://github.com/ 用於 Git 託管。
一個 完整的示例 說明了在此過程中編寫、打包和釋出新的 Haskell 庫。
本頁面至少部分內容來自 Haskell wiki 文章 如何編寫 Haskell 程式,根據其簡單許可證。 如果您想修改本頁面,並且您的更改對該 wiki 也很有用,您不妨考慮修改該源頁面而不是本頁面,因為來自該頁面的更改可能會傳播到此處,但反之則不然。 或者,您可以明確地將您的貢獻雙重許可到簡單許可證下。 還請注意,原始教程包含有關釋出軟體和加入 Haskell 社群的額外資訊,您可能對此感興趣。 |
注意
- ↑ 這實際上是一件好事,因為它允許我們做一些事情,比如建立一個不會包含在 tarball 中的精心設計的測試套件,這樣使用者就不會被它打擾。 它還可以揭示我們程式碼中的隱藏假設和遺漏——也許你的程式碼之所以能夠構建和執行,是因為一個意外生成的檔案。