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: [email protected]] 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: [email protected] 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? - "<[email protected]>" [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? - "<[email protected]>" [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 中的精心设计的测试套件,这样用户就不会被它打扰。 它还可以揭示我们代码中的隐藏假设和遗漏——也许你的代码之所以能够构建和运行,是因为一个意外生成的文件。