Менеджер пакетов и система сборки для Рефала
Opened this issue · 5 comments
Мотивация
У других языков программирования есть свои пакетные менеджеры, а у Рефала нет. И мне обидно.
Кроме того, у меня есть несколько своих проектов на Рефале:
Пишутся и другие проекты на Рефале-5:
К каждому из них скрипт сборки и скрипт запуска пишутся индивидуально.
Между двумя моими проектами есть зависимость: Рефал-05 зависит от фреймворка. Также не исключена зависимость между фреймворком и Рефалом-5λ — не хочется синхронно поддерживать две библиотеки LibraryEx
.
Декомпилятор на выходе самостоятельно формирует .rsl-ки, но он выиграл бы, если бы для их формирования он использовал фреймворк (правда, он требует доделки для вставки комментариев в код).
Рефал-5λ содержит копию исходника декомпилятора, она подключена через хитрое слияние ветвей. Тоже было бы неплохо, если бы декомпилятор подключался бы как зависимость.
MSCP-A зависит от библиотеки prefal
и для корректной работы вынужден её включать себе в репозиторий. prefal
доступен в интернете для загрузки:
http://www.botik.ru/pub/local/scp/refal5/bin/prefal_180216.zip
Запуск программ на Рефале-5 не очень удобен: в командной строке refgo
нужно явно перечислять все имена скомпилированных модулей, например так:
https://github.com/TonitaN/MSCP-A/blob/bf4a5175549ff913c52a68f5270a3370d3bf9daa/mscpdo.bat#L1-L7
Компиляция неудобна тем, что нужно явным образом перечислять все файлы, т.к. обнаруживать зависимости между файлами refc
не умеет.
Про зависимости
В Рефале-5λ имеется утилита rlmake
, которая умеет находить зависимые файлы в специальным образом размеченных исходниках. Она принимает в командной строке имя единственного файла, находит все файлы, связанные с ним и вызывает компилятор rlc
для сборки всех этих файлов.
Разметка осуществляется комментариями
*$FROM ‹имя-файла›
В ‹имени-файла›
расширение .ref
писать не обязательно. Файлы ищутся в т.н. путях поиска, которые явно указываются в командной строке rlmake
, опция --ref5rsl
позволяет добавить к путям поиска также и содержимое переменной среды REF5RSL
.
Имя метки *$FROM
предполагает, что после этого комментария располагается список *$EXTERN
, содержащий имена функций, импортируемых из искомого модуля:
*$FROM LibraryEx
$EXTERN ArgList, LoadFile, SaveFile, Map;
Исторически комментарии *$FROM
я использовал при раскрутке Модульного Рефала в 2007 году:
- Mazdaywik/mrefal@101a325
https://github.com/Mazdaywik/mrefal/commit/101a32537fff559e26d98da11a640df3f1591b99.patch (здесь можно посмотреть дату) - Mazdaywik/mrefal@d93bbdb
https://github.com/Mazdaywik/mrefal/commit/d93bbdb5b222b78d8f0fe74434a8d82579874de5.patch (здесь можно посмотреть дату)
С тех пор я уже привык использовать подобного рода комментарии, не только для утилиты rlmake
, но и вообще.
Другие программисты тоже используют подобные комментарии перед списками $EXTERN
. Пример из MSCP-A:
https://github.com/TonitaN/MSCP-A/blob/bf4a5175549ff913c52a68f5270a3370d3bf9daa/Drive.ref#L69-L88
Пример из SCP4 (версия scp4_000925
, файл access.ref
):
* basic.ref:
$EXTRN SubsAs, Subs;
* drive.ref:
$EXTRN Dn0;
* trace.ref:
$EXTRN CurrFile, FOutput, Trace, Trace0, CommTrace;
* key.ref:
$EXTRN OutDir, InpDir;
$EXTRN Length, Depth, CallDepth, Simplify, Transient, PutInHistory, Strategy, GoAhead;
Черновик спецификации
Источник вдохновения
Источником вдохновения послужили пакетные менеджеры и системы сборки языков Go и Rust:
- Управление пакетами с помощью модулей Go: Прагматическое руководство
- Привет, Cargo!
- Пакеты и крейты
- Генерация секретного числа
У языка Go была позаимствована децентрализованность, у Rust — выделение библиотечного крейта.
Зависимости
Предполагается, что на машине пользователя доступны команды git
, wget
или curl
и какой-то разархиватор zip-файлов (unzip
на unix-подобных, для Windows разархиватор указывает пользователь в настройках), а также имеется какая-нибудь реализация Рефала-5 (сам Рефал-5, Рефал-5λ, Рефал-05 с компилятором Си).
Структура проекта
Проект представляет собой папку, в которой имеются следующие файлы и каталоги:
bin/
— создаётся автоматически, в неё помещаются скомпилированные файлы,lib/
— создаёт пользователь, в неё помещаются компоненты (исходники*.ref
), которые можно повторно использовать,modules/
— создаётся автоматически, в неё скачиваются зависимости,src/
— создаёт пользователь, в неё помещаются исходники, которые повторно использоваться не должны, из них собираются целевые файлы,tmp/
— папка для временных файлов (создаётся автоматически),r5pm.prj
— ini-файл, описывающий текущий проект.
Файл конфигурации r5pm.prj
Файл имеет следующий формат:
[common]
; Компилятор: одно из refc, crefal, refal-5-lambda (или lambda), refal-05
compiler = refc
; Флаги для refgo
refgo_flags = -l1000 -c200
; целевые файлы, имеют вид:
; имя_в_папке_bin = имя_в_папке_src
[targets]
virus = virus.ref
trojan = my_cool_trojan.ref
backdoor = backdoor.ref
; для целей глобальные настройки можно переопределять
[targets.virus]
refgo_flags = -l2000 -c300
[targets.trojan]
compiler = lambda
lambda_flags = -ODPRS
; зависимости, имеют вид
; имя_зависимости = путь_для_скачивания
; имя_зависимости нужно для указания дополнительных опций,
; кроме того, имя папки в папке modules/ будет основываться
; на имени зависимости (возможно, в конец припишется число).
[dependencies]
; если путь заканчивается на .zip — это архив,
pr = http://www.botik.ru/pub/local/scp/refal5/bin/prefal_180216.zip
; иначе — репозиторий git, после @ — номер версии (тег)
fw = https://github.com/Mazdaywik/refal-5-framework@1.0
; для зависимостей можно указывать дополнительные опции
[dependencies.pr]
; если распаковать архив prefal_180216.zip, то мы увидим,
; что rsl’ка лежит не в корне архива, а в подпапке prefal
subdir = prefal
Рабочий процесс
Доступны следующие команды:
r5pm fetch
— скачивает все зависимости в папкуmodules/
.r5pm build [цель…]
— собирает указанные цели. Если цели не указаны, собирает все цели.r5pm clean
— удаляет промежуточные скомпилированные файлы (*.rsl
для Рефала-5,*.c
для Рефала-05 и т.д.).r5pm distclean
— удаляет всё (папкиbin/
,modules/
,tmp/
).
Если при скачивании зависимостей в скачанном репозитории/архиве в корне нашёлся файл r5pm.prj
, извлекаются зависимости из него и скачиваются тоже и далее рекурсивно.
Процесс сборки:
-
В секции
[targets]
указываются «корневые» имена исходных файлов (как правило, они содержат функциюGo
илиGO
). -
Система сборки их сканирует на предмет наличия комментариев
*$FROM
— имена файлов, указанные в них, назовём импортируемыми файлами или импортами (не нашёл более подходящего термина). -
Затем система сборки по очереди смотрит в папку
lib/
, во все папкиmodules/*/lib
у скачанных зависимостей (если явно не указана другая подпапка в опцииsubdir
) и в папкуsrc/
до тех пор, пока не найдёт импортируемый файл с соответствующим расширением, если его не указал пользователь явно в комментарии.Соответствующее расширение — это сначала расширение
.ref
, а затем расширение, зависящее от компилятора в опцииcompiler
:.rsl
дляrefc
илиcrefal
,.c
для Рефала-05 и т.д. Если файл уже скомпилирован (например, импортhello
нашёлся как файлhello.rsl
), то рядом с этим файлом ищется файл с расширением.imports
(например,hello.imports
), который тоже сканируется на предмет строчек*$FROM
(см. далее). -
Каждый найденный импортируемый файл рекурсивно сканируется на комментарии
*$FROM
. -
Если импортируемый файл не найден, сборка прерывается с ошибкой.
-
Если список импортов для некоторой цели построен полностью, импортируемые файлы компилируются и в папке
bin/
создаётся запускаемый модуль.
Запускаемый модуль для компиляторов Рефал-05 и Рефал-5λ — исполнимый файл (хотя для Рефала-5λ может быть, например, .dll
или .so
в зависимости от lambda_flags
), тут всё просто.
Запускаемый модуль для Рефала-5 — это пара из скрипта ОС (.bat-файл на Windows ‹имя-цели›.cmd
и shell-скрипт для unix-подобных ОС ‹имя-цели›
без расширения и с флагом исполнимости) и папки с rsl-ками ‹имя-цели›.rsls
. Предполагается, что пользователь эти файл и папку будет копировать, перемещать и переименовывать синхронно. В папке ‹имя-цели›.rsls
.rsl-ки будут лежать, переименованными в a.rsl
, b.rsl
, …, z.rsl
, a1.rsl
… Возможный вид скриптов запуска (не уверен на счёт экранирования скобок в Bash):
@refgo "%~dpn0%.rsls(a+b+c+d)" -l2000 -c300 %*
#!/bin/sh
refgo "${0}.rsls\(a+b+c+d\)" -l2000 -c300 "$@"
Параметры -l2000 -c300
берутся из refgo_flags
в файле конфигурации.
В проекте может отсутствовать папка src/
, в этом случае проект может быть использован только как зависимость, ничего в папке bin/
не создаётся. В проекте может отсутствовать папка lib/
, в этом случае проект использовать как зависимость бессмысленно.
Плагины
Предполагается, что поддержка отдельных компиляторов (значения опции compiler
: refc
, crefal
, refal-05
, refal-5-lambda
) будет оформлена как плагины, т.е. не захардкожена напрямую в сам пакетный менеджер.
Плагин будет представлять собой команду r5pm-plugin-compiler-‹имя›
вроде r5pm-plugin-compiler-refal-05
. Пакетный менеджер будет вызывать соответствующую команду функцией System
, передавая ей необходимые опции — интерфейс взаимодействия нам пока не важен. Будут отдельные команды для запроса свойств (вроде расширений импортируемых файлов) и запуска сборки (передаётся файл, где перечислены исходные файлы + всякие опции).
Предустановленными будут плагины для refc
и crefal
, плагины для Рефала-05 и Рефала-5λ будут устанавливаться вместе с соответствующими компиляторами.
Судьба rlmake
В перспективе этот сборщик должен заменить собой утилиту rlmake
, последняя скукожится до плагина.
@TonitaN, вопросы, предложения, замечания, комментарии?
Идею всецело поддерживаю. Насчёт деталей реализации - пока не приходит в голову, чего не хватает, кроме одного. Если я собираю проект сразу двумя компиляторами (например, refc и лямбдой - я так делала с MSCP-A; или crefal-ом и refc), то как будет выглядеть результат?
Об этом я не подумал, спасибо.
Возможный вариант — переопределять опции в командной строке:
r5pm build +common.compiler=lambda
r5pm build +targets.trojan.compiler=refal-05
Надо ещё подумать — делать пакетный менеджер в этом репозитории или создать новый. GitHub позволяет перемещать заявки между репозиториями:
Я уже пробовал эту функцию, работает. Попробуй перейти по ссылке https://github.com/Mazdaywik/Refal-05/issues/27.
А конфликтов не будет, в случае если используется crefal и refc? Т.е. не появятся ли в одной директории перекрывающие друг друга rsl-файлы?
В самой первой версии инкрементной сборки не будет, всё будет пересобираться каждый раз заново. А в случае инкрементной сборки нужно будет обеспечить раздельное хранение промежуточных файлов (.rsl
,.rasl
, .cpp
) не только для разных компиляторов, но и для одного компилятора с разными флагами оптимизации.
А по поводу папки ‹имя-цели›.rsls
— я предполагаю, что она при каждой пересборке будет создаваться заново. А значит, последующая сборка с другим компилятором перетрёт предыдущую. Точно также, если попеременно собирать одну цель Рефалом-05 и Рефалом-5λ, ‹имя-цели›.exe
будет перезаписываться.
Если нужно обеспечить одновременную сборку разными компиляторами (например, для сравнения производительности), то можно сделать несколько целей:
[targets]
my-prog-refc = my-prog.ref
my-prog-crefal = my-prog.ref
my-prog-lambda = my-prog.ref
my-prog-lambda-opt = my-prog.ref
[targets.my-prog-refc]
compiler = refc
[targets.my-prog-crefal]
compiler = crefal
[targets.my-prog-lambda]
compiler = lambda
[targets.my-prog-lambda-opt]
compiler = lambda
lambda_flags = -ODPRS
Кстати, имя файла в папке bin/
может не совпадать с именем слева от =
в секции [targets]
:
[targets]
abcd = defg.ref
[targets.abcd]
name = my-cool-program.v1.exe