Code Loading
この章では、パッケージの読み込みに関する技術的な詳細を説明します。パッケージをインストールするには、Pkg、Juliaの組み込みパッケージマネージャを使用して、アクティブな環境にパッケージを追加します。アクティブな環境にすでにあるパッケージを使用するには、import X または using X と書きます。これは、Modules documentationで説明されています。
Definitions
Juliaにはコードを読み込むための2つのメカニズムがあります:
- コードのインクルージョン: 例:
include("source.jl")。インクルージョンを使用すると、単一のプログラムを複数のソースファイルに分割できます。式include("source.jl")は、include呼び出しが発生するモジュールのグローバルスコープでsource.jlファイルの内容を評価させます。include("source.jl")が複数回呼び出されると、source.jlは複数回評価されます。インクルードされたパスsource.jlは、include呼び出しが発生するファイルに対して相対的に解釈されます。これにより、ソースファイルのサブツリーを簡単に移動できます。REPLでは、インクルードされたパスは現在の作業ディレクトリに対して相対的に解釈されます。pwd()。 - パッケージの読み込み: 例:
import Xまたはusing X。インポートメカニズムは、パッケージ—すなわち、モジュールにラップされた独立した再利用可能なJuliaコードのコレクション—を読み込むことを可能にし、インポートするモジュール内でXという名前で結果のモジュールを利用できるようにします。同じXパッケージが同じJuliaセッションで複数回インポートされる場合、最初のインポート時にのみ読み込まれます—その後のインポートでは、インポートするモジュールは同じモジュールへの参照を取得します。ただし、import Xは異なるコンテキストで異なるパッケージを読み込むことができることに注意してください:Xはメインプロジェクト内のXという名前の1つのパッケージを指すことができますが、各依存関係内で同じ名前の異なるパッケージを指す可能性もあります。これについては以下で詳しく説明します。
コードのインクルージョンは非常に簡単で明確です:指定されたソースファイルを呼び出し元のコンテキストで評価します。パッケージのロードはコードのインクルージョンの上に構築されており、different purposeに役立ちます。この章の残りの部分は、パッケージのロードの動作とメカニズムに焦点を当てています。
パッケージは、他のJuliaプロジェクトで再利用可能な機能を提供する標準的なレイアウトを持つソースツリーです。パッケージはimport Xまたはusing Xステートメントによってロードされます。これらのステートメントは、パッケージコードをロードすることによって得られるXという名前のモジュールを、インポートステートメントが発生するモジュール内で利用可能にします。import XにおけるXの意味は文脈に依存します:どのXパッケージがロードされるかは、そのステートメントが発生するコードによって決まります。したがって、import Xの処理は二段階で行われます。第一に、この文脈でXとして定義される何のパッケージがあるかを決定し、第二に、その特定のXパッケージがどこにあるかを決定します。
これらの質問は、プロジェクトファイル(Project.tomlまたはJuliaProject.toml)、マニフェストファイル(Manifest.tomlまたはJuliaManifest.toml、または特定のバージョンのために-v{major}.{minor}.tomlでサフィックスされた同じ名前)、またはソースファイルのフォルダーを探すことによって、LOAD_PATHにリストされたプロジェクト環境を通じて回答されます。
Federation of packages
ほとんどの場合、パッケージはその名前から一意に識別できます。しかし、時にはプロジェクトが同じ名前を持つ2つの異なるパッケージを使用する必要がある状況に直面することがあります。1つのパッケージの名前を変更することでこれを修正できるかもしれませんが、そうすることを強いられると、大規模で共有されたコードベースでは非常に混乱を招く可能性があります。代わりに、Juliaのコード読み込みメカニズムは、同じパッケージ名がアプリケーションの異なるコンポーネントで異なるパッケージを指すことを可能にします。
Juliaは、複数の独立した当事者が公共およびプライベートのパッケージとパッケージのレジストリを維持できることを意味する連邦パッケージ管理をサポートしています。また、プロジェクトは異なるレジストリからの公共およびプライベートのパッケージの混合に依存できます。さまざまなレジストリからのパッケージは、共通のツールとワークフローを使用してインストールおよび管理されます。Juliaに付属するPkgパッケージマネージャーを使用すると、プロジェクトの依存関係をインストールおよび管理できます。これは、プロジェクトが依存する他のプロジェクトを説明するプロジェクトファイルの作成と操作を支援し、プロジェクトの完全な依存関係グラフの正確なバージョンをスナップショットするマニフェストファイルを作成します。
フェデレーションの一つの結果は、パッケージ名に対する中央権限が存在しないことです。異なるエンティティが無関係なパッケージを指すために同じ名前を使用することがあります。この可能性は避けられません。なぜなら、これらのエンティティは調整を行わず、お互いの存在すら知らないかもしれないからです。中央の命名権限がないため、単一のプロジェクトが同じ名前を持つ異なるパッケージに依存することがあるかもしれません。Juliaのパッケージロードメカニズムは、パッケージ名が単一のプロジェクトの依存関係グラフ内でさえもグローバルに一意である必要はありません。代わりに、パッケージは universally unique identifiers (UUID) によって識別され、各パッケージが作成されるときに割り当てられます。通常、これらのやや扱いにくい128ビットの識別子を直接扱う必要はありません。なぜなら、Pkg がそれらの生成と追跡を行ってくれるからです。しかし、これらのUUIDは 「X はどのパッケージを指しているのか?」 という質問に対する決定的な答えを提供します。
分散型命名問題はやや抽象的であるため、具体的なシナリオを通じて問題を理解するのが役立つかもしれません。あなたが App というアプリケーションを開発しているとしましょう。このアプリケーションは、Pub と Priv の2つのパッケージを使用しています。Priv はあなたが作成したプライベートパッケージであり、Pub はあなたが使用しているが制御していないパブリックパッケージです。あなたが Priv を作成したとき、Priv という名前のパブリックパッケージは存在していませんでした。しかし、その後、無関係なパッケージが Priv という名前で公開され、人気を博しました。実際、Pub パッケージはそれを使用し始めました。したがって、次に Pub をアップグレードして最新のバグ修正や機能を取得すると、App は異なる2つの Priv パッケージに依存することになります—アップグレード以外のあなたの行動によるものではありません。App はあなたのプライベート Priv パッケージに直接依存しており、Pub を通じて新しいパブリック Priv パッケージに間接的に依存しています。これらの2つの Priv パッケージは異なりますが、App が正しく動作し続けるためには両方とも必要です。そのため、import Priv という表現は、App のコード内で発生するか、Pub のコード内で発生するかによって異なる Priv パッケージを指す必要があります。これを処理するために、Julia のパッケージ読み込みメカニズムは、UUID によって2つの Priv パッケージを区別し、その文脈(import を呼び出したモジュール)に基づいて正しいものを選択します。この区別がどのように機能するかは、以下のセクションで説明される環境によって決まります。
Environments
環境は、さまざまなコードコンテキストにおける import X と using X の意味、およびこれらのステートメントがどのファイルをロードするかを決定します。Juliaは2種類の環境を理解しています:
- プロジェクト環境は、プロジェクトファイルとオプションのマニフェストファイルを含むディレクトリであり、明示的な環境を形成します。プロジェクトファイルは、プロジェクトの直接依存関係の名前とアイデンティティを決定します。マニフェストファイルが存在する場合、すべての直接および間接依存関係、各依存関係の正確なバージョン、および正しいバージョンを特定してロードするための十分な情報を含む完全な依存関係グラフを提供します。
- パッケージディレクトリは、一連のパッケージのソースツリーをサブディレクトリとして含むディレクトリであり、暗黙の環境を形成します。
Xがパッケージディレクトリのサブディレクトリであり、X/src/X.jlが存在する場合、パッケージディレクトリ環境でパッケージXが利用可能であり、X/src/X.jlはそれがロードされるソースファイルです。
これらはスタック環境を作成するために混合することができます:プロジェクト環境とパッケージディレクトリの順序付けられたセットが重ね合わされて、単一の複合環境を作ります。その後、優先順位と可視性のルールが組み合わさって、どのパッケージが利用可能で、どこからロードされるかを決定します。たとえば、Juliaのロードパスはスタック環境を形成します。
これらの環境はそれぞれ異なる目的に役立ちます:
- プロジェクト環境は再現性を提供します。プロジェクト環境をバージョン管理にチェックインすることで—例えば、gitリポジトリに—プロジェクトのソースコードの残りとともに、プロジェクトの正確な状態とそのすべての依存関係を再現できます。特にマニフェストファイルは、ソースツリーの暗号学的ハッシュによって特定されたすべての依存関係の正確なバージョンをキャプチャし、
Pkgが正しいバージョンを取得し、すべての依存関係に対して記録された正確なコードを実行していることを確認できるようにします。 - パッケージディレクトリは、完全に注意深く追跡されたプロジェクト環境が不要な場合に便利です。これらは、一連のパッケージをどこかに配置し、それらを直接使用できるようにしたいときに役立ちます。プロジェクト環境を作成する必要はありません。
- スタック環境は、ツールを追加することを可能にします。開発ツールの環境をスタックの末尾にプッシュすることで、REPLやスクリプトから利用できるようになりますが、パッケージ内からは利用できません。
別の環境からスタック内のパッケージを読み込むと、アクティブな環境のコンテキストでパッケージが読み込まれます。これは、パッケージがアクティブな環境でインポートされたかのように読み込まれることを意味し、その依存関係のバージョンの解決に影響を与える可能性があります。そのようなパッケージがプリコンパイルされる際には、(serial) プリコンパイルジョブとしてマークされ、同じジョブ内で依存関係がシリーズでプリコンパイルされることになります。これはおそらく遅くなるでしょう。
高レベルでは、各環境は概念的に3つのマップを定義します:ルーツ、グラフ、パスです。import Xの意味を解決する際に、ルーツとグラフのマップがXのアイデンティティを決定するために使用され、パスのマップがXのソースコードを特定するために使用されます。3つのマップの具体的な役割は次のとおりです:
ルーツ:
name::Symbol⟶uuid::UUID環境のルーツマップは、環境がメインプロジェクトに提供するすべてのトップレベルの依存関係に対して、パッケージ名をUUIDに割り当てます(つまり、
Mainで読み込むことができるもの)。Juliaがメインプロジェクトでimport Xに遭遇すると、roots[:X]としてXの識別子を検索します。グラフ:
context::UUID⟶name::Symbol⟶uuid::UUID環境のグラフは、各
contextUUID に対して名前から UUID へのマップを割り当てる多層マップであり、ルーツマップに似ていますが、そのcontextに特有のものです。Julia が UUID がcontextのパッケージのコード内でimport Xを見たとき、Xのアイデンティティをgraph[context][:X]として検索します。特に、これはimport Xがcontextに応じて異なるパッケージを参照できることを意味します。パス:
uuid::UUID×name::Symbol⟶path::Stringパスマップは、各パッケージのUUID-名前ペアに、そのパッケージのエントリーポイントソースファイルの場所を割り当てます。
import XにおけるXの識別が、ルーツまたはグラフを介してUUIDに解決されると(それがメインプロジェクトから読み込まれるか、依存関係から読み込まれるかに応じて)、Juliaは環境内のpaths[uuid,:X]を参照してXを取得するために読み込むべきファイルを決定します。このファイルを含めることで、Xという名前のモジュールが定義されるはずです。このパッケージが読み込まれると、その後の同じuuidに解決されるインポートは、すでに読み込まれたパッケージモジュールへの新しいバインディングを作成します。
各種環境は、以下のセクションで詳述されているように、これらの3つのマップを異なって定義します。
この章の例では、ルート、グラフ、パスの完全なデータ構造を示しており、理解を容易にしています。しかし、Juliaのパッケージロードコードは、これらを明示的に作成するわけではありません。代わりに、特定のパッケージをロードするために必要な分だけ、各構造を遅延的に計算します。
Project environments
プロジェクト環境は、Project.tomlというプロジェクトファイルを含むディレクトリによって決定され、オプションでManifest.tomlというマニフェストファイルも含まれます。これらのファイルは、JuliaProject.tomlおよびJuliaManifest.tomlと呼ばれることもあり、その場合はProject.tomlおよびManifest.tomlは無視されます。これにより、Project.tomlおよびManifest.tomlと呼ばれるファイルを重要視する他のツールとの共存が可能になります。しかし、純粋なJuliaプロジェクトの場合、Project.tomlおよびManifest.tomlという名前が好まれます。ただし、Julia v1.10.8以降、(Julia)Manifest-v{major}.{minor}.tomlは、特定のマニフェストファイルを使用するために特定のjuliaバージョンを使用する形式として認識されます。つまり、同じフォルダー内で、Manifest-v1.11.tomlはv1.11によって使用され、Manifest.tomlは他の任意のjuliaバージョンによって使用されます。
プロジェクト環境のルーツ、グラフ、パスマップは次のように定義されています:
環境のルーツマップは、プロジェクトファイルの内容によって決定されます。具体的には、最上位のnameおよびuuidエントリと[deps]セクション(すべてオプション)です。以下は、前述の仮想アプリケーションAppの例のプロジェクトファイルです。
name = "App"
uuid = "8f986787-14fe-4607-ba5d-fbff2944afa9"
[deps]
Priv = "ba13f791-ae1d-465a-978b-69c3ad90f72b"
Pub = "c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"このプロジェクトファイルは、次のルートマップを示唆しています。これは、Juliaの辞書で表現された場合です:
roots = Dict(
:App => UUID("8f986787-14fe-4607-ba5d-fbff2944afa9"),
:Priv => UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b"),
:Pub => UUID("c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"),
)与えられたこのルーツマップでは、Appのコード内のimport Privという文は、Juliaがroots[:Priv]を参照することを引き起こし、ba13f791-ae1d-465a-978b-69c3ad90f72b、つまりそのコンテキストで読み込まれるPrivパッケージのUUIDを返します。このUUIDは、メインアプリケーションがimport Privを評価する際に、どのPrivパッケージを読み込んで使用するかを特定します。
プロジェクト環境の依存関係グラフは、存在する場合、マニフェストファイルの内容によって決まります。マニフェストファイルがない場合、グラフは空です。マニフェストファイルには、プロジェクトの直接または間接の依存関係ごとにスタンザが含まれています。各依存関係について、ファイルにはパッケージのUUIDとソースコードへのソースツリーのハッシュまたは明示的なパスがリストされます。以下はAppのための例のマニフェストファイルです:
[[Priv]] # the private one
deps = ["Pub", "Zebra"]
uuid = "ba13f791-ae1d-465a-978b-69c3ad90f72b"
path = "deps/Priv"
[[Priv]] # the public one
uuid = "2d15fe94-a1f7-436c-a4d8-07a9a496e01c"
git-tree-sha1 = "1bf63d3be994fe83456a03b874b409cfd59a6373"
version = "0.1.5"
[[Pub]]
uuid = "c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"
git-tree-sha1 = "9ebd50e2b0dd1e110e842df3b433cb5869b0dd38"
version = "2.1.4"
[Pub.deps]
Priv = "2d15fe94-a1f7-436c-a4d8-07a9a496e01c"
Zebra = "f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"
[[Zebra]]
uuid = "f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"
git-tree-sha1 = "e808e36a5d7173974b90a15a353b564f3494092f"
version = "3.4.2"このマニフェストファイルは、Appプロジェクトの可能な完全依存関係グラフを説明しています:
アプリケーションが使用する
Privという名前の異なる2つのパッケージがあります。1つはルート依存関係であるプライベートパッケージで、もう1つはPubを通じての間接依存関係であるパブリックパッケージです。これらはそれぞれ異なるUUIDによって区別されており、異なる依存関係を持っています:- プライベート
PrivはPubとZebraパッケージに依存しています。 - 公開の
Privは依存関係がありません。
- プライベート
アプリケーションはまた、
Pubパッケージに依存しており、これはさらに公開されたPrivと、プライベートPrivパッケージが依存しているのと同じZebraパッケージに依存しています。
この依存関係グラフは辞書として表現され、次のようになります:
graph = Dict(
# Priv – the private one:
UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b") => Dict(
:Pub => UUID("c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"),
:Zebra => UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"),
),
# Priv – the public one:
UUID("2d15fe94-a1f7-436c-a4d8-07a9a496e01c") => Dict(),
# Pub:
UUID("c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1") => Dict(
:Priv => UUID("2d15fe94-a1f7-436c-a4d8-07a9a496e01c"),
:Zebra => UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"),
),
# Zebra:
UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62") => Dict(),
)与えられた依存関係 graph において、Julia が UUID c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1 を持つ Pub パッケージ内で import Priv を見たとき、次のように検索します:
graph[UUID("c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1")][:Priv]2d15fe94-a1f7-436c-a4d8-07a9a496e01cを取得し、Pubパッケージの文脈では、import Privがアプリが直接依存しているプライベートなものではなく、パブリックなPrivパッケージを指すことを示しています。これにより、Privという名前がメインプロジェクトの異なるパッケージを指すことができ、パッケージエコシステム内で重複した名前を許可します。
import ZebraがメインのAppコードベースで評価されるとどうなりますか?Zebraがプロジェクトファイルに表示されていないため、インポートは失敗しますが、Zebraはマニフェストファイルには存在します。さらに、import ZebraがUUID 2d15fe94-a1f7-436c-a4d8-07a9a496e01cの公開Privパッケージで発生した場合、それも失敗します。なぜなら、そのPrivパッケージにはマニフェストファイルに宣言された依存関係がないため、パッケージをロードできないからです。Zebraパッケージは、マニフェストファイルに明示的な依存関係として表示されているパッケージ、つまりPubパッケージおよびいずれかのPrivパッケージによってのみロードできます。
プロジェクト環境のパスマップは、マニフェストファイルから抽出されます。パッケージ uuid の名前 X のパスは、以下のルール(順番に)によって決定されます:
ディレクトリ内のプロジェクトファイルが
uuidと名前Xに一致する場合、次のいずれか:- それにはトップレベルの
entryfileエントリがあり、その後uuidはプロジェクトファイルを含むディレクトリに対して相対的に解釈されたそのパスにマッピングされます。 - そうでなければ、
uuidはプロジェクトファイルを含むディレクトリに対して相対的にsrc/X.jlにマッピングされます。
- それにはトップレベルの
もし上記が当てはまらず、プロジェクトファイルに対応するマニフェストファイルがあり、マニフェストに
uuidに一致するスタンザが含まれている場合は:* `path` エントリがある場合は、そのパスを使用します(マニフェストファイルを含むディレクトリに対して相対的です)。 * `git-tree-sha1` エントリがある場合、`uuid` と `git-tree-sha1` の決定論的ハッシュ関数を計算し、それを `slug` と呼びます。そして、Julia の `DEPOT_PATH` グローバル配列内の各ディレクトリで `packages/X/$slug` という名前のディレクトリを探します。存在する最初のディレクトリを使用します。もしこれがディレクトリであれば、
uuidはsrc/X.jlにマッピングされます。ただし、一致するマニフェストスタンザにentryfileエントリがある場合は、そちらが使用されます。いずれの場合も、これらは2.1のディレクトリに対して相対的です。
成功した場合、ソースコードのエントリポイントへのパスは、その結果、またはその結果からの相対パスに src/X.jl を追加したものになります。そうでない場合、uuid に対するパスマッピングは存在しません。X をロードする際に、ソースコードのパスが見つからない場合、ルックアップは失敗し、ユーザーは適切なパッケージバージョンをインストールするように促されるか、他の修正アクションを取るように求められることがあります(例:X を依存関係として宣言する)。
上記の例のマニフェストファイルでは、最初の Priv パッケージ—UUID ba13f791-ae1d-465a-978b-69c3ad90f72b のもの—のパスを見つけるために、Julia はマニフェストファイル内のそのスタンザを探し、path エントリがあることを確認し、App プロジェクトディレクトリに対して相対的に deps/Priv を見て、App コードが /home/me/projects/App にあると仮定すると、/home/me/projects/App/deps/Priv が存在することを確認し、したがってそこから Priv をロードします。
もし、他方で、Juliaが別の Privパッケージ(UUID 2d15fe94-a1f7-436c-a4d8-07a9a496e01cを持つもの)を読み込んでいる場合、マニフェスト内のそのスタンザを見つけ、pathエントリがないことを確認し、git-tree-sha1エントリがあることを確認します。次に、このUUID/SHA-1ペアのslugを計算します。これはHDkrTです(この計算の正確な詳細は重要ではありませんが、一貫して決定論的です)。これは、このPrivパッケージへのパスがpackages/Priv/HDkrT/src/Priv.jlになることを意味します。DEPOT_PATHの内容が["/home/me/.julia", "/usr/local/julia"]であると仮定すると、Juliaは次のパスを確認して存在するかどうかを確認します:
/home/me/.julia/packages/Priv/HDkrT/usr/local/julia/packages/Priv/HDkrT
Juliaは、見つかったデポ内のファイル packages/Priv/HDKrT/src/Priv.jl から公開の Priv パッケージを読み込もうとする際に、これらのうち最初に存在するものを使用します。
ここに、上記のマニフェストで提供された依存関係グラフのための例 App プロジェクト環境の可能なパスマップの表現があります。ローカルファイルシステムを検索した後のものです:
paths = Dict(
# Priv – the private one:
(UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b"), :Priv) =>
# relative entry-point inside `App` repo:
"/home/me/projects/App/deps/Priv/src/Priv.jl",
# Priv – the public one:
(UUID("2d15fe94-a1f7-436c-a4d8-07a9a496e01c"), :Priv) =>
# package installed in the system depot:
"/usr/local/julia/packages/Priv/HDkr/src/Priv.jl",
# Pub:
(UUID("c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"), :Pub) =>
# package installed in the user depot:
"/home/me/.julia/packages/Pub/oKpw/src/Pub.jl",
# Zebra:
(UUID("f7a24cb4-21fc-4002-ac70-f0e3a0dd3f62"), :Zebra) =>
# package installed in the system depot:
"/usr/local/julia/packages/Zebra/me9k/src/Zebra.jl",
)この例のマップには、3つの異なる種類のパッケージの場所が含まれています(最初と3番目はデフォルトのロードパスの一部です):
- プライベート
Privパッケージは "vendored" で、Appリポジトリ内にあります。 - システムデポには、公開されている
PrivとZebraパッケージがあり、ここにはシステム管理者によってインストールおよび管理されているパッケージが存在します。これらはシステム上のすべてのユーザーが利用可能です。 Pubパッケージはユーザーデポにあり、ユーザーがインストールしたパッケージが存在します。これらはインストールしたユーザーのみが利用可能です。
Package directories
パッケージディレクトリは、名前の衝突を処理する能力なしに、よりシンプルな環境を提供します。パッケージディレクトリでは、トップレベルパッケージのセットは「パッケージのように見える」サブディレクトリのセットです。パッケージディレクトリにパッケージ X が存在するのは、ディレクトリに次のいずれかの「エントリポイント」ファイルが含まれている場合です:
X.jlX/src/X.jlX.jl/src/X.jl
パッケージディレクトリ内のパッケージがインポートできる依存関係は、パッケージにプロジェクトファイルが含まれているかどうかに依存します:
- プロジェクトファイルがある場合、そのファイルの
[deps]セクションで特定されたパッケージのみをインポートできます。 - プロジェクトファイルがない場合、任意のトップレベルパッケージをインポートできます。つまり、
MainやREPLで読み込むことができるのと同じパッケージです。
ルーツマップは、パッケージディレクトリの内容を調べて、存在するすべてのパッケージのリストを生成することによって決定されます。さらに、各エントリには次のようにUUIDが割り当てられます: フォルダーX内で見つかった特定のパッケージについて...
X/Project.tomlが存在し、uuidエントリがある場合、uuidはその値です。X/Project.tomlが存在し、かつトップレベルの UUID エントリがない場合、uuidはX/Project.tomlの標準的(実際の)パスをハッシュ化して生成されたダミー UUID です。- そうでない場合(
Project.tomlが存在しない場合)、uuidはすべてゼロの nil UUID です。
プロジェクトディレクトリの依存関係グラフは、各パッケージのサブディレクトリ内にあるプロジェクトファイルの存在と内容によって決まります。ルールは次のとおりです:
- パッケージのサブディレクトリにプロジェクトファイルがない場合、そのディレクトリはグラフから省略され、そのコード内のインポート文はメインプロジェクトやREPLと同様にトップレベルとして扱われます。
- パッケージのサブディレクトリにプロジェクトファイルがある場合、そのUUIDのグラフエントリはプロジェクトファイルの
[deps]マップであり、セクションが存在しない場合は空であると見なされます。
パッケージディレクトリが以下の構造と内容を持っていると仮定します:
Aardvark/
src/Aardvark.jl:
import Bobcat
import Cobra
Bobcat/
Project.toml:
[deps]
Cobra = "4725e24d-f727-424b-bca0-c4307a3456fa"
Dingo = "7a7925be-828c-4418-bbeb-bac8dfc843bc"
src/Bobcat.jl:
import Cobra
import Dingo
Cobra/
Project.toml:
uuid = "4725e24d-f727-424b-bca0-c4307a3456fa"
[deps]
Dingo = "7a7925be-828c-4418-bbeb-bac8dfc843bc"
src/Cobra.jl:
import Dingo
Dingo/
Project.toml:
uuid = "7a7925be-828c-4418-bbeb-bac8dfc843bc"
src/Dingo.jl:
# no importsここに辞書として表現された対応するルーツ構造があります:
roots = Dict(
:Aardvark => UUID("00000000-0000-0000-0000-000000000000"), # no project file, nil UUID
:Bobcat => UUID("85ad11c7-31f6-5d08-84db-0a4914d4cadf"), # dummy UUID based on path
:Cobra => UUID("4725e24d-f727-424b-bca0-c4307a3456fa"), # UUID from project file
:Dingo => UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"), # UUID from project file
)対応するグラフ構造は、辞書として表現されています:
graph = Dict(
# Bobcat:
UUID("85ad11c7-31f6-5d08-84db-0a4914d4cadf") => Dict(
:Cobra => UUID("4725e24d-f727-424b-bca0-c4307a3456fa"),
:Dingo => UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"),
),
# Cobra:
UUID("4725e24d-f727-424b-bca0-c4307a3456fa") => Dict(
:Dingo => UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"),
),
# Dingo:
UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc") => Dict(),
)いくつかの一般的なルールに注意してください:
- プロジェクトファイルのないパッケージは、任意のトップレベルの依存関係に依存することができ、パッケージディレクトリ内のすべてのパッケージがトップレベルで利用可能であるため、環境内のすべてのパッケージをインポートすることができます。
- プロジェクトファイルを持つパッケージは、プロジェクトファイルを持たないパッケージに依存することはできません。なぜなら、プロジェクトファイルを持つパッケージは
graph内のパッケージのみをロードでき、プロジェクトファイルを持たないパッケージはgraphに表示されないからです。 - プロジェクトファイルはあるが明示的なUUIDがないパッケージは、プロジェクトファイルのないパッケージのみが依存できる。なぜなら、これらのパッケージに割り当てられたダミーUUIDは厳密に内部的なものであるからだ。
以下の具体的な例を観察してください:
アードバークはボブキャット、コブラ、またはディンゴのいずれかでインポートできます。ボブキャットとコブラをインポートします。BobcatはCobraとDingoの両方をインポートすることができ、実際にインポートします。これらはどちらも UUID を持つプロジェクトファイルを持ち、Bobcatの[deps]セクションで依存関係として宣言されています。BobcatはAardvarkに依存できません。なぜなら、Aardvarkにはプロジェクトファイルがないからです。CobraはDingoをインポートすることができ、実際にインポートします。DingoにはプロジェクトファイルとUUIDがあり、Cobraの[deps]セクションで依存関係として宣言されています。CobraはAardvarkやBobcatに依存できません。なぜなら、どちらも実際のUUIDを持っていないからです。Dingoは[deps]セクションのないプロジェクトファイルを持っているため、何もインポートできません。
パスマップはパッケージディレクトリ内でシンプルです:サブディレクトリ名をそれに対応するエントリポイントパスにマッピングします。言い換えれば、私たちの例のプロジェクトディレクトリへのパスが/home/me/animalsである場合、pathsマップはこの辞書で表現できます:
paths = Dict(
(UUID("00000000-0000-0000-0000-000000000000"), :Aardvark) =>
"/home/me/AnimalPackages/Aardvark/src/Aardvark.jl",
(UUID("85ad11c7-31f6-5d08-84db-0a4914d4cadf"), :Bobcat) =>
"/home/me/AnimalPackages/Bobcat/src/Bobcat.jl",
(UUID("4725e24d-f727-424b-bca0-c4307a3456fa"), :Cobra) =>
"/home/me/AnimalPackages/Cobra/src/Cobra.jl",
(UUID("7a7925be-828c-4418-bbeb-bac8dfc843bc"), :Dingo) =>
"/home/me/AnimalPackages/Dingo/src/Dingo.jl",
)パッケージディレクトリ環境内のすべてのパッケージは、定義上、期待されるエントリポイントファイルを持つサブディレクトリであるため、paths マップエントリは常にこの形式になります。
Environment stacks
第三の最終的な環境の種類は、いくつかの環境を重ね合わせて他の環境を組み合わせるものであり、それぞれの環境にあるパッケージを単一の複合環境で利用できるようにします。これらの複合環境は環境スタックと呼ばれます。JuliaのLOAD_PATHグローバルは、環境スタックを定義します。これは、Juliaプロセスが動作する環境です。Juliaプロセスが特定のプロジェクトまたはパッケージディレクトリ内のパッケージにのみアクセスできるようにしたい場合は、それをLOAD_PATHの唯一のエントリにしてください。しかし、作業しているプロジェクトの依存関係でなくても、お気に入りのツール(標準ライブラリ、プロファイラ、デバッガ、個人ユーティリティなど)にアクセスできることは非常に便利です。これらのツールを含む環境をロードパスに追加することで、プロジェクトに追加する必要なく、トップレベルのコードでそれらにすぐにアクセスできます。
環境スタックのコンポーネントのルート、グラフ、パスデータ構造を組み合わせるメカニズムはシンプルです:それらは辞書としてマージされ、キーの衝突が発生した場合には後のエントリよりも前のエントリが優先されます。言い換えれば、stack = [env₁, env₂, …] がある場合、次のようになります:
roots = reduce(merge, reverse([roots₁, roots₂, …]))
graph = reduce(merge, reverse([graph₁, graph₂, …]))
paths = reduce(merge, reverse([paths₁, paths₂, …]))添字付きの rootsᵢ、graphᵢ および pathsᵢ 変数は、stack に含まれる添字付きの環境 envᵢ に対応しています。reverse が存在するのは、merge が引数の辞書内のキーに衝突がある場合、最初の引数よりも最後の引数を優先するためです。この設計にはいくつかの注目すべき特徴があります:
- プライマリ環境—すなわちスタック内の最初の環境—は、スタックされた環境に忠実に埋め込まれています。スタック内の最初の環境の完全な依存関係グラフは、すべての依存関係の同じバージョンを含む形で、スタックされた環境に完全に含まれることが保証されています。
- 非プライマリ環境のパッケージは、自身の環境が完全に互換性があっても、依存関係の互換性のないバージョンを使用することがあります。これは、依存関係の1つがスタック内の以前の環境のバージョンによって影響を受ける場合(グラフまたはパス、またはその両方によって)に発生する可能性があります。
プロジェクトに取り組んでいる環境が主な環境であるため、スタックの後半にある環境には追加のツールが含まれていることが一般的です。このため、これは正しいトレードオフです:開発ツールを壊すよりもプロジェクトを動作させ続ける方が良いです。このような非互換性が発生した場合、通常はメインプロジェクトと互換性のあるバージョンに開発ツールをアップグレードしたいと思うでしょう。
Package Extensions
パッケージ「extension」は、指定された一連の他のパッケージ(その「トリガー」)が現在のJuliaセッションで読み込まれると、自動的に読み込まれるモジュールです。拡張機能は、プロジェクトファイルの[extensions]セクションで定義されます。拡張機能のトリガーは、プロジェクトファイルの[weakdeps](おそらく、しかし一般的ではない[deps])セクションにリストされているパッケージのサブセットです。それらのパッケージは、他のパッケージと同様に互換性のエントリを持つことができます。
name = "MyPackage"
[compat]
ExtDep = "1.0"
OtherExtDep = "1.0"
[weakdeps]
ExtDep = "c9a23..." # uuid
OtherExtDep = "862e..." # uuid
[extensions]
BarExt = ["ExtDep", "OtherExtDep"]
FooExt = "ExtDep"
...extensionsの下のキーは拡張機能の名前です。それらは、その拡張機能の右側にあるすべてのパッケージ(トリガー)が読み込まれたときに読み込まれます。拡張機能にトリガーが1つだけの場合、トリガーのリストは簡潔に文字列として書くことができます。拡張機能のエントリポイントの場所は、ext/FooExt.jlまたはext/FooExt/FooExt.jlのいずれかです(拡張機能FooExtの場合)。拡張機能の内容はしばしば次のように構成されています:
module FooExt
# Load main package and triggers
using MyPackage, ExtDep
# Extend functionality in main package with types from the triggers
MyPackage.func(x::ExtDep.SomeStruct) = ...
endパッケージに拡張機能が追加されると、そのパッケージのセクションに weakdeps と extensions セクションがマニフェストファイルに保存されます。パッケージの依存関係の検索ルールは、その「親」と同じですが、リストされたトリガーも依存関係として考慮されます。
Workspaces
プロジェクトファイルは、そのワークスペースの一部であるプロジェクトのセットを指定することによって、ワークスペースを定義できます:
[workspace]
projects = ["test", "benchmarks", "docs", "SomePackage"]各サブフォルダーには独自の Project.toml ファイルが含まれており、追加の依存関係や互換性制約が含まれている場合があります。そのような場合、パッケージマネージャーはワークスペース内のすべてのプロジェクトから依存関係情報を収集し、すべての依存関係のバージョンを組み合わせた単一のマニフェストファイルを生成します。
さらに、ワークスペースは「ネスト」することができ、ワークスペースを定義するプロジェクトも別のワークスペースの一部となることができます。このシナリオでは、単一のマニフェストファイルが引き続き使用され、"ルートプロジェクト"(他のワークスペースに含まれていないプロジェクト)と一緒に保存されます。例として、ファイル構造は次のようになる可能性があります:
Project.toml # projects = ["MyPackage"]
Manifest.toml
MyPackage/
Project.toml # projects = ["test"]
test/
Project.tomlPackage/Environment Preferences
Preferencesは、環境内でのパッケージの動作に影響を与えるメタデータの辞書です。Preferencesシステムは、コンパイル時にPreferencesを読み取ることをサポートしており、これはコード読み込み時に、Juliaによって選択されたプリコンパイルファイルが現在の環境と同じPreferencesでビルドされていることを確認する必要があることを意味します。Preferencesを変更するための公開APIは、Preferences.jlパッケージ内に含まれています。Preferencesは、現在アクティブなプロジェクトの隣にある(Julia)LocalPreferences.tomlファイル内にTOML辞書として保存されます。もしPreferenceが「エクスポート」されると、それは代わりに(Julia)Project.toml内に保存されます。この意図は、共有プロジェクトが共有Preferencesを含むことを可能にし、ユーザー自身がLocalPreferences.tomlファイル内で自分の設定でそれらのPreferencesを上書きできるようにすることです。このファイルは、その名前が示すように.gitignoreされるべきです。
コンパイル中にアクセスされる設定は自動的にコンパイル時設定としてマークされ、これらの設定に記録された変更は、Juliaコンパイラがそのモジュールのキャッシュされたプリコンパイルファイル(.jiおよび対応する.so、.dll、または.dylibファイル)を再コンパイルする原因となります。これは、コンパイル中にすべてのコンパイル時設定のハッシュをシリアライズし、適切なファイルをロードする際にそのハッシュを現在の環境と照合することによって行われます。
デポ全体のデフォルトで設定された設定を使用できます。パッケージ Foo がグローバル環境にインストールされており、設定がある場合、これらの設定はグローバル環境が LOAD_PATH の一部である限り適用されます。環境スタックの上位にある環境の設定は、ロードパス内のより近いエントリによって上書きされ、現在アクティブなプロジェクトで終了します。これにより、デポ全体の設定デフォルトが存在し、アクティブなプロジェクトがこれらの継承された設定をマージしたり、完全に上書きしたりできるようになります。設定をマージを許可または不許可にする方法の詳細については、Preferences.set_preferences!() のドキュメント文字列を参照してください。
Conclusion
フェデレーテッドパッケージ管理と正確なソフトウェア再現性は、パッケージシステムにおいて困難ではあるが価値のある目標です。これらの目標を組み合わせることで、ほとんどの動的言語が持つよりも複雑なパッケージロードメカニズムが生まれますが、同時に静的言語に一般的に関連付けられるスケーラビリティと再現性も得られます。通常、Juliaユーザーは、これらの相互作用を正確に理解することなく、組み込みのパッケージマネージャを使用してプロジェクトを管理できるはずです。Pkg.add("X")を呼び出すと、Pkg.activate("Y")を介して選択された適切なプロジェクトおよびマニフェストファイルに追加されるため、将来のimport Xの呼び出しは、さらなる考慮なしにXをロードします。