Code Loading
この章では、パッケージの読み込みに関する技術的な詳細を説明します。パッケージをインストールするには、Pkg
を使用して、アクティブな環境にパッケージを追加します。アクティブな環境に既にあるパッケージを使用するには、import X
またはusing X
と記述します。これは、Modules documentationで説明されています。
Definitions
ジュリアにはコードを読み込むための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
パッケージがどこにあるかを特定します。
これらの質問は、LOAD_PATH
にリストされているプロジェクト環境を検索することで回答されます。プロジェクトファイル(Project.toml
または JuliaProject.toml
)、マニフェストファイル(Manifest.toml
または JuliaManifest.toml
、または特定のバージョンのために -v{major}.{minor}.toml
でサフィックスされた同じ名前)、またはソースファイルのフォルダを探します。
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やスクリプトから利用できるようになりますが、パッケージ内からは利用できません。
高レベルでは、各環境は概念的に3つのマップを定義します:ルーツ、グラフ、パスです。import X
の意味を解決する際に、ルーツとグラフのマップがX
のアイデンティティを決定するために使用され、パスのマップがX
のソースコードを特定するために使用されます。3つのマップの具体的な役割は次のとおりです:
ルーツ:
name::Symbol
⟶uuid::UUID
環境のルーツマップは、環境がメインプロジェクトに提供するすべてのトップレベルの依存関係に対して、パッケージ名をUUIDに割り当てます(つまり、
Main
でロードできるもの)。Juliaがメインプロジェクトでimport X
に遭遇すると、X
の識別子をroots[:X]
として検索します。グラフ:
context::UUID
⟶name::Symbol
⟶uuid::UUID
環境のグラフは、各
context
UUID に対して名前から 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
は、特定のマニフェストファイルを使用するための形式として認識されます。つまり、同じフォルダー内で、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 が Pub
パッケージ(UUID c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1
)内で 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
に一致する場合、次のいずれか:- それにはトップレベルの
path
エントリがあり、その後uuid
はプロジェクトファイルを含むディレクトリに対して相対的に解釈されるそのパスにマッピングされます。 - そうでなければ、
uuid
はプロジェクトファイルを含むディレクトリに対して相対的にsrc/X.jl
にマッピングされます。
- それにはトップレベルの
もし上記が当てはまらず、プロジェクトファイルに対応するマニフェストファイルがあり、マニフェストに
uuid
と一致するスタンザが含まれている場合は:path
エントリがある場合は、そのパスを使用します(マニフェストファイルを含むディレクトリに対して相対的です)。git-tree-sha1
エントリがある場合、uuid
とgit-tree-sha1
の決定論的ハッシュ関数を計算し、それをslug
と呼びます。そして、Julia のDEPOT_PATH
グローバル配列内の各ディレクトリでpackages/X/$slug
という名前のディレクトリを探します。存在する最初のディレクトリを使用します。
成功した場合、ソースコードエントリポイントへのパスは、その結果、またはその結果からの相対パスに 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
をロードします。
もし、逆にジュリアが別の 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"]
であると仮定すると、ジュリアは次のパスを見て、それらが存在するかどうかを確認します:
/home/me/.julia/packages/Priv/HDkrT
/usr/local/julia/packages/Priv/HDkrT
ジュリアは、見つかったデポ内のファイル 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.jl
X/src/X.jl
X.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
セクションがマニフェストファイルに保存されます。パッケージの依存関係の検索ルールは、その「親」と同じですが、リストされたトリガーも依存関係として考慮されます。
Package/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
をロードします。