Spec-Zone.ru › Julia 1.10

Загрузка кода

В этом разделе рассматриваются технические детали загрузки пакетов. Для установки пакетов используйте Pkg, встроенный менеджер пакетов Julia, для добавления пакетов в вашу активную среду. Чтобы использовать пакеты, уже присутствующие в вашей активной среде, запишите import X или using X, как описано в документации по модулям.

Определения

Julia имеет два механизма для загрузки кода:

  1. Включение кода: например, include("source.jl"). Включение позволяет разделить одну программу на несколько исходных файлов. Выражение include("source.jl") вызывает оценку содержимого файла source.jl в глобальной области видимости модуля, в котором происходит вызов include. Если вызов include("source.jl") выполняется несколько раз, source.jl оценивается несколько раз. Путь включённого файла, source.jl, интерпретируется относительно файла, в котором происходит вызов include. Это упрощает перемещение поддерева исходных файлов. В REPL пути включённых файлов интерпретируются относительно текущего каталога, pwd().
  2. Загрузка пакета: например, import X или using X. Механизм импорта позволяет загрузить пакет — независимую, многократно используемую коллекцию кода Julia, упакованную в модуль, — и делает полученный модуль доступным под именем X внутри импортирующего модуля. Если один и тот же пакет X импортируется несколько раз в одной сессии Julia, он загружается только один раз — при последующих импортах импортирующий модуль получает ссылку на тот же модуль. Однако обратите внимание, что import X может загружать разные пакеты в разных контекстах: X может ссылаться на один пакет под названием X в основном проекте, но потенциально на разные пакеты, также названные X в каждой зависимости. Подробнее об этом ниже.

Включение кода довольно просто и легко: оно оценивает данный исходный файл в контексте вызывающей стороны. Загрузка пакета основана на включении кода и служит различной цели. Остальная часть этой главы посвящена поведению и механике загрузки пакетов.

Пакет — это дерево исходных кодов со стандартной структурой, предоставляющей функциональность, которую можно повторно использовать в других проектах Julia. Пакет загружается с помощью операторов import X или using X. Эти операторы также делают модуль с именем X — полученный в результате загрузки кода пакета — доступным в модуле, где происходит оператор импорта. Значение X в import X зависит от контекста: какой пакет X загружается, зависит от кода, в котором происходит оператор. Таким образом, обработка import X происходит в два этапа: во-первых, определяется, какой пакет определён как X в данном контексте; во-вторых, определяется, где этот конкретный пакет X находится.

На эти вопросы отвечают, просматривая среды проектов, перечисленные в LOAD_PATH, в поисках файлов проекта (Project.toml или JuliaProject.toml), файлов манифеста (Manifest.toml или JuliaManifest.toml) или папок с исходными файлами.

Федерация пакетов

Большинство времени пакет однозначно идентифицируется просто по его имени. Однако иногда проект может столкнуться со случаем, когда ему нужно использовать два разных пакета с одинаковым именем. Хотя вы можете решить эту проблему, переименовав один из пакетов, это может быть весьма дискомфортно в большом, общем кодовом базисе. Вместо этого механизм загрузки кода Julia позволяет одному и тому же имени пакета ссылаться на разные пакеты в разных частях приложения.

Julia поддерживает федеративное управление пакетами, что означает, что несколько независимых сторон могут поддерживать как общедоступные, так и частные пакеты и реестры пакетов, а проекты могут зависеть от смешанных общедоступных и частных пакетов из разных реестров. Пакеты из различных реестров устанавливаются и управляются с помощью общего набора инструментов и рабочих процессов. Менеджер пакетов Pkg, поставляемый с Julia, позволяет устанавливать и управлять зависимостями ваших проектов. Он помогает в создании и манипулировании файлами проекта (которые описывают, от каких других проектов зависит ваш проект) и файлами манифеста (которые создают моментальный снимок точных версий всей вашей зависимости).

Одним из последствий федерации является то, что не может быть центрального органа управления именами пакетов. Разные сущности могут использовать одно и то же имя для ссылки на несвязанные пакеты. Эта возможность неизбежна, поскольку эти сущности не координируются и могут даже не знать друг о друге. Из-за отсутствия центрального органа управления именами один проект может оказаться зависимым от различных пакетов, имеющих одинаковое имя. Механизм загрузки пакетов 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 различает два пакета Priv по их UUID и выбирает правильный пакет в зависимости от контекста (модуль, который вызвал import). Как происходит это различие, определяется средами, как объяснено в следующих разделах.

Среды

