🐡

Juliaのプロジェクト環境とパッケージ

2023/05/27に公開

Juliaを使った開発では、自作パッケージを作り、それらを複数組み合わせながら開発を行うことが多くあります。その時、パッケージ、プロジェクト、環境の意味を理解していないと混乱することがあります。

その時は、https://docs.julialang.org/en/v1/manual/code-loading/を読んで下さい。

プロジェクト環境 (Project environment)

プロジェクト環境とは、プロジェクトファイルProject.toml、オプションとしてManifest.tomlが含まれるディレクトリです。Project.tomlには、そのプロジェクト (以下プロジェクトA)が直接依存するパッケージやバージョンの範囲が記述されています。それらのパッケージは、別のパッケージに依存してます。これらを間接的な依存関係と呼びます。

ここで、プロジェクトファイルを含むディレクトリは、多くの場合パッケージの作成に用いることが多いですが、必ずしもその必要はないことに注意しましょう。例えば、ある特定の作業に必要な、ソースファイル、ノートブック等とそれらの依存性をまとめることもできます。

そして、ここで重要なのは、「グローバル環境」の概念です。これは、特定のプロジェクト環境がアクティブになっていない場合にデフォルトとして使用される環境を指します。Julia 1.9の場合、~/.julia/environments/v1.9/Project.tomlで管理されています。このように、グローバル環境もプロジェクト環境と統一して扱われているのです。

ここでは、話を単純にするために、パッケージを作成して、それをプロジェクト環境として使用する場合に特定しましょう。

独自パッケージの作り方

Pkg.generateを使って新しいパッケージを生成することができます。実用的には、PkgTemplates.jlなどを使って、ユニットテスト等のひな形なども同時に作ることが多いですが、先ずは最もシンプルな方法を試してみましょう。以下では、~/workspace以下にいることを仮定します。

using Pkg
Pkg.generate("MyPackage")

これにより、workspace以下にMyPackageという名前の新しいパッケージが作成されます。ファイル構成はこのようになっているはずです。

MyPackage
├── Project.toml
└── src
    └── MyPackage.jl

ここで、Project.tomlがプロジェクト環境の定義で、”src/MyPackage.jl”はパッケージのソースコードです。Project.tomlは以下の様な内容のはずです。

name = "MyPackage"
uuid = "eb66ca03-d5f6-4852-a7de-5dc300cf5d77"
authors = ["Hiroshi Shinaoka <h.shinaoka@gmail.com>"]
version = "0.1.0"

ここで、UUIDは "Universally Unique Identifier" の略で、ランダムに生成された一意性が保証される識別子のことを指します。今作ったパッケージのUUIDは、地球上の他の全てのパッケージとは異なります。このようにすることで、同じ名前を持つ複数のパッケージを区別します。

一方、authorsフィールドには、Gitの情報 (後述)から取得された著者の情報が利用されました。設定していない場合には、コンピュータのアカウント名が用いられます。パッケージの生成前に、以下の様に名前とe-mail addressを設定しておきましょう。

git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

一方、src/MyPackage.jlでは、MyModuleという名前空間と、その中にgreet()という関数が定義されています。

module MyPackage

greet() = print("Hello World!")

end # module MyPackage

プロジェクト環境の有効化

JuliaのREPLを起動した直後、グローバル環境が有効になっています。確認するために、]キーを押してパッケージモードに入り、次のように入力します。

(@v1.9) pkg> status
Status `~/.julia/environments/v1.9/Project.toml`

出力の1行目は、グローバル環境を定義するプロジェクトファイルのpath、2行目以降はグローバル環境にインストールされているパッケージの一覧が表示されます。

さて、作ったMyPackageをプロジェクト環境として使ってみましょう。3通りの方法を紹介します。

(方法1) Pkgモジュールのactivate関数

Pkgモジュールのactivate関数を使用して、特定のディレクトリ内のプロジェクト環境を有効にすることができます。

julia> using Pkg
julia> Pkg.activate("~/workspace/MyPackage")

(方法2) JuliaのREPL経由のパッケージモード

JuliaのREPLで、]キーを押してパッケージモードに入り、次のように入力します。この場合、activate後、パッケージモードのプロンプトが現在有効なパッケージ環境に変わったことが分かります。

