OFFSET偏移
← 文章目录OFFSET / FIELD NOTESNO. 030

Flakes 项目与依赖管理教程

从输入语法到多主机组织,完整梳理 Flake 命令和项目结构。

所在专题Flakes:项目与依赖管理 ↗
约 26 分钟阅读开始阅读

示例版本与验证范围#

本教程保留 NixOS/Home Manager 24.05 分支示例,展示结构而非推荐部署这个历史版本。实际配置应使用相互匹配且受支持的分支,保留原有 stateVersion,并按锁定版本检查接口。占位 commit/hash 和省略号需要替换;代码未在目标系统构建或运行验证。

本章是 Nix 最重要的进步。Flakes 提供了可复现的、版本锁定的项目定义方式。


什么是 Flakes?#

Flakes 是 Nix 的实验性功能,提供了:

  • 可复现性:所有依赖通过 flake.lock 锁定
  • 可组合性:轻松组合多个项目
  • 标准化:统一的输入/输出接口

启用 Flakes#

NixOS 系统

# /etc/nixos/configuration.nix
{ pkgs, ... }: {
  nix.settings.experimental-features = [ "nix-command" "flakes" ];
  
  # 允许非自由软件
  nixpkgs.config.allowUnfree = true;
}

非 NixOS 系统

# ~/.config/nix/nix.conf
experimental-features = nix-command flakes

或通过命令行参数临时启用:

nix --extra-experimental-features "nix-command flakes" <命令>

Flake 文件结构#

基本语法

{
  # === 输入依赖 ===
  inputs = {
    # 标准格式: type:location?ref
    # type = github, gitlab, sourcehut, git, path, etc.
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    # 本地路径
    my-package.url = "path:./my-package";
    
    # Git 仓库
    my-project.url = "git+https://github.com/user/repo?ref=main";
  };

  # === 输出定义 ===
  outputs = { self, nixpkgs, ... }@inputs: {
    # packages, devShells, nixosConfigurations, etc.
  };
}

完整示例

{
  description = "我的项目";

  # ========== 输入依赖 ==========
  inputs = {
    # nixpkgs 官方包集合
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    # 跟随 nixpkgs(使用同一版本)
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # 锁定到特定 commit
    nixpkgs-old.url = "github:NixOS/nixpkgs/a1b2c3d4";
    
    # 本地 Flake
    my-local.url = "path:/home/user/my-flake";
  };

  # ========== 输出定义 ==========
  outputs = { self, nixpkgs, home-manager, ... }@inputs:
    let
      # 定义系统平台
      supportedSystems = [
        "x86_64-linux"
        "aarch64-linux"
        "x86_64-darwin"
        "aarch64-darwin"
      ];

      # 为每个系统生成配置(自动化工具)
      forAllSystems = nixpkgs.lib.genAttrs supportedSystems;

      # 获取对应平台的 nixpkgs
      pkgsFor = system: nixpkgs.legacyPackages.${system};
    in
    {
      # === 软件包 ===
      packages = forAllSystems (system: {
        default = (pkgsFor system).callPackage ./default.nix {};
        my-app = (pkgsFor system).callPackage ./my-app.nix {};
      });

      # === 开发环境 ===
      devShells = forAllSystems (system: {
        default = (pkgsFor system).mkShell {
          packages = with (pkgsFor system); [
            git nodejs jq
          ];
        };
      });

      # === NixOS 配置 ===
      nixosConfigurations.my-server = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
          home-manager.nixosModules.home-manager
        ];
        specialArgs = { inherit inputs; };
      };

      # === Home Manager 配置 ===
      homeConfigurations.alice = home-manager.lib.homeManagerConfiguration {
        pkgs = nixpkgs.legacyPackages.x86_64-linux;
        modules = [ ./home.nix ];
        extraSpecialArgs = { inherit inputs; };
      };

      # === 格式化检查 ===
      formatter = forAllSystems (system: (pkgsFor system).alejandra);
    };
}

输入(Inputs)详解#

常见输入源

{
  inputs = {
    # GitHub(最常用)
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    # 指定分支/标签/commit
    nixpkgs-unstable.url = "github:NixOS/nixpkgs/master";
    nixpkgs-some-commit.url = "github:NixOS/nixpkgs/abc123def";
    
    # GitLab
    gitlab-repo.url = "gitlab:owner/repo/main";
    
    # SourceHut
    srht-repo.url = "sourcehut:owner/repo";
    
    # 任意 Git 仓库
    my-git.url = "git+https://example.com/repo.git?ref=main";
    my-git-ssh.url = "git+ssh://git@github.com/owner/repo";
    
    # 本地路径
    local-pkg.url = "path:./local-package";
    
    # 跟随其他输入(避免重复下载)
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      # 使用与 nixpkgs 相同的版本
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    # 禁用某些输入
    some-input.inputs.unused.follows = "";
  };
}

输入属性

{
  inputs = {
    my-input = {
      url = "github:owner/repo";
      # 将内容当作普通源码而非 Flake;固定 commit 应写入 URL/rev
      flake = false;  # 非 flake 输入(纯内容)
      
      # 子目录
      # 使用 Flake 子目录(URL 查询参数)
      # url = "github:owner/repo?dir=subdirectory";
    };
  };
}

