Juliaのプロジェクトと環境

# はじめに

Juliaでパッケージを作るとき，`Project.toml`などのファイルや，`activate`などのコマンドを使うことになります。また，これらは，パッケージほど大げさではないけれども，いくつかのスクリプトをまとめて管理したい場合にも便利です。この記事は，これらがどんな働きをして，Juliaの環境とどう関係するかを整理したものです。

Juliaには，複数のスクリプトをまとめて「プロジェクト」として管理する機能があります。これは自作のプログラムを再利用するとき，あるいは他者と共有するときに便利です。そして，そのプロジェクトでは，それが呼び出すパッケージの依存関係とバージョン指定が可能です。Juliaのパッケージは，プロジェクトを再利用しやすいように構成したものです。

Juliaでは，複数のスクリプトとその特定の依存パッケージが利用できる状態を，環境（environment）と呼んでいます。Juliaには環境を切り替える機能があり，これを利用してパッケージの依存関係で生じるバージョンの齟齬（依存地獄）を解消しています。Juliaの環境は，自作パッケージ開発を念頭に置いて設計されています。

以下，簡単なプロジェクトの作成手順を紹介しながら，Juliaの環境についてみてゆきます。これを自作パッケージへと転換する方法は，また改めて記事にする予定です。なお，この記事の執筆にあたり，以下のwebサイトを参考にしました。

