如何使用 Nix 轻松获取依赖项
如果您还没有听说过 Nix,那就太好了!如果你已经看过它并且有点困惑......我明白了。 Nix 一开始可能看起来很不寻常,但它对您的软件项目也有巨大的价值。我希望我们可以重新开始这篇文章。 🙂 管理依赖关系和创建可重现的环境是一个非常困难TM 的问题 管理软件开发中的依赖关系可能是一个很大的痛苦,我们为此创建了大量的解决方案:我们有 apt、npm、brew、pip、gem、rpm 等等.
如果您还没有听说过 Nix,那就太好了!如果你已经看过它并且有点困惑......我明白了。 Nix 一开始可能看起来很不寻常,但它对您的软件项目也有巨大的价值。我希望我们可以重新开始这篇文章。 🙂
管理依赖关系和创建可重现的环境是一个非常困难TM 的问题
管理软件开发中的依赖关系可能是一个很大的痛苦,我们为此创建了大量的解决方案:我们有 apt、npm、brew、pip、gem、rpm 等等......我们还使用像 Docker 这样的容器来管理软件包并创建相对可重现的环境。
所有这些解决方案都有其局限性。它们要么特定于某种语言(Python 为pip
,Ruby 为gem
,Haskell 为stack
......),某些操作系统(Debian/Ubuntu 为apt
,Fedora & co 为rpm
......),我们的某些部分堆栈(后端/前端),或者它们具有额外的复杂性(容器)。它们可能或多或少可靠(npm
),它们的结果或多或少是可重复的。很少有解决方案能够明智地管理同一个包的多个版本。
所有依赖管理难题的解决方案
解决方案是明显:我们需要另一个包管理器来管理它们!
Nix非常接近于成为理想的跨操作系统、跨语言、跨堆栈的包管理器,同时非常可靠和可重复。当然,它并不完美,它有自己的权衡和限制。关于 Nix 有很多要解释的内容,如何有一种称为Nix
的函数式编程语言、一个称为Nixpkgs
的包存储库、一个称为NixOS
的整个操作系统......等等。但是对于这篇文章,我想向您展示一个简单的、最小的用例,它可能已经对您非常有价值!
简单示例:使用 Nix 管理开发依赖项
假设要破解我们的一个项目,我们需要curl
、jq
、entr
和[# 7rpm
。pg_tmp
又取决于Postgres二进制文件。这是使用 Nix 获得所有这些的方法:
# Install Nix
curl https://nixos.org/nix/install | sh
# Create a `shell.nix` file
cat > shell.nix << EOF
let
pkgs = import <nixpkgs> {};
in
pkgs.stdenv.mkDerivation {
name = "my-env";
buildInputs =
[
pkgs.curl
pkgs.jq
pkgs.entr
pkgs.ephemeralpg
];
}
EOF
# Run `nix-shell`
nix-shell
# Have all dependencies that we need ready to go on your $PATH! 🎉
而已!
如上所述安装 Nix 是对系统的相对侵入性更改,例如,您会在根文件系统中找到一个新的“/nix/”目录。我建议先在虚拟机或 Docker 容器中尝试一下。
我想我有必要多解释一下这里发生的事情以及如何使用它。
我们在本例中使用的 Nix 生态系统的一部分
我们正在使用 Nix 生态系统的三个部分。
1/3:Nix 编程语言
这是一种非常小的语言,语法可以在一页上描述。如果您熟悉函数式编程语言(尤其是来自 ML 家族、Haskell、Elm),那么您就对了。如果你不是,它可能看起来很陌生和限制性(没有循环?!)。
Nix 语言中的所有内容都是表达式,也就是说,您编写的所有内容都会计算为一个值。该语言大多是纯粹的:大多数函数调用没有副作用,并且对于相同的输入总是返回相同的结果。剩余的杂质主要由加密哈希处理。基于此,任何 Nix 表达式的结果都是高度可重现的。
2/3:Nix 软件包存储库
Nixpkgs是一个巨大但组织良好的 Nix 表达式,它定义了如何构建超过 40,000 个软件包,包括_所有_它们的依赖项。目前总共超过 17 mb。
Nix 编程语言通过懒惰并仅评估您当前需要的部分来继续高效地处理这个巨大的表达式。
Nixpkgs 是您在使用 Nix 时可能遇到的许多复杂性的来源。mkDerivation
、callPackage
等抽象非常适合消除重复并保持 Nixpkgs 可维护,但它们的抽象可能很难理解。想太多其中一些会让我头疼。
3/3:nix-shell
实用程序
nix-shell
二进制文件从文件加载 Nix 表达式(默认情况下从文件shell.nix
或,如果该文件不存在,则从default.nix
),评估该表达式,然后将我们放入一个 shell 中,其中所有依赖项都在表达式中定义(神奇地)可用。
更多解释和细节
话虽如此,让我们更详细地看一下上面的例子。
shell.nix
文件解释
让我们再看一下我们之前创建的shell.nix
文件:
let
pkgs = import <nixpkgs> {};
in
pkgs.stdenv.mkDerivation {
name = "my-env";
buildInputs =
[
pkgs.curl
pkgs.jq
pkgs.entr
pkgs.ephemeralpg
];
}
let [DEFINITIONS] in [EXPRESSION]
表达式允许我们在let
之后的第一部分中定义局部范围内的变量,然后使用该范围计算in
之后的第二部分。因此,在第一部分中定义的变量pkgs
可以在第二部分中使用。
第二行的import <nixpkgs> {}
是我们如何导入默认情况下在我们的 Nix 安装中可用的 Nixpkgs 表达式。我将在另一篇文章中解释如何将 Nixpkgs“固定”到一个特定的、可重现的版本。
pkgs.stdenv.mkDerivation
是一个复杂函数,它在整个 Nixpkgs 中用于定义如何构建不同的包。我们现在需要知道的只是它接受一个集合({...}
)作为参数并返回一个推导(用 Nix 的话来说就是“可以构建的东西”)。如果我们在nix-shell
中运行这个表达式,构建输入将能够在我们的路径上。
对于name = "my-env";
,我们将用作参数的集合的name
属性设置为mkDerivation
。我们放什么并不重要。
buildInputs
是最重要的部分:我们将其设置为我们希望在nix-shell
中可用的派生列表。列表中的项目由空格分隔。我们只使用pkgs
值中的属性。请记住,pkgs
是由导入巨大的 Nixpkgs Nix 表达式产生的,因此它的属性中包含所有超过 40,000 个包。我们只挑选合适的。
在 Nixpkgs 上找到正确的属性并不总是那么简单,但是在谷歌上搜索一下就会有很长的路要走。例如,Google 告诉我pg_tmp
是 Nix 中ephemeralpg
包的一部分。
nix-shell
实用程序解释
当我们运行nix-shell
时,会发生很多奇妙的魔法。让我们来看看它执行的最重要的步骤:
1.nix-shell
会在当前目录中查找shell.nix
的文件,在我们的例子中立即找到。
-
它评估文件中包含的 Nix 表达式。在大多数情况下,表达式非常庞大,因为正在导入包含超过 40,000 个包的整个 Nixpkgs 表达式。请记住,Nix 仅通过评估我们实际需要的部分来保持这种合理的效率。
-
现在 Nix 表达式已经被求值,
nix-shell
确切地知道需要哪些依赖项。如果已经构建了各个依赖项,它会检查位于/nix/store
目录中的本地缓存。如果没有,它将尝试从位于cache.nixos.org
的二进制缓存中获取它们。只有作为最后的手段,它才会实际构建所需的包本身。
4、当所有的依赖都成功缓存到/nix/store
中后,nix-shell
将一个$PATH
的环境变量放在一起,将所有请求的依赖绑定在一起。然后它使用该路径集启动一个新的 shell。
如果您只想运行单个命令而不是将其放入 shell,则可以使用--run
标志。例如,nix-shell --run "curl http://google.com/"
将使用 Nix 安装的 curl 版本查询 Google。
一旦您再次使用Ctrl-D
离开 Nix shell,nix-shell
对我们的环境所做的任何更改都将消失。看起来我们定义为依赖项的entr
包从未安装,除了/nix/store
中的缓存二进制文件。后者可以用nix-collect-garbage
清理或保留以便下次更快启动nix-shell
。
更多
恭喜你在 Nix 的第一次试鞋中幸存下来!通过上面的示例,您已经有了一个可用的第一部分,可以很容易地用额外的包进行扩展。在即将发布的文章中,我想向您展示如何固定您的 Nixpkgs 版本,以使您的依赖项可跨时间和不同系统重现!
更多推荐
所有评论(0)