В r5098 я перевела автосборку с Make на Tup,
http://gittup.org/tup/index.html. Я написала подробно о новой системе в файле build.txt в корне репозитория.
Главные отличия Tup от Make:
1. Tup автоматически отслеживает зависимости. Если изменился .inc или .h файл, Tup поймёт, что программу надо пересобрать, даже если в файлах системы сборки изменённый файл вообще не упоминается. Для этого Tup под Linux использует fuse, запуская создаваемые процессы на виртуальной файловой системе, Tup под Windows перехватывает функции работы с файлами в создаваемых процессах. Если в файле системы сборки изменились опции вызова компилятора, Tup поймёт, что программу надо пересобрать. Если в файле системы сборки что-то изменилось, но на опции компиляции изменение не влияет, Tup поймёт, что программу пересобирать не надо.
2. Если считать, что исходные файлы находятся "внизу", а собираемые по ним бинарные файлы - "вверху", как на картинке,
Attachment:
kosgl-deps.png [ 82.59 KiB | Viewed 15624 times ]
то Make идёт сверху вниз, а Tup - снизу вверх.
2а. Знание о том, как компилировать программу, теперь находится в папке программы в виде файла Tupfile.lua, а не где-то в data/, посыпанное магической пылью макросов make. В data/Tupfile.lua остаётся знание о том, как собирать kolibri.img и kolibri.iso. Разделение по языкам остаётся, но только для того, чтобы можно было работать, не коллекционируя все компиляторы
и для понимания масштабов проблемы ЯВУ.
2б. Make пересобирает только файлы, от которых зависит kolibri.img или kolibri.iso. Tup пересобирает всё, для чего есть Tupfile.lua и что изменилось либо зависит от того, что изменилось. Если вам всё-таки хочется залить в репозиторий некомпилирующийся код, то, во-первых, подумайте, так ли вам это надо, во-вторых, ещё раз подумайте, точно ли вам это надо, если не передумали, то, в-третьих, удалите Tupfile.lua или закомментируйте создание правила для компиляции.
2в. Для того, чтобы собрать одну программу с помощью Tup, подходят ровно те же файлы Tupfile.lua, что и для сборки всего репозитория. Чтобы начать работать с tup, нужно выбрать папку, которую tup будет считать корнем, и скомандовать в ней tup init; после этого tup без аргументов где угодно внутри поддерева будет собирать всё, что изменилось в рамках поддерева. Поддеревом может быть как весь репозиторий, так и папка программы.
3. Tup понимает указания сборки на языке Lua, где граблей намного меньше, чем в Makefile'ах, а возможностей намного больше. В частности, нет проблемы с табуляцией, которую ни в коем случае нельзя заменять пробелами, и длинные массивы можно записывать таблицами, а не строками со слешами на конце, после которых ни в коем случае нельзя ставить ничего, даже пробела, и в которых нельзя закомментировать одну строчку в середине - и это только грабли, на которые в data/*/Makefile не раз наступали и которые я сходу помню.
Собирать весь репозиторий с помощью Tup, если вы не пытаетесь сделать ещё и kolibri.img, не так страшно, как звучит. Отсутствующие компиляторы можно выключить в конфиге, kpack и kerpack настраивать необязательно. Tup будет при запуске пересканировать всё поддерево, но это довольно быстро: у меня под Windows с "прогретым" дисковым кешем это занимает примерно две секунды - я на всякий случай уточню, что это включая contrib, с "холодным" - порядка минуты. На Linux вообще можно включить inotify-монитор, исключающий сканирование.
Возможные переменные для конфига с комментариями собраны в tup.config.template в корне репозитория. Его можно скопировать как tup.config в ту папку, где вы сделали tup init. Файлы сборки написаны так, что при отсутствии конфига рассчитывают на наличие компилятора и пытаются сделать что-нибудь разумное. В частности, язык, если он нужен для компиляции, будет английским, и вызов kpack отключён.
Типичный Tupfile.lua для программы на fasm:
Code:
if tup.getconfig("NO_FASM") ~= "" then return end
tup.rule("echo lang fix " .. ((tup.getconfig("LANG") == "") and "en" or tup.getconfig("LANG")) .. " > lang.inc", {"lang.inc"})
tup.rule({"test.asm", extra_inputs = {"lang.inc"}}, "fasm %f %o " .. tup.getconfig("KPACK_CMD"), "test")
Оператор "==" в Lua - проверка равенства, "~=" - проверка неравенства, ".." - конкатенация строк. Однострочные комментарии начинаются с "--".
Первая строчка: если в конфиге выставлена переменная CONFIG_NO_FASM, то скрипт немедленно завершает работу. Аналогичная строчка есть во всех Tupfile.lua, чтобы можно было собирать всё, не обладая какими-нибудь компиляторами.
Вторая и третья строчки создают по одному правилу сборки. У правила сборки может быть ноль или больше входов, команда сборки и ноль или больше выходов. Входы и выходы - почти всегда файлы, желающие могут почитать мануал Tup для уточнений. У функции tup.rule может быть два или три аргумента: обязательно указать команду и можно указать список входов, список выходов или и то, и другое. В форме с тремя аргументами список из одного элемента можно указывать просто строкой, как в третьей строке: {"test"} = "test". В форме с двумя аргументами это порождает неоднозначность input+command vs command+output, так что список нужно указывать списком. Возвращаясь к правилам, вторая строка создаёт lang.inc командой "echo lang fix LL > lang.inc", по умолчанию LL = en, его можно определить в конфиге. Третья строка компилирует test.asm. tup старается запускать параллельно всё, что можно, для этого ему нужно обязательно указывать зависимости между командами; tup проверяет все обращения к файлам, и попытка не указать сгенерированный "lang.inc" во входах при условии, что он действительно используется, приведёт к ошибке компиляции. Даже если lang.inc уже создан. Зависимости от не-генерируемых файлов указывать необязательно, tup вычислит их сам. Отличие входов из подсписка extra_inputs от входов из основного списка - в том, что первые не подпадают под %f в команде. Tup поддерживает некоторые %-спецификаторы внутри команды и выходных файлов. Полный список есть в мануале по tup, основные: %f - имена всех входных файлов, кроме extra_inputs, через пробел, %o - имена всех выходных файлов, кроме extra_outputs, %B - имена всех входных файлов без путей и расширений. В принципе, при отсутствии kpack третья строка могла бы не использовать %-спецификаторов и не указывать test.asm явно - он не-генерируемый, примерно так:
Code:
tup.rule("lang.inc", "fasm test.asm test", "test")
и это бы прекрасно работало, но в форме со спецификаторами появляются возможности переиспользовать команды в разных файлах: например, CONFIG_KPACK_CMD, если она задана, использует "%o".
Если в одной папке нужно компилировать два разнородных проекта - библиотеку и программу, например, - придётся явно создать два правила. Если в одной папке несколько программ, как в programs/develop/libraries/buf2d/trunk/examples, можно использовать tup.foreach_rule вместо tup.rule; foreach_rule создаст по одному правилу для каждого входа, кроме extra_inputs. В качестве выхода придётся указать что-нибудь с %-спецификатором, например, "%B" или "%B.exe", чтобы в каждом правиле выход раскрылся в что-то уникальное.
Пример Tupfile.lua для programs/demos/cubeline/trunk, зависящей от menuetlibc и TinyGL:
Code:
if tup.getconfig("NO_GCC") ~= "" then return end
HELPERDIR = (tup.getconfig("HELPERDIR") == "") and "../../.." or tup.getconfig("HELPERDIR")
tup.include(HELPERDIR .. "/use_gcc.lua")
tup.include(HELPERDIR .. "/use_menuetlibc.lua")
tup.include(HELPERDIR .. "/use_tinygl.lua")
compile_gcc{"main.cpp", "fps.cpp"}
link_gcc("cubeline")
Первая строчка аналогична предыдущему примеру. Дальше есть некоторые сложности из-за того, что, в отличие от примера с fasm, этот Tupfile.lua несамодостаточен и подключает programs/use_gcc.lua, programs/use_menuetlibc.lua, programs/use_tinygl.lua. В режиме компиляции всего репозитория дополнительных настроек делать не надо, HELPERDIR станет "../../..", относительным путём к programs/, три следующих строки установят параметры компиляции с использованием gcc, menuetlibc, tinygl. В режиме компиляции одной программы tup откажется подключать файлы сборки извне поддерева, так что придётся скопировать use_gcc.lua, use_menuetlibc.lua, use_tinygl.lua в папку с программой и указать в tup.config CONFIG_HELPER_DIR=.; в двух последних файлах нужно будет ещё заменить первую строку на актуальный путь к библиотеке. Предпоследняя строка создаст правила для компиляции main.cpp -> main.o и fps.cpp -> fps.o, а также запомнит созданные файлы .o в переменной OBJS; обратите внимание на фигурные скобки, compile_gcc получает один параметр-список. Последняя строка создаст правило для линковки всего, что накопилось в OBJS, в бинарник с именем cubeline.