Среда определяет, что означают import X и using X в различных контекстах кода и какие файлы эти операторы вызывают для загрузки. Julia понимает два типа сред:

  1. Среда проекта — это каталог с файлом проекта и необязательным файлом манифеста, образующий явную среду. Файл проекта определяет, каковы имена и идентификаторы прямых зависимостей проекта. Файл манифеста, если он есть, даёт полный граф зависимостей, включая все прямые и косвенные зависимости, точные версии каждой зависимости и достаточной информации для поиска и загрузки правильной версии.
  2. Каталог пакета — это каталог, содержащий деревья исходных кодов набора пакетов в качестве подкаталогов, и образующий неявную среду. Если X является подкаталогом каталога пакета и X/src/X.jl существует, то пакет X доступен в среде каталога пакета, и X/src/X.jl — это исходный файл, посредством которого он загружается.

Эти среды можно комбинировать, чтобы создать многослойную среду: упорядоченный набор сред проекта и каталогов пакетов, наложенных друг на друга для создания единой составной среды. Путь загрузки Julia, например, образует многослойную среду.

Эти среды служат разным целям:

  • Среды проектов обеспечивают воспроизводимость. Записав среду проекта в систему контроля версий (например, в репозиторий git) вместе с остальным исходным кодом проекта, вы можете воспроизвести точное состояние проекта и все его зависимости. В частности, файл манифеста фиксирует точную версию каждой зависимости, идентифицированную криптографическим хешем её дерева исходных кодов, что позволяет Pkg извлечь правильные версии и быть уверенным, что вы запускаете точно тот же код, что был записан для всех зависимостей.
  • Каталоги пакетов обеспечивают удобство, когда полная тщательно отслеживаемая среда проекта не нужна. Они полезны, когда вы хотите поместить набор пакетов в определённое место и иметь возможность непосредственно их использовать без необходимости создания среды проекта для них.
  • Многослойные среды позволяют добавлять инструменты в основную среду. Вы можете добавить среду инструментов разработки в конец стека, чтобы сделать их доступными из REPL и скриптов, но не изнутри пакетов.

На высоком уровне каждая среда концептуально определяет три карты: корни, граф и пути. При разрешении значения import X используются карты корней и графа, чтобы определить идентификатор X, а карта путей используется для поиска исходного кода X.

  • roots: name::Symbol ⟶ uuid::UUID

    Карта корней среды сопоставляет имена пакетов с UUID для всех зависимостей верхнего уровня, предоставляемых средой основному проекту (т. е. тех, которые могут быть загружены в Main). Когда Julia встречает import X в главном проекте, она ищет идентификатор X как roots[:X].

  • graph: context::UUID ⟶ name::Symbol ⟶ uuid::UUID

    Граф среды — это многоуровневая карта, которая сопоставляет каждому UUID context, карту от имён к UUID, аналогично карте корней, но специфично для этого context. Когда Julia видит import X в коде пакета, чья UUID равна context, она ищет идентификатор X как graph[context][:X]. В частности, это означает, что import X может ссылаться на разные пакеты в зависимости от context.

  • paths: uuid::UUID × name::Symbol ⟶ path::String

    Карта путей сопоставляет каждой паре UUID-имя пакета расположение исходного файла точки входа этого пакета. После того, как идентификатор X в import X был разрешен в UUID через корни или граф (в зависимости от того, загружен ли он из основного проекта или из зависимости), Julia определяет, какой файл загрузить, чтобы получить X, посмотрев на paths[uuid,: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, UUID пакета Priv, который должен быть загружен в данном контексте. Этот UUID идентифицирует, какой пакет Priv загрузить и использовать при оценке основным приложением import 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 и того же пакета Zebra, от которого зависит частный пакет Priv.

Этот граф зависимостей, представленный как словарь, выглядит так:

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 видит import Priv в пакете Pub, который имеет UUID c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1—она ищет:

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 встречается в общедоступном пакете Priv — том, с UUID 2d15fe94-a1f7-436c-a4d8-07a9a496e01c — это также завершится неудачей, поскольку у этого пакета Priv нет объявленных зависимостей в файле манифеста, и поэтому он не может загрузить пакеты. Пакет Zebra может загружаться только пакетами, для которых он явно указан как зависимость в файле манифеста: пакет Pub и один из пакетов Priv.

Карта путей среды проекта извлекается из файла манифеста. Путь к пакету uuid с именем X определяется этими правилами (в порядке):

  1. Если файл проекта в каталоге соответствует uuid и имя X, то либо:
    • У него есть запись path верхнего уровня, тогда uuid будет сопоставлена с этим путем, интерпретируемым относительно каталога, содержащего файл проекта.
    • В противном случае uuid сопоставляется с src/X.jl относительно каталога, содержащего файл проекта.
  2. Если это не так, и файл проекта имеет соответствующий файл манифеста, а манифест содержит строку, соответствующую uuid, то:
    • Если у него есть запись path, использовать этот путь (относительно каталога, содержащего файл манифеста).
    • Если у него есть запись git-tree-sha1, вычислить детерминированную функцию хеширования uuid и git-tree-sha1 — назовем её slug — и искать каталог с именем packages/X/$slug в каждом каталоге в глобальном массиве Julia DEPOT_PATH. Использовать первый такой каталог, который существует.

Если какое-либо из этих действий завершается успешно, путь к исходному коду точки входа будет либо этим результатом, либо относительный путь от этого результата плюс src/X.jl; в противном случае нет сопоставления пути для uuid. При загрузке X, если путь к исходному коду не найден, поиск завершится неудачей, и пользователю может быть предложено установить соответствующую версию пакета или принять другие меры исправления (например, объявив X как зависимость).

В примере файла манифеста выше, чтобы найти путь к первому пакету Priv — тому с UUID ba13f791-ae1d-465a-978b-69c3ad90f72b — Julia ищет его строку в файле манифеста, видит, что у него есть запись path, смотрит на deps/Priv относительно каталога проекта App — предположим, что код App находится в /home/me/projects/App — видит, что /home/me/projects/App/deps/Priv существует и поэтому загружает Priv оттуда.

С другой стороны, если Julia загружала другой пакет Priv — тот с UUID 2d15fe94-a1f7-436c-a4d8-07a9a496e01c — она находит его строку в манифесте, видит, что у него нет записи path, но у него есть запись git-tree-sha1. Затем она вычисляет slug для этой пары UUID/SHA-1, которая равна HDkrT (точности вычислений не важны, но оно согласованное и детерминированное). Это означает, что путь к этому пакету Priv будет packages/Priv/HDkrT/src/Priv.jl в одном из хранилищ пакетов. Предположим, что содержимое DEPOT_PATH равно ["/home/me/.julia", "/usr/local/julia"], тогда Julia проверит следующие пути, чтобы увидеть, существуют ли они:

  1. /home/me/.julia/packages/Priv/HDkrT
  2. /usr/local/julia/packages/Priv/HDkrT

Julia использует первый из них, который существует, чтобы попытаться загрузить общедоступный пакет Priv из файла packages/Priv/HDKrT/src/Priv.jl в хранилище, где он был найден.

Вот представление возможной карты путей для нашего примера проекта 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",
)

