Главные отличия Tup от Make:
1. Tup автоматически отслеживает зависимости. Если изменился .inc или .h файл, Tup поймёт, что программу надо пересобрать, даже если в файлах системы сборки изменённый файл вообще не упоминается. Для этого Tup под Linux использует fuse, запуская создаваемые процессы на виртуальной файловой системе, Tup под Windows перехватывает функции работы с файлами в создаваемых процессах. Если в файле системы сборки изменились опции вызова компилятора, Tup поймёт, что программу надо пересобрать. Если в файле системы сборки что-то изменилось, но на опции компиляции изменение не влияет, Tup поймёт, что программу пересобирать не надо.
2. Если считать, что исходные файлы находятся "внизу", а собираемые по ним бинарные файлы - "вверху", как на картинке, то 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: Select all
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")
Первая строчка: если в конфиге выставлена переменная 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: Select all
tup.rule("lang.inc", "fasm test.asm test", "test")
Если в одной папке нужно компилировать два разнородных проекта - библиотеку и программу, например, - придётся явно создать два правила. Если в одной папке несколько программ, как в 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: Select all
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")