載入程式碼
定義
Julia 有兩種載入程式碼的機制
- 程式碼包含:例如
include("source.jl")
。包含允許您將單一程式分割成多個原始檔。表達式include("source.jl")
會導致檔案source.jl
的內容在執行include
呼叫的模組的全球範圍內進行評估。如果呼叫include("source.jl")
多次,則會評估source.jl
多次。包含的路徑source.jl
會根據執行include
呼叫的檔案進行詮釋。這使得重新配置原始檔的子樹變得簡單。在 REPL 中,包含的路徑會根據目前的作業目錄pwd()
進行詮釋。 - 套件載入:例如
import X
或using X
。匯入機制允許您載入套件,也就是一個獨立、可重複使用的 Julia 程式碼集合,包裝在模組中,並讓產生的模組在匯入模組中以X
名稱使用。如果同一個X
套件在同一個 Julia 會話中匯入多次,它只會在第一次載入,在後續匯入時,匯入模組會取得同一個模組的參考。但請注意,import X
可以載入不同環境中的不同套件:X
可以參考主專案中名為X
的一個套件,但也有可能參考每個相依性中也名為X
的不同套件。後續將有更多說明。
程式碼包含非常直接且簡單:它在呼叫者的環境中評估給定的原始檔。套件載入建立在程式碼包含之上,並提供 不同的目的。本章節的其餘部分將重點放在套件載入的行為和機制上。
套件 是具有標準配置的原始碼樹,提供可供其他 Julia 專案重複使用的功能。套件載入時使用 import X
或 using X
陳述式。這些陳述式也會讓名為 X
的模組(載入套件程式碼後產生)在載入陳述式出現的模組中可用。import X
中的 X
含義取決於語境:載入哪個 X
套件取決於陳述式出現在哪個程式碼中。因此,處理 import X
會分為兩個階段:首先,它會決定在此語境中哪個套件定義為 X
;其次,它會決定在哪裡找到特定 X
套件。
這些問題的解答方式是搜尋 LOAD_PATH
中列出的專案環境,尋找專案檔案(Project.toml
或 JuliaProject.toml
)、清單檔案(Manifest.toml
或 JuliaManifest.toml
)或原始碼檔案資料夾。
套件聯合會
大部分時間,套件的名稱就能唯一辨識套件。不過,有時專案可能會遇到需要使用兩個名稱相同的套件的情況。雖然您可以透過變更其中一個套件的名稱來解決這個問題,但在大型的共用程式碼庫中,被迫這麼做可能會造成很大的破壞。Julia 的程式碼載入機制允許同一個套件名稱在應用程式的不同元件中指向不同的套件。
Julia 支援聯合套件管理,這表示多個獨立的參與者可以維護公開和私人套件和套件註冊表,而專案可以依賴來自不同註冊表的公開和私人套件組合。來自不同註冊表的套件會使用一套常見的工具和工作流程來安裝和管理。隨 Julia 附帶的 Pkg
套件管理員讓您可以安裝和管理專案的相依性。它協助建立和處理專案檔案(描述專案依賴的其他專案)和明細檔案(快照專案完整相依性圖的精確版本)。
聯合的一項後果是套件命名無法有中央權限。不同的實體可能會使用相同的名稱來指涉不相關的套件。這種可能性無法避免,因為這些實體不會協調,甚至可能不知道彼此。由於缺乏中央命名權限,單一專案可能會依賴具有相同名稱的不同套件。Julia 的套件載入機制不需要套件名稱在全球範圍內唯一,即使是在單一專案的相依性圖中。相反地,套件會透過 通用唯一識別碼 (UUID) 來識別,這些識別碼會在建立每個套件時指派。通常您不必直接使用這些有點麻煩的 128 位元識別碼,因為 Pkg
會負責為您產生和追蹤它們。然而,這些 UUID 提供了「X
指涉什麼套件?」這個問題的明確答案。
由於分散式命名問題有點抽象,因此可以逐步了解具體情況以了解問題。假設您正在開發一個名為 App
的應用程式,它使用兩個套件:Pub
和 Priv
。Priv
是您建立的私人套件,而 Pub
是您使用但無法控制的公開套件。當您建立 Priv
時,沒有公開套件的名稱為 Priv
。然而,隨後,另一個名為 Priv
的無關套件已發布並變得流行。事實上,Pub
套件已開始使用它。因此,當您下次升級 Pub
以取得最新的錯誤修正和功能時,App
將最終依賴兩個不同的套件,其名稱為 Priv
,而您除了升級之外沒有其他動作。App
對您的私人 Priv
套件有直接依賴性,並透過 Pub
對新的公開 Priv
套件有間接依賴性。由於這兩個 Priv
套件不同,但 App
都需要它們才能繼續正確運作,因此表達式 import Priv
必須參照不同的 Priv
套件,具體取決於它出現在 App
的程式碼或 Pub
的程式碼中。為了處理這個問題,Julia 的套件載入機制依據其 UUID 區分兩個 Priv
套件,並根據其內容(呼叫 import
的模組)選擇正確的套件。此區別如何運作是由環境決定的,如下列各節所述。
環境
環境 決定 import X
和 using X
在各種程式碼內容中的意義,以及這些陳述導致載入哪些檔案。Julia 了解兩種環境
- 專案環境 是具有專案檔案和選用清單檔案的目錄,並形成明確環境。專案檔案決定專案的直接依賴項的名稱和身分。清單檔案(如果存在)會提供完整的依賴性圖表,包括所有直接和間接依賴性、每個依賴項的確切版本,以及找到並載入正確版本的足夠資訊。
- 套件目錄是一個包含一組套件的原始樹狀目錄,作為子目錄,並形成一個隱含環境。如果
X
是套件目錄的子目錄,且X/src/X.jl
存在,則套件X
在套件目錄環境中可用,且X/src/X.jl
是用於載入它的原始檔。
這些可以相互混合以建立堆疊環境:一個專案環境和套件目錄的有序集合,疊加以形成一個單一的複合環境。優先權和可見性規則會結合以決定哪些套件可用,以及從何處載入它們。例如,Julia 的載入路徑會形成一個堆疊環境。
這些環境各有不同的用途
- 專案環境提供可重製性。透過將專案環境檢查到版本控制中(例如 git 存放庫)以及專案其他原始碼,您可以重製專案的確切狀態及其所有依賴項。特別是,清單檔會擷取每個依賴項的確切版本,由其原始樹狀目錄的密碼雜湊識別,這使得
Pkg
能夠擷取正確的版本,並確保您執行的是所有依賴項所記錄的確切程式碼。 - 當不需要完整且仔細追蹤的專案環境時,套件目錄會提供便利性。當您想要將一組套件放在某個地方,並能夠直接使用它們,而不需要為它們建立專案環境時,它們會很有用。
- 堆疊環境允許新增工具到主要環境。您可以將開發工具環境推送到堆疊的末端,以使其可從 REPL 和指令碼中使用,但不能從套件內部使用。
在高層級,每個環境概念上定義三個映射:根、圖形和路徑。在解析 import X
的意義時,根和圖形映射用於確定 X
的身分,而路徑映射用於定位 X
的原始碼。這三個映射的具體角色為
根:
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
的載入都會建立一個新的繫結到已載入的套件模組。
每種類型的環境都以不同的方式定義這三個映射,如下列各節所述。
為了便於理解,本章節中的範例顯示了根節點、圖形和路徑的完整資料結構。然而,Julia 的套件載入程式碼並未明確建立這些結構。相反地,它僅會根據需要載入每個結構,以載入特定套件。
專案環境
專案環境是由包含名為 Project.toml
專案檔案的目錄所決定的,而且可以選擇包含名為 Manifest.toml
的清單檔案。這些檔案也可以稱為 JuliaProject.toml
和 JuliaManifest.toml
,這種情況下會忽略 Project.toml
和 Manifest.toml
。這允許與其他工具共存,這些工具可能會將名為 Project.toml
和 Manifest.toml
的檔案視為重要檔案。然而,對於純 Julia 專案,建議使用名稱 Project.toml
和 Manifest.toml
。
專案環境的根節點、圖形和路徑對應如下
環境的根節點對應是由專案檔案的內容決定的,特別是其頂層的 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
的套件。它使用私人套件(是根相依項)和公用套件(透過Pub
是間接相依項)。它們由不同的 UUID 區分,並且有不同的 deps- 私人的
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
套件,而不是 app 直接相依的私人套件。這就是名稱 Priv
在主要專案中可以參照與其套件相依項中不同的套件的方式,這允許套件生態系統中有重複的名稱。
如果在主程式碼庫 App
中評估 import Zebra
會發生什麼事?由於 Zebra
沒有出現在專案檔案中,因此即使 Zebra
有 出現在清單檔案中,匯入仍會失敗。此外,如果 import Zebra
出現在公開的 Priv
套件中(即 UUID 為 2d15fe94-a1f7-436c-a4d8-07a9a496e01c
的套件),那麼也會失敗,因為該 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
。
另一方面,如果 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",
)
此範例對應包含三種類型的套件位置(第一個和第三個是預設載入路徑的一部分)
- 私有的
Priv
套件在App
儲存庫內「供應」。 - 公開的
Priv
和Zebra
套件在系統儲存庫中,系統管理員安裝和管理的套件存在於其中。這些套件可供系統上的所有使用者使用。 Pub
套件位於使用者儲存區,其中存放使用者安裝的套件。這些套件僅供安裝它們的使用者使用。
套件目錄
套件目錄提供一種更簡單的環境,無法處理名稱衝突。在套件目錄中,頂層套件的集合是「看起來像」套件的子目錄集合。如果目錄包含下列其中一個「進入點」檔案,則套件 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 絕對是內部的。
在我們的範例中觀察這些規則的下列特定實例
Aardvark
可以匯入Bobcat
、Cobra
或Dingo
中的任何一個;它確實匯入了Bobcat
和Cobra
。Bobcat
可以且確實匯入了Cobra
和Dingo
,這兩個套件都有專案檔案和 UUID,並在Bobcat
的[deps]
區段中宣告為相依性。Bobcat
不能依賴Aardvark
,因為Aardvark
沒有專案檔案。Cobra
可以且確實匯入了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
對應條目總是具有這個形式。
環境堆疊
第三種也是最後一種環境是將其他環境結合起來,透過覆蓋其中幾個環境,讓每個環境中的封裝在單一複合環境中可用。這些複合環境稱為環境堆疊。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ᵢ
變數對應到帶下標的環境 envᵢ
,包含在 stack
中。存在 reverse
是因為當其參數字典中的金鑰發生衝突時,merge
優先使用最後一個參數,而不是第一個參數。此設計有幾個值得注意的功能
- 主要環境,也就是堆疊中的第一個環境,會忠實地嵌入在堆疊環境中。堆疊中第一個環境的完整依賴關係圖形保證會完整包含在堆疊環境中,包括所有依賴關係的相同版本。
- 非主要環境中的套件可能會結束使用其依賴關係的不相容版本,即使它們自己的環境完全相容。這可能會發生在它們的其中一個依賴關係被堆疊中較早環境中的版本遮蔽時(透過圖形或路徑,或兩者)。
由於主要環境通常是您正在處理的專案的環境,而堆疊中較後的環境包含其他工具,因此這是正確的權衡:最好中斷您的開發工具,但讓專案繼續運作。當發生此類不相容時,您通常會想要將開發工具升級到與主要專案相容的版本。
套件擴充功能
套件「延伸模組」是在當前 Julia 會話中載入一組特定其他套件(其「延伸模組相依性」)時會自動載入的模組。延伸模組在專案檔案的 [extensions]
區段中定義。延伸模組的延伸模組相依性是專案檔案 [weakdeps]
區段中所列套件的子集。這些套件可以像其他套件一樣有相容性項目。
name = "MyPackage"
[compat]
ExtDep = "1.0"
OtherExtDep = "1.0"
[weakdeps]
ExtDep = "c9a23..." # uuid
OtherExtDep = "862e..." # uuid
[extensions]
BarExt = ["ExtDep", "OtherExtDep"]
FooExt = "ExtDep"
...
extensions
底下的金鑰是延伸模組的名稱。當該延伸模組右側(延伸模組相依性)的所有套件都載入時,這些延伸模組就會載入。如果延伸模組只有一個延伸模組相依性,則延伸模組相依性清單可以簡寫為一個字串。延伸模組的進入點位置對於延伸模組 FooExt
來說,會在 ext/FooExt.jl
或 ext/FooExt/FooExt.jl
中。延伸模組的內容通常會以以下方式建構
module FooExt
# Load main package and extension dependencies
using MyPackage, ExtDep
# Extend functionality in main package with types from the extension dependencies
MyPackage.func(x::ExtDep.SomeStruct) = ...
end
當套件連同延伸模組加入環境時,weakdeps
和 extensions
區段會儲存在該套件區段的清單檔案中。套件的相依性查詢規則與其「父項」相同,但所列的延伸模組相依性也會視為相依性。
套件/環境偏好設定
偏好設定是元資料的字典,會影響環境中的套件行為。偏好設定系統支援在編譯時讀取偏好設定,這表示在載入程式碼時,我們必須確保 Julia 選擇的預編譯檔案與載入前目前的環境具有相同的偏好設定。修改偏好設定的公開 API 包含在 Preferences.jl 套件中。偏好設定儲存在目前使用中的專案旁的 (Julia)LocalPreferences.toml
檔案中的 TOML 字典中。如果偏好設定「已匯出」,則會儲存在 (Julia)Project.toml
中。目的是讓共用專案包含共用偏好設定,同時允許使用者自行使用 LocalPreferences.toml 檔案中的設定覆寫這些偏好設定,而該檔案應如名稱所示,忽略 .git。
編譯期間存取的偏好設定會自動標示為編譯時間偏好設定,而對這些偏好設定記錄的任何變更都會導致 Julia 編譯器重新編譯該模組的任何快取預編譯檔案 (.ji
和對應的 .so
、.dll
或 .dylib
檔案)。這會在編譯期間序列化所有編譯時間偏好設定的雜湊,然後在搜尋要載入的適當檔案時,將該雜湊與目前的環境進行比對。
偏好設定可以設定為儲存庫範圍的預設值;如果套件 Foo 安裝在您的全域環境中,且已設定偏好設定,只要您的全域環境是 LOAD_PATH
的一部分,這些偏好設定就會套用。環境堆疊中較高層級的偏好設定會被載入路徑中較近的項目覆寫,最後是目前使用的專案。這允許存在儲存庫範圍的偏好設定預設值,而使用中的專案可以合併或甚至完全覆寫這些繼承的偏好設定。請參閱 Preferences.set_preferences!()
的文件字串,以取得如何設定偏好設定以允許或不允許合併的完整詳細資料。
結論
聯邦套件管理和精確軟體可複製性在套件系統中是困難但有價值的目標。這些目標結合起來,會導致比大多數動態語言更複雜的套件載入機制,但它也產生通常與靜態語言相關的可擴充性和可複製性。通常,Julia 使用者應該能夠使用內建套件管理員來管理他們的專案,而不需要精確了解這些互動。呼叫 Pkg.add("X")
會新增到適當的專案和清單檔案,透過 Pkg.activate("Y")
選擇,因此未來呼叫 import X
會載入 X
而不需要進一步思考。