本文假设读者对以下内容有基础认识:
- nix 语法,见 Nix Pills 04,05
- flakes 的作用和语法,见 nixos wiki
- nix module system
讲述 flake-parts 所解决的问题和基础的使用方法。
Nix Flakes 的形制如下所示:
{
inputs = ...;
outputs = {...}: {
# ...
};
}
outputs 是一个函数,它的输出就是flake最后的output
,我们可以通过在任意一个 flake 仓库使用 nix flake show
来观察:
> nix flake show
git+file:///home/my/nixos
├───allSystems: unknown
├───packages
│ ├───aarch64-linux
│ │ └───a omitted (use '--all-systems' to show)
│ └───x86_64-linux
│ └───a: derivation 'a package, declared in flake'
├───debug: unknown
#...
一部分 output attribute 如 checks
, packages
的形式1类似 checks.system.name
, packages.system.name
,
这部分的属性在flake声明的时候,通常采用如下方式:
# pure nix flake way
{
outputs = {nixpkgs, ...}: {
packages.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.callPackage ./package.nix {
some-special-arg = ...;
};
packages.aarch64-linux.default = nixpkgs.legacyPackages.aarch64-linux.callPackage ./package.nix {
some-special-arg = ...;
};
};
}
在 flake-parts 出现前大家通常使用 flake-utils 来简化对多架构声明的工作:
# flake-utils way
{
outputs = {nixpkgs, flake-utils, ...}: flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux"] (system: {
packages.default = nixpkgs.legacyPackages.${system}.callPackage = ./package.nix {
some-special-arg = ...;
};
});
}
细心的小伙伴可能会发现,flake-utils 定义的 eachSystem
函数实际上就是一个简单的 lib.genAttrs
,-utils 所带来的功能完全没有必要通过引入一个input来解决,
而且它实际上带来了一些问题,例如你写坏了但是它没有类型检查,
导致你的flake能 pass eval 但悄咪咪地坏了。此部分在ayats的博客中亦有记载,这章节的一部分示例也来自这篇文章2。
当你开始使用 flake-parts,perSystem
将代替这部分产生每个架构的配置的工作,并检查你写的东西是否合乎最基本的类型规定:
# -parts way
{
outputs = inputs@{nixpkgs, flake-parts, ...}: flake-parts.lib.mkFlake {inherit inputs;} {
systems = [
"x86_64-linux"
"aarch64-linux"
];
perSystem = {pkgs, system, ...}: {
packages.default = pkgs.callPackage ./package.nix {};
# ↓ flake-parts will reject this, hooray!
nixosConfigurations.nixos = ...;
};
};
}
你要是问 nixosConfigurations
或者 nixosModules
这类attribute怎么办?当然是直接放 flake
里面就好了:
# -parts way
{
outputs = inputs@{nixpkgs, flake-parts, ...}: flake-parts.lib.mkFlake {inherit inputs;} {
systems = [ ];
perSystem = {pkgs, system, ...}: { };
flake = { nixosConfigurations.foo = { }; };
};
}
flake-parts.lib.mkFlake
这个函数最后产生的配置和以上提到的 pure nix flake way 和 flake-utils way 的配置是等价的。
perSystem
在功能上基本超越了 flake-utils 并额外提供了一定程度的类型安全。而这是 flake-parts 其中一个feature的一部分。
还有一些零碎但是比较能提升幸福感的小功能,比如 perSystem
的函数输入中默认提供了一个 self'
,你可以在 perSystem
中将 self.packages.x86_64-linux.hello
简写为 self'.packages.hello
等等。
nix 的模块系统3声名远扬,一言概之其作用就是:
options
定义选项,包括类型、默认值等config
写实际配置,连同options
丢进 module system 经过计算合并等操作得出结果
在 flake-parts 之前 nix flake 本身是没有 module 的,在flake toplevel定义的东西都只能遵循flake schema本身然后以flake的方式解析,
例如你不能在 flake toplevel import 任何东西、定义任何option,最直观的影响就是你的 nixosConfigurations
, packages
等等只能写在 flake.nix
中,
然后写错了你自己也难以发现,因为没有 module system 和其能够附带的任何检查。
这部分玩法比较多样,但熟悉nix module 的用户能够很快上手。通过 flake-parts 你可以像使用 nixos module 一样使用flake,通过在flake.nix
中修改nix风味的配置项来定制它。可以参考 flake-parts Reference Documentation 那一节4的应用,既然有了 module system 就可以进行更进一步的包装,关于它可以将一些功能的使用简化到什么程度,可参考nix-topology的文档5。
easyOverlay
可以通过 overlayAttrs
来直接导出本地flake已经定义的packages来作为overlay outputs,节省了很多字数:
before
packages.x86_64-linux.foo =
(import inputs.nixpkgs {system = "x86_64-linux";}).callPackage ./foo.nix {};
packages.x86_64-linux.bar =
(import inputs.nixpkgs {system = "x86_64-linux";}).callPackage ./bar.nix {};
overlays.foo = prev: final: prev.callPackage ./foo.nix {};
overlays.bar = prev: final: prev.callPackage ./bar.nix {};
after
perSystem = {pkgs,...}: {
packages.foo = pkgs.callPackage ./foo.nix {};
packages.bar = pkgs.callPackage ./bar.nix {};
}
flake.overlayAttrs = config.packages;
不过这个特性可能将来会有变动。
partition
作用是拆分flake(A)的一部分input到另外一个独立的分区flake(B),以便独立eval,
在项目(A)被用户作为 flake input 导入使用时,
避免一并引入flake(B) input的内容,
从使用上表现为可以只eval packages
而避免eval check
之类develop相关的input。
将 nixosModules 拆分
可以用更优雅的方式在flake定义的 nixosModules.*
中访问flake本身的attribute等。
before
outputs = {inputs, self, ...}: {
nixosModules.default = import ./nixos-module.nix self;
# packages.x86_64-linux.foo = #...
}
# ./nixos-module.nix
self: # passed the outer flake self in
{
config,
pkgs,
# config and pkgs, pass from nixos module, contains system archetecture info
}: {
options.services.foo.package = mkOption {
type = lib.types.package;
default = self.packages.${pkgs.system}.default;
defaultText = lib.literalMD "`packages.default` from the foo flake";
};
config = { };
}
after
# Flake module
{ withSystem, ... }: {
flake.nixosModules.default = { pkgs, ... }: {
imports = [ ./nixos-module.nix ];
services.foo.package = withSystem pkgs.stdenv.hostPlatform.system ({ config, ... }:
config.packages.default
);
};
}
# nixos-module.nix
{ lib, config, ... }: {
options = {
services.foo = {
package = mkOption {
defaultText = lib.literalMD "`packages.default` from the foo flake";
};
};
};
config = { };
}