Spec-Zone.ru › Julia 1.5

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

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

    Граф среды — это многоуровневая карта, которая для каждого 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, и у них разные 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 относительно каталога, содержащего файл проекта.
  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 — это 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 всегда имеют эту форму.

Стек сред

Третий и последний тип среды — это среда, которая объединяет другие среды, накладывая несколько из них, делая пакеты в каждой доступными в одной составной среде. Эти составные среды называются стеками сред. Глобальный LOAD_PATH Julia определяет стек сред — среду, в которой работает процесс 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.5.3/manual/code-loading/

Spec-Zone.ru

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