使用 autotools 工具链推导的最佳实践

首先要明晰我们的构建方式:

  1. 为构建指定依赖包,这包括构建时依赖和运行时依赖;
  2. 指定构建脚本进行构建,并修复生成文件,删减不需要的可执行文件依赖库;
  3. 将生成的推导加入到用户的自定义仓库中。

基于此,我们可以分为 4 步。

构建统一依赖制定 nix 脚本

# autotools.nix
pkgs: attrs:
let
	default_attrs = {
		builder = "${pkgs.bash}/bin/bash";
		args = [ ./builder.sh ];
		base_pkgs = with pkgs; [
			gnutar
			gzip
			gnumake
			gcc
			coreutils
			gawk
			gnused
			gnugrep
			binutils.bintools
			findutils
			patchelf
		];
		build_pkgs = [ ];
		system = builtins.currentSystem;
	};
in
derivation (default_attrs // attrs)

这是一个双变量函数,或者称为双层函数,这样我们可以对于该函数进行复用。它指定了如下的属性:

  1. 构建主程序,也就是 bash ./builder.sh
  2. autotools 所依赖的构建依赖包 base_pkgs,以及可调整的针对于特定包的依赖 build_pkgs
  3. 可执行文件运行系统,指定为当前系统,对于笔者来说是 x86_64-linux
  4. 最后一行表示将默认集合与用户自定义集合合并,如果有冲突以用户为准,将合并集合用于生成推导。

一个使用实例如下:

let
    pkgs = import <nixpkgs> { };
    mk_derivation = import ./autotools.nix pkgs;
in
    mk_derivation {
        name = "my_application";
        src = "./application.tar.gz";
    }

注意以上的 autotools.nix 中提到的 builder.sh 将马上给予解释。

构建脚本

以下的通用构建脚本适用于读者已经将 tar 形式压缩包下载之后,分为四个步骤:

  1. 初始化环境变量:将传入的推导依赖的可执行文件添加到 PATH 环境变量中,将传入的推导依赖的库文件 添加到 PKG_CONFIG_PATH 环境变量中;
  2. 解包阶段:将源文件解压并进入到解压目录中;
  3. 构建阶段:执行系统构建,并且指定安装到 NixOS 推导的默认路径中,也就是 --prefix,这将会影响 生成的可执行文件的硬编码路径;
  4. 修复阶段:这将会移除可执行文件中的冗余依赖与冗余调试信息。
set -e

# set up environment phase
unset PATH
unset PKG_CONFIG_PATH
for p in $base_pkgs $build_pkgs; do
	if [ -d $p/bin ]; then
		export PATH="$p/bin${PATH:+:}$PATH"
	fi
	if [ -d $p/lib/pkgconfig ]; then
		export PKG_CONFIG_PATH="$p/lib/pkgconfig${PKG_CONFIG_PATH:+:}$PKG_CONFIG_PATH"
	fi
done

# unpack phase
tar xf $src
for d in *; do
	if [ -d "$d" ]; then
		cd "$d"
		break
	fi
done

# build phase
./configure --prefix=$out
make
make install

# fix up phase
find $out -type f -exec patchelf --shrink-rpath '{}' \; -exec strip '{}' \; 2>/dev/null

使用 input pattern 的 nix 推导脚本

以下将所需依赖以及需要配置的选项作为参数传入,以达到依据用户要求动态生成的目的。

# graphviz.nix
{ mk_derivation, lib, gd_support? true, gd, pkg-config }:

mk_derivation {
	name = "graphviz";
	src = ./graphviz-2.49.3.tar.gz;
	build_pkgs =
		if gd_support
			then [
				pkg-config
				(lib.getLib gd)
				(lib.getDev gd)
			]
			else [];
}

使用 default.nix 将推导目标添加到仓库中

我们将可以在 nix repl 环境中 :l ./default.nix 将推导目标添加进来。

也可以直接在目录下执行 nix-build 生成所有推导目标。

也可以在目录下执行 nix-build -A gnu-hello 生成特定推导目标。

如果不在该目录下,那么可以执行 nix-build {repository path} -A {specfic derivation}

# default.nix
let
	pkgs = import <nixpkgs> { };
	mk_derivation = import ./autotools.nix pkgs;
in
with pkgs; # will ignore mk_derivation because it is defined in `let` block
{
	gnu-hello = import ./gnu_hello/hello.nix { inherit mk_derivation; };
	graphviz = import ./graphviz/graphviz_opt.nix {
		inherit
			mk_derivation
			lib
			gd
			pkg-config
			;
	};
	graphviz_core = import ./graphviz/graphviz_opt.nix {
		inherit
			mk_derivation
			lib
			gd
			pkg-config
			;
		gd_support = false;
	};
}

目录结构

├── autotools.nix
├── builder.sh
├── default.nix
├── gnu_hello
│   ├── hello.nix
│   ├── hello-2.12.1.tar.gz
├── graphviz
│   ├── graphviz-2.49.3.tar.gz
│   └── graphviz_opt.nix