"大規模ソフトウェアを手探る"をやる (2: Nixに移行編)

経緯

なぜNixに移行するのか?

part 1になる前回の投稿日は2025年の3月の末だったので、投稿からほぼ1年も経過してしまっています。 進捗としては、gnuplotのビルドまでと本当に序盤を触れただけです。 さらに、その間に普段一番触っているラップトップマシンをArch LinuxからNixOSに移行したことで、環境がガラリと変りました。

これまで通りにやるならば、システムにグローバルにツール群をインストールしてビルドしてしまうのも一つの手ではあります。 しかし、せっかくNixOSをflakeによって運用しているならば、flakeでローカルに環境を作りビルドした方が"Nixらしい"と思ったので移行してみることにしました。

移行作業は?

“地獄"とまではいきませんが、目的のビルド成果物を思い通りに動くようにできるまでは少々大変ではありました。

とりあえずビルドができるところまでは、案外すんなりいったのですがGUIでplotの画面が開かない問題が中々解決できませんでした。

環境構築 by flake.nix

今回構築した環境では、以下のことを要件としました。

  1. 再現性のある環境を構築すること
  2. ソースコードを変更&ビルドを手軽に繰り返すことが可能なこと。
  3. gdb(デバッガ)を使ってステップ実行ができるようにすること。
  4. clangd(Language Server)を使ってコードを読む時に定義ジャンプ等が使えるようにすること

以下では、各要件を満す環境をどう構築したかについて説明します。

要件1,2(ビルド環境)について

要件1,2は、一旦nix buildでビルドが可能な環境を作ってから、それに要したパッケージ等の設定をmakeShell関数にも設定することで実現しました。 というのも、ビルドが可能なnix-shell環境できたつもりでもグローバルにインストールされているツールに依存していわゆる"暗黙の依存"を作ってしまう可能性があります。 暗黙の依存作ってしまうと、せっかくのNixの再現性が台無しです。

まずは、特に依存パッケージ等を考えずに、普通のgccをコンパイラツールチェーンとして指定して以下の環境でnix buildを実行してビルドを行いました。

  • flake.nix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
  description = "flake for Seach of LS software";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs =
    { self, nixpkgs }:
    let
      platform = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages."${platform}";

      nParallels = "11";

      gnuplot_debug = pkgs.stdenv.mkDerivation rec {
        pname = "gnuplot";
        version = "5.0.1";
        src = pkgs.fetchurl {
          url = "https://sourceforge.net/projects/gnuplot/files/gnuplot/${version}/gnuplot-${version}.tar.gz";
          hash = "sha256-fLxVfnHfWB6lIBI/tDnepfBzrcyQEKKIXcgNTtKLPEc=";
        };

        nativeBuildInputs = with pkgs; [
          gcc
          gnumake
        ];
        buildInputs = with pkgs; [ ];

        dontConfigure = true;

        buildPhase = ''
          CFLAGS="-O0 -g" ./configure --prefix=$out
          make -j${nParallels}
        '';

        installPhase = ''
          make install 
        '';

      };
    in
    {
      packages.${platform}.default = gnuplot_debug;
    };
}

しかし、ビルドには失敗して、以下のようなエラーメッセージが表示されました。

どうやら、buildPhasemakeの実行でコケているようです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
nix build
error: Cannot build '/nix/store/xy2d25qg4ip4aa7jyssazrr6f2fscdm0-gnuplot-5.0.1.drv'.
       Reason: builder failed with exit code 2.
       Output paths:
         /nix/store/kcizqzm8hmm7fzq1bhch7mfc1dq5isr6-gnuplot-5.0.1
       Last 25 log lines:
       >                  from stdfn.h:49,
       >                  from dynarray.h:41,
       >                  from dynarray.c:43:
       > /nix/store/dj43clc5ff7jjnfmhbaj6q4q0h8kpfpm-glibc-2.40-66-dev/include/features.h: At top level:
       > /nix/store/dj43clc5ff7jjnfmhbaj6q4q0h8kpfpm-glibc-2.40-66-dev/include/features.h:422:4: warning: #warning _FORTIFY_SOURCE requires compiling with optimization (-O) [-Wcpp]
       >   422 | #  warning _FORTIFY_SOURCE requires compiling with optimization (-O)
       >       |    ^~~~~~~
       > make[4]: *** [Makefile:896: breaders.o] Error 1
       > make[4]: *** Waiting for unfinished jobs....
       > make[4]: *** [Makefile:896: eval.o] Error 1
       > make[4]: *** [Makefile:896: alloc.o] Error 1
       > make[4]: *** [Makefile:896: dynarray.o] Error 1
       > make[4]: *** [Makefile:896: command.o] Error 1
       > make[4]: *** [Makefile:896: datafile.o] Error 1
       > make[4]: *** [Makefile:896: color.o] Error 1
       > make[4]: *** [Makefile:896: contour.o] Error 1
       > make[4]: *** [Makefile:896: axis.o] Error 1
       > make[4]: Leaving directory '/build/gnuplot-5.0.1/src'
       > make[3]: *** [Makefile:955: all-recursive] Error 1
       > make[3]: Leaving directory '/build/gnuplot-5.0.1/src'
       > make[2]: *** [Makefile:630: all] Error 2
       > make[2]: Leaving directory '/build/gnuplot-5.0.1/src'
       > make[1]: *** [Makefile:417: all-recursive] Error 1
       > make[1]: Leaving directory '/build/gnuplot-5.0.1'
       > make: *** [Makefile:355: all] Error 2
       For full logs, run:
         nix log /nix/store/xy2d25qg4ip4aa7jyssazrr6f2fscdm0-gnuplot-5.0.1.drv

