Модули
Модули в Julia помогают организовать код в связные единицы. Они синтаксически ограничены внутри module NameOfModule ... end, и обладают следующими особенностями:
Модули являются отдельными пространствами имён, каждый из которых вводит новое глобальное пространство. Это полезно, потому что позволяет использовать одно и то же имя для разных функций или глобальных переменных без конфликтов, при условии, что они находятся в разных модулях.
Модули имеют средства для управления пространствами имён: каждый определяет набор имён, которые он
exportет, и может импортировать имена из других модулей с помощьюusingиimport(мы объясним это ниже).Модули могут быть предварительно скомпилированы для более быстрого загрузки и могут содержать код для инициализации во время выполнения.
Обычно в больших пакетах Julia вы увидите, что код модуля организован в файлы, например
module SomeModule
# export, using, import statements are usually here; we discuss these below
include("file1.jl")
include("file2.jl")
end
Файлы и имена файлов в основном не связаны с модулями; модули ассоциированы только с выражениями модулей. Можно иметь несколько файлов на один модуль и несколько модулей в одном файле. include ведет себя так, как будто содержимое исходного файла оценивается в глобальном пространстве имён включающего модуля. В этой главе мы используем короткие и упрощённые примеры, поэтому мы не будем использовать include.
Рекомендуемый стиль — не вдаваться в отступы тела модуля, поскольку это обычно приводит к отступам всего файла. Также принято использовать UpperCamelCase для имён модулей (как и для типов), и использовать множественное число, если это применимо, особенно если модуль содержит идентификатор с похожим именем, чтобы избежать конфликтов имён. Например,
module FastThings
struct FastThing
...
end
end
Управление пространствами имён
Управление пространствами имён относится к средствам, которые предоставляет язык для обеспечения доступности имён в одном модуле в других модулях. Мы подробнее обсудим соответствующие концепции и функциональность ниже.
Квалифицированные имена
Имена функций, переменных и типов в глобальном пространстве имён, таких как sin, ARGS, и UnitRange всегда принадлежат модулю, называемому родительским модулем, который можно найти интерактивно с помощью parentmodule, например
julia> parentmodule(UnitRange) Base
Также можно ссылаться на эти имена за пределами родительского модуля, предваряя их именем модуля, например Base.UnitRange. Это называется квалифицированным именем. Родительский модуль может быть доступен с помощью цепочки подмодулей, например Base.Math.sin, где Base.Math называется путь модуля. Из-за синтаксических неоднозначностей квалификация имени, содержащего только символы, например оператор, требует вставки двоеточия, например Base.:+. Небольшое количество операторов дополнительно требует скобок, например Base.:(==).
Если имя квалифицировано, то оно всегда доступно, и в случае функции к нему также можно добавить методы, используя квалифицированное имя в качестве имени функции.
Внутри модуля имя переменной может быть «зарезервировано» без присваивания, объявив его как global x. Это предотвращает конфликты имён для глобальных переменных, инициализированных после загрузки. Синтаксис M.x = y не работает для присваивания глобальной переменной в другом модуле; глобальное присваивание всегда является локальным для модуля.
Списки экспорта
Имена (относящиеся к функциям, типам, глобальным переменным и константам) можно добавить в список экспорта модуля с помощью export: это символы, которые импортируются при using модуля. Обычно они находятся в начале или рядом с началом определения модуля, чтобы читатели исходного кода могли легко их найти, как в
julia> module NiceStuff
export nice, DOG
struct Dog end # singleton type, not exported
const DOG = Dog() # named instance, exported
nice(x) = "nice $x" # function, exported
end;
но это всего лишь рекомендация по стилю — модуль может иметь несколько export операторов в произвольных местах.
Обычно имена, которые являются частью API (интерфейса программирования приложений), экспортируются. В приведенном выше коде список экспорта предполагает, что пользователи должны использовать nice и DOG. Однако, поскольку квалифицированные имена всегда делают идентификаторы доступными, это просто вариант для организации API: в отличие от других языков, Julia не имеет средств для истинного скрытия внутренних деталей модулей.
Также некоторые модули вообще не экспортируют имена. Это обычно делается, если они используют общие слова, такие как derivative, в своём API, которые могут легко конфликтовать со списками экспорта других модулей. Мы увидим, как управлять конфликтами имён ниже.
Отдельные using и import
Возможно, самый распространённый способ загрузки модуля — using ModuleName. Это загружает код, связанный с ModuleName, и вносит
имя модуля
и элементы списка экспорта в окружающее глобальное пространство имён.
Технически, оператор using ModuleName означает, что модуль, называемый ModuleName, будет доступен для разрешения имён по мере необходимости. Когда встречается глобальная переменная, которая не определена в текущем модуле, система будет искать её среди переменных, экспортированных из ModuleName, и использовать её, если она найдена там. Это означает, что все использования этой глобальной переменной в текущем модуле будут разрешены к определению этой переменной в ModuleName.
Для загрузки модуля из пакета можно использовать оператор using ModuleName. Для загрузки модуля из локально определённого модуля перед именем модуля необходимо добавить точку, как в using .ModuleName.
Для продолжения нашего примера,
julia> using .NiceStuff
загрузил бы вышеуказанный код, сделав NiceStuff (имя модуля), DOG и nice доступными. Dog нет в списке экспорта, но к нему можно получить доступ, если имя квалифицируется путём модуля (здесь это просто имя модуля), как NiceStuff.Dog.
Важно, что using ModuleName — это единственная форма, для которой списки экспорта вообще имеют значение.
В отличие от этого,
julia> import .NiceStuff
вносит в область видимости только имя модуля. Пользователям необходимо использовать NiceStuff.DOG, NiceStuff.Dog, и NiceStuff.nice для доступа к его содержимому. Обычно import ModuleName используется в контекстах, когда пользователь хочет сохранить пространство имён чистым. Как мы увидим в следующем разделе, import .NiceStuff эквивалентно using .NiceStuff: NiceStuff.
Вы можете объединить несколько using и import операторов одного типа в выражении, разделённом запятыми, например
julia> using LinearAlgebra, Statistics
using и import со специфическими идентификаторами и добавлением методов
Когда using ModuleName: или import ModuleName: следуют за списком имён, разделённым запятыми, модуль загружается, но в пространство имён вносятся только эти конкретные имена оператором. Например,
julia> using .NiceStuff: nice, DOG
импортирует имена nice и DOG.
Важно, что имя модуля NiceStuff не будет в пространстве имён. Если вы хотите сделать его доступным, вы должны указать его явно, как
julia> using .NiceStuff: nice, DOG, NiceStuff
Julia имеет две формы для, казалось бы, одинаковой вещи, потому что только import ModuleName: f позволяет добавлять методы к f без пути модуля. То есть, следующий пример выдаст ошибку:
julia> using .NiceStuff: nice julia> struct Cat end julia> nice(::Cat) = "nice 😸" ERROR: error in method definition: function NiceStuff.nice must be explicitly imported to be extended Stacktrace: [1] top-level scope @ none:0 [2] top-level scope @ none:1
Эта ошибка предотвращает случайное добавление методов к функциям в других модулях, которые вы намеревались только использовать.
Есть два способа справиться с этим. Вы всегда можете квалифицировать имена функций путём модуля:
julia> using .NiceStuff julia> struct Cat end julia> NiceStuff.nice(::Cat) = "nice 😸"
Или вы можете import конкретное имя функции:
julia> import .NiceStuff: nice julia> struct Cat end julia> nice(::Cat) = "nice 😸" nice (generic function with 2 methods)
Выбор зависит от стиля. Первая форма ясно показывает, что вы добавляете метод к функции в другом модуле (помните, что импорты и определение метода могут находиться в разных файлах), а вторая короче, что особенно удобно, если вы определяете несколько методов.
После того как переменная становится видимой с помощью using или import, модуль не может создать свою собственную переменную с тем же именем. Импортированные переменные являются только для чтения; присваивание глобальной переменной всегда влияет на переменную, принадлежащую текущему модулю, или же приводит к ошибке.
Переименование с as
Идентификатор, внесённый в область видимости import или using, может быть переименован с помощью ключевого слова as. Это полезно для устранения конфликтов имён, а также для сокращения имён. Например, Base экспортирует имя функции read, но пакет CSV.jl также предоставляет CSV.read. Если мы собираемся многократно вызывать чтение CSV, было бы удобно убрать квалификатор CSV.. Но тогда неясно, ссылаемся ли мы на Base.read или CSV.read:
julia> read; julia> import CSV: read WARNING: ignoring conflicting import of CSV.read into Main
Переименование предлагает решение:
julia> import CSV: read as rd
Импортированные пакеты также могут быть переименованы:
import BenchmarkTools as BT
as работает с using только при внесении в область видимости одного идентификатора. Например, using CSV: read as rd работает, но using CSV as C не работает, так как он действует на все экспортированные имена в CSV.
Комбинирование нескольких using и import операторов
При использовании нескольких using или import операторов любого из вышеперечисленных форматов их эффект комбинируется в порядке их появления. Например,
julia> using .NiceStuff # exported names and the module name julia> import .NiceStuff: nice # allows adding methods to unqualified functions
внесёт все экспортированные имена NiceStuff и само имя модуля в область видимости, а также позволит добавлять методы к nice без префикса имени модуля.
Обработка конфликтов имён
Рассмотрим ситуацию, когда два (или более) пакета экспортируют одно и то же имя, как в
julia> module A
export f
f() = 1
end
A
julia> module B
export f
f() = 2
end
B
Оператор using .A, .B работает, но когда вы пытаетесь вызвать f, вы получаете предупреждение
julia> using .A, .B julia> f WARNING: both B and A export "f"; uses of it in module Main must be qualified ERROR: UndefVarError: `f` not defined
Здесь Julia не может определить, к какому f вы ссылаетесь, поэтому вам нужно сделать выбор. Обычно используются следующие решения:
Просто продолжайте с квалифицированными именами, такими как
A.fиB.f. Это делает контекст ясным для читателя вашего кода, особенно еслиfслучайно совпадает, но имеет разное значение в различных пакетах. Например,degreeимеет различные применения в математике, естественных науках и в повседневной жизни, и эти значения должны быть разделены.-
Используйте ключевое слово
asвыше, чтобы переименовать один или оба идентификатора, напримерjulia> using .A: f as f julia> using .B: f as g
это сделает
B.fдоступным какg. Здесь мы предполагаем, что вы не использовалиusing A, что привело быfв пространство имен. Когда имена в вопросе действительно разделяют значение, для одного модуля обычно импортируется оно из другого, или существует легкий «базовый» пакет с единственной функцией определения интерфейса, как в этом случае, который может быть использован другими пакетами. Принято, чтобы такие имена пакетов заканчивались на
...Base(что не имеет отношения к модулюBaseязыка Julia).
Определения по умолчанию верхнего уровня и модули без имен
Модули автоматически содержат using Core, using Base, и определения функций eval и include, которые вычисляют выражения/файлы в глобальной области видимости этого модуля.
Если эти определения по умолчанию не нужны, модули могут быть определены с использованием ключевого слова baremodule вместо этого (обратите внимание: Core все равно импортируется). С точки зрения baremodule, стандартный module выглядит так:
baremodule Mod using Base eval(x) = Core.eval(Mod, x) include(p) = Base.include(Mod, p) ... end
Если даже Core не нужен, модуль, который ничего не импортирует и не определяет никаких имен, может быть определён с помощью Module(:YourNameHere, false, false) и код может быть вычислен в него с помощью @eval или Core.eval:
julia> arithmetic = Module(:arithmetic, false, false) Main.arithmetic julia> @eval arithmetic add(x, y) = $(+)(x, y) add (generic function with 1 method) julia> arithmetic.add(12, 13) 25
Стандартные модули
Существует три важных стандартных модуля:
-
Coreсодержит всю функциональность, "встроенную" в язык. -
Baseсодержит базовую функциональность, полезную практически во всех случаях. -
Mainявляется модулем верхнего уровня и текущим модулем при запуске Julia.
По умолчанию Julia поставляется с некоторыми стандартными библиотечными модулями. Они ведут себя как обычные пакеты Julia, за исключением того, что вам не нужно устанавливать их явно. Например, если вы хотите выполнить некоторые unit-тесты, вы можете загрузить стандартную библиотеку Test следующим образом:
using Test
Подмодули и относительные пути
Модули могут содержать подмодули, вложенные с тем же синтаксисом module ... end. Они могут быть использованы для введения отдельных пространств имен, что может быть полезно для организации сложных кодовых баз. Обратите внимание, что каждый module вводит своё собственное область видимости, поэтому подмодули не «унаследуют» имена автоматически от своих родительских модулей.
Рекомендуется, чтобы подмодули ссылались на другие модули в заключительном родительском модуле (включая последний) с использованием относительных квалификаторов модулей в операциях using и import. Относительный квалификатор модуля начинается с точки (.), которая соответствует текущему модулю, и каждый последующий . ведет к родительскому модулю. За этим должны следовать модули при необходимости, и, в конечном счете, само имя для доступа, все разделенные ..
Рассмотрим следующий пример, где подмодуль SubA определяет функцию, которая затем расширяется в его «братском» модуле:
julia> module ParentModule
module SubA
export add_D # exported interface
const D = 3
add_D(x) = x + D
end
using .SubA # brings `add_D` into the namespace
export add_D # export it from ParentModule too
module SubB
import ..SubA: add_D # relative path for a “sibling” module
struct Infinity end
add_D(x::Infinity) = x
end
end;
Вы можете увидеть код в пакетах, который в аналогичной ситуации использует
julia> import .ParentModule.SubA: add_D
Однако, это работает через загрузку кода, и поэтому работает только если ParentModule находится в пакете. Лучше использовать относительные пути.
Обратите внимание, что порядок определений также имеет значение, если вы оцениваете значения. Рассмотрим
module TestPackage export x, y x = 0 module Sub using ..TestPackage z = y # ERROR: UndefVarError: `y` not defined end y = 1 end
где Sub пытается использовать TestPackage.y до его определения, поэтому у него нет значения.
По аналогичным причинам, вы не можете использовать циклический порядок:
module A module B using ..C # ERROR: UndefVarError: `C` not defined end module C using ..B end end
Инициализация и предварительная компиляция модулей
Большие модули могут загружаться несколько секунд, потому что выполнение всех операторов в модуле часто включает компиляцию большого количества кода. Julia создаёт предварительно скомпилированные кэши модулей, чтобы уменьшить это время.
Файлы предварительно скомпилированного модуля (иногда называемые "файлами кэша") создаются и используются автоматически, когда import или using загружает модуль. Если файл(ы) кэша ещё не существуют, модуль будет скомпилирован и сохранён для последующего использования. Вы также можете вручную вызвать Base.compilecache(Base.identify_package("modulename")), чтобы создать эти файлы без загрузки модуля. Результирующие файлы кэша будут храниться в подкаталоге compiled от DEPOT_PATH[1]. Если ничего не изменится на вашем компьютере, такие файлы кэша будут использоваться при загрузке модуля с помощью import или using.
Файлы кэша предварительной компиляции хранят определения модулей, типов, методов и констант. Они также могут хранить специализации методов и сгенерированный для них код, но это обычно требует, чтобы разработчик добавил явные директивы precompile или выполнил рабочие нагрузки, которые вынуждают компиляцию во время сборки пакета.
Однако, если вы обновите зависимости модуля или измените его исходный код, модуль будет автоматически перекомпилирован при using или import. Зависимости - это модули, которые он импортирует, компиляция Julia, файлы, которые он включает, или явные зависимости, объявленные с помощью include_dependency(path) в файлах модуля.
Для зависимостей от файлов изменение определяется путём проверки того, что время изменения (mtime) каждого загруженного файлом include или добавленного явно include_dependency не изменилось, или равно времени изменения усечённому до ближайшей секунды (для учёта систем, которые не могут копировать mtime с точностью до долей секунды). Также учитывается, соответствует ли путь к файлу, выбранный логикой поиска в require, пути, который создал файл предварительной компиляции. Также учитывается набор зависимостей, уже загруженных в текущий процесс, и не будет перекомпилировать эти модули, даже если их файлы изменятся или исчезнут, чтобы избежать создания несовместимостей между работающей системой и кэшем предварительной компиляции. И, наконец, учитываются изменения в любых предпочтениях во время компиляции.
Если вы знаете, что модуль не подходит для предварительной компиляции (например, по одной из причин, описанных ниже), вы должны поместить __precompile__(false) в файл модуля (обычно в самом верху). Это заставит Base.compilecache выбросить ошибку и заставит using / import загрузить его непосредственно в текущий процесс и пропустить предварительную компиляцию и кэширование. Это также предотвращает импорт модуля любыми другими предварительно скомпилированными модулями.
Вам, возможно, нужно знать о некоторых особенностях, связанных с созданием инкрементных общих библиотек, которые могут потребовать внимательного обращения при написании модуля. Например, внешнее состояние не сохраняется. Чтобы учесть это, явно разделяйте любые шаги инициализации, которые должны произойти во время работы, от шагов, которые могут произойти во время компиляции. Для этой цели Julia позволяет вам определить функцию __init__() в вашем модуле, которая выполняет любые шаги инициализации, которые должны выполняться во время работы. Эта функция не будет вызвана во время компиляции (--output-*). По сути, вы можете предположить, что она будет выполнена ровно один раз за время работы кода. Конечно, вы можете вызвать её вручную, если это необходимо, но по умолчанию предполагается, что эта функция обрабатывает вычисление состояния для локальной машины, что не нужно — и даже не должно — фиксироваться в скомпилированном изображении. Она будет вызвана после загрузки модуля в процесс, в том числе, если он загружается в инкрементную компиляцию (--output-incremental=yes ), но не если он загружается в процесс полной компиляции.
В частности, если вы определили function __init__() в модуле, то Julia вызовет __init__() сразу после загрузки модуля (например, с помощью import, using, или require ) во время выполнения в первый раз (то есть, __init__ вызывается только один раз и только после выполнения всех операторов в модуле). Поскольку она вызывается после полной загрузки модуля, все подмодули или другие импортированные модули имеют свои функции __init__ вызываемые до выполнения функции __init__ модуля-контейнера.
Два типичных использования __init__ — вызов функций инициализации внешних библиотек C во время выполнения и инициализация глобальных констант, связанных с указателями, возвращаемыми внешними библиотеками. Например, предположим, что мы используем библиотеку C libfoo, которая требует вызова функции инициализации foo_init() во время выполнения. Предположим, что мы также хотим определить глобальную константу foo_data_ptr, которая хранит возвращаемое значение функции void *foo_data(), определённой libfoo — эта константа должна быть инициализирована во время выполнения (не во время компиляции), потому что адрес указателя будет изменяться от запуска к запуску. Вы можете добиться этого, определив функцию __init__ в вашем модуле:
const foo_data_ptr = Ref{Ptr{Cvoid}}(0)
function __init__()
ccall((:foo_init, :libfoo), Cvoid, ())
foo_data_ptr[] = ccall((:foo_data, :libfoo), Ptr{Cvoid}, ())
nothing
end
Заметьте, что вполне возможно определить глобальную переменную внутри функции, как в __init__; это одно из преимуществ использования динамического языка. Но сделав её константой в глобальной области, мы можем гарантировать, что тип известен компилятору, и позволит ему генерировать более оптимизированный код. Очевидно, любые другие глобальные переменные в вашем модуле, которые зависят от foo_data_ptr, также должны быть инициализированы в __init__.
Константы, связанные с большинством объектов Julia, которые не создаются с помощью ccall, не нужно помещать в __init__: их определения можно предварительно скомпилировать и загрузить из кэшированного образа модуля. Это включает сложные объекты, размещаемые в куче, такие как массивы. Однако любая процедура, возвращающая значение сырого указателя, должна вызываться во время выполнения, чтобы предварительная компиляция работала (Ptr объекты превратятся в нулевые указатели, если они не скрыты внутри объекта isbits). Это включает значения, возвращаемые функциями Julia @cfunction и pointer.
Типы словарей и множеств, или в общем случае всё, что зависит от результата метода hash(key), — более сложный случай. В общем случае, когда ключами являются числа, строки, символы, диапазоны, Expr, или комбинации этих типов (через массивы, кортежи, множества, пары и т.д.), их можно безопасно предварительно скомпилировать. Однако для некоторых других типов ключей, таких как Function или DataType и общие пользовательские типы, для которых вы не определили метод hash, метод обратного вызова hash зависит от адреса памяти объекта (через его objectid) и, следовательно, может изменяться от запуска к запуску. Если у вас есть один из этих типов ключей или вы не уверены, для безопасности вы можете инициализировать этот словарь внутри вашей функции __init__. В качестве альтернативы вы можете использовать тип словаря IdDict, который специально обрабатывается предварительной компиляцией, так что его безопасно инициализировать во время компиляции.
При использовании предварительной компиляции важно чётко понимать различие между фазой компиляции и фазой выполнения. В этом режиме часто становится намного яснее, что Julia — это компилятор, который позволяет выполнять произвольный код Julia, а не автономный интерпретатор, который также генерирует скомпилированный код.
Другие известные потенциальные сценарии ошибок включают:
-
Глобальные счётчики (например, для попытки уникальной идентификации объектов). Рассмотрим следующий фрагмент кода:
mutable struct UniquedById myid::Int let counter = 0 UniquedById() = new(counter += 1) end endХотя цель этого кода заключалась в присвоении каждому экземпляру уникального идентификатора, значение счётчика записывается в конце компиляции. Все последующие использования этого инкрементально скомпилированного модуля будут начинаться с того же значения счётчика.
Обратите внимание, что
objectid(который работает путём хеширования указателя памяти) имеет аналогичные проблемы (см. примечания по использованиюDictниже).Одна из альтернатив — использовать макрос для захвата
@__MODULE__и сохранения его вместе с текущим значениемcounter, однако может быть лучше перепроектировать код, чтобы он не зависел от этого глобального состояния. Ассоциативные коллекции (например,
DictиSet) необходимо перехешировать в__init__. (В будущем может быть предоставлен механизм для регистрации функции инициализации.)В зависимости от сохраняющихся во время загрузки последствий действий во время компиляции. К примеру: изменение массивов или других переменных в других модулях Julia; поддержание дескрипторов открытых файлов или устройств; хранение указателей на другие системные ресурсы (включая память);
-
Создание случайных «копий» глобального состояния из другого модуля путём прямого обращения к нему вместо использования его пути поиска. Например, (в глобальной области):
#mystdout = Base.stdout #= will not work correctly, since this will copy Base.stdout into this module =# # instead use accessor functions: getstdout() = Base.stdout #= best option =# # or move the assignment into the runtime: __init__() = global mystdout = Base.stdout #= also works =#
Несколько дополнительных ограничений накладываются на операции, которые могут выполняться во время предварительной компиляции кода, чтобы помочь пользователю избежать других проблем с неправильным поведением:
- Вызов
evalдля вызова побочного эффекта в другом модуле. Это также вызовет предупреждение при установке флага инкрементной предварительной компиляции. -
global constоператоры из локальной области после того, как__init__()был запущен (см. вопрос #12010 для планов добавления ошибки для этого) - Замена модуля — ошибка во время выполнения при выполнении инкрементной предварительной компиляции.
Несколько других моментов, о которых следует знать:
- Перезагрузка кода/очистка кэша не выполняется после внесения изменений в исходные файлы (включая изменения от
Pkg.update), и очистка не выполняется послеPkg.rm - Поведение совместного использования памяти переформатированного массива игнорируется предварительной компиляцией (каждый вид получает свою копию)
- Ожидание неизменности файловой системы между временем компиляции и временем выполнения, например,
@__FILE__/source_path()для поиска ресурсов во время выполнения, или макроса BinDeps@checked_lib. Иногда это неизбежно. Однако, когда это возможно, хорошей практикой является копирование ресурсов в модуль во время компиляции, чтобы они не требовались для поиска во время выполнения. -
WeakRefобъекты и финализаторы в настоящее время не обрабатываются сериализатором должным образом (это будет исправлено в предстоящем выпуске). - В большинстве случаев следует избегать захвата ссылок на экземпляры внутренних объектов метаданных, таких как
Method,MethodInstance,MethodTable,TypeMapLevel,TypeMapEntryи поля этих объектов, так как это может сбить с толку сериализатор и может не привести к желаемому результату. Это не обязательно ошибка, но вам просто нужно быть готовыми к тому, что система попытается скопировать некоторые из них и создать единственный уникальный экземпляр других.
Иногда при разработке модулей полезно отключить инкрементную предварительную компиляцию. Флаг командной строки --compiled-modules={yes|no} позволяет переключать предварительную компиляцию модулей включено/выключено. При запуске Julia с флагом --compiled-modules=no сериализованные модули в кэше компиляции игнорируются при загрузке модулей и зависимостей модулей. Более тонкий контроль доступен с помощью --pkgimages=no, который подавляет только хранение кода нативных языков во время предварительной компиляции. Base.compilecache по-прежнему можно вызывать вручную. Состояние этого флага командной строки передаётся Pkg.build для отключения автоматического запуска предварительной компиляции при установке, обновлении и явном построении пакетов.
Вы также можете отладить некоторые ошибки предварительной компиляции с помощью переменных среды. Установка JULIA_VERBOSE_LINKING=true может помочь в решении проблем с линковкой общих библиотек скомпилированного кода нативных языков. Более подробную информацию см. в разделе Документация разработчика руководства по Julia, в разделе, документирующем внутренности Julia, в подразделе «Образы пакетов».
© 2009–2023 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.9/manual/modules/