§1. helloのビルド
いつまでHello, world!するんだと思ったあなたへ、こんにちは!
この本は入門書なので最後までハローワールドします。挨拶は大事ですからね。
と言っても、ここに至るまでの間に何度もGNU Helloをビルドしてきました。今回は、FlakeをGit込みでセットアップし、一から自作のhelloをNixパッケージ化します。プログラムの作成からビルドまでの一連の流れを確認しましょう。
1. Flakeのセットアップ
nix flake new hello-nix
コマンドを実行するとhello-nix/ディレクトリが作成され、その中に以下のflake.nixが配置されます。
{
description = "A very basic flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
};
outputs = { self, nixpkgs }: {
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
};
}
2. inputsの導入
初期のinputsにはgithub:nixos/nixpkgs?ref=nixos-unstable(github:nixos/nixpkgs/nixos-unstableと等価)が設定されていますが、今回作るパッケージはNixOS以外のシステムでも利用したいので、nixpkgs-unstableを使います。また、flake-utilsで複数のプラットフォームに対応します。
{
- description = "A very basic flake";
+ description = "hello package written in Rust";
inputs = {
- nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
+ nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
};
- outputs = { self, nixpkgs }: {
-
- packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
-
- packages.x86_64-linux.default = self.packages.x86_64-linux.hello;
-
- };
+ outputs =
+ { nixpkgs, flake-utils, ... }:
+ flake-utils.lib.eachDefaultSystem (
+ system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in
+ {
+ packages = {
+ hello = pkgs.hello;
+ default = pkgs.hello;
+ };
+ }
+ );
}
今は初期状態と同じくNixpkgsのGNU Helloをエクスポートしていますが、これから私たちhelloに置き換えていきます。
3. helloを作る
今のコードはただNixpkgsが提供するGNU Helloを再エクスポートしているだけなので、自作のhelloに置き換えましょう。前回と同じではつまらないので、今回はRustで書きます。
mkdir src
touch ./src/hello.c
fn main() {
println!("Hello, world!");
}
ここで親切なRustaceanは「Hey you! Cargo(Rustのプロジェクト管理ツール)を使いなよ!」とアドバイスをくれると思いますが、諸事情により今回はrustc(Rustコンパイラ)のみを使います。
mkDerivationでビルド式を書きます。
{
description = # (略)
inputs = # (略)
outputs =
{ nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
+ hello = pkgs.stdenv.mkDerivation {
+ pname = "hello";
+ version = "0.0.1";
+ src = ./src;
+ nativeBuildInputs = with pkgs; [ rustc ];
+ buildPhase = ''
+ rustc ./hello.rs
+ '';
+ installPhase = ''
+ mkdir -p $out/bin
+ cp ./hello $out/bin/hello
+ '';
+ };
in
{
packages = {
- hello = pkgs.hello;
- default = pkgs.hello;
+ inherit hello;
+ default = hello;
};
}
);
}
nativeBuildInputsでビルド環境にrustcを導入し、buildPhaseでコンパイルします。生成された実行ファイルはinstallPhaseで$out/binにコピーします。
nix runで実行しましょう。
$ nix run
# エラー発生!
「ファイルがないぞ!」というエラーが発生しました。今回はFlakeをGitリポジトリ化したため、NixはGit経由でファイルを探しますが、まだ追加したファイルをステージしていないのでエラーが発生します。git addしてからnix runしましょう。
$ git add .
$ nix run
Hello, world!
4. リファクタリング
今回のような小さなプロジェクトならfleke.nix1つで十分ですが、大きなプロジェクトならflake.nixに記述する内容は最小限に留めた方がいいので、ファイル分割してみましょう。また、パッケージのメタ情報が不足しているのでmkDerivationに設定を追加します。
リファクタリングに入る前にコミットしておきます。
git commit --message="add hello-rs"
5.1. ファイル分割
importを使ってもいいですが、ここはNixpkgsが採用しているcallPackageパターン[1]を用います。nix/ディレクトリを作り、flake.nixに記述していたビルド式をnix/hello.nixに移します。
{ stdenv, rustc }:
stdenv.mkDerivation {
pname = "hello";
version = "0.0.1";
src = ../src; # 注意!nix/hello-rs.nixから見たsrc/への相対パス
nativeBuildInputs = [ rustc ];
buildPhase = ''
rustc ./hello.rs
'';
installPhase = ''
mkdir -p $out/bin
cp ./hello $out/bin/hello
'';
}
pkgs.callPackageでnix/hello.nixをインポートします。
{
description = # (略)
inputs = # (略)
outputs =
{ nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
- hello = # (略)
in
{
packages = {
- inherit hello;
- default = hello;
+ hello = pkgs.callPackage ./nix/hello.nix { };
+ default = pkgs.callPackage ./nix/hello.nix { };
};
}
);
}
実行して同じ結果が得られることを確認してください。
# nix/hello.nixを追加したのでステージング
$ git add .
$ nix run
Hello, world!
callPackage関数はPathとAttrSetを引数に取り、Pathで指定されたNixファイルの関数にpkgsを渡します。1.1. Nix言語の基本で扱ったように、AttrSetを引数にとる関数は受け取ったAttrSetからattributeを取り出して記述できるので、前述のhello.nixでは{ stdenv, rustc }というようにpkgs.stdenvとpkgs.rustcを取り出しています。
また、今回はcallPackageの第二引数として空のAttrSetを渡していますが、このAttrSetはpkgsにマージされます。つまり、正確にはpkgs // {}がhello.nixに与えられます。
callPackage :: Path -> AttrSet -> Derivation
5.2. meta attributeの追加
mkDerivationではmetaというattributeで詳細なメタ情報を設定できます。
-{ stdenv, rustc }:
+{
+ stdenv,
+ rustc,
+ lib,
+}:
stdenv.mkDerivation {
pname = "hello";
version = "0.0.1";
src = ../src; # 注意!nix/hello-rs.nixから見たsrc/への相対パス
nativeBuildInputs = [ rustc ];
buildPhase = ''
rustc ./hello.rs
'';
installPhase = ''
mkdir -p $out/bin
cp ./hello $out/bin/hello
'';
+ meta = {
+ mainProgram = "hello";
+ description = "A hello world program written in Rust";
+ longDescription = ''
+ This is a demo package for the Nix-Hands-On, which is a hello world program written in Rust.
+ '';
+ license = lib.licenses.mit;
+ platforms = lib.platforms.all;
+ };
}
mainProgram
mainProgramはnix runで実行されるプログラムを指します。複数の実行可能ファイルを含むパッケージやパッケージ名と実行可能ファイルの名前が異なるパッケージで利用します。nix runはデフォルトでは<ストアパス>/bin/<pname>を実行します。
descriptionとlongDescription
description/longDescriptionに記述された説明はsearch.nixos.orgで表示されます。
また、descriptionは、nix search <flake-url> <検索ワード>による検索の対象になります。
# "rust"はパッケージ名には含まれないが、descriptionには含まれるためヒットする
❯ nix search . rust
* packages.x86_64-linux.default (0.0.1)
A hello world program written in **Rust**
* packages.x86_64-linux.hello (0.0.1)
A hello world program written in **Rust**
license
licenseにはパッケージのライセンスを指定します。lib.licensesは様々なライセンスを収録したAttrSetで、nixpkgs/lib/licenses.nixで定義されています。meta.licenseにunfreeとしてマークされているライセンスを指定すると、デフォルト設定では評価時にエラーが発生します。
# 非営利・改変禁止なライセンスを指定
# https://creativecommons.org/licenses/by-nc-nd/4.0/deed.ja
license = lib.licenses.cc-by-nc-40;
$ nix run
# エラー発生!
$NIXPKGS_ALLOW_UNFREE環境変数を設定したり、NixpkgsをインポートするときにallowUnfree = trueを指定することで、unfreeなライセンスのパッケージの評価を許可できます。
# Nix言語の非純粋な関数(getEnv)で環境変数を読み取るため、--impureオプションを付ける
$ NIXPKGS_ALLOW_UNFREE=1 nix run --impure
Hello, world!
pkgs = import nixpkgs {
inherit system;
config = {
allowUnfree = true;
};
};
platforms
platformsはパッケージがサポートするプラットフォームをListで指定します。nixpkgs/lib/systems/doubles.nixで定義されており、主に以下の区分でプラットフォームを指定できます。
- OS(Linux, Darwin, Windows, FreeBSD, Cygwin, UNIXなど)
- CPUアーキテクチャ(x86, ARM, RISC-Vなど)
- etc...
platformsで指定されていないプラットフォームでパッケージを評価しようとするとエラーが発生します。例えば、platformsにlib.platforms.darwin(macOS)のみを指定し、Linuxで評価しようとするとエラーが発生します。
platforms = lib.platforms.darwin;
今回はクロスプラットフォームなパッケージなので、lib.platforms.allを指定しています。
その他のattribute
基本的にmetaの情報はNixpkgsで利用されるものなので、これ以上は省略します。その他のattributeや詳細については以下の公式マニュアルを参照してください。