Выпуск 34. Декабрь 2015
← Взгляд на 2015 г. | Содержание | Perl 6-винегрет →Управление модулями и прекомпиляция в Perl 6
25 декабря 2015 г. вышел первый стабильный релиз Rakudo Perl 6, среди новшеств которого совершенно новая система управления модулями и прекомпиляция. Рассмотрим в деталях процесс загрузки, разрешения зависимостей и компиляции модулей.
Обычная загрузка модуля
Традиционный процесс загрузки модуля в Perl 5 выглядит достаточно просто: имя модуля преобразуется к имени файла, например, Foo::Bar
ожидается быть найденным в файле Foo/Bar.pm. В массиве @INC
содержится список каталогов, в которых следует производить поиск файлов модулей. Часть каталогов является фиксированными и получили свои обозначения, например:
- site — каталог для поиска модулей, установленных администратором системы,
- vendor — каталог для модулей, поставляемых с дистрибутивом,
- perl — каталог модулей, идущих в поставке Perl
Модули ищутся и загружаются во время компиляции программы из исходного кода на диске. Такой же метод до недавнего времени использовался и в Perl 6, но рождественский релиз внёс существенные изменения в такое положение вещей.
Репозитории
Теперь в Perl 6 мы работаем с репозиториями — определённым образом организованное хранилище модулей. Репозитории могут быть различных типов, и это первое отличие от традиционных каталогов. Простейший вариант репозитория совместим с традиционным подходом и хранит файлы модулей в соответствии с именем пакета. Более сложный вариант поддерживает установку дистрибутивов модулей вместе с метаинформацией и ресурсами, а также производит прекомпиляцию модулей в байткод.
Ранее используемый массив @*INC
ушёл в небытие и теперь при загрузке модулей используется переменная процесса PROCESS::<$REPO>
, которая является связанным списком с информацией о доступных репозиториях.
$ perl6 -e 'for $*REPO.repo-chain -> $n { say $n }'
inst#/home/user/.perl6/2015.12
inst#/usr/share/perl6/site
inst#/usr/share/perl6/vendor
inst#/usr/share/perl6
...
Как видно, в отличие от Perl 5, в цепочке репозиториев присутствует новый каталог, находящийся в каталоге пользователя, запустившего программу, и он имеет максимальный приоритет. Кроме того, присутствует префикс inst#
, который указывает на тип репозитория, в данном случае тип CompUnit::Repository::Installation
, который поддерживает инсталляцию.
По-прежнему мы можем дополнять список с помощью флага -I
или переменной окружения PERL6LIB
, например:
$ perl6 -I /opt/perl6 'for $*REPO.repo-chain -> $n { say $n }'
file#/opt/perl6
inst#/home/user/.perl6/2015.12
inst#/usr/share/perl6/site
inst#/usr/share/perl6/vendor
...
Как видно, если не указать тип репозитория перед путём, то он будет считаться традиционным файловым file#
.
Что касается выражения use lib '/path/to/lib'
, то теперь её нельзя использовать в модулях, только в скриптах.
Инсталляция в репозиторий
Инсталляция в традиционный файловый репозиторий — это простое копирование файлов. С новым типом репозитория всё несколько сложнее. Рассмотрим как организован такой репозиторий:
\
+- dist
|
+- sources
|
+- resources
|
+- short
|
+- precomp
\
+ compiler.id
\
+ ..
При инсталляции дистрибутива Foo
, содержащего модуль Foo::Bar
, в каталоге dist создаётся файл с именем, представляющим собой SHA1 от уникального идентификатора дистрибутива, содержащий метаинформацию о дистрибутиве в формате JSON. В каталог sources помещаются исходные коды модуля, в каталог resource - связанные ресурсные файлы, все имена файлов генерируются как уникальные 40-символьные идентификаторы.
В каталог short помещаются файлы с именами как результат хеш-функции SHA1 от имени соответствующего модуля. В таком файле помещается 40-символьный идентификатор дистрибутива, в состав которого входит данный модуль.
В каталог precomp помещается результат компиляции исходного кода в байткод. Внутри каталога precomp создаётся каталог для текущей версии компилятора (в имя включается даже время сборки компилятора). Дальнейшая организация вложенных каталогов precomp напоминает git: внутри создаётся до 256 двухбуквенных каталогов, в который помещаются скомпилированный байткод, например:
+- 5C
\
+ 5C74B123E79B373BB96EC583A5C7EE4F458931BD
+ 5C74B123E79B373BB96EC583A5C7EE4F458931BD.deps
+ 5C74B123E79B373BB96EC583A5C7EE4F458931BD.rev-deps
+- 7E
\
+ 7E4AC40F59629C48CCBD390FEB4B272004591024
+ 7E4AC40F59629C48CCBD390FEB4B272004591024.deps
+ 7E4AC40F59629C48CCBD390FEB4B272004591024.rev-deps
Создаются также файлы .deps и .rev-deps куда помещается данные о зависимостях и обратных зависимостях (sha1-хеш имени модуля).
Таким образом, когда в коде встречается запись use Foo::Bar
, компилятор выполняет такую цепочку действий:
- вычисляет sha1-сумму имени
Foo::Bar
; - ищет файл с получившимся id в каталоге short;
- если находит, то по содержимому узнаёт id дистрибутива;
- загружает метаинформацию дистрибутива из каталога dist;
- из метаинформации узнаёт расположение исходного кода нужного модуля дистрибутива;
- проверяет наличие исходного кода в каталога sources (но не загружает);
- проверяет наличие прекомпилированного кода в precomp;
- если нет прекомпилированного кода, то выполняет загрузку исходного кода и прекомпиляцию с сохранением байткода;
- загружает из каталога precomp файл с прекомпилированным кодом модуля;
- проверяет наличие в каталоге precomp файла с таким же именем, но с окончанием .deps;
- если есть deps-файл, то находит в нём имена source-файлов зависимостей;
- в цикле проходит шаги 2–12 для всех зависимостей.
За инсталляцию отвечает класс CompUnit::Repository::Installation
. Пример кода, выполняющего установку, можно увидеть в скрипте установки дистрибутива CORE при сборке Rakudo. В целом примерно так:
my %provides =
"Foo::Bar" => "lib/Foo/Bar.pm6",
;
# задаём путь к репозиторию для данного процесса
PROCESS::<$REPO> := CompUnit::RepositoryRegistry
.repository-for-spec("inst#/path/to/repo");
# устанавливаем/прекомпилируем
$*REPO.install(
Distribution.new(
name => "Foo",
auth => "vendor",
ver => "0.1",
provides => %provides,
),
%provides,
:force,
);
Недостатки
К сожалению, скорость, с которой появились новые репозитории (хотели успеть к Рождеству), помешала достаточно хорошо протестировать и нормально реализовать функционал.
$(DESTDIR)
Большинство дистрибутивов Linux и UNIX-систем используют переменную окружения $(DESTDIR)
, чтобы указать временный каталог для инсталляции при сборке (например, при сборке пакетов). Новый механизм репозиториев затрудняет указание временного префикса, поэтому вышедший Rakudo 2015.12 просто падает с ошибкой при установке с использованием $(DESTDIR)
.
К счастью, это быстро заметили и ввели новую переменную окружения RAKUDO_PREFIX
, которая позволяет переопределить префикс при операциях с репозиториями. Но это уже войдёт только в следующий релиз.
Прекомпиляция
Использование результатов прекомпиляции и сама прекомпиляция выполняется только в самом первом репозитории из списка. По умолчанию это репозиторий из домашнего каталога пользователя. Даже если исходный код находится, например, в репозитории vendor и там же есть прекомпилированный код, то rakudo не будет использовать результат прекомпиляции, а заново выполнит прекомпиляцию и создаст необходимые файлы в каталоге пользователя.
В этом отношении каталоги прекомпиляции выглядят вообще отдельной сущностью от репозиториев.
Процесс прекомпиляция каждого модуля выполняется в отдельном процессе (exec), что безумно затормаживает первый запуск программы, импортирующей множество модулей.
Более того, если репозиторий окажется недоступным для записи пользователю, то программа просто не запустится с ошибкой.
Rakudo 2016.01
16 января 2016 г. ожидается очередной релиз Rakudo. Вероятно, что-то из обнаруженных проблем будет исправлено.
← Взгляд на 2015 г. | Содержание | Perl 6-винегрет →