NixOS+home-manager環境にJulia+IJuliaをインストールした。

自分用メモ。

JuliaのインストールにもIJulia.jlのインストールにもハマりどころがあった。

## Juliaのインストール

まず、NixpkgsのJuliaはかなり古いので、 `nix-env -iA nixpkgs.julia` で済ませられない。
そこで、涙を飲んでJuliaのバイナリを[公式のダウンロードサイト](https://julialang.org/downloads/)からダウンロードして、実行してみようとする。

```shell-session
> curl -fLO "https://julialang-s3.julialang.org/bin/linux/x64/1.5/julia-1.5.3-linux-x86_64.tar.gz"

> tar -xf julia-1.5.3-linux-x86_64.tar.gz

> ./julia-1.5.3/bin/julia
Failed to execute process 'julia-1.5.3/bin/julia'. Reason:
The file 'julia-1.5.3/bin/julia' does not exist or could not be executed.
```

かなしいことに、うまくいかない。

なぜならjuliaのバイナリは実行環境としてFHSに準拠したファイルシステムを想定しているからであり、NixOSはFHSに準拠していないからである。

とりわけ、`/lib64/ld-linux-x86-64.so.2`が必要なのだが、NixOSには`/lib64`ディレクトリがない。楽だけどNixらしくない方法としては、シンボリックを貼ってしまうという手が考えられる。

```shell-session
> sudo mkdir /lib64

> sudo ln -s /nix/store/9df65igwjmf2wbw0gbrrgair6piqjgmi-glibc-2.31/lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2
```

一応はこれですぐに動くけど、正直こんなやり方はNixらしくない。第一、nix-env -u をしたらいつの間にかシンボリックが壊れてしまいそうである。

そこで、overlayを作る。まず、こんな感じのテンプレートファイルを作る。

```nix:.config/nixpkgs/overlays/julia/template.nix
{ stdenv
, glibc
, autoPatchelfHook
, version
, sha256
}:

stdenv.mkDerivation rec {
  pname = "julia-bin";
  inherit version;

  src = builtins.fetchTarball {
    url = "https://julialang-s3.julialang.org/bin/linux/x64/1.5/julia-${version}-linux-x86_64.tar.gz";
    inherit sha256;
  };

  buildInputs = [ glibc ];
  nativeBuildInputs = [ autoPatchelfHook ];
  installPhase = ''
    mkdir -p $out
    cp -a $src/* $out
  '';
}
```

なにが起こっているかわからないが、`buildInputs`に`autoPatchElfHook`というのを入れておくと、Nixがパッケージのビルドのときによしなにリンクを貼ってバイナリが動くようにしてくれるらしい。

そんでこんな感じのoverlayを作る。

```nix:.config/nixpkgs/overlays/julia/default.nix
self: super: {
  julia-bin = super.callPackage ./template.nix {
    version = "1.5.3";
    sha256 = "1yc60dl39sa0rbiaj2v7vf79j0c9zd93h1mwcahq99w44c81k3q6";
  };
}
```

で、`home.nix`の`home.packages`に`julia-bin`を追加して`home-manager switch`すればjuliaのインストールができる。

```shell-session
> home-manager switch

> julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.5.3 (2020-11-09)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> 
```

## IJuliaのインストール

何も考えずに`add IJulia`でインストールできるわけではない。

```shell-session
julia> ]

(@v1.5) pkg> add IJulia

julia> using IJulia

julia> notebook()
install Jupyter via Conda, y/n? [y]: 
[ Info: Downloading miniconda installer ...
[ Info: Installing miniconda ...
PREFIX=/home/hnakano/.julia/conda/3
Unpacking payload ...
/home/hnakano/.julia/conda/3/installer.sh: 行 412: /home/hnakano/.julia/conda/3/conda.exe: No such file or directory
/home/hnakano/.julia/conda/3/installer.sh: 行 414: /home/hnakano/.julia/conda/3/conda.exe: No such file or directory
ERROR: failed process: Process(`/home/hnakano/.julia/conda/3/installer.sh -b -f -p /home/hnakano/.julia/conda/3`, ProcessExited(1)) [1]
```

どうやらIJulia.jlがminicondaのインストーラを使ってcondaコマンドとjupyterをインストールしようとするのだが、このminicondaインストーラがやはりFHSを前提としているようでNixOSでは動かない。

そこでまずはNixOS上にjupyterをインストールし、juliaにはそのjupyterの場所を教えてあげることでminicondaインストーラを使わずにことを済ませよう。

### Jupyter Notebookのインストール

ここにも罠があるのだが、`nixpkgs.jupyter`をインストールしても`jupyter`コマンドが使えるようになるわけではない。

```shell-session
> nix-env -iA nixpkgs.jupyter

> jupyter notebook
The program ‘jupyter’ is currently not installed. It is provided by
several packages. You can install it by typing one of the following:
  nix-env -iA nixos.python27Packages.jupyter_core
  nix-env -iA nixos.python37Packages.jupyter_core
  nix-env -iA nixos.python38Packages.jupyter_core
```

大変わかりにくいことに、エラーメッセージに従って`nixos.python37Packages.jupyter_core`をインストールしたところで問題は解決しない。

解決策として、依存ライブラリに`notebook`というのを入れたpythonごとインストールするとJupyter Notebookが使えるようになる。

まず、以下のshell.nixを書いて、

```nix:shell.nix
with import <nixpkgs> { };
python38.withPackages (p: [ p.notebook ])
```

以下のコマンドを打てば、Jupyter Notebookが使える。

```shell-session
> nix-shell shell.nix --run 'jupyter notebook'
```

### Juliaから使えるようにする

IJulia.jlは、`ENV["JUPYTER"]`に`jupyter`コマンドのパスを渡してやるとそちらを優先して使ってくれる。そこで、`startup.jl`に`ENV["JUPYTER"]`を設定するスクリプトを書いてしまう。

```julia:.julia/config/startup.jl
ENV["JUPYTER"] = "/nix/store/smfflp34ggglyyamw45sazgiv49snh7h-python3-3.8.6-env/bin/jupyter"
```

この`/nix/store/smff...-python3-3.8.6-env`というのは、さっき`shell.nix`を作ったときに作成されたnix storeで、ここの`bin`以下に`jupyter`コマンドが入っている。

もちろんこれでjuliaを起動して`build IJulia`すればIJuliaが使えるようになるのだが、この方法はNixらしくない(2回目)し、うっかり`nix-collect-garbage -d`とかした日には動かなくなってしまうかもしれない。

そこで、juliaの設定用にhome-managerのモジュールを作成する。

まず、nixを使って`startup.jl`を書くために、テンプレートファイル`startup.jl.in`を作成する。

```nix:.config/nixpkgs/hm-modules/julia/startup.jl.in
ENV["JUPYTER"] = @pythonWithJupyter@/bin/jupyter
```

`@pythonWithJupyter@`の部分がNixで置換される。

このテンプレートファイルから実際の`startup.jl`を作成するためのhome-manager moduleを作成する。

```nix:.config/nixpkgs/hm-modules/julia/default.nix
{ config, pkgs, lib, ... }:
with pkgs;
with lib;
let
  pythonWithJupyter = python38.withPackages (p: [ p.notebook ]);
  startup-jl = runCommand "startup.jl" { inherit pythonWithJupyter; } ''
    substituteAll "${./startup.jl.in}" $out
  '';
in
{
  home.packages = [
    julia-bin
  ];

  home.file.".julia/config/startup.jl".source = startup-jl;
}
```

キモは、`runCommand`の中で`substituteAll`を使ってstartup.jl.inの中の@pythonWithJupyter@の部分をNixで作成したjupyter環境へのパスで置換していること、そして、置換してできた`startup.jl`のパス(これの実態はnix storeの中のテキストファイル)から`home.file`を利用して`~/.julia/config/startup.jl`へシンボリックを貼っていることである。

このモジュールを`home.nix`でインポートする。

```nix:.config/nixpkgs/home.nix
imports = [ ./hm-modules/julia ];
```

ついでに、`julia-bin`のインストールは`julia/default.nix`の方に書いてあるので、`home.nix`の`home.packages`に`julia-bin`の記述がある場合は消しておいたほうがよい。

以上のことを終えたうえで`home-manager switch`して`build IJulia`すればIJulia.jlがちゃんと機能する。

```shell-session
> home-manager switch

> julia -q

julia> ]

(@v1.5) pkg> build IJulia
   Building Conda ─→ `~/.julia/packages/Conda/x5ml4/deps/build.log`
   Building IJulia → `~/.julia/packages/IJulia/IDNmS/deps/build.log`

julia> using IJulia

julia> notebook()
[ Info: running `/nix/store/smfflp34ggglyyamw45sazgiv49snh7h-python3-3.8.6-env/bin/jupyter notebook`
```

やったぜ。