Linux中國

我的一些 nix 學習經驗:安裝和打包

最近,我首次嘗試了 Mac。直至現在,我注意到的最大缺點是其軟體包管理比 Linux 差很多。一段時間以來,我對於 homebrew 感到相當不滿,因為每次我安裝新的軟體包時,它大部分時間都花在了升級上。於是,我萌生了試試 nix 包管理器的想法!

公認的,nix 的使用存在一定困惑性(甚至它有自己單獨的編程語言!),因此,我一直在努力以最簡潔的方式掌握使用 nix,避開複雜的配置文件管理和新編程語言學習。以下是我至今為止學習到的內容, 敬請期待如何進行:

  • 使用 nix 安裝軟體包
  • 為一個名為 paperjam 的 C++ 程序構建一個自定義的 nix 包
  • 用 nix 安裝五年前的 hugo 版本

如同以往,由於我對 nix 的了解還停留在入門階段,本篇文章可能存在一些表述不準確的地方。甚至我自己也對於我是否真的喜歡上 nix 感到模稜兩可 —— 它的使用真的讓人相當困惑!但是,它幫我成功編譯了一些以前總是難以編譯的軟體,並且通常來說,它比 homebrew 的安裝速度要快。

nix 為何引人關注?

通常,人們把 nix 定義為一種「聲明式的包管理」。儘管我對此並不太感興趣,但以下是我對 nix 的兩個主要欣賞之處:

  • 它提供了二進位包(託管在 https://cache.nixos.org/ 上),你可以迅速下載並安裝
  • 對於那些沒有二進位包的軟體,nix 使編譯它們變得更容易

我認為 nix 之所以擅長於編譯軟體,主要有以下兩個原因:

  • 在你的系統中,可以安裝同一庫或程序的多個版本(例如,你可能有兩個不同版本的 libc)。舉個例子,我當前的計算機上就存在兩個版本的 node,一個位於 /nix/store/4ykq0lpvmskdlhrvz1j3kwslgc6c7pnv-nodejs-16.17.1,另一個位於 /nix/store/5y4bd2r99zhdbir95w5pf51bwfg37bwa-nodejs-18.9.1
  • 除此之外,nix 在構建包時是在隔離的環境下進行的,只使用你明確聲明的依賴項的特定版本。因此,你無需擔心這個包可能依賴於你的系統里的其它你並不了解的包,再也不用與 LD_LIBRARY_PATH 戰鬥了!許多人投入了大量工作,來列出所有包的依賴項。

在本文後面,我將給出兩個例子,展示 nix 如何使我在編譯軟體時遇到了更小的困難。

我是如何開始使用 nix 的

下面是我開始使用 nix 的步驟:

  • 安裝 nix。我忘記了我當時是如何做到這一點,但看起來有一個官方安裝程序 和一個來自 zero-to-nix.com非官方安裝程序。在 MacOS 上使用標準的多用戶安裝卸載 nix 的 教程 有點複雜,所以選擇一個卸載教程更為簡單的安裝方法可能值得。
  • ~/.nix-profile/bin 添加到我的 PATH
  • nix-env -iA nixpkgs.NAME 命令安裝包
  • 就是這樣。

基本上,是把 nix-env -iA 當作 brew install 或者 apt-get install

例如,如果我想安裝 fish,我可以這樣做:

nix-env -iA nixpkgs.fish

這看起來就像是從 https://cache.nixos.org 下載一些二進位文件 - 非常簡單。

有些人使用 nix 來安裝他們的 Node 和 Python 和 Ruby 包,但我並沒有那樣做 —— 我仍然像我以前一樣使用 npm installpip install

一些我沒有使用的 nix 功能

有一些 nix 功能/工具我並沒有使用,但我要提及一下。我最初認為你必須使用這些功能才能使用 nix,因為我讀過的大部分 nix 教程都討論了它們。但事實證明,你並不一定要使用它們。

我不去深入討論它們,因為我並沒真正使用過它們,而且網上已經有很多詳解。

安裝軟體包

nix 包在哪裡定義的?

我認為 nix 包主倉庫中的包是定義在 https://github.com/NixOS/nixpkgs/

你可以在 https://search.nixos.org/packages 查找包。似乎有兩種官方推薦的查找包的方式:

  • nix-env -qaP NAME,但這非常緩慢,並且我並沒有得到期望的結果
  • nix --extra-experimental-features 'nix-command flakes' search nixpkgs NAME,這倒是管用,但顯得有點兒冗長。並且,無論何種原因,它輸出的所有包都以 legacyPackages 開頭

我找到了一種我更喜歡的從命令行搜索 nix 包的方式:

  • 運行 nix-env -qa '*' > nix-packages.txt 獲取 Nix 倉庫中所有包的列表
  • 編寫一個簡潔的 nix-search 腳本,僅在 packages.txt 中進行 grep 操作(cat ~/bin/nix-packages.txt | awk '{print $1}' | rg "$1"

所有的東西都是通過符號鏈接來安裝的

nix 的一個主要設計是,沒有一個單一的 bin 文件夾來存放所有的包,而是使用了符號鏈接。有許多層的符號鏈接。比如,以下就是一些符號鏈接的例子:

  • 我機器上的 ~/.nix-profile 最終是一個到 /nix/var/nix/profiles/per-user/bork/profile-111-link/ 的鏈接
  • ~/.nix-profile/bin/fish 是到 /nix/store/afkwn6k8p8g97jiqgx9nd26503s35mgi-fish-3.5.1/bin/fish 的鏈接

當我安裝某樣東西的時候,它會創建一個新的 profile-112-link 目錄並建立新的鏈接,並且更新我的 ~/.nix-profile 使其指向那個目錄。

我認為,這意味著如果我安裝了新版本的 fish 但我並不滿意,我可以很容易地退回先前的版本,只需運行 nix-env --rollback,這樣就可以讓我回到之前的配置文件目錄了。

卸載包並不意味著刪除它們

如果我像這樣卸載 nix 包,實際上並不會釋放任何硬碟空間,而僅僅是移除了符號鏈接:

$ nix-env --uninstall oil

我尚不清楚如何徹底刪除包 - 我試著運行了如下的垃圾收集命令,這似乎刪除了一些項目:

$ nix-collect-garbage
...
85 store paths deleted, 74.90 MiB freed

然而,我系統上仍然存在 oil 包,在 /nix/store/8pjnk6jr54z77jiq5g2dbx8887dnxbda-oil-0.14.0

nix-collect-garbage 有一個更具攻擊性的版本,它也會刪除你配置文件的舊版本(這樣你就不能回滾了)。

$ nix-collect-garbage -d --delete-old

儘管如此,上述命令仍無法刪除 /nix/store/8pjnk6jr54z77jiq5g2dbx8887dnxbda-oil-0.14.0,我不明白原因。

升級過程

你可以通過以下的方式升級 nix 包:

nix-channel --update
nix-env --upgrade

(這與 apt-get update && apt-get upgrade 類似。)

我還沒真正嘗試升級任何東西。我推測,如果升級過程中出現任何問題,我可以通過以下方式輕鬆地回滾(因為在 nix 中,所有事物都是不可變的!):

nix-env --rollback

有人向我推薦了 Ian Henry 的 這篇文章,該文章討論了 nix-env --upgrade 的一些令人困惑的問題 - 也許它並不總是如我們所料?因此,我會對升級保持警惕。

下一個目標:創建名為 paperjam 的自定義包

經過幾個月使用現有的 nix 包後,我開始考慮製作自定義包,對象是一個名為 paperjam 的程序,它還沒有被打包封裝。

實際上,因為我系統上的 libiconv 版本不正確,我甚至在沒有 nix 的情況下也遇到了編譯 paperjam 的困難。我認為,儘管我還不懂如何製作 nix 包,但使用 nix 來編譯它可能會更為簡單。結果證明我的想法是對的!

然而,理清如何實現這個目標的過程相當複雜,因此我在這裡寫下了一些我實現它的方式和步驟。

構建示例包的步驟

在我著手製作 paperjam 自定義包之前,我想先試手構建一個已存在的示例包,以便確保我已經理解了構建包的整個流程。這個任務曾令我頭痛不已,但在我在 Discord 提問之後,有人向我闡述了如何從 https://github.com/NixOS/nixpkgs/ 獲取一個可執行的包並進行構建。以下是操作步驟:

步驟 1: 從 GitHub 的 nixpkgs 下載任意一個包,以 dash 包為例:

wget https://raw.githubusercontent.com/NixOS/nixpkgs/47993510dcb7713a29591517cb6ce682cc40f0ca/pkgs/shells/dash/default.nix -O dash.nix

步驟 2:with import <nixpkgs> {}; 替換開頭的聲明({ lib , stdenv , buildPackages , autoreconfHook , pkg-config , fetchurl , fetchpatch , libedit , runCommand , dash }:)。我不清楚為何需要這樣做,但事實證明這麼做是有效的。

步驟 3: 運行 nix-build dash.nix

這將開始編譯該包。

步驟 4: 運行 nix-env -i -f dash.nix

這會將該包安裝到我的 ~/.nix-profile 目錄下。

就這麼簡單!一旦我完成了這些步驟,我便感覺自己能夠逐步修改 dash 包,進一步創建屬於我自己的包了。

製作自定義包的過程

因為 paperjam 依賴於 libpaper,而 libpaper 還沒有打包,所以我首先需要構建 libpaper 包。

以下是 libpaper.nix,我基本上是從 nixpkgs 倉庫中其他包的源碼中複製粘貼得到的。我猜測這裡的原理是,nix 對如何編譯 C 包有一些默認規則,例如 「運行 make install」,所以 make install 實際上是默認執行的,並且我並不需要明確地去配置它。

with import <nixpkgs> {};

stdenv.mkDerivation rec {
  pname = "libpaper";
  version = "0.1";

  src = fetchFromGitHub {
    owner = "naota";
    repo = "libpaper";
    rev = "51ca11ec543f2828672d15e4e77b92619b497ccd";
    hash = "sha256-S1pzVQ/ceNsx0vGmzdDWw2TjPVLiRgzR4edFblWsekY=";
  };

  buildInputs = [ ];

  meta = with lib; {
    homepage = "https://github.com/naota/libpaper";
    description = "libpaper";
    platforms = platforms.unix;
    license = with licenses; [ bsd3 gpl2 ];
  };
}

這個腳本基本上告訴 nix 如何從 GitHub 下載源代碼。

我通過運行 nix-build libpaper.nix 來構建它。

接下來,我需要編譯 paperjam。我製作的 nix 包 的鏈接在這裡。除了告訴它從哪裡下載源碼外,我需要做的主要事情有:

  • 添加一些額外的構建依賴項(像 asciidoc
  • 在安裝過程中設置一些環境變數(installFlags = [ "PREFIX=$(out)" ];),這樣它就會被安裝在正確的目錄,而不是 /usr/local/bin

我首先從散列值為空開始,然後運行 nix-build 以獲取一個關於散列值不匹配的錯誤信息。然後我從錯誤信息中複製出正確的散列值。

我只是在 nixpkgs 倉庫中運行 rg PREFIX 來找出如何設置 installFlags 的 —— 我認為設置 PREFIX 應該是很常見的操作,可能之前已經有人做過了,事實證明我的想法是對的。所以我只是從其他包中複製粘貼了那部分代碼。

然後我執行了:

nix-build paperjam.nix
nix-env -i -f paperjam.nix

然後所有的東西都開始工作了,我成功地安裝了 paperjam!耶!

下一個目標:安裝一個五年前的 Hugo 版本

當前,我使用的是 2018 年的 Hugo 0.40 版本來構建我的博客。由於我並不需要任何的新功能,因此我並沒有感到有升級的必要。對於在 Linux 上操作,這個過程非常簡單:Hugo 的發行版本是靜態二進位文件,這意味著我可以直接從 發布頁面 下載五年前的二進位文件並運行。真的很方便!

但在我的 Mac 電腦上,我遇到了一些複雜的情況。過去五年中,Mac 的硬體已經發生了一些變化,因此我下載的 Mac 版 Hugo 二進位文件並不能運行。同時,我嘗試使用 go build 從源代碼編譯,但由於在過去的五年內 Go 的構建規則也有所改變,因此沒有成功。

我曾試圖通過在 Linux docker 容器中運行 Hugo 來解決這個問題,但我並不太喜歡這個方法:儘管可以工作,但它運行得有些慢,而且我個人感覺這樣做有些多餘。畢竟,編譯一個 Go 程序不應該那麼麻煩!

幸好,Nix 來救援!接下來,我將介紹我是如何使用 nix 來安裝舊版本的 Hugo。

使用 nix 安裝 Hugo 0.40 版本

我的目標是安裝 Hugo 0.40,並將其添加到我的 PATH 中,以 hugo-0.40 作為命名。以下是我實現此目標的步驟。儘管我採取了一種相對特殊的方式進行操作,但是效果不錯(可以參考 搜索和安裝舊版本的 Nix 包 來找到可能更常規的方法)。

步驟 1: 在 nixpkgs 倉庫中搜索找到 Hugo 0.40。

我在此鏈接中找到了相應的 .nix 文件 https://github.com/NixOS/nixpkgs/blob/17b2ef2/pkgs/applications/misc/hugo/default.nix

步驟 2: 下載該文件並進行構建。

我下載了帶有 .nix 擴展名的文件(以及同一目錄下的另一個名為 deps.nix 的文件),將文件的首行替換為 with import <nixpkgs> {};,然後使用 nix-build hugo.nix 進行構建。

雖然這個過程幾乎無需進行修改就能成功運行,但我仍然做了兩處小調整:

  • with stdenv.lib 替換為 with lib
  • 為避免與我已安裝的其他版本的 hugo 衝突,我把包名改為了 hugo040

步驟 3:hugo 重命名為 hugo-0.40

我編寫了一個簡短的後安裝腳本,用以重命名 Hugo 二進位文件。

postInstall = &apos;&apos;
    mv $out/bin/hugo $out/bin/hugo-0.40
  &apos;&apos;;

我是通過在 nixpkgs 倉庫中運行 rg &apos;mv &apos; 命令,然後複製和修改一條看似相關的代碼片段來找到如何實施此步驟。

步驟 4: 安裝。

我通過運行 nix-env -i -f hugo.nix 命令,將 Hugo 安裝到了 ~/.nix-profile/bin 目錄中。

所有的步驟都順利運行了!我把最終的 .nix 文件存放到了我自己的 nixpkgs 倉庫 中,這樣我以後如果需要,就能再次使用它了。

可重複的構建過程並非神秘,其實它們極其複雜

我覺得值得一提的是,這個 hugo.nix 文件並不是什麼魔法——我之所以能在今天輕易地編譯 Hugo 0.40,完全歸功於許多人長期以來的付出,他們讓 Hugo 的這個版本得以以可重複的方式打包。

總結

安裝 paperjam 和這個五年前的 Hugo 版本過程驚人地順利,實際上比沒有 nix 來編譯它們更簡單。這是因為 nix 極大地方便了我使用正確的 libiconv 版本來編譯 paperjam 包,而且五年前就已經有人辛苦地列出了 Hugo 的確切依賴關係。

我並無計劃詳細深入地使用 nix(真的,我很可能對它感到困擾,然後最後選擇回歸使用 homebrew!),但我們將拭目以待!我發現,簡單入手然後按需逐步掌握更多功能,遠比一開始就全面接觸一堆複雜功能更容易掌握。

我可能不會在 Linux 上使用 nix —— 我一直都對 Debian 基礎發行版的 apt 和 Arch 基礎發行版的 pacman 感到滿意,它們策略明晰且少有混淆。而在 Mac 上,使用 nix 似乎會有所得。不過,誰知道呢!也許三個月後,我可能會對 nix 感到不滿然後再次選擇回歸使用 homebrew。

(題圖:MJ/f68aaf37-4a34-4643-b3a1-8728d49cf887)

via: https://jvns.ca/blog/2023/02/28/some-notes-on-using-nix/

作者:Julia Evans 選題:lkxed 譯者:ChatGPT 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國