GNU Guix 函数式包管理教程 / 第四章 包定义详解
第四章:包定义详解
4.1 包定义的结构
每个 Guix 包都用一个 S-expression(S 表达式)来定义,它是纯粹的 Scheme 代码。理解包定义是成为 Guix 高级用户的关键。
4.1.1 最小包定义示例
(package
(name "hello")
(version "2.12")
(source (origin
(method url-fetch)
(uri (string-append "mirror://gnu/hello/hello-"
version ".tar.gz"))
(sha256
(base32
"0ssi1wpaf7plaswqqjwigppsg5f漖qyvr2as4wd1g"))))
(build-system gnu-build-system)
(home-page "https://www.gnu.org/software/hello/")
(synopsis "Hello, GNU world: An example GNU package")
(description "GNU Hello prints a friendly message.")
(license license:gpl3+))
4.1.2 包定义字段详解
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
name | 字符串 | ✅ | 包名称(小写,用连字符分隔) |
version | 字符串 | ✅ | 版本号 |
source | origin | ✅ | 源码来源 |
build-system | 构建系统 | ✅ | 构建方式(如 gnu-build-system) |
inputs | 列表 | ❌ | 构建时和运行时依赖 |
native-inputs | 列表 | ❌ | 仅构建时依赖(不会出现在运行环境中) |
propagated-inputs | 列表 | ❌ | 传播依赖(安装此包会自动安装这些依赖) |
arguments | 列表 | ❌ | 传递给构建系统的参数 |
home-page | 字符串 | ❌ | 项目主页 |
synopsis | 字符串 | ✅ | 简短描述(一行) |
description | 字符串 | ✅ | 详细描述 |
license | license 对象 | ✅ | 软件许可证 |
native-search-paths | 列表 | ❌ | 原生搜索路径 |
replacement | package | ❌ | 替代包(安全修复) |
4.2 Scheme 语法基础
如果你不熟悉 Scheme/Lisp,以下是理解包定义所需的最低限度知识。
4.2.1 S-expression 基础
;; 注释以分号开头
;; 函数调用:(函数名 参数1 参数2 ...)
(string-append "hello" " " "world") ; => "hello world"
(+ 1 2 3) ; => 6
;; 定义变量
(define name "vim")
(define version "9.0")
;; 列表
(list "git" "vim" "htop")
;; 引用(quote):不求值
'(a b c) ; => (a b c)
;; 函数定义
(define (greet name)
(string-append "Hello, " name "!"))
(greet "Guix") ; => "Hello, Guix!"
4.2.2 常用 Scheme 构造
;; 条件表达式
(if (> 3 2)
"yes"
"no") ; => "yes"
;; let 绑定
(let ((x 10)
(y 20))
(+ x y)) ; => 30
;; 字符串操作
(string-append "foo" "-" "bar") ; => "foo-bar"
(string-length "hello") ; => 5
;; 路径操作(Guix 内置)
(string-append "/gnu/store/" name "-" version)
4.2.3 Guix 特有的语法
;; use-modules:导入模块(类似 Python 的 import)
(use-modules (gnu packages vim)
(gnu packages python)
(guix gexp))
;; define-public:定义公共包(可被其他模块引用)
(define-public my-package
(package ...))
;; origin:描述源码来源
(origin
(method url-fetch)
(uri "https://example.com/foo-1.0.tar.gz")
(sha256 (base32 "0abc...")))
;; 查看源码哈希
;; $ guix download https://example.com/foo-1.0.tar.gz
;; 0abc...
4.3 源码来源(Origin)
4.3.1 常用获取方式
| 方法 | 说明 | 用途 |
|---|---|---|
url-fetch | 从 URL 下载 | 官方发布包 |
git-fetch | Git 克隆 | 开发版本、GitHub 项目 |
hg-fetch | Mercurial 克隆 | 使用 Mercurial 的项目 |
svn-fetch | SVN 检出 | 使用 SVN 的项目 |
patch-origin | 对已有 origin 打补丁 | 修补漏洞 |
4.3.2 URL 下载
(source (origin
(method url-fetch)
(uri (string-append "https://example.com/"
name "-" version ".tar.gz"))
(sha256
(base32 "1abc..."))))
获取 sha256 哈希值:
# 使用 guix download 自动计算并显示哈希
guix download https://example.com/foo-1.0.tar.gz
# 输出:
# 1abc... (base32 hash)
# /gnu/store/...-foo-1.0.tar.gz
4.3.3 Git 克隆
(source (origin
(method git-fetch)
(uri (git-reference
(url "https://github.com/user/project")
(commit "v1.0.0")))
(sha256
(base32 "0xyz..."))))
# 获取 Git 源码的哈希
guix download --git-fetch --commit=v1.0.0 \
https://github.com/user/project
4.3.4 文件列表和补丁
(source (origin
(method url-fetch)
(uri "https://example.com/foo-1.0.tar.gz")
(sha256 (base32 "1abc..."))
(patches (search-patches "foo-fix-build.patch"
"foo-security-fix.patch"))
(modules '((guix build utils)))
(snippet '(begin
(delete-file "configure")
(copy-file "configure.ac" "configure")))))
4.4 构建系统(Build System)
构建系统决定了如何编译和安装软件包。
4.4.1 常用构建系统
| 构建系统 | 说明 | 典型项目 |
|---|---|---|
gnu-build-system | GNU Autotools(./configure && make && make install) | 大多数 C 项目 |
cmake-build-system | CMake | C++ 项目 |
cargo-build-system | Rust Cargo | Rust 项目 |
python-build-system | Python setuptools/pip | Python 包 |
perl-build-system | Perl ExtUtils::MakeMaker | Perl 模块 |
meson-build-system | Meson | GNOME 生态项目 |
go-build-system | Go modules | Go 项目 |
haskell-build-system | Haskell Cabal/Stack | Haskell 项目 |
dune-build-system | OCaml Dune | OCaml 项目 |
r-build-system | R | R 包 |
texlive-build-system | TeX Live | LaTeX 宏包 |
trivial-build-system | 无特殊构建过程 | 纯脚本、数据包 |
4.4.2 GNU Build System
最常用的构建系统,对应经典的 ./configure && make && make install 流程:
(package
(name "example")
;; ...
(build-system gnu-build-system)
(arguments
(list
#:configure-flags #~(list "--disable-static")
#:make-flags #~(list "CC=gcc")
#:phases
#~(modify-phases %standard-phases
(add-after 'unpack 'fix-paths
(lambda* (#:key inputs #:allow-other-keys)
(substitute* "src/main.c"
(("/usr/local")
(assoc-ref outputs "out"))))))))
;; ...)
4.4.3 构建系统参数(Arguments)
arguments 字段可以定制构建行为:
(arguments
(list
;; 跳过测试(某些包的测试在沙箱中无法运行)
#:tests? #f
;; 自定义 configure 参数
#:configure-flags #~(list "--with-feature"
(string-append "--prefix=" #$output))
;; 自定义 make 参数
#:make-flags #~(list (string-append "CC=" #$(cc-for-target)))
;; 修改构建阶段
#:phases
#~(modify-phases %standard-phases
;; 在 unpack 阶段之后执行
(add-after 'unpack 'patch-source
(lambda* (#:key inputs #:allow-other-keys)
(substitute* "Makefile"
(("/usr/bin/env python3")
(which "python3")))))
;; 替换 install 阶段
(replace 'install
(lambda* (#:key outputs #:allow-other-keys)
(let ((out (assoc-ref outputs "out")))
(install-file "myapp" (string-append out "/bin"))))))))
4.4.4 构建阶段
GNU Build System 的标准阶段(phases):
| 阶段 | 说明 |
|---|---|
unpack | 解压源码 |
patch-usr-bin-file | 修复 /usr/bin/file 引用 |
patch-source-shebangs | 修补脚本的 shebang 行 |
configure | 运行 ./configure |
build | 运行 make |
check | 运行 make check(测试) |
install | 运行 make install |
patch-shebangs | 修补安装后脚本的 shebang |
strip | 剥离调试符号 |
4.5 依赖管理
4.5.1 依赖类型
Guix 区分三种依赖:
| 类型 | 字段 | 说明 |
|---|---|---|
| 普通依赖 | inputs | 构建和运行时都需要 |
| 原生依赖 | native-inputs | 仅构建时需要(如构建工具) |
| 传播依赖 | propagated-inputs | 安装此包时自动引入 |
(package
;; ...
;; 运行时依赖
(inputs (list ncurses glib gtk))
;; 仅构建时依赖
(native-inputs
(list pkg-config
autoconf
automake
python)) ; Python 仅在构建脚本中使用
;; 传播依赖:安装此包的用户也会得到这些包
(propagated-inputs
(list python-setuptools))) ; Python 包通常需要 setuptools
4.5.2 包的输出(Outputs)
一个包可以有多个输出:
(package
(name "openssl")
;; ...
(outputs '("out" ; 运行时库和命令
"doc" ; 文档
"dev"))) ; 头文件和 pkg-config
使用时可以只安装需要的输出:
# 只安装运行时库
guix install openssl
# 安装开发头文件
guix install openssl:dev
4.5.3 跨编译支持
(arguments
(list
#:make-flags
#~(list (string-append "CC=" #$(cc-for-target)))))
cc-for-target 会自动在交叉编译时使用正确的工具链。
4.6 版本管理
4.6.1 版本号约定
Guix 遵循 GNU 版本号规范:
major.minor.patch
在包定义中,版本号通常与上游发布保持一致:
(package
(name "example")
(version "1.2.3")
;; source 自动使用 version 字段
(source (origin
(method url-fetch)
(uri (string-append "https://example.com/example-"
version ".tar.gz"))
(sha256 (base32 "0abc..."))))
;; ...)
4.6.2 预发布版本
;; 开发版本
(version "1.2.3-rc1")
;; Git 快照
(version (git-version "1.2.3" revision commit))
;; 生成类似 "1.2.3-1.abc1234" 的版本号
4.6.3 查看包的版本历史
# 查看通道中某个包的所有版本
guix show vim
# 查看特定包在 store 中的多个版本
guix package --list-installed | grep vim
4.7 完整包定义示例
示例一:简单的 C 程序
(define-public my-c-tool
(package
(name "my-c-tool")
(version "1.0.0")
(source (origin
(method url-fetch)
(uri (string-append "https://example.com/my-c-tool-"
version ".tar.gz"))
(sha256
(base32 "0abc123..."))))
(build-system gnu-build-system)
(native-inputs (list pkg-config))
(inputs (list zlib libpng))
(home-page "https://example.com/my-c-tool")
(synopsis "A simple C tool for image processing")
(description "My-C-Tool provides fast image compression
and decompression using zlib and libpng.")
(license license:gpl3+)))
示例二:Python 包
(define-public python-mylib
(package
(name "python-mylib")
(version "2.1.0")
(source (origin
(method url-fetch)
(uri (pypi-uri "mylib" version))
(sha256
(base32 "0def456..."))))
(build-system python-build-system)
(propagated-inputs
(list python-numpy python-scipy))
(native-inputs
(list python-pytest))
(home-page "https://pypi.org/project/mylib/")
(synopsis "Scientific computing library")
(description "MyLib provides numerical methods for
scientific computing applications.")
(license license:bsd-3)))
💡 提示:对于 Python 包,pypi-uri 辅助函数可以自动生成 PyPI 下载 URL。
示例三:Rust 包
(define-public my-rust-cli
(package
(name "my-rust-cli")
(version "0.5.0")
(source (origin
(method url-fetch)
(uri (crate-uri "my-rust-cli" version))
(sha256
(base32 "0ghi789..."))))
(build-system cargo-build-system)
(arguments
(list
#:cargo-inputs
`(("rust-clap" ,rust-clap-4)
("rust-serde" ,rust-serde-1)
("rust-tokio" ,rust-tokio-1))))
(home-page "https://github.com/user/my-rust-cli")
(synopsis "Fast command-line tool written in Rust")
(description "A blazing-fast CLI tool for data processing.")
(license license:expat)))
💡 提示:对于 Rust 包,使用 crate-uri 自动生成 crates.io 下载 URL,并使用 #:cargo-inputs 声明 Rust 依赖。
4.8 包定义的测试
4.8.1 本地构建测试
# 构建包(不安装)
guix build my-package
# 构建并查看完整日志
guix build my-package -v 3
# 仅运行测试阶段
guix build my-package --with-tests
# 跳过测试
guix build my-package --without-tests
4.8.2 使用 guix lint 检查
# 对包定义进行静态检查
guix lint my-package
# 检查内容包括:
# - 描述格式
# - 许可证是否正确
# - 主页是否可达
# - 版本是否有更新
# - 是否有已知安全漏洞
4.8.3 提交前检查清单
# 1. 编译通过
guix build my-package
# 2. Lint 检查
guix lint my-package
# 3. 风格检查
guix style my-package
# 4. 检查是否已有同名包
guix search ^my-package$
4.9 本地包开发工作流
4.9.1 使用 GUIX_PACKAGE_PATH
# 设置本地包定义路径
export GUIX_PACKAGE_PATH="$HOME/src/guix-packages:$GUIX_PACKAGE_PATH"
# 在该目录下创建模块结构
mkdir -p $HOME/src/guix-packages/myproject
;; $HOME/src/guix-packages/myproject/packages.scm
(define-module (myproject packages)
#:use-module (guix packages)
#:use-module (guix build-system gnu)
#:use-module (guix gexp)
#:use-module (guix licenses)
#:use-module (guix download))
(define-public my-tool
(package
(name "my-tool")
(version "1.0")
(source (origin
(method url-fetch)
(uri (string-append "https://example.com/my-tool-"
version ".tar.gz"))
(sha256 (base32 "0abc..."))))
(build-system gnu-build-system)
(home-page "https://example.com")
(synopsis "My tool")
(description "A useful tool.")
(license gpl3+)))
# 现在可以直接安装
guix install my-tool
# 或构建
guix build my-tool
4.9.2 使用 -L 参数
# 临时添加包定义路径
guix build -L ~/src/guix-packages my-tool
4.10 包定义字段速查表
| 场景 | 字段/方法 |
|---|---|
| 添加 C 库依赖 | (inputs (list zlib)) |
| 添加构建工具 | (native-inputs (list cmake pkg-config)) |
| Python 传播依赖 | (propagated-inputs (list python-numpy)) |
| 跳过测试 | (arguments (list #:tests? #f)) |
| 自定义编译选项 | (arguments (list #:configure-flags #~(list "--opt")))) |
| 修改构建阶段 | #~(modify-phases %standard-phases ...) |
| 禁用并行构建 | (arguments (list #:parallel-build? #f)) |
| 设置环境变量 | (arguments (list #:make-flags #~(list "CC=gcc"))) |
4.11 总结
本章深入讲解了 Guix 包定义的内部结构:
- 包定义结构——所有字段的含义和用法
- Scheme 语法——理解包定义所需的最低限度知识
- 源码来源——URL 下载、Git 克隆等多种方式
- 构建系统——GNU、CMake、Cargo 等构建系统
- 依赖管理——三种依赖类型和多输出支持
- 完整示例——C、Python、Rust 包的实际定义
下一章我们将学习通道(Channels)的管理机制。