(@v1.9) pkg> activate ~/workspace/MyPackage
  Activating project at `~/workspace/MyPackage`
(MyPackage) pkg>

現在有効なプロジェクト環境を確認するには、パッケージモードに入ってプロンプトを確認するか、REPLで以下のようにProject.tomlの場所を調べてください。

julia> using Pkg
julia> Pkg.project()

プロジェクト環境の中では、MyPackageパッケージをimportして使うことが出来ます。

(@v1.9) pkg> activate .
  Activating project at `~/workspace/MyPackage`

julia> import MyPackage

julia> MyPackage.greet()
Hello World!

(方法3) VS Code

VS Codeでパッケージディレクトリを開いて、プロジェクト環境を有効に出来ます。詳しくは、以下のURLを参考にして下さい。特に、VS codeのJupyter拡張機能を使ってJupyterノートブックを使う場合にも便利です。

https://www.julia-vscode.org/docs/stable/userguide/env/

試しに、MyPackage/notebook/import_test.ipynbという空のファイルを作成して、VS Codeから開いてみましょう。

> cd ~/workspace/MyPackage
> mkdir notebook
> touch notebook/import_test.ipynb
> code . # VS Codeを起動。お好みの方法で!

プロジェクト環境に依存パッケージを追加

さて、MyPackageプロジェクト (今回の場合にはライブラリとして使う)に依存ライブラリを追加していきましょう。手順は以下の通りです。

  1. パッケージモードに入って、MyPackage環境を有効にする (上記参照)
  2. 依存するライブラリをaddコマンドで追加

以下の例では、LinearAlgebraライブラリを追加しています。

cd MyPackage # パッケージのTOPディレクトリへ移動
# JuliaのREPLを起動
julia>
(@v1.9) pkg> # ]キーを押してパッケージモードへ (デフォルトはグローバル環境)
(@v1.9) pkg> activate . # プロジェクト環境を有効化
(MyPackage) pkg> add LinearAlgebra
   Resolving package versions...
    Updating `~/workspace/MyPackage/Project.toml`
  [37e2e46d] + LinearAlgebra
    Updating `~/workspace/MyPackage/Manifest.toml`
  [56f22d72] + Artifacts
  [8f399da3] + Libdl
  [37e2e46d] + LinearAlgebra
  [e66e0078] + CompilerSupportLibraries_jll v1.0.2+0
  [4536629a] + OpenBLAS_jll v0.3.21+4
  [8e850b90] + libblastrampoline_jll v5.7.0+0
Precompiling project...
  1 dependency successfully precompiled in 1 seconds

LinearAlgebraやその依存ライブラリがインストールされました。また、同時に、MyPackageプロジェクトのProject.tomlファイル、Manifest.tomlファイルが更新されたことも分かります。

この段階で、Project.tomlファイルをみると、

name = "MyPackage"
uuid = "eb66ca03-d5f6-4852-a7de-5dc300cf5d77"
authors = ["Hiroshi Shinaoka <h.shinaoka@gmail.com>"]
version = "0.1.0"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

となっているでしょう。[deps] (直接的な依存関係)のセクションに、LinearAlgebraとそのUUIDが追加されたことが分かります。一方、Manifest.tomlファイルには、LinearAlgebraやそれを通して間接的に依存しているパッケージと、今回インストールされた具体的なバージョン番号が書き込まれました。

# This file is machine-generated - editing it directly is not advised

julia_version = "1.9.0"
manifest_format = "2.0"
project_hash = "ac1187e548c6ab173ac57d4e72da1620216bce54"

[[deps.Artifacts]]
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"

