Spec-Zone.ru › Julia 1.4

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

В этом разделе рассматриваются технические детали загрузки пакетов. Для установки пакетов используйте 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.

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

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

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

    Граф среды — это многоуровневая карта, которая сопоставляет каждому context UUID карту из имен в 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 и имеют разные зависимости:
    • Частный пакет 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 относительно каталога, содержащего файл проекта.
  1. Если вышесказанное не имеет места, и файл проекта имеет соответствующий файл манифеста, а манифест содержит строку, соответствующую 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 должны использовать встроенный менеджер пакетов для управления своими проектами, не нуждаясь в точном понимании этих взаимодействий. Вызов Pkg.add("X") будет добавлен в соответствующие файлы проекта и манифеста, выбранные с помощью Pkg.activate("Y"), чтобы последующий вызов import X загрузил X без лишних размышлений.

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

Spec-Zone.ru

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