Выпуск 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, компилятор выполняет такую цепочку действий:

  1. вычисляет sha1-сумму имени Foo::Bar;
  2. ищет файл с получившимся id в каталоге short;
  3. если находит, то по содержимому узнаёт id дистрибутива;
  4. загружает метаинформацию дистрибутива из каталога dist;
  5. из метаинформации узнаёт расположение исходного кода нужного модуля дистрибутива;
  6. проверяет наличие исходного кода в каталога sources (но не загружает);
  7. проверяет наличие прекомпилированного кода в precomp;
  8. если нет прекомпилированного кода, то выполняет загрузку исходного кода и прекомпиляцию с сохранением байткода;
  9. загружает из каталога precomp файл с прекомпилированным кодом модуля;
  10. проверяет наличие в каталоге precomp файла с таким же именем, но с окончанием .deps;
  11. если есть deps-файл, то находит в нём имена source-файлов зависимостей;
  12. в цикле проходит шаги 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-винегрет
Нас уже 1393. Больше подписчиков — лучше выпуски!

Комментарии к статье