Flake 命令详解#

基础命令

# 查看 flake 信息
nix flake info
nix flake metadata

# 仅检查求值,不构建 checks
nix flake check --no-build
# 求值并构建 checks(不激活系统)
nix flake check

# 更新所有依赖
nix flake update

# 更新特定输入
nix flake update nixpkgs
nix flake update home-manager

# 编辑 flake
nix flake edit

# 克隆 flake 仓库
nix flake clone <flake-url>

# 显示 flake 目录
nix flake init  # 在当前目录初始化

构建命令

# 构建默认包
nix build

# 构建特定包
nix build .#my-package
nix build .#packages.x86_64-linux.my-app

# 构建并输出路径
nix build .#my-package --out-link ./result

# 构建并打印路径
nix build .#my-package --print-out-paths

# 从其他 flake 构建
nix build github:nixos/nixpkgs#hello
nix build gitlab:owner/repo#package

运行命令

# 运行默认包
nix run

# 运行特定包
nix run .#my-app

# 运行临时软件包(不安装)
nix run nixpkgs#htop
nix run nixpkgs#nodejs -- --version
nix run nixpkgs#cowsay -- "Hello Nix"

# 从 URL 运行
nix run github:owner/repo#script

开发环境

# 进入开发环境
nix develop
nix develop .#default

# 进入特定 shell
nix develop .#python-dev

# 临时进入环境执行命令
nix develop --command cargo build
nix develop .#rust --command rustc --version

# nix shell 是新 CLI 的临时包环境,与 nix develop 不同
nix shell .#my-package
nix shell nixpkgs#git nixpkgs#vim

系统配置

# NixOS
sudo nixos-rebuild switch --flake .#my-host
sudo nixos-rebuild build --flake .#my-host
sudo nixos-rebuild test --flake .#my-host

# 查看系统配置
nixos-rebuild build-vm --flake .#my-host

# Home Manager
home-manager switch --flake .#username
home-manager build --flake .#username

# Darwin (nix-darwin)
darwin-rebuild switch --flake .#my-mac

NixOS Flake 配置#

单主机配置

# flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

  outputs = { self, nixpkgs }: {
    nixosConfigurations.my-laptop = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./configuration.nix
        {
          # 自动传递 self 作为 specialArgs
          _module.args.self = self;
        }
      ];
    };
  };
}

多主机配置

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
  inputs.home-manager.url = "github:nix-community/home-manager/release-24.05";

  outputs = { self, nixpkgs, home-manager }:
    let
      mkSystem = { hostname, system, users }: nixpkgs.lib.nixosSystem {
        inherit system;
        modules = [
          ./hosts/${hostname}/hardware-configuration.nix
          ./hosts/${hostname}/configuration.nix
          ./modules/common
          
          home-manager.nixosModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.users = nixpkgs.lib.genAttrs users (user: 
              import ./home/${user}.nix
            );
          }
        ];
        specialArgs = { inherit self; };
      };
    in
    {
      nixosConfigurations = {
        # 笔记本
        laptop = mkSystem {
          hostname = "laptop";
          system = "x86_64-linux";
          users = [ "alice" ];
        };
        
        # 台式机
        desktop = mkSystem {
          hostname = "desktop";
          system = "x86_64-linux";
          users = [ "alice" "bob" ];
        };
        
        # 服务器
        server = mkSystem {
          hostname = "server";
          system = "x86_64-linux";
          users = [ "deploy" ];
        };
      };
    };
}

模块化 Flake#

目录结构

~/nixos-config/
├── flake.nix              # 入口
├── flake.lock             # 锁定文件
├── lib/                   # 自定义库函数
│   └── default.nix
├── modules/               # 可复用模块
│   ├── core/              # 核心系统配置
│   ├── desktop/           # 桌面环境
│   ├── development/       # 开发环境
│   └── services/          # 服务
├── hosts/                 # 主机特定配置
│   ├── laptop/
│   │   ├── default.nix
│   │   └── hardware-configuration.nix
│   └── server/
│       ├── default.nix
│       └── hardware-configuration.nix
├── home/                  # Home Manager 配置
│   ├── alice.nix
│   └── bob.nix
└── pkgs/                  # 自定义包
    └── default.nix

模块化入口

# flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

  outputs = inputs@{ self, nixpkgs }:
    let
      inherit (nixpkgs) lib;
      
      # 系统类型
      systems = [ "x86_64-linux" "aarch64-linux" ];
      
      # 为每个系统应用函数
      forEachSystem = f: lib.genAttrs systems (sys: f nixpkgs.legacyPackages.${sys});
    in
    {
      # 导入 lib
      lib = import ./lib { inherit lib; };
      
      # 导入包
      packages = forEachSystem (pkgs: import ./pkgs { inherit pkgs; });
      
      # NixOS 配置
      nixosConfigurations = {
        laptop = lib.nixosSystem {
          system = "x86_64-linux";
          modules = [
            # 核心模块
            ./modules/core
            ./modules/desktop
            ./modules/development
            
            # 主机特定
            ./hosts/laptop
            
            # 本地包
            { nixpkgs.overlays = [ self.overlays.default ]; }
          ];
          specialArgs = { inherit self inputs; };
        };
      };
      
      # Overlay
      overlays.default = final: prev: {
        my-packages = self.packages.${final.system};
      };
    };
}

