开发环境的两种方式
shell.nix(经典方式)
在项目根目录创建 shell.nix,通过
nix-shell 命令进入环境。无需启用 Flakes,兼容旧版 Nix,适合快速迁移现有项目。flake.nix devShells(现代方式)
在 flake.nix 中定义
devShells,通过 nix develop 进入环境。与 Flakes 的依赖锁定集成,跨机器完全一致,推荐新项目使用。shell.nix — 经典开发环境
# shell.nix — 放在项目根目录
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
# 构建/运行时需要的包
buildInputs = with pkgs; [
nodejs_20
yarn
git
curl
jq
];
# 进入 shell 时执行的命令(设置提示符、别名等)
shellHook = ''
echo "🔧 Dev environment ready"
echo "Node: $(node --version)"
echo "Yarn: $(yarn --version)"
export PS1="\[\033[1;34m\][nix-dev]\[\033[0m\] \w $ "
'';
# 设置环境变量
DATABASE_URL = "postgresql://localhost/myapp";
NODE_ENV = "development";
}
# 进入开发环境
nix-shell # 在项目目录下运行,自动查找 shell.nix
nix-shell shell.nix # 显式指定文件
# 在开发环境中执行单个命令(不进入交互式 shell)
nix-shell --run "yarn install"
nix-shell --run "yarn build"
mkShell 详解
mkShell 是专为开发环境设计的辅助函数,它创建一个不产生可执行产物、只提供环境的 derivation:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
# buildInputs:运行时和构建时都需要的包(最常用)
buildInputs = with pkgs; [
gcc
gnumake
pkg-config
openssl
];
# nativeBuildInputs:只在构建机器上需要的工具(交叉编译时区分)
nativeBuildInputs = with pkgs; [
cmake
ninja
];
# 直接设置环境变量
RUST_LOG = "debug";
OPENSSL_DIR = "${pkgs.openssl.dev}"; # 使用包的路径
# shellHook:进入 shell 时执行(bash 代码)
shellHook = ''
export CFLAGS="-O2 -Wall"
alias ll="ls -la"
source .env 2>/dev/null || true # 可选:加载 .env 文件
'';
}
nix develop — Flakes 开发环境
使用 Flakes 的现代方式,将开发环境定义在 flake.nix 的 devShells 中:
# flake.nix
{
description = "My project dev environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in {
# 默认开发环境(nix develop)
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs_20
typescript
nodePackages.ts-node
];
shellHook = ''
echo "TypeScript dev environment"
'';
};
# 多个开发环境(nix develop .#ci)
devShells.ci = pkgs.mkShell {
buildInputs = with pkgs; [ nodejs_20 curl jq ];
};
}
);
}
# 进入默认开发环境
nix develop
# 进入指定环境
nix develop .#ci
# 执行单条命令后退出
nix develop --command yarn build
# 使用 --ignore-environment 获得更纯净的环境(去除大部分系统变量)
nix develop --ignore-environment
direnv + nix-direnv:自动激活
手动执行 nix develop 很繁琐。direnv 可以在进入目录时自动激活环境,离开时自动停用:
# 安装 direnv 和 nix-direnv
nix profile install nixpkgs#direnv nixpkgs#nix-direnv
# 将 direnv hook 添加到 Shell 配置
# ~/.bashrc 或 ~/.zshrc:
eval "$(direnv hook bash)" # bash
eval "$(direnv hook zsh)" # zsh
# 在项目目录创建 .envrc 文件
echo "use flake" > .envrc # 使用 flake.nix 中的 devShell
# 或者:echo "use nix" > .envrc # 使用 shell.nix
# 首次允许(安全确认)
direnv allow
# 之后:进入目录自动激活,离开目录自动停用!
cd myproject/ # 自动进入 nix develop 环境
cd .. # 环境自动停用
nix-direnv 缓存优化
# 在 ~/.config/direnv/direnvrc 中加载 nix-direnv
# 这样 nix-direnv 会缓存 shell 路径,重启后不需要重新构建
source $(nix-direnv-find)/share/nix-direnv/direnvrc
# 或者通过 Home Manager 配置(第8章会详述)
语言特定开发环境实战
Python 项目
# shell.nix — Python 项目
{ pkgs ? import <nixpkgs> {} }:
let
# 选择 Python 版本并添加额外包
python = pkgs.python311.withPackages (ps: with ps; [
pip
virtualenv
setuptools
wheel
requests
numpy
pandas
]);
in
pkgs.mkShell {
buildInputs = [ python pkgs.poetry ];
shellHook = ''
# 创建并激活虚拟环境(用于 pip install 的额外包)
if [ ! -d .venv ]; then
python -m venv .venv
fi
source .venv/bin/activate
echo "Python $(python --version) ready"
'';
}
Rust 项目
# flake.nix — Rust 项目(使用 fenix 提供 nightly toolchain)
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
fenix.url = "github:nix-community/fenix";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, fenix, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
# 使用 stable Rust toolchain
toolchain = fenix.packages.${system}.stable.toolchain;
in {
devShells.default = pkgs.mkShell {
buildInputs = [
toolchain
pkgs.pkg-config
pkgs.openssl
pkgs.libiconv
];
RUST_LOG = "debug";
OPENSSL_DIR = "${pkgs.openssl.dev}";
OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib";
};
}
);
}
Go 项目
# shell.nix — Go 项目
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
go_1_22
gopls # Go LSP
golangci-lint
delve # Go 调试器
gotools # goimports 等工具
];
shellHook = ''
export GOPATH="$HOME/go"
export PATH="$GOPATH/bin:$PATH"
echo "Go $(go version) ready"
'';
}
实战:为 Python 项目创建完整隔离开发环境
完整流程:① 在项目根目录创建 flake.nix(见 Rust 示例结构),在 devShells.default 中用 pkgs.python311.withPackages 列出依赖;② 运行 nix flake lock 锁定 nixpkgs commit;③ 创建 .envrc 写入 use flake;④ 执行 direnv allow。之后任何人 clone 这个仓库,进入目录即可自动获得完全一致的开发环境。
本章小结
Nix 开发环境的核心是 mkShell:声明需要哪些工具(buildInputs)、设置哪些环境变量、进入时运行什么初始化脚本(shellHook)。配合 direnv,实现进目录自动激活的无缝体验。将 devShell 放入 Flakes 还能通过 flake.lock 锁定所有工具的精确版本,真正做到跨机器一致。