Эта карта включает три различных вида расположений пакетов (первый и третий являются частью стандартного пути загрузки):

  1. Частный пакет Priv размещён «встроенный» внутри хранилища App.
  2. Общедоступные пакеты Priv и Zebra находятся в системном хранилище, где хранятся пакеты, установленные и управляемые системным администратором. Они доступны всем пользователям системы.
  3. Пакет Pub находится в хранилище пользователя, где хранятся пакеты, установленные пользователем. Они доступны только пользователю, который их установил.

Каталоги пакетов

Папки пакетов обеспечивают более простой вид среды без возможности обработки конфликтов имён. В папке пакетов набор основных пакетов — это набор подпапок, которые «выглядят» как пакеты. Пакет X существует в папке пакетов, если папка содержит один из следующих файлов «точки входа»:

  • X.jl
  • X/src/X.jl
  • X.jl/src/X.jl

Какие зависимости может импортировать пакет в папке пакетов, зависит от наличия файла проекта в пакете:

  • Если у него есть файл проекта, он может импортировать только те пакеты, которые указаны в разделе [deps] файла проекта.
  • Если у него нет файла проекта, он может импортировать любой основной пакет — то есть такие же пакеты, которые можно загрузить в Main или REPL.

Карта корней определяется путём анализа содержимого папки пакетов для создания списка всех существующих пакетов. Кроме того, каждому элементу будет присвоен UUID следующим образом: для данного пакета, найденного внутри папки X...

  1. Если X/Project.toml существует и содержит запись uuid, то uuid — это это значение.
  2. Если X/Project.toml существует, но не содержит записи UUID верхнего уровня, то uuid — это псевдо-UUID, сгенерированный путём хэширования канонического (действительного) пути к X/Project.toml.
  3. В противном случае (если 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(),
)

Несколько общих правил:

  1. Пакет без файла проекта может зависеть от любой основной зависимости, и поскольку каждый пакет в папке пакетов доступен на верхнем уровне, он может импортировать все пакеты в среде.
  2. Пакет с файлом проекта не может зависеть от пакета без файла проекта, поскольку пакеты с файлами проекта могут загружать только пакеты из graph, а пакеты без файлов проекта не отображаются в graph.
  3. Пакет с файлом проекта, но без явного 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 отдает предпочтение последнему аргументу, а не первому, когда возникают столкновения между ключами в словарях аргументов. В этом дизайне есть несколько примечательных особенностей:

  1. Основная среда — то есть первая среда в стеке — надёжно встраивается в среду стека. Полный граф зависимостей первой среды в стеке гарантируется, что он будет включён в среду стека, включая те же версии всех зависимостей.
  2. Пакеты в средах, отличных от основной, могут использовать несовместимые версии своих зависимостей, даже если их собственные среды полностью совместимы. Это может произойти, когда одна из их зависимостей скрывается версией в более ранней среде стека (либо по графу, либо по пути, или по обоим).

