概要
nixpkgsには現在80,000を超えるパッケージが登録されていますが、ArchLinuxと比べて特定の分野でかつ利用者が少ないソフトやAsusやRazerなどベンダー固有の設定をするユーティリティなどはまだ十分とは言い難い状況です。
このとき自分でbuildしてpathを通すことになりますが、nixではbuild手順の提供とnixpkgs、NixOSを拡張できるテンプレートが用意されているため比較的簡単に自前パッケージを取り組むことができます。
ここでは、ビルド手順の自動化からFlakesを用いるかつパッケージバージョンの自動追尾までを順に解説します。
ToDO
- GitHub Action+nvfetcherでのパッケージ更新の自動追尾 (2022/10/29)
-
pythonやrustのパッケージの拡張の仕方 (2022/10/29)
-
buildPythonPackages
を使ってパッケージとして拡張するのでいい? -
pythonPackages
のoverlaysはできる?(このnixpkgs(awscli2)とか参考になる???)
-
パッケージ作製前に
-
nix 2.7
で廃止されたSchemeが多くある。(2022/03/07以前の書き方だと警告が出る)- ここのコードは新しいSchemeで書いているため
nix 2.6
以下では動かない - NixOS/Templatesも更新されていなくて新旧の書き方が混在している
- ここのコードは新しいSchemeで書いているため
-
Flakeを用いると
- プロジェクトのテンプレートを提供する
- 実行可能なプログラムであることを定義する
- 外部のgitリポジトリから依存関係を取り組むことができる
- パッケージのビルドと環境構築を同じリポジトリで管理できる
- nixpkgs、NixOSの拡張を簡単にできる
-
nixは複数言語のためのビルドツール及び環境作製ツールである(nixos.orgでも例はどう環境をつくるかが多い)
-
nixでパッケージを作るということは、1. どこかしらのリポジトリやダウンロードページからソースを取ってきて 2. 手順にそってビルドして 3. そのパッケージが入った環境に入る みたいな認識
- NixOS(nixパッケージマネージャー)はFilesystem Hierarchy Standard(FHS)に対応していない(
/lib
,/usr/bin
がない) - ビルド時に依存関係のパスを解決する必要がある→基本的に自動でやってくれる
- まれにパスを自分で明示してやる必要がある(Tipsにて簡単に説明)
- NixOS(nixパッケージマネージャー)はFilesystem Hierarchy Standard(FHS)に対応していない(
-
パッケージが作れればdockerコンテナのように自前の環境を作ることに応用できる(逆も然り)
-
nix build
(Flake)を用いる方法だと自前のパッケージをモジュールとして組み込みやすい
nixを用いたbuildおよびbuild環境の提供
nix-build
のための記法
- 書き方はnixpkgsのパッケージを見たほうがいい
- (先頭を
{pkgs ? (import <nixpkgs>) {} }: with pkgs; stdenv.mkDerivation{...}
に置き換える)
- (先頭を
- リファレンスはNixpkgs ManualやNixpkgs Users and Contributors Guide
- nix-tutorialもわかりやすい
- AURにパッケージがある場合、そのPKGBUILDをnixで読み替えれば大体書ける。(patchはいるかもしれないしいらないかもしれない)
- sha256はtarball→
nix-prefetch-url
、git→nix-prefetch-git
で得られる
gnugrepのビルド
{ pkgs ? (import <nixpkgs> {}) }:
let
version = "3.7";
in
with pkgs; stdenv.mkDerivation {
pname = "gnugrep"; # パッケージ情報
inherit version;
src = fetchurl { # ソースコードの取得
url = "mirror://gnu/grep/grep-${version}.tar.xz";
sha256 = "0g42svbc1nq5bamxfj6x7320wli4dlj86padk0hwgbk04hqxl42w";
};
nativeBuildInputs = [ perl ]; # ビルド時に必要なもの(コンパイラとか)
outputs = [ "out" "info" ]; # 分割ビルド、 $outはstore-path, $infoはmanとか
# pkgs/build-support/setup-hooks/multiple-outputs.shや
# Multiple-output packages: https://jtojnar.github.io/dumpling/#chap-multiple-outputを参照
buildInputs = [ pcre libiconv ]; # コンパイラ以外で実行時に必要なもの(システムパッケージとしてインストールされる)
doCheck = false;
preConfigure = '' # ビルドは各Phasesがありその中でPrePhase, Phase, PostPhaseなどがある
export MKDIR_P="mkdir -p" # https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases参照
'';
postInstall =
''
rm $out/bin/egrep $out/bin/fgrep
echo "#! /bin/sh" > $out/bin/egrep
echo "exec $out/bin/grep -E \"\$@\"" >> $out/bin/egrep
echo "#! /bin/sh" > $out/bin/fgrep
echo "exec $out/bin/grep -F \"\$@\"" >> $out/bin/fgrep
chmod +x $out/bin/egrep $out/bin/fgrep
'';
meta = with lib; { # meta情報、自前パッケージならhomepageとdescriptionとlicenseとplatformsがあればいい?
homepage = "https://www.gnu.org/software/grep/";
description = "GNU implementation of the Unix grep command";
longDescription = ''
The grep command searches one or more input files for lines
containing a match to a specified pattern. By default, grep
prints the matching lines.
'';
license = licenses.gpl3Plus;
platforms = platforms.all;
mainProgram = "grep";
};
passthru = {inherit pcre;};
}
Flakeを用いた書き方
- flake-utilsを使うとアーキテクチャ管理がしやすくなる
- 基本的にビルド部分はnix-buildで使える書き方と同じ
flake-utilsを使わない書き方
-
nix flake new --template templates/#c-hello ./c-hello
でプロジェクトを作る -
NixOS/Templetes:c-helloを
nix >= 2.7
の書き方に直した。
コードと結果
{
description = "Sample no using flake-utils";
# Nixpkgs / NixOS version to use.
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
outputs = { self, nixpkgs }:
let
# to work with older version of flakes
lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";
# Generate a user-friendly version number.
version = builtins.substring 0 8 lastModifiedDate;
# System types to support.
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Nixpkgs instantiated for supported system types.
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in
{
packages = forAllSystems (system:
let
pkgs = nixpkgsFor.${system};
in
with pkgs; {
hello = stdenv.mkDerivation rec {
name = "hello-${version}";
src = ./.;
nativeBuildInputs = [ autoreconfHook ];
};
});
};
}
$ nix flake show
path:/home/<name/Templates/samplepkgs/c-hello?lastModified=1666067866&narHash=sha256-2x+v9Wl9PjeeraiBChPiSdfD89cIkbLHsSYJv4Udgpw=
└───packages
├───aarch64-darwin
│ └───hello: package 'hello-20221018'
├───aarch64-linux
│ └───hello: package 'hello-20221018'
├───x86_64-darwin
│ └───hello: package 'hello-20221018'
└───x86_64-linux
└───hello: package 'hello-20221018'
複数パッケージある場合にデフォルトのパッケージを設定
- 指定なしで
nix build
やnix shell
を実行した際にビルドされる
コードと結果
-
rec
をつけて再帰的に変数を呼び出す- 自前パッケージは
<nixpkgs>
にないため、変数定義がされてないとなってエラーとなる
- 自前パッケージは
{
description = "Multi-package sample";
# Nixpkgs / NixOS version to use.
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
outputs = { self, nixpkgs }:
let
# to work with older version of flakes
lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";
# Generate a user-friendly version number.
version = builtins.substring 0 8 lastModifiedDate;
# System types to support.
supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
# Nixpkgs instantiated for supported system types.
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
in
{
packages = forAllSystems (system:
let
pkgs = nixpkgsFor.${system};
in
with pkgs; rec { # 再帰的に変数を呼び出す
c-hello = stdenv.mkDerivation {
name = "c-hello-${version}";
src = ./c-hello;
nativeBuildInputs = [ autoreconfHook ];
};
go-hello = buildGoModule {
pname = "go-hello";
inherit version;
src = ./go-hello;
vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
};
default = c-hello; # デフォルトパッケージの定義
}
);
};
}jj
$ nix flake show
# 各アーキテクチャーでdefaultが定義されている
path:/home/<name>/Templates/samplepkgs/sample-flakes?lastModified=1666072389&narHash=sha256-WcPrywIo3N596bZ1A0ZpoIcKnGIaogAsnP%2fxcuHzdhY=
└───packages
├───aarch64-darwin
│ ├───c-hello: package 'c-hello-20221018'
│ ├───default: package 'c-hello-20221018'
│ └───go-hello: package 'go-hello-20221018'
├───aarch64-linux
│ ├───c-hello: package 'c-hello-20221018'
│ ├───default: package 'c-hello-20221018'
│ └───go-hello: package 'go-hello-20221018'
├───x86_64-darwin
│ ├───c-hello: package 'c-hello-20221018'
│ ├───default: package 'c-hello-20221018'
│ └───go-hello: package 'go-hello-20221018'
└───x86_64-linux
├───c-hello: package 'c-hello-20221018'
├───default: package 'c-hello-20221018'
└───go-hello: package 'go-hello-20221018'
flake-utilsを使った方法
- apps周りなどちょっとスッキリ書ける
- アーキテクチャ・システムを指定しなくても主要なものは勝手に入れてくれる
-
flake-utils.lib.eachDefaultPackages ["x86_64-linux" "aarch64-linux"]
のように指定することも可能
-
コードと結果
{
description = "A sample with flake-utils";
# Nixpkgs / NixOS version to use.
inputs.nixpkgs.url = "nixpkgs/nixos-22.05";
# Using flake-utils
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
let
# to work with older version of flakes
lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101";
# Generate a user-friendly version number.
version = builtins.substring 0 8 lastModifiedDate;
in
flake-utils.lib.eachDefaultSystem (system:
# 特定のシステムのみをサポートすることも可能
# flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux"] (system:
let pkgs = nixpkgs.legacyPackages.${system}; in
with pkgs; {
packages = flake-utils.lib.flattenTree rec{ # Flakesはネストされたパッケージは見えないのでそれの解消
c-hello = stdenv.mkDerivation {
name = "c-hello-${version}";
src = ./c-hello;
nativeBuildInputs = [ autoreconfHook ];
};
go-hello = buildGoModule {
pname = "go-hello";
inherit version;
src = ./go-hello;
vendorSha256 = "sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=";
};
default = c-hello;
};
apps = rec {
c-hello = flake-utils.lib.mkApp { drv = packages.c-hello; };
go-hello = flake-utils.lib.mkApp { drv = packages.go-hello; };
default = c-hello;
};
}
);
}
$ nix flake show
path:/home/<name>/Templates/samplepkgs/flake-templates/sample-with-flake-utils?lastModified=1666083014&narHash=sha256-dVeym+Lp4EgaVMzlXtmXqX61zSKoNbAkCvzX1Avwwdw=
├───apps
│ ├───aarch64-darwin
│ │ ├───c-hello: app
│ │ ├───default: app
│ │ └───go-hello: app
│ ├───aarch64-linux
│ │ ├───c-hello: app
│ │ ├───default: app
│ │ └───go-hello: app
│ ├───i686-linux
│ │ ├───c-hello: app
│ │ ├───default: app
│ │ └───go-hello: app
│ ├───x86_64-darwin
│ │ ├───c-hello: app
│ │ ├───default: app
│ │ └───go-hello: app
│ └───x86_64-linux
│ ├───c-hello: app
│ ├───default: app
│ └───go-hello: app
└───packages
├───aarch64-darwin
│ ├───c-hello: package 'c-hello-20221018'
│ ├───default: package 'c-hello-20221018'
│ └───go-hello: package 'go-hello-20221018'
├───aarch64-linux
│ ├───c-hello: package 'c-hello-20221018'
│ ├───default: package 'c-hello-20221018'
│ └───go-hello: package 'go-hello-20221018'
├───i686-linux
│ ├───c-hello: package 'c-hello-20221018'
│ ├───default: package 'c-hello-20221018'
│ └───go-hello: package 'go-hello-20221018'
├───x86_64-darwin
│ ├───c-hello: package 'c-hello-20221018'
│ ├───default: package 'c-hello-20221018'
│ └───go-hello: package 'go-hello-20221018'
└───x86_64-linux
├───c-hello: package 'c-hello-20221018'
├───default: package 'c-hello-20221018'
└───go-hello: package 'go-hello-20221018'
Overlays可能なパッケージの作製
- ビルドを定義したファイルに
passthru = {runnable = (true or false);}
を追記することでnix run .#<package>
で実行できるように追加してくれる-
passthru
はビルド時には無視される任意の属性を定義できる。(meta情報の付与、ラベル付といった用途でつける)
-
- ディレクトリ構造は以下を想定
- packages/<package>/default.nixは各パッケージのビルド手順を定義
- sources/sources.nixはパッケージバージョンやハッシュ、提供元を定義
.
├── flake.lock
├── flake.nix
├── packages
│ ├── c-hello
│ │ └── default.nix
│ └── go-hello
│ └── default.nix
└── sources
└── sources.nix
コードと結果
- flake.nix
{
description = "A sample with flake-utils";
# Nixpkgs / NixOS version to use.
inputs.nixpkgs.url = "nixpkgs/nixos-unstable";
# Using flake-utils
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils } @ inputs:
let
lib = nixpkgs.lib;
genPkg = func: name: { # 別ファイルに記されたビルド定義とバッケージ情報などをまとめる
inherit name;
value = func name;
};
genApp = pkgs: name: {
inherit name;
value = flake-utils.lib.mkApp { drv = pkgs.${name}; };
};
# default.nixの属性にpassthru = { runnable = true }を追記するとnix runで実行できるように自動でappsに追加する
isRunableApp = pkgs: name: if pkgs.${name}.passthru.runnable or false then name else null;
runnableApps = pkgs: lib.remove null (map (isRunableApp pkgs) names);
pkgDir = ./packages; # ビルと手順が記されたパッケージディレクトリ
sources = import ./sources/sources.nix; # パッケージ情報が記されたディレクトリ
ls = builtins.readDir pkgDir;
names = with builtins; lib.remove null (map ((ts: t: if ts.${t} == "directory" then t else null) ls) (attrNames ls)); # ディレクトリのみを列挙
withContents = func: with builtins; listToAttrs (map (genPkg func) names);
mkApps = pkgs: appNames: with builtins; listToAttrs (map (genApp pkgs) appNames);
in
{ # Overlaysの定義
overlays.default = final: prev: # final: prev:はpythonでいうself, super
let
sources' = sources {};
in withContents (name:
let
pkgs = import (pkgDir + "/${name}");
override = builtins.intersectAttrs (builtins.functionArgs pkgs) ({
pythonPackages = final.python3.pkgs;
mySource = sources'.${name};
});
in final.callPackage pkgs override
) // { sources = sources'; };
} # ここから先はnix buildやnix runで使うための設定(このリポジトリ単体でも使えるようにする)
// flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux"] (system:
let
pkgs = import nixpkgs {
system = "${system}";
overlays = [ self.overlays.default ];
config.allowUnfree = true;
};
in with pkgs.legacyPackages.${system}; rec {
packages = withContents (name: pkgs.${name});
apps = mkApps pkgs (runnableApps pkgs);
}
);
}
├───apps
│ ├───aarch64-linux
│ └───x86_64-linux
├───overlays
│ └───default: Nixpkgs overlay
└───packages
├───aarch64-linux
│ ├───c-hello: package 'c-hello-20221020'
│ └───go-hello: package 'go-hello-20221020'
└───x86_64-linux
├───c-hello: package 'c-hello-20221020'
└───go-hello: package 'go-hello-20221020'
nvfetcherを用いて独自パッケージの自動追跡をする
-
nvfetcherは独自パッケージの更新を自動追跡してくれるツール
- 自動追跡はGitHub Actionsを使うといい
- GitHubのタグやブランチだけでなく、AURやweb・http header+正規表現を用いてバージョンを自動で取得してくれる
-
TOMLでパッケージのソース元、
nix run
で実行可能かなどを定義することができる-
nvfetcher -c "/path/to/toml-file"
で_sources/
以下に生成される
-
-
これまでの解説はこれをするための布石だった。
コードと結果
- flake.nix
{
description = "A sample with flake-utils";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
nvfetcher = {
url = "github:berberman/nvfetcher";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, flake-utils, nvfetcher } @ inputs:
let
lib = nixpkgs.lib;
genPkg = func: name: { # 別ファイルに記されたビルド定義とバッケージ情報などをまとめる
inherit name;
value = func name;
};
genApp = pkgs: name: {
inherit name;
value = flake-utils.lib.mkApp { drv = pkgs.${name}; };
};
# default.nixの属性にpassthru = { runnable = true }を追記するとnix runで実行できるように自動でappsに追加する
isRunableApp = pkgs: name: if pkgs.${name}.passthru.runnable or false then name else null;
runnableApps = pkgs: lib.remove null (map (isRunableApp pkgs) names);
pkgDir = ./packages; # ビルと手順が記されたパッケージディレクトリ
sources = import ./_sources/generated.nix; # パッケージ情報が記されたディレクトリ
broken = import ./packages/broken.nix; # ビルドできないパッケージを列挙
ls = builtins.readDir pkgDir;
isDir = ts: t: if ts.${t} == "directory" then t else null; # ディレクトリのみを列挙
names = with builtins; lib.subtractLists broken (lib.remove null (map (isDir ls) (attrNames ls)));
withContents = func: with builtins; listToAttrs (map (genPkg func) names);
mkApps = pkgs: appNames: with builtins; listToAttrs (map (genApp pkgs) appNames);
in
{ # Overlaysの定義
overlays.default = final: prev: # final: prev:はpythonでいうself, super
let
sources' = sources { inherit (final) fetchgit fetchurl fetchFromGitHub ; };
in withContents (name:
let
pkgs = import (pkgDir + "/${name}");
override = builtins.intersectAttrs (builtins.functionArgs pkgs) ({
pythonPackages = final.python3.pkgs;
mySource = sources'.${name};
});
in final.callPackage pkgs override
) // { sources = sources'; };
} # ここから先はnix buildやnix runで使うための設定(このリポジトリ単体でも使えるようにする)
// flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux"] (system:
let
pkgs = import nixpkgs {
system = "${system}";
overlays = [ self.overlays.default nvfetcher.overlay]; # nvfetcherもoverlayする
config.allowUnfree = true;
};
in with pkgs.legacyPackages.${system}; rec {
packages = withContents (name: pkgs.${name});
apps = mkApps pkgs (runnableApps pkgs);
checks = packages; # For `nix flake check`
devShells.default = nvfetcher.packages.${system}.ghcWithNvfetcher; # For `nix develop`
}
);
}
- packages.toml
[c-hello]
src.manual = "2.12.1"
fetch.url = "mirror://gnu/hello/hello-2.12.1.tar.gz"
Tips
-
stdenv.mkDerivation
はネットワーク接続のない環境でビルドを行う- あくまでソースのみを用いてビルドする場合に使える
-
cargo
やpip
が使えない
-
buildInputs
はビルド時に必要なコマンドを提供する依存関係を示す(gccやcargoなど) -
nativeBuildInputs
はビルド時にコマンドとしては必要ないが、libなどにパスを通さなければいけない依存関係を示す。- 一般的な依存関係
-
nixpkgsは各々のパッケージが独立している
- そのパッケージを実行するのに必要なPATH(環境)しか持たない
- ホストのターミナルではPATHが通ってコマンドも実行できるのに、あるプログラムを実行するとPATHが通っていないと怒られるというのが多々ある
-
ビルド時に依存関係のパスは勝手に解釈してくれるが、シェルスクリプトやプログラムの中からshellを叩く(pythonのsubprocessなど)場合は解釈してくれない
- この場合、実行スクリプトに
wrapProgram
を適用してPATHを通さなければならない - 例
... postFixup = '' wrapProgram $out/bin/hoge \ # プログラムはシェルスクリプトでもバイナリでもいい --prefix PATH : ${lib.makeBinPATH [ foo ]} \ # バイナリへのパスを通す --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPATH [ bar ] # ライブラリへのパスを通す '' ...
- この場合、実行スクリプトに
-
cargo
やpip
を用いて依存関係を得る場合はそれぞれ、rustPlatform.buildRustPackage
、pythonPackages.buildPythonPackage
を使用する- nixpkgsのマニュアルに各言語のビルドの仕方が載っている