makeの中ではどの処理でコケているのかを知るために、このエラーメッセージの末尾に表示された

1
nix log /nix/store/xy2d25qg4ip4aa7jyssazrr6f2fscdm0-gnuplot-5.0.1.drv

を実行することで得られる詳細なエラーメッセージを見てみました(とても長いので、以下にリンクを置いておきます)。

build_error.log

この詳細なエラーメッセージからは、非常に多くのソースコード上で共通して以下の部分でコケていることが分りました。

1
2
3
#ifndef lint
static char *RCSid() { return RCSid("$Id: datafile.c,v 1.290.2.9 2015/04/22 22:25:26 sfeam Exp $"); }
#endif

この時点では、これをCのバージョンの問題だと思いbuildPhaseにてCFLAGSstd=c11,std=c99等を追加して試しましたがダメでした。 そこで、gcc自体のバージョンの問題では?と考えgccの代りに gcc14,gcc13を試したところ、gcc13を指定した場合で正常にビルドができるようになりました。

記事の執筆中に調べて分ったことなのですが、先程のコードスニペットは、最近のC23で廃止されたK&Rスタイルの関数のコードなので、恐らくそれをコンパイルできないコンパイラ&コンパイルオプションでビルドしようとしたことが原因と考えられます。

さて、ここまででビルドに成功したので、以下のような簡単なgnuplotのコマンドを実行してみました。

1
plot sin(x)

しかし、GUIのplot画面が表示されませんでした。

最初は、GUI関係だろうと考えて、xorg 関係のパッケージ(xorg.libX11など)の追加も試しましたが、ダメでした。 そこで、nixpkgsではもちろんgnuplotは提供されているので、nixpkgsでのビルドの設定を見てみました。

なんとなく、pangoというパッケージが関係しそうに思えたのでこれをbuildInputsに追加してビルドを実行してみたところ、GUIのplot表示が可能になりました。

要件3,4(コードを読む環境)について

ここまで来れば後は簡単です。 gdbgdbパッケージを追加すれば良いし、clangdclang-toolsに含まれるのでそれを追加するだけです。

少しだけややこしいのは、clangdでソースを解析するに必要なcompile_commands.jsonです。 CMakeでビルドされるプロジェクトでは、CMakeの機能で生成が可能なのですが、gnuplotはCMakeではない方法でビルドされるプロジェクトなのでこの方法が使えません。

そこで、bearというツールを使ってビルドを

1
bear -- make

で実行して、ビルドプロセスを解析することでcompile_commands.jsonを生成しました。

完成したflake.nix

最終的に完成したflake.nixは以下のようになりました

このflakeでは、nix buildによる全行程が自動で実行されるビルドもnix-shellに入って手動で行うビルドも両方とも可能です。

  • flake.nix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
{
  description = "flake for Seach of LS software";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  };

  outputs =
    { self, nixpkgs }:
    let
      platform = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages."${platform}";

      nParallels = "11";

      gnuplot_debug = pkgs.stdenv.mkDerivation rec {
        pname = "gnuplot";
        version = "5.0.1";
        src = pkgs.fetchurl {
          url = "https://sourceforge.net/projects/gnuplot/files/gnuplot/${version}/gnuplot-${version}.tar.gz";
          hash = "sha256-fLxVfnHfWB6lIBI/tDnepfBzrcyQEKKIXcgNTtKLPEc=";
        };

        nativeBuildInputs = with pkgs; [
          gcc13
          gnumake
          pango
        ];
        buildInputs = with pkgs; [ ];

        dontConfigure = true;

        buildPhase = ''
          CFLAGS="-O0 -g" ./configure --prefix=$out
          make -j${nParallels}
        '';

        installPhase = ''
          make install 
        '';

      };
    in
    {
      packages.${platform}.default = gnuplot_debug;

      devShells.${platform}.default = pkgs.mkShell {
        packages = with pkgs; [
          # for build
          gcc13
          gnumake
          pango

          # for code reading
          bear
          ## for clangd
          clang-tools
          # debugger
          gdb
        ];

      };

    };
}

最後にビルド&実行方法を載せておきます。

  • nix buildによるビルド&実行
1
2
3
nix build # ビルド

./result/bin/gnuplot # 実行
  • ソースの変更&ビルドの繰り返しで使用する手動の方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
wget https://sourceforge.net/projects/gnuplot/files/gnuplot/5.0.1/gnuplot-5.0.1.tar.gz
tar -xzvf gnuplot-5.0.1.tar.gz

mkdir build && cd build
# インストール先はお好みで、筆者は `<project root>/build/install`にインストールした.
CFLAGS="-O0 -g" ../gnuplot-5.0.1/configure --prefix="$PWD/install" 
bear -- make -j11 # to generate compile_commands.json
make install

./build/install/bin/gnuplot # 実行
CC BY
Hugo で構築されています。
テーマ StackJimmy によって設計されています。