Поскольку основная среда обычно является средой проекта, над которым вы работаете, а среды, расположенные позже в стеке, содержат дополнительные инструменты, это правильный компромисс: лучше сломать инструменты разработки, но сохранить работоспособность проекта. При возникновении таких несовместимостей вы обычно захотите обновить инструменты разработки до версий, совместимых с основным проектом.

Расширения пакетов

«Расширение» пакета — это модуль, который автоматически загружается, когда указанный набор других пакетов (его «триггеры») загружается в текущей сессии Julia. Расширения определяются в разделе [extensions] файла проекта. Триггеры расширения — это подмножество пакетов, перечисленных в разделе [weakdeps] (и, возможно, но редко, в разделе [deps] файла проекта). У этих пакетов могут быть записи compat, как и у других пакетов.

name = "MyPackage"

[compat]
ExtDep = "1.0"
OtherExtDep = "1.0"

[weakdeps]
ExtDep = "c9a23..." # uuid
OtherExtDep = "862e..." # uuid

[extensions]
BarExt = ["ExtDep", "OtherExtDep"]
FooExt = "ExtDep"
...

Ключи в extensions — это имена расширений. Они загружаются, когда все пакеты в правой части (триггеры) этого расширения загружаются. Если у расширения только один триггер, список триггеров можно записать в виде одной строки для краткости. Местоположение точки входа расширения находится либо в 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 сохраняются в файле манифеста в разделе для этого пакета. Правила поиска зависимостей для пакета такие же, как для его «родительского», за исключением того, что перечисленные триггеры также считаются зависимостями.

Настройки пакетов/среды

Настройки — это словари метаданных, которые влияют на поведение пакетов в среде. Система настроек поддерживает чтение настроек на этапе компиляции, что означает, что во время загрузки кода мы должны убедиться, что файлы предварительной компиляции, выбранные Julia, были построены с теми же настройками, что и текущая среда, прежде чем загружать их. Открытый API для изменения настроек содержится в пакете Preferences.jl. Настройки хранятся как словари TOML в файле (Julia)LocalPreferences.toml рядом с текущим активным проектом. Если настройка «экспортирована», она вместо этого сохраняется в (Julia)Project.toml. Цель состоит в том, чтобы предоставить возможность совместным проектам содержать общие настройки, при этом позволяя пользователям переопределять эти настройки собственными настройками в файле LocalPreferences.toml, который, как следует из названия, должен быть проигнорирован системой контроля версий.

Настройки, к которым обращаются во время компиляции, автоматически отмечаются как настройки времени компиляции, и любое изменение, записанное в эти настройки, заставит компилятор Julia перекомпилировать любые кэшированные файлы предварительной компиляции (.ji и соответствующие .so, .dll, или .dylib файлы) для данного модуля. Это делается путём сериализации хэша всех настроек времени компиляции во время компиляции, а затем проверки этого хэша на предмет текущей среды при поиске соответствующих файлов для загрузки.

Настройки могут быть установлены с предустановками на уровне хранилища; если пакет Foo установлен в вашей глобальной среде и имеет настройки, эти настройки будут применяться до тех пор, пока ваша глобальная среда является частью LOAD_PATH. Настройки в средах выше в стеке сред перекрываются более близкими записями в пути загрузки, завершаясь текущим активным проектом. Это позволяет существовать предустановкам настроек на уровне хранилища, при этом активные проекты могут объединять или даже полностью перекрывать эти унаследованные настройки. См. строку документации для Preferences.set_preferences!() для получения подробной информации о том, как установить настройки для разрешения или запрещения объединения.

Заключение

Федеративное управление пакетами и точная воспроизводимость программного обеспечения являются сложными, но достойными целями в системе пакетов. В сочетании эти цели приводят к более сложному механизму загрузки пакетов, чем у большинства динамических языков, но также обеспечивают масштабируемость и воспроизводимость, которые чаще ассоциируются со статическими языками. Как правило, пользователи Julia должны иметь возможность использовать встроенный менеджер пакетов для управления своими проектами, не имея точного понимания этих взаимодействий. Вызов Pkg.add("X") будет добавлен в соответствующие файлы проекта и манифеста, выбранные с помощью Pkg.activate("Y"), так что будущий вызов import X загрузит X без дополнительных размышлений.

© 2009–2024 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.10/manual/code-loading/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API