[[deps.CompilerSupportLibraries_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
version = "1.0.2+0"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

[[deps.LinearAlgebra]]
deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"]
uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

[[deps.OpenBLAS_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"]
uuid = "4536629a-c528-5b80-bd46-f80d51c5b363"
version = "0.3.21+4"

[[deps.libblastrampoline_jll]]
deps = ["Artifacts", "Libdl"]
uuid = "8e850b90-86db-534c-a0d3-1478176c7d93"
version = "5.7.0+0"

実験のため、src/MyPackage.jlに、与えられた2つのベクトルの内積を使う関数を追加してみましょう。実際には、LinearAlgebra内のdot関数を呼び出します。

module MyPackage

import LinearAlgebra # import忘れないように!

greet() = print("Hello World!")

# Compute dot
function mydot(x, y)
    return LinearAlgebra.dot(x, y)
end

end # module MyPackage

新しいREPLを起動して、MyPackageプロジェクト環境の中で実行できることが分かります。

julia> using Pkg; Pkg.activate("~workspace/MyPackage")
  Activating new project at `~/~workspace/MyPackage`

julia> import MyPackage
[ Info: Precompiling MyPackage [eb66ca03-d5f6-4852-a7de-5dc300cf5d77]

julia> MyPackage.mydot([1, 2], [2, 1])
4

プロジェクト環境に依存パッケージを追加 (手動編)

Project.tomlは手動で編集することもできますが、Manifest.tomlは手で修正してはいけません。また、ライブラリとしてMyPackageを公開する際、後者は通常配付しません。Project.tomlの使い方を理解するために、手動で編集してみましょう。

例えば、Plots.jl (グラフ描画ライブラリ)を依存関係に追加するには、まずUUIDを調べる必要があります。Juliaの公式パッケージレジストリである、https://github.com/JuliaRegistries/General.gitにアクセスして、アルファベット文字毎に整理されたディレクトリを探索して、以下の、PlotsパッケージのPackage.tomlを読むと、UUID (91a5bcdd-55d7-5caf-9e0b-520d859cae80)が分かります。

https://github.com/JuliaRegistries/General/blob/master/P/Plots/Package.toml

次に、以下の様にProject.tomlに、依存関係を追加します。

name = "MyPackage"
uuid = "eb66ca03-d5f6-4852-a7de-5dc300cf5d77"
authors = ["Hiroshi Shinaoka <h.shinaoka@gmail.com>"]
version = "0.1.0"

[deps]
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"

次にREPLを起動して、パッケージモードに入り、MyPackage環境を有効化して、以下のコマンドを実行します。

(MyPackage) pkg> status
Project MyPackage v0.1.0
Status `~/workspace/MyPackage/Project.toml`
Warning The project dependencies or compat requirements have changed since the manifest was last resolved. It is recommended to `Pkg.resolve()` or consider `Pkg.update()` if necessary.

ここでは、Pkg.resolve()を使って、プロジェクトの依存関係を解決してみましょう。パッケージモードのまま、resolveコマンドを実行すると、

(MyPackage) pkg> resolve
   Installed GR_jll ────────── v0.72.7+0
   Installed Unitful ───────── v1.14.0
   Installed UnitfulLatexify ─ v1.6.3
   Installed Plots ─────────── v1.38.13
   Installed GR ────────────── v0.72.7
  Downloaded artifact: GR
    Updating `~/workspace/Project.toml`
  [91a5bcdd] + Plots v1.38.13
    Updating `~/workspace/Manifest.toml`
  [d1d4a3ce] + BitFlags v0.1.7
  [944b1d66] + CodecZlib v0.7.1
  [35d6a980] + ColorSchemes v3.21.0

# (中略)

  [91a5bcdd] + Plots v1.38.13

# (略)

となります。手動で追加された依存パッケージ (Plots)と、その依存パッケージがパッケージがプロジェクト環境にインストールされると共に、実際にインストールされたパッケージの情報がManifest.tomlに記入されます。

この段階で、この章の最初にLinearAlgebraパッケージをaddコマンドで追加したとき、パッケージのインストールと、依存関係の追加という2つの作業が同時に行われたことが分かるでしょう。多くの一般ユーザの場合、addコマンドを利用する方が楽でしょう。

グローバル環境から開発中のライブラリを利用する

以上では、MyPackage環境の「中」で作業する方法を学んできました。では、グローバル環境からMyPackageをパッケージ・ライブラリとして使うのにはどうすればいいでしょうか?

ここでは、開発モードとしてインストールする方法を紹介します。グローバル環境から、複数の開発中のパッケージを同時に利用する場合、便利です。
ただ、後述のように、使い方に注意が必要です。

devモードでこのパッケージをインストールするには、グローバル環境のパッケージモードで以下の様に実行します。

(@v1.9) pkg> dev ~/workspace/MyPackage
   Resolving package versions...
    Updating `~/.julia/environments/v1.9/Project.toml`
  [eb66ca03] + MyPackage v0.1.0 `~/workspace/MyPackage`
    Updating `~/.julia/environments/v1.9/Manifest.toml`
  [eb66ca03] + MyPackage v0.1.0 `~/workspace/MyPackage`

ここで、グローバル環境のProject.tomlが更新されたことに注意して下さい。この段階で、もしMyPacakgeの依存関係が、グローバル環境にインストールされている他のパッケージの依存関係と矛盾する場合、インストールに失敗します。

グローバル環境からMyPackageをアンインストールするには、以下の様にします。

(@v1.9) pkg> rm MyPackage
    Updating `~/.julia/environments/v1.9/Project.toml`
  [eb66ca03] - MyPackage v0.1.0 `~/workspace/MyPackage`
    Updating `~/.julia/environments/v1.9/Manifest.toml`
  [eb66ca03] - MyPackage v0.1.0 `~/workspace/MyPackage`

devモードでインストールした場合、パッケージモードからMyPackageのstatusを調べると、~/workspace/MyPacakgeが参照されている事が分かります。

(@v1.9) pkg> status MyPackage
Status `~/.julia/environments/v1.9/Project.toml`
  [eb66ca03] MyPackage v0.1.0 `~/workspace/MyPackage`

ここで、重要な注意です。もし、MyPackage/Project.tomlを変更し、MyPackageの依存関係を更新した場合、グローバル環境にどのように影響するのでしょうか?以下の様な実験をしてみましょう

  1. MyPackageがグローバル環境にdevモードでインストールされている
  2. MyPackage環境を有効にして、新しい依存パッケージ”Revise”を追加
(MyPackage) pkg> add Revise
   Resolving package versions...
    Updating `~/workspace/MyPackage/Project.toml`
  [295af30f] + Revise v3.5.2
    Updating `~/workspace/MyPackage/Manifest.toml`
  [da1fd8a2] + CodeTracking v1.3.1
  [aa1ae85d] + JuliaInterpreter v0.9.23
  [6f1432cf] + LoweredCodeUtils v2.3.0
  [295af30f] + Revise v3.5.2
  [8ba89e20] + Distributed
  1. MyPackage/src/MyPackage.jlに”import Revise”を追加
module MyPackage

greet() = print("Hello World!")

import Revise # 追加

end # module MyPackage
  1. 新しくRERLを起動して、グローバル環境でMyPackageをimportする。
julia> import MyPackage
[ Info: Precompiling MyPackage [eb66ca03-d5f6-4852-a7de-5dc300cf5d77]
ERROR: LoadError: ArgumentError: Package MyPackage does not have Revise in its dependencies:
- You may have a partially installed environment. Try `Pkg.instantiate()`
  to ensure all packages in the environment are installed.
- Or, if you have MyPackage checked out for development and have
  added Revise as a dependency but haven't updated your primary
  environment's manifest file, try `Pkg.resolve()`.
- Otherwise you may need to report an issue with MyPackage

ここで、グローバル環境には、開発モードでインストールされているMyPackageの依存関係が変わったことが正しく反映されていないため、エラーが起きました。「グローバル環境」の依存関係を解消するため、グローバル環境のパッケージモードでresolveを実行することで、正しく反映されます。

(@v1.9) pkg> resolve # グローバル環境で実行
  No Changes to `~/.julia/environments/v1.9/Project.toml`
  No Changes to `~/.julia/environments/v1.9/Manifest.toml`

julia> import MyPackage
[ Info: Precompiling MyPackage [eb66ca03-d5f6-4852-a7de-5dc300cf5d77]

julia>

グローバル環境に開発中のライブラリを追加する際には注意しておきましょう。

今回解説しなかった重要事項

  • Project.tomlにおいて、依存パッケージのバージョンを指定する方法
  • MyPackageのバージョン変更・管理
  • 複数の開発パッケージ間での依存性

Discussion