tob*_*ora 16 packaging nixos nix
如何编写一个简单的推导来打包 nix 程序以及如何创建 PR 将其包含在 nixpkgs 中?
(我写这篇文章是因为我找不到简单的解释)
tob*_*ora 40
注意:这个答案尚未完全完成,但它已经是一个很好的起点。我计划稍后添加更多特定于语言的内容(或者可能为每种语言创建一个问题,也保持这个答案\xe2\x80\xa6“”“合理地”“”简短)。
\n以下是一些参考:
\n通俗地说,推导是构建程序的秘诀。当你做饭时,你需要一些原料(又名 nix 中的来源和依赖项)以及一些将原料组合成蛋糕的步骤(又名programs\xe2\x80\xa6)。
\n让我们从一个简单的例子开始,这是我能想象到的最简单的 C 程序。您可以将其写入您喜欢的任何文件夹中的文件program.c
(对于本例):
#include <stdio.h>\n\nint main() {\n printf("Hello, World!\\n");\n return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n然后,我们需要告诉 nix 如何编译这个程序。所以创建一个文件derivation.nix
:
{ stdenv }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n src = ./.;\n\n nativeBuildInputs = [ ];\n buildInputs = [ ];\n\n buildPhase = \'\'\n gcc program.c -o myprogram\n \'\';\n\n installPhase = \'\'\n mkdir -p $out/bin\n cp myprogram $out/bin\n \'\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n这描述了一个函数,其中输入是依赖项(蛋糕的“成分”;这里仅stdenv
提供有用的实用程序),并输出一个推导,感谢stdenv.mkDerivation
:非正式地,您可以想象这个过程将输出一个包含所有内容的文件夹编译好的文件。我提供了mkDerivation
一些信息:
nativeBuildInputs
编译程序所需的依赖项(gcc
默认情况下始终包含\xe2\x80\xa6,因此您无需在此处指定任何内容)buildInputs
运行程序所需的依赖项(您通常会将库放在这里)buildPhase
构建程序的说明(它是一个 bash 脚本)。在此阶段开始时,您将被放入包含源代码的文件夹中installPhase
描述如何“安装”程序的说明(见下文)。实际上还有更多阶段(解压缩源、修补、配置\xe2\x80\xa6),但本示例不需要它们。
\ninstallPhase
?安装阶段在这里说明最终的可执行文件/库/资产/\xe2\x80\xa6 应该位于哪里。在典型的 Linux 环境中,二进制文件通常复制在/bin
、/usr/bin
或 中/usr/local/bin
,库在/lib
或中/lib64
,资产在/share
\xe2\x80\xa6 中,当所有程序将自己的东西放在同一个地方时,它很快就会变得一团糟。
在 Nix 中,所有程序在类似路径中都有自己的文件夹(该路径的值在 中/nix/store/someUniqueHash-programName-version
设置为),然后二进制文件转到,库转到,资产转到\xe2\x80\xa6 ,再现典型的 Linux 文件夹层次结构。因此,如果您不确定应该将文件放在哪里,您肯定需要检查将其放在普通 Linux 发行版中的位置,并在路径前面添加(很少有例外,就像我们使用而不是因为没有更多原因)有一个文件夹)。请注意,许多构建系统(cmake\xe2\x80\xa6)都有一个变量,例如表示程序应安装在哪里。:通常可能是or ,这会将二进制文件安装到等位置。在这种情况下,我们通常可以简单地设置并运行常用的编译命令。当您安装程序时,Nix 将正确创建指向已安装软件文件的链接,例如在 NixOs 中,全局安装的二进制文件链接在$out
installPhase
$out/bin
$out/lib
$out/share
$out/
$out/bin
$out/usr/local/bin
local
PREFIX
PREFIX
/
/usr/local
PREFIX/bin
PREFIX=$out
/run/current-system/sw/bin
{ stdenv }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n src = ./.;\n\n nativeBuildInputs = [ ];\n buildInputs = [ ];\n\n buildPhase = \'\'\n gcc program.c -o myprogram\n \'\';\n\n installPhase = \'\'\n mkdir -p $out/bin\n cp myprogram $out/bin\n \'\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n因此,在我们的示例中,在安装阶段我们只需要创建文件夹$out/bin
并复制复制阶段获得的二进制文件\xe2\x80\xa6 这正是我们所做的!
要尝试它,您仍然需要指定从哪里获取依赖项(同样,当您烹饪蛋糕时,您应该首先拜访您最喜欢的农民购买一些鸡蛋)。因此,创建另一个文件default.nix
(名称在这里很重要,因为nix
将首先查找该文件)包含
{ pkgs ? import <nixpkgs> {} }:\npkgs.callPackage ./derivation.nix {}\n
Run Code Online (Sandbox Code Playgroud)\n在这里,您基本上告诉 nix 使用通道<nixpkgs>
来获取依赖项,callPackage
并将正确填充derivation.nix
.
然后,只需运行
\n$ ls /run/current-system/sw/bin -al | grep firefox\nlrwxrwxrwx 1 root root 70 janv. 1 1970 firefox -> /nix/store/152drilm2qhjimzfx8mch0hmqvr27p29-firefox-99.0.1/bin/firefox\n
Run Code Online (Sandbox Code Playgroud)\n最后,您应该有一个新文件夹result
,并且该文件夹链接到$out
您的派生文件夹:
{ pkgs ? import <nixpkgs> {} }:\npkgs.callPackage ./derivation.nix {}\n
Run Code Online (Sandbox Code Playgroud)\n然后您可以使用以下命令执行二进制文件:
\n$ nix-build\n
Run Code Online (Sandbox Code Playgroud)\n恭喜,您完成了第一次推导!
\n下面我们将看到如何打包更复杂的应用程序。但在此之前,让我们看看如何安装该软件包并为 nixpkgs 做出贡献。
\n您当然可以安装这个派生。复制您的文件(除非default.nix
不需要)并将/etc/nixos
已安装软件包的列表更改为:
environment.systemPackages = with pkgs; [\n (callPackage ./derivation.nix {})\n]\n
Run Code Online (Sandbox Code Playgroud)\n干得好!
\n您还可以使用命令式将其安装在任何系统上
\n$ ls -al | grep result\nlrwxrwxrwx 1 leo users 55 sept. 13 20:59 result -> /nix/store/xi0hx472hzykl6xjw0hnmh0zjyp6sc52-program-1.0\n
Run Code Online (Sandbox Code Playgroud)\nnixpkgs 项目中的所有包表达式都位于https://github.com/NixOS/nixpkgs中,您可以在那里添加自己的包!为此,首先分叉(以执行拉取请求)并克隆您的存储库。然后复制进去,其中derivation.nix
根据你的程序的应用范围适当选择,就是你的程序的名称。pkgs/CATEGORY/PACKAGE/default.nix
CATEGORY
PACKAGE
当然,nixpkgs 存储库不包含程序的源代码,因此您应该更改源属性以指向外部源(见下文)。
\n然后,nixpkgs 中可用的所有程序的列表位于,pkgs/top-level/all-packages.nix
因此您应该添加一行:
myprogram = callPackage ../CATEGORY/PACKAGE { };\n
Run Code Online (Sandbox Code Playgroud)\n在此文件中(程序按字母顺序排序)。要测试它,请转到存储库的根目录并调用
\n$ ./result/bin/myprogram\nHello, World!\n
Run Code Online (Sandbox Code Playgroud)\n它应该像以前一样编译您的程序并创建一个result
文件夹来测试它。
完成后,提交您的工作并将其作为拉取请求提交!
\n如果您不熟悉 git 或想要更多详细信息,您可能会喜欢这个线程https://discourse.nixos.org/t/how-to-find-needed-librarys-for-lined-source-bin-applications/39118 /43?u=托比亚斯博拉
\n大多数时候,您会尝试下载在线托管的源。没问题,只需更改您的src
属性,例如,如果您从 github 下载(请参阅此处的获取器列表):
{ stdenv, lib, fetchFromGitHub }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n # For https://github.com/myuser/myexample\n src = fetchFromGitHub {\n owner = "myuser";\n repo = "myexample";\n rev = "v${version}"; # If there is a release like v1.0, otherwise put the commit directly \n sha256 = ""; # <-- dummy hash: after the first compilation this line will give an error and the correct hash. Replace lib.fakeSha256 with "givenhash". Or use nix-prefetch-git. On older nix, this might fail, use sha256 = lib.fakeSha256; instead.\n };\n\n buildPhase = \'\'\n gcc program.c -o myprogram\n \'\';\n\n installPhase = \'\'\n mkdir -p $out/bin\n cp myprogram $out/bin\n \'\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n确保sha256
使用您自己的哈希更改该行(需要验证下载的文件是否正确)。lib.fakeSha256
是一个虚拟哈希,因此第一次编译时会给出错误,指出哈希是错误的,而它是truehash
。因此,用这个值替换哈希(还有类似的工具nix-prefetch-git
,但我不得不承认我不使用它们)。警告:如果您使用缓存中已有的另一个程序的哈希值,它不会给出任何错误,而是会很好地峰值另一个包的源!
另请注意,nix 会自动尝试对源代码执行正确的操作,特别是它会自动解压下载的压缩文件
\n src = fetchurl {\n url = "http://example.org/libfoo-source-${version}.tar.bz2";\n sha256 = "0x2g1jqygyr5wiwg4ma1nd7w4ydpy82z9gkcv8vh2v8dn3y58v5m";\n };\n
Run Code Online (Sandbox Code Playgroud)\n现在,让我们使用一个库(本示例中的 ncurses)使程序稍微复杂一些。我们将使用ncurses
hello-world 程序:
environment.systemPackages = with pkgs; [\n (callPackage ./derivation.nix {})\n]\n
Run Code Online (Sandbox Code Playgroud)\n如果你像上面那样直接编译这个程序,你会得到一个错误
\nprogram.c:1:10: fatal error: ncurses.h: No such file or directory\n
Run Code Online (Sandbox Code Playgroud)\n这是预期的,因为我们没有添加 ncurses 作为依赖项。为此,请在(空格分隔)列表中添加buildInputs
库ncurses
(您还必须将其添加到输入依赖项的第一行中):这样做将确保buildInputs
编译器搜索的程序的二进制文件可用子目录\xe2\x80\xa6中的头文件include
同时更新编译命令-lncurses
:
{ stdenv, ncurses }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n src = ./.;\n\n buildInputs = [\n ncurses\n ];\n\n buildPhase = \'\'\n gcc -lncurses program.c -o myprogram\n \'\';\n\n installPhase = \'\'\n mkdir -p $out/bin\n cp myprogram $out/bin\n \'\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n像以前一样编译并运行程序,就是这样!
\nnix-shell
nix-build
有时使用as来调试程序可能会很烦人nix-build
,因为它不会缓存编译:每次失败时,它都会在下一次从头开始编译(这是确保可重复性所必需的)。然而在实践中这可能有点烦人\xe2\x80\xa6 nix-shell
的创建(也)是为了解决这个问题。如果您运行gcc
命令来编译上述文件,它将直接失败,因为 gcc 和 ncurses 库未全局安装(并且它是一个功能,例如它允许多个项目使用同一库的不同版本)。要创建安装此程序的 shell,只需运行nix-shell
,它会自动检查程序的依赖项:
$ nix-env -i -f default.nix\n
Run Code Online (Sandbox Code Playgroud)\n稍后我们将看到 的更高级用法nix-shell
。
编译程序的方法通常是相同的,因为许多程序都是简单地使用以下方式编译的:
\n myprogram = callPackage ../CATEGORY/PACKAGE { };\n
Run Code Online (Sandbox Code Playgroud)\n因此,nix 默认情况下会尝试上述命令(当它尝试修补时,还会尝试更多命令,test\xe2\x80\xa6),这就是为什么 nixpkgs 中的许多程序实际上并不需要编写任何阶段。
\n大多数阶段都是真正可配置的:例如,您可以启用/禁用阶段的某些部分,提供一些参数,例如makeFlags = [ "PREFIX=$(out)" ];
向 makefile\xe2\x80\xa6 添加标志。这些阶段的完整文档在手册中提供,更多具体在本小节中。如果您确实想检查正在运行的内容,可以检查genericBuild
文件pkgs/stdenv/generic/setup.sh中的函数,该函数调用文件中上面写入的默认阶段,除非它们被派生覆盖。您还可以直接从 中读取使用的代码,nix-shell
我们稍后会看到。
请注意,这些默认阶段也可以被依赖项覆盖。例如,如果您的程序使用cmake
,添加nativeBuildInputs = [ cmake ];
将自动调整配置阶段以使用 cmake (也可以按照此处记录的方式进行配置)。scons、ninja、meson\xe2\x80\xa6 也会发生类似的行为。更一般地,nix 定义了许多“钩子”,这些“钩子”将在给定阶段之前或之后运行,以便修改构建过程。只要将它们包含在内nativeBuildInputs
就足以触发它们。大多数钩子都记录在此处,其中包括:
autoPatchelfHook
自动修补(通常是专有的)二进制文件,使它们可以在 nix 中使用(另请参阅我的其他答案)例如,我们可以在 (ncurse) 程序中使用 CMake,如下所示:创建一个CMakeLists.txt
包含常用 cmake 规则的文件来编译程序:
cmake_minimum_required(VERSION 3.10)\n\n# set the project name\nproject(myprogram)\n\n# Configure curses as a dependency\nfind_package(Curses REQUIRED)\ninclude_directories(${CURSES_INCLUDE_DIR})\n\n# add the executable\nadd_executable(myprogram program.c)\n\n# Link the curses library\ntarget_link_libraries(myprogram ${CURSES_LIBRARIES})\n\n# Explains how to install the program\ninstall(TARGETS myprogram DESTINATION bin)\n
Run Code Online (Sandbox Code Playgroud)\n现在可以大大简化我们的derivation.nix
:
{ stdenv, ncurses, cmake }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n src = ./.;\n\n buildInputs = [\n ncurses\n cmake\n ];\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n注意:本节不是理解其余部分所必需的,您可以安全地跳过它。
\n我们在上面看到了如何nix-shell
将我们放入具有所有必需依赖项的 shell 中,以通过利用缓存来节省编译时间。在这个 shell 中,当然可以像以前一样运行常用命令来编译程序,但有时最好运行与 nix 构建器运行的命令完全相同的命令。
这也是了解更多有关 nix 内部结构的机会(我们还参考Nix 药丸以获取更多详细信息和wiki)。当您编写派生时,nix 将从中派生出一个.drv
文件,该文件以简单的 json 格式解释如何构建包。
要查看该文件,您可以运行:
\n$ nix-build -A myprogram\n
Run Code Online (Sandbox Code Playgroud)\n确切的输出并不重要,但请注意有几个重要部分:首先,派生指定输出文件夹、源和依赖项、构建期间可用的一些环境变量以及 nix-shell 自动填充的我们:看到了吗"out": \xe2\x80\xa6
?由于以下原因,您已经正确配置了它nix-shell
:
{ stdenv, lib, fetchFromGitHub }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n # For https://github.com/myuser/myexample\n src = fetchFromGitHub {\n owner = "myuser";\n repo = "myexample";\n rev = "v${version}"; # If there is a release like v1.0, otherwise put the commit directly \n sha256 = ""; # <-- dummy hash: after the first compilation this line will give an error and the correct hash. Replace lib.fakeSha256 with "givenhash". Or use nix-prefetch-git. On older nix, this might fail, use sha256 = lib.fakeSha256; instead.\n };\n\n buildPhase = \'\'\n gcc program.c -o myprogram\n \'\';\n\n installPhase = \'\'\n mkdir -p $out/bin\n cp myprogram $out/bin\n \'\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n更重要的是这些行:
\n src = fetchurl {\n url = "http://example.org/libfoo-source-${version}.tar.bz2";\n sha256 = "0x2g1jqygyr5wiwg4ma1nd7w4ydpy82z9gkcv8vh2v8dn3y58v5m";\n };\n
Run Code Online (Sandbox Code Playgroud)\n这意味着要生成输出,nix 将简单地使用/nix/store/\xe2\x80\xa6/bin/bash
参数运行构建器(这里只是 bash 解释器)-e /nix/store/9krlzvny65gdc8s7kpb6lkx8cd02c25b-default-builder.sh
这个文件非常简单:
\n#include <ncurses.h>\n\nint main(int argc, char ** argv)\n{\n initscr(); // init screen and sets up screen\n printw("Hello World"); // print to screen\n refresh(); // refreshes the screen\n getch(); // pause the screen output\n endwin(); // deallocates memory and ends ncurses\n return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n如果你输入
\nprogram.c:1:10: fatal error: ncurses.h: No such file or directory\n
Run Code Online (Sandbox Code Playgroud)\n你会发现它完全等于您将意识到它与配置默认阶段的pkgs/stdenv/generic/setup.sh
\n因此,在 中nix-shell
,您可以使用类似的东西一次运行所有阶段(创建不同的$out
文件夹允许您不以只读方式写入/nix/store
):
{ stdenv, ncurses }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n src = ./.;\n\n buildInputs = [\n ncurses\n ];\n\n buildPhase = \'\'\n gcc -lncurses program.c -o myprogram\n \'\';\n\n installPhase = \'\'\n mkdir -p $out/bin\n cp myprogram $out/bin\n \'\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n您还可以通过将最后一行替换为以下内容来指定要运行的几个阶段:
\n$ nix-shell\n$ gcc -lncurses program.c -o myprogram\n$ ./myprogram\n
Run Code Online (Sandbox Code Playgroud)\n要获取阶段列表,您可以执行以下操作:
\n$ ./configure --prefix=$out\n$ make\n$ make install\n
Run Code Online (Sandbox Code Playgroud)\n如果它为空,则默认值例如通过
\n$ typeset -f genericBuild | grep \'phases=\'\nphases="${prePhases:-} unpackPhase patchPhase ${preConfigurePhases:-} configurePhase ${preBuildPhases:-} buildPhase checkPhase ${preInstallPhases:-} installPhase ${preFixupPhases:-} fixupPhase installCheckPhase ${preDistPhases:-} distPhase ${postPhases:-}"\n
Run Code Online (Sandbox Code Playgroud)\n上面提供的说明当然适用于许多语言和情况,但某些语言提供了一些其他工具来处理环境变量和依赖项方面的自身要求(例如,我们不能真正使用pip
来安装 python 依赖项)。很难在此页面上列出所有现有语言,因此这里有一些需要遵循的通用建议:
rg
(更好的 grep)在本地副本中搜索,以使用您想要使用的工具查找派生。不过,为了简单起见,我将在下面列出一些您可能经常遇到的情况。
\n我已经在这里做了相当广泛的回答。您当然对解决方案 4 (autoPatchElf) 或 5-6 (buildFHSUserEnv)\xe2\x80\xa6 感兴趣 基本上将您的二进制文件复制到$out/bin
,如果您幸运的话添加autoPatchelfHook
应该nativeBuildInputs
足够了(如果程序有资产,您也可以复制它并$out/opt
放入$out/bin
一些调用 ) 中的程序的链接或脚本$out/opt
。
让我们考虑一下该文件myshellscript.sh
:
#!/usr/bin/bash\n\necho "Hello, world"\n
Run Code Online (Sandbox Code Playgroud)\n只需使用
\n{ stdenv }:\nstdenv.mkDerivation rec {\n name = "program-${version}";\n version = "1.0";\n\n src = ./.;\n\n installPhase = \'\'\n mkdir -p $out/bin\n cp myshellscript.sh $out/bin\n chmod +x $out/bin/myshellscript.sh # not needed if the file is already executable\n \'\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n并且 bash 脚本将通过修复阶段默认存在的patchShebangsAuto
钩子自动进行修补。
进一步阅读以了解如何使用平凡的构建器使这个推导变得更小!
\n假设我们的包需要一些可执行文件才能工作,例如cowsay
. 因为 nix 试图保持包之间的“隐秘性”(又名纯度),以限制冲突,正如这里所解释的那样(也许不同的程序需要不同版本的cowsay
),所以您不能假设
归档时间: |
|
查看次数: |
12278 次 |
最近记录: |