Spec-Zone.ru › Julia 1.11

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

В этом разделе рассматриваются технические детали загрузки пакетов. Для установки пакетов используйте 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, или те же имена с добавленным суффиксом -v{major}.{minor}.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

    Граф среды — это многоуровневое отображение, которое сопоставляет каждому 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. Однако начиная с Julia v1.11, (Julia)Manifest-v{major}.{minor}.toml распознаётся как формат для использования заданной версии julia с определённым файлом манифеста, т.е. в том же каталоге, Manifest-v1.11.toml будет использоваться v1.11, а Manifest.toml — любой другой версией julia.

Отображения корней, графа и путей среды проекта определяются следующим образом:

Отображение корней среды определяется содержимым файла проекта, в частности, его записями верхнего уровня name и uuid и его разделом [deps] (все необязательны). Рассмотрим следующий пример файла проекта для гипотетического приложения App, как описано ранее:

name = "App"
uuid = "8f986787-14fe-4607-ba5d-fbff2944afa9"

[deps]
Priv = "ba13f791-ae1d-465a-978b-69c3ad90f72b"
Pub  = "c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"

Этот файл проекта подразумевает следующее отображение корней, если бы он представлял собой словарь Julia:

roots = Dict(
    :App  => UUID("8f986787-14fe-4607-ba5d-fbff2944afa9"),
    :Priv => UUID("ba13f791-ae1d-465a-978b-69c3ad90f72b"),
    :Pub  => UUID("c07ecb7d-0dc9-4db7-8803-fadaaeaf08e1"),
)

Учитывая это отображение корней, в коде App оператор import Priv заставит Julia найти roots[:Priv], что даст ba13f791-ae1d-465a-978b-69c3ad90f72b, 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 относительно каталога, содержащего файл проекта.
  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 — это 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's [deps].
  • Bobcat не может зависеть от Aardvark так как Aardvark не имеет файла проекта.
  • Cobra может и импортирует Dingo, у которого есть файл проекта и UUID, и он объявлен как зависимость в разделе Cobra's [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.11/manual/code-loading/

Spec-Zone.ru

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