跳至主要內容

纯净是我们的至高追求

NixOS-CN大约 5 分钟

纯净是我们的至高追求

整个构建系统并不是绝对纯净

我们前面一直在强调,NixOS 的生态系统有多函数式,好吧的确是有非常多的函数,但是这些函数似乎并不纯净。这些都要从整个函数机器的齿轮 —— Nix 文件说起。

为了设计一个纯净的函数,我们应该尽量地把它设计得封闭,使之隔离外部环境。但是 Nix 并不是这样,Nix 为了编译那些 Nix 文件,依然需要访问到外部世界的东西。

提示

Nix 文件可以访问任意文件(如 ~/.config/nixpkgs/config.nix)、环境变量、Git仓库、Nix搜索路径($NIX_PATH)中的文件、命令行参数(--arg)以及系统类型(builtins.currentSystem)。

这就意味着这个函数的求值过程并不完全封闭。如果你在两台不同的机器上运行相同的 Nix 表达式,可能会得到不同的结果,因为这两台机器上的环境变量或文件系统可能是不同的。所以我们建议尽量避开任何带有路径依赖的写法。

但是这样做并不能完全解决问题,因为 Nix 文件本身就是一个路径依赖(当你使用 import 函数来引入其他 Nix 文件时,你必须指定一个相对或绝对路径)这就导致了一个问题:如果你想把你的 Nix 项目分享给别人,你必须保证他们能够找到你引用的所有文件。这就需要你把所有相关的文件都打包成一个压缩文件或者上传到一个 Git 仓库,并且告诉别人如何正确地使用它们。

这显然是很麻烦的,而且也违背了函数式编程的原则。我们希望能够用一种更简单和优雅的方式来管理和共享我们的 Nix 项目,而不需要关心它们所依赖的具体路径。

我们好像漏掉了什么细节

在前面的章节中,我们还一直试图给阅读者灌输 “NixOS” 只要靠配置文件就能复现系统的理念?但是事实果真是这样吗,让我们回溯一下:不变的函数之所以能有不变的输出,是因为有不变的输入。输入果真是一成不变的吗?

我们以最大的输入 pkgs 举例,这个输入指代的是 Nix 包管理器的软件仓库 Nixpkgs,里面有数不尽的包(Nixpkgs 是事实上最大的单体包仓库)日日夜夜在提交与修改,导致 nixpkgs 一直处于 unstable 状态。与之俱来的是 options 也经常变动,这意味着你的配置文件可能对旧版包是生效的,但是新版包的 options 变动了,现在又无效了。

你会如何解决这个由依赖版本与配置不匹配的问题呢?答案是现在很多语言的包管理(比如 Cargo,pnpm)都采用的版本锁定。本网站的就是被 pnpm 管理着依赖,你可以访问本站源代码仓库,根目录下有个叫做 pnpm-lock.yaml 的文件,里面描述了各依赖互相兼容的版本。每次更新依赖时,包管理器会在尽可能让依赖版本比较新的条件下保持最大兼容,并更新 Lock 文件。

于是我们怀着两个需求:

  • 尽量排除 Nix 文件互相引用时对本地文件系统路径的依赖
  • 控制输入的版本,从而达到输出的可预期性
  • 将 Nix 项目组织成一种易管理的形式

于是 Flakes 诞生了!

终极解决方案 Flakes

Flakes 是 Nix 2.4 版本引入的一个新特性,它可以让你用一种声明式和纯净的方式来定义和使用 Nix 项目。Flakes 使用了一种 Flake 引用的方式来代替文件系统路径,URL 等。Flake 引用大致就是下面的用法:

  • 类 URL 句法,例如 github:NixOS/nixpkgs 表示 Github 托管平台上一个叫做 NixOS 用户的 nixpkgs 仓库。这与裸 URL 不同,要是哪天 Github 域名搬家了,也不用你批量替换 URL,只需要等 Nix 更新 Flakes 引用的解析规则就成。

  • 类路径句法,比如 /absolute/path/to/the/Flakes./relative/path/to/the/Flakes。你可能就会疑惑,这不就是平时的相对路径与绝对路径写法吗,究竟“类”在哪里了呢?“类”在这种引用既可能指向一个本地文件系统路径,也可能指向一个本地 Git 仓库。如果你的 URL 指向一个本地的 Git 仓库,它就会在 flake.lock 里面记录当前仓库的 commit hash,这就保证了输入的版本是不变的。那你又问,万一我要引入的目录没有版本控制呢?那我只能说你自己人工去保证输入的这个目录的内容是不变的吧。

为了保证输入的可获取性,我们一般用网络上的仓库作为输入,因为只要有网络,我们就能获得相同的输入。而使用本地文件系统路径作为输入则不然,我们很难保证每台主机的相应文件系统路径下都有一样的文件。因此,我们的建议是更多地依赖网络上的输入,除非你有一些隐私信息,才有必要使用本地文件系统输入。

{
  description = "A simple Flakes";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = { self, nixpkgs }: {
    packages.x86_64-linux.hello =
      with nixpkgs.legacyPackages.x86_64-linux;
      stdenv.mkDerivation {
        name = "hello";
        src = hello.src;
        buildInputs = [ gcc ];
        installPhase = ''
          mkdir -p $out/bin
          echo "Hello, world!" > $out/bin/hello
          chmod +x $out/bin/hello
        '';
      };
  };
}

钉住版本

当你在 Flakes.nix 文件中指定了一个 Flakes引用(例如github:NixOS/nixpkgs/nixos-unstable)后,Nix会在第一次运行 nix build 或其他 Nix 命令时会生成一个 flake.lock 文件。这个文件会记录下所有输入 Flakes 的具体版本(例如,Git 提交哈希)。有了版本锁,你什么时候构建,都是会根据 flake.lock 文件来确认依赖版本,使得构建结果也与之前并无二致。

如果你想更新到最新的提交,你可以运行 nix flake update 命令。这个命令会去更新输入,并重新钉住最新输入的版本