Flake.lock 详解#

锁定文件结构

{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1698611440,
        "narHash": "sha256-...",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "a1b2c3d4...",
        "type": "github"
      },
      "original": {
        "owner": "NixOS",
        "ref": "nixos-24.05",
        "repo": "nixpkgs",
        "type": "github"
      }
    },
    "root": {
      "inputs": {
        "nixpkgs": "nixpkgs"
      }
    }
  },
  "root": "root",
  "version": 7
}

锁定命令

# 创建/更新锁定
nix flake lock

# 更新特定输入
nix flake update nixpkgs

# 使用特定版本
# 临时覆盖不写入锁文件;持久固定请先修改 flake.nix
nix flake metadata --override-input nixpkgs github:NixOS/nixpkgs/<rev>

# 从锁定文件恢复
nix flake metadata --reference-lock-file ./old-flake.lock

# 查看锁定信息
nix flake metadata --json

实际使用示例#

项目模板

# flake.nix - Python 项目
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
      
      python = pkgs.python311;
      
      my-python-packages = ps: with ps; [
        requests
        flask
        pytest
        black
        mypy
      ];
    in
    {
      devShells.${system}.default = pkgs.mkShell {
        name = "python-project";
        
        packages = with pkgs; [
          (python.withPackages my-python-packages)
          git
          just  # 命令运行器
        ];
        
        shellHook = ''
          # python.withPackages 的包装器提供声明的包。
          # 不设置虚假的 VIRTUAL_ENV;如需 venv,应单独创建并激活。
          echo "Python 开发环境已加载"
          python --version
        '';
      };
      
      packages.${system}.default = pkgs.python311Packages.buildPythonApplication {
        pname = "my-app";
        version = "1.0.0";
        src = ./.;
        propagatedBuildInputs = with pkgs.python311Packages; [
          requests flask
        ];
      };
    };
}

完整系统配置

# flake.nix
{
  description = "NixOS 完全配置";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    
    home-manager = {
      url = "github:nix-community/home-manager/release-24.05";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    
    nix-doom-emacs = {
      url = "github:nix-community/nix-doom-emacs";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = inputs@{ self, nixpkgs, home-manager, ... }:
    let
      lib = nixpkgs.lib;
      
      mkSystem = { config, system ? "x86_64-linux" }: lib.nixosSystem {
        inherit system;
        modules = [
          config
          home-manager.nixosModules.home-manager
          { 
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            
            # 自动传递 inputs
            _module.args.inputs = inputs;
          }
        ];
      };
    in
    {
      nixosConfigurations = {
        laptop = mkSystem {
          config = ./hosts/laptop;
        };
        
        work = mkSystem {
          config = ./hosts/work;
        };
      };
      
      # 格式化工具
      formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.alejandra;
      
      # 模板
      templates.default = {
        path = ./templates/basic;
        description = "基础项目模板";
      };
    };
}

常见问题#

如何查看 flake 提供的输出?

nix flake show
nix flake show github:owner/repo

如何调试构建失败?

# 进入项目开发环境(不等于自动复现失败 derivation 的完整环境)
nix develop .#devShells.x86_64-linux.default -L

# 查看详细日志
nix build .#package --print-build-logs
nix build .#package -L

如何引用本地路径?

{
  inputs.local-pkg.url = "path:/absolute/path/to/package";
  # 或相对路径
  inputs.my-config.url = "path:./my-config";
}

如何兼容旧版入口?

nix-build 不会自动把 Flake 转换成传统表达式。项目需要提供 default.nix/shell.nix,可使用 flake-compat 适配锁定的 Flake;适配后才使用:

nix-build

最佳实践#

  1. 始终锁定版本:使用 nix flake lock
  2. 分离系统配置:每个主机独立目录
  3. 使用 follows:避免重复下载
  4. 定期更新:但不要盲目更新
  5. 备份 lock 文件:版本控制中保留

参考与延伸

  1. Nix Flake 手册 ↗
  2. Nix flake update ↗
  3. Nix flake check ↗

根据技术笔记重新整理,示例使用通用名称。

沿专题继续阅读

Flakes:项目与依赖管理

  1. 01Flake 结构:inputs 与 outputs
  2. 02flake.lock 与可复现性
  3. 03Flake Utils 与项目模板
  4. 04外部 Flake 引用与协作
  5. 05将 NixOS 配置迁移到 Flakes
  6. 06Flakes 项目与依赖管理教程
  7. 07Flake 更新机制
  8. 08flake.lock 回退与手动编辑
CONTINUE READING / 下一篇Flake 更新机制

区分输入更新、软件包集合、系统代际与缓存下载。