- [Julia v1.0.0で入った仮想環境の管理について](https://qiita.com/YuK_Ota/items/15780a61fb7aa4a49de6); Juliaの環境に関する，コンパクトな紹介記事です。
- [Pkg](https://docs.julialang.org/en/v1.0/stdlib/Pkg/); パッケージマネージャ`Pkg`の，Standard Libraryに含まれる公式ドキュメントです。

なお，以下に示すサンプルコードはWindows 10で実行していますので，パス表記がWindowsのものになっています。また，ここではJulia 1.10を使用しましたが，それ以外のバージョンでも（表示は異なるものの）挙動は同じです。

# プロジェクトと環境

## 2つの設定ファイル

Juliaのプロジェクト（project）とは，ソースファイルを含んだディレクトリ構造（ソースツリー）を指します。よくある構造は以下のようなものです。

```
└───MyProject
  │   Project.toml
  │   Manifest.toml
  │
  ├───src
  │       MyProject.jl
  │
  └───test
          runtests.jl
```

まず親ディレクトリ`MyProject`があり、その中に2つのサブディレクトリが含まれています。親ディレクトリ名はユーザが自由に決められますが、サブディレクトリ名は決められた名前を使うことになっています。上記の例は最小限のもので，2つのサブディレクトリしかありませんが，典型的には以下のようなディレクトリを含むことができます。

- ソースコードを保持する`src` 
- テストコードを含む`test`
- ドキュメントを含む`doc`
- 外部ライブラリなどの依存プログラムを含む`deps`

通常，`src`以下にはプロジェクト名と同名の`.jl`ファイル，`test`以下には`runtests.jl`が含まれます。

ソースファイルと依存ライブラリの情報を保持するファイルとして，`Project.toml`と`Manifest.toml`をプロジェクトのルートに置きます。後述するように，あるJuliaコマンドを使えば，`Project.toml`のひな形が生成されます。ユーザがすべきことは，このファイルを必要に応じて書き換えることです。また，`Manifest.toml`は，パッケージマネージャが自動で生成・更新してくれるので，ユーザが自分で編集する必要はありません。

- `Project.toml`: プロジェクトファイル。そのプロジェクトの名前，このプロジェクトが直接利用するパッケージの名前とUUID，作者名，ライセンスなどを，TOMLフォーマットで記述します。必要に応じてユーザが編集します。
- `Manifest.toml`: マニフェストファイル。自作プロジェクトが依存するすべてのパッケージの名前とUUID，およびそれらの依存関係が記述されます。自動更新されるので，ユーザが触る必要はありません。

Juliaの環境とは，プロジェクトファイルとマニフェストファイルによって定義される，一連のスクリプトとその依存パッケージが動作できる状態をいいます。プロジェクトは，その環境を提供する仕組みです。このプロジェクトのうち，他のプロジェクトで再利用されることが前提のものをパッケージ（package），特に再利用の前提にないものをアプリケーション(application)と呼びます。

この仕組みは，自作スクリプトを他者に使ってもらう場合に便利です。開発者はスクリプトと一緒に`Project.toml`と`Manifest.toml`を配布します。利用者は，それらを同一のディレクトリに置くことで，開発者の環境を再現でき，依存パッケージの解決も問題なく行えます。パッケージがやろうとしているのは，これの自動化です。

## デフォルトの環境

JuliaでREPLを立ち上げると，ユーザにはデフォルトの環境が割り当てられます。ユーザごとに`Project.toml`と`Manifest.toml`が用意されていて，これがデフォルト環境を定義しています。REPLのpkgモードでパッケージを追加すると，以下のようなメッセージを目にするでしょう。

```
# pkgモードに入る
Julia> ]

# パッケージを追加する
# パッケージのコンパイルが終わるまで待つ
(@v1.10) pkg> add Revise
   Resolving package versions...
   Installed OrderedCollections ─ v1.6.3
   Installed CodeTracking ─────── v1.3.5
   Installed JuliaInterpreter ─── v0.9.26
   Installed Revise ───────────── v3.5.9
    Updating `C:\Users\mametank\.julia\environments\v1.10\Project.toml`
  [295af30f] + Revise v3.5.9
    Updating `C:\Users\mametank\.julia\environments\v1.10\Manifest.toml`
  [da1fd8a2] + CodeTracking v1.3.5
  [aa1ae85d] + JuliaInterpreter v0.9.26
  [6f1432cf] + LoweredCodeUtils v2.3.0
  [bac558e1] + OrderedCollections v1.6.3

（以下省略）

Precompiling project...
  6 dependencies successfully precompiled in 32 seconds. 2 already precompiled.
```

ここに見えている2つのTOMLファイルが，デフォルトの環境を定義しています。このファイルの位置（`v1.10`の部分）は，Juliaのバージョンが変わるたびに変化します。

- `C:\Users\mametank\.julia\environments\v1.10\Project.toml`
- `C:\Users\mametank\.julia\environments\v1.10\Manifest.toml`

上の例では，ユーザが能動的に追加したパッケージは`Revise`で，このパッケージ名が`Project.toml`に記録されます。また，`Revise`が依存する多数のパッケージ情報は`Manifest.toml`に書き込まれます。パッケージを追加したりアップデートするたびに，これらのTOMLファイルが書き換わります。自分独自のプロジェクトを作るということは，デフォルトではないTOMLファイルを作ることであり，結果として独自環境を作ることと同じです。


# 新規プロジェクトの開発

## プロジェクトを作成する

この小節ではREPLのPkgモードで`generate`コマンドを利用します。このコマンドは，プロジェクトの，最小限のひな形を作成します。

2021年2月21日追記：正規のパッケージを作成するには，もう少し多くの手順が必要です。Juliaにはパッケージの作成を助ける仕組みがいくつかあります。たとえば，他のブログやQiita記事で言及されることの多いPkgTemplateは，そのためのものです。詳しくは[次の記事](https://qiita.com/mametank/items/43330a9452f0039ca22d)で紹介します。この記事では，それらの基礎である，素のプロジェクトを作成・編集することを目的としています。

新たにプロジェクトを作ってみましょう。最初のステップはpkgモードで，`generate`コマンドを入力することです。以下の例は，`SmallProject`というプロジェクトを新規作成します。

```
# プロジェクトの新規作成
(v1.1) pkg> generate SmallProject
Generating project SmallProject:
    SmallProject\Project.toml
    SmallProject/src/SmallProject.jl
```

このコマンドは，現在のディレクトリの直下に`SmallProject`という名前のディレクトリを作成し，その中に`Project.toml`のひな形を作成します。同名のディレクトリが存在する場合にはエラーになります。もしすでにソースツリーを持っているならば，この作業は不要です。

## 独立した環境に移行する

続けて，`activate`コマンドを使い，いま作成したディレクトリ（あるいは既存のソースツリー）をプロジェクトのルートとして，そのディレクトリ内を独立した環境として扱います。

```
# プロジェクトを別の環境として扱う
(v1.1) pkg> activate SmallProject
```

この`activate`の引数は，パス名です。もちろんフルパスも利用できます。もしすでに既存プロジェクトのルートに移動しているなら，パス名に `.` が指定できます。

このコマンドで環境を変更すると，pkgモードの表記が変わり，プロジェクトを含むディレクトリ名になります。上の例の場合は，以下の表記になるでしょう。

```
# activate後の表示
(SmallProject) pkg>
```

現在の環境を去ってデフォルト環境に戻る（deactivate）には，引数なしで`activate`を実行します。なお、deactivateという名前のコマンドは存在しないので，ハマらないように注意が必要です。

```
# 現在の環境を去る
(SmallProject) pkg> activate
```

## 現在の環境のカスタマイズ

新しい環境ができたら，まずは必要なパッケージを追加します。この過程で`Project.toml`と`Manifest.toml`が新規作成（あるいは既存のファイルがアップデート）されます。

### 必要なパッケージを列挙する

現在の新しい環境は，デフォルトの環境とは独立です。ただし，その環境でも，デフォルト環境でインストールされたパッケージが利用できます。ここで新しい環境が「独立」なのは，特定のバージョンのパッケージを他の環境とは別にインストールできるという意味です。また，新しい環境でのみインストールされたパッケージは，他の環境で使うことはできません。つまり，必要なパッケージをすべて指定さえすれば，その環境は他とは独立になります。

まず手始めに，新しい環境で，デフォルト環境が持たないパッケージをインストールしてみましょう。Juliaにはサンプル用のダミーパッケージ`Example`があります。これを新しい環境にインストールします。

```
(SmallProject) pkg> add Example
Resolving package versions...
 Updating `c:\Users\mametank\local\src\julia\SmallProject\Project.toml`
 [7876af07] + Example v0.5.1
 Updating `c:\Users\mametank\local\src\julia\SmallProject\Manifest.toml`
 [7876af07] + Example v0.5.1
 [2a0f44e3] + Base64
 [8ba89e20] + Distributed
 [b77e0a4c] + InteractiveUtils
 [56ddb016] + Logging
 [d6f4376e] + Markdown
 [9a3f8284] + Random
 [9e88b42a] + Serialization
 [6462fe0b] + Sockets
 [8dfed614] + Test
```

パッケージの情報が，さきほど作成したプロジェクト内の`Project.toml`と`Manifest.toml`に書き込まれています。このパッケージは，新しい環境では`using`で読み込めますが，デフォルト環境では読み込めません。


```
# 新しい環境では読み込める
(SmallProject) pkg> ^C

julia> using Example

julia>

# デフォルト環境に戻る
julia> ]

(SmallProject) pkg> activate

pkg> ^C

# デフォルト環境で読み込むとエラーになる
julia> using Example
ERROR: ArgumentError: Package Example not found in current path:
- Run `import Pkg; Pkg.add("Example")` to install the Example package.
```

同様に，デフォルト環境とは違うバージョンのパッケージをインストールできます。Githubなどのオンラインレポジトリ上のパッケージもインストールできます。いちどにすべてのパッケージを追加する必要はありません。プログラムを書きながら，必要に応じてこのプロジェクトで必要なパッケージを追加します。

現在の環境でどのパッケージが利用できるかは，`status`コマンドで確認できます。

```
(SmallProjects) pkg> status
Project SmallProject v0.1.0
    Status `c:\Users\mametank\local\src\julia\SmallProject\Project.toml`
  [7876af07] Example v0.5.1
```


### プロジェクトの詳細を記述する

さて，自動生成された`Project.toml`は，アプリケーションとして使う場合には，特に編集する必要はありません。このプロジェクトをパッケージにする場合には，このファイルをエディタで開き，情報を追加する必要があります。このファイルの中身は以下のようになっています。

```toml
name = "SmallProject"
uuid = "43072010-71ec-11e9-01e7-a1d42b769800"
authors = ["Your Name <yourname@example.com>"]
version = "0.1.0"

[deps]
Example = "7876af07-990d-54b4-ab0e-23690620f79a"
```

この中の`[deps]`ブロックは，パッケージマネージャが自動的に書き換えるので触れずにおきます。ユーザが編集する必要があるのは，`name`，`authors`，`version`です。この中のUUIDは，プロジェクトを識別するための文字列で，`generate`の段階でパッケージマネージャがランダムに生成したものです。この仕組みのおかげで，同じ名前のプロジェクト（ただし異なるUUID）をいくつも作ることができます。

このファイルは，これ以外にも手作業で編集する余地があります。詳しくは[パッケージ作成の記事](https://qiita.com/mametank/items/43330a9452f0039ca22d)で紹介します。

### ソースコードの編集

`generate`コマンドは，`src`以下にソースコードのひな形を作ります。このファイルには，シンプルなモジュールが定義されています。特にモジュールが必要ないのであれば，中身をすべて書き換えてもよいでしょう。もしパッケージを作る予定があるなら，ソースをこのモジュールに含めるように開発します。


# まとめ

- プロジェクトは規定のディレクトリといくつかのファイルを含む。
- プロジェクトを再利用可能にしたものがパッケージである。
- プロジェクト内にある2つのファイル`Project.toml`と`Manifest.toml`が環境を定義する。
    - `Project.toml`はそのプロジェクトのメタ情報と，それが直接呼び出すパッケージの情報が含まれる。ユーザが必要に応じて編集する。
    - `Manifest.toml`には，パッケージの依存関係が再帰的にすべて記録される。パッケージマネージャが自動生成するので、ユーザが編集する必要はない。
- pkgモードのコマンドは，プロジェクト（環境）に関する処理を行う。
    - `generate`は，プロジェクトを新規作成する。
    - `activate`は，そのプロジェクト固有の環境に切り替える（あるいはデフォルトの環境に戻す）。
    - `add`は，現在の環境にパッケージを追加する。
    - `status`は，ある環境で利用できるパッケージを表示できる。