Преимущества и недостатки redo
redo это предложенная
=> DJB
альтернатива Make системе сборки. Своё видение такой системы он просто описал
=> тремя небольшими страницами
Никакого кода не опубликовал.
Alan Grosskurth написал работу в институте на
=> эту тему
А десятки других людей позже написали свои реализации этой
системы сборки на самых разных языках. Очень хорошее пояснение и
введение с примерами есть в документации к
=> apenwarr/redo
=> Ещё одно хорошее введение
Программ/реализаций Make -- десятки. Систем сборки несовместимых с Make
-- с полсотни минимум. Десяток систем генерирующих Makefile-ы.
Несовместимых между собой диалектов Make-ов тоже десятки (например GNU
Make и BSD Make). Так чем же примечательна ещё одна система? Почему
нельзя использовать POSIX Make подмножество, которое, по идее, должно
поддерживаться и одинаково работать на широком круге Make реализаций?
Чуть ли не каждый заявляет что он лучше Make или как минимум удобнее.
Начну с единственного, что можно причислить к недостатку: redo из
коробки не часто стоит в ОС. Впрочем, как и почти все системы сборки,
кроме какого-то одного из Make, часто вас всё равно не удовлетворяющего
(сколько вам приходится дополнительно ставить GNU Make в вашу систему
чтобы хоть как-то собиралась масса софта)?
Преимущества:
* Не смотря на POSIX, всё равно будут не регламентированные отличия даже
в мелочах, существенно затрудняющие разработку Makefile-ов. Например,
одни Make реализации переходят в директорию при исполнении "make -C dir",
а другие нет.
* Полная реализация redo, с возможностью распараллеливания заданий, на
чистом C, без зависимостей, с встроенной полной реализацией SHA256,
занимает менее 1k SLOC. Реализация на чистом POSIX shell запросто
умещается в 100 SLOC. Поэтому, даже если redo не установлен в системе,
то достаточно скопировать небольшой shell скрипт (например
https://github.com/apenwarr/redo/blob/main/minimal/do занимает одну
треть от размера GPLv3 лицензии) или один бинарный исполняемый файл в
пару десятков килобайт. Реализации имеются и на других shell, Python,
Haskell, Go, C++, Inferno Shell.
* redo позволяет делать абсолютно *все* те же самые задачи что и Make. В
отличии от Make, в нём нет ни предположений о том, что вы собираете
(.SUFFIXES), ни специфичного для языка (часто это C/C++) hard-code.
* *Минимальный* порог входа -- не требуется изучение ни нового языка, ни
нового синтаксиса. По умолчанию, используется POSIX shell, но вы
вольны использовать *любой* язык по вашему усмотрению (или даже
смешивать их). Тогда как в Make нельзя удобно записывать shell в
несколько строчек. Лично у меня, на практике, redo описания получаются
меньше по размеру чем Makefile-ы.
* Задание зависимостей более простое и чёткое. Описание сборки цели
автоматом является зависимостью (если делать зависимость от Makefile,
то любое его изменение означает пересборку всех целей). Тривиально
делать динамические зависимости (например на основе #include из
.[ch]), что в Makefile невозможно. Зависимости можно задать даже после
сборки цели. Зависимости от изменённых переменных окружения или
переменных Make, аналогично, делаются тривиально. На практике, мало
кто делает подобное для Make из-за сложности, зачастую просто вручную
выполняя make clean, с последующей полной пересборкой. Более того, в
redo можно задавать зависимости уже *после* сборки цели!
* Отсутствие проблем с атомарностью сборки целей: redo все цели собирает
атомарно, через fsync и переименование временного файла. Make ничем не
помогает в этом -- вы вынуждены вручную эмулировать атомарность
сборки, чего, на практике, мало кто делает из-за сложности и
неудобства. Частично скопированные/скачанные файлы или упавшая команда
не будут более считаться успешно выполненной целью.
* Распараллеливание сборок целей и понимание изменения зависимостей
делается на основе сохраняемого на диске состояния, поэтому не
возникает проблем с "слепотой" Make к изменению файлов, например из-за
NFS, mmap, VCS, FUSE (со всеми ними
=> нет гарантий
об изменениях в mtime), небольшой гранулярностью времени mtime,
нестабильных часов. Многие реализации используют криптографические
хэши для выяснения изменённости файлов.
* По умолчанию, описания правил сборки целей изолированы друг от друга.
redo позволяет делать зависимости на цели в других директориях, с
полным централизованным глобальным ведением информации о зависимостях,
с state-ом и lock-ами, без каких-либо недостатков описанных в
=> Recursive Make Considered Harmful
* Возможность задания зависимостей на несуществующие цели, например на
флаговые файлы, новые описания правил сборки.
* Никаких проблем с именами файлов содержащие пробелы.
Как же пользоваться этой системой сборки?
Приведу её полное описание с точки зрения пользователя.
Для конкретных примеров советую ссылки в начале этой статьи.
* Целью является единственный файл. Результат работы цели может и не
создать файл.
* Правила сборки цели target прописываются в target.do файле. По
умолчанию он запускается как "/bin/sh -e" скрипт. Но это может
зависеть от конкретных redo реализаций: некоторые, если файл является
исполняемым, запускают его как полноценную программу; некоторые,
позволяют запускать иные интерпретаторы при указании shebang-а.
Некоторые реализации при задании отладочного флага, добавляют -x к
вызову /bin/sh.
* Цели можно указывать и в других директориях. Соответствующие .do файлы
будут искаться в них для начала.
* Если dir/base.a.b.do не найден, то ищутся:
dir/default.a.b.do
dir/default.b.do
dir/default.do
default.a.b.do
default.b.do
default.do
А для цели ../a/b/xtarget.y ищутся:
./../a/b/xtarget.y.do
./../a/b/default.y.do
./../a/b/default.do
./../a/default.y.do
./../a/default.do
./../default.y.do
./../default.do
В принципе, единственный default.do можно использовать как аналог
единственного Makefile.
* .do запускается всегда в той же директории где и сам .do.
* .do запускается с тремя аргументами:
$1 -- имя цели
$2 -- базовое имя цели. Совпадает с $1 если это не default файл. В
противном случае оно будет без расширения, например для цели a.b.c:
a.b.c.do -> $2=a.b.c
default.do -> $2=a.b.c
default.c.do -> $2=a.b
default.b.c.do -> $2=a
$3 -- имя временного выходного файла, который будет переименован в "цель"
* stdout .do скрипта записывается во временный файл и становится
результатом выполнения цели. Вместо stdout можно использовать $3 файл.
* Некоторые реализации не создают пустой файл цели, если вывода в
stdout/$3 не было, для удобства.
* Если цель не существует, то она считается устаревшей.
* Если цель не задана, то многие реализации по умолчанию собирают all.
* Зависимость в .do файле прописывается как вызов команды redo-ifchange
с передачей в качестве аргументов множества зависимых целей. Если
файла перечисленной зависимости нет, то она собирается и запоминается
в state как зависимость. Если файл зависимости не изменился, то цель
зависимости не пересобирается. Зависимость от текущего .do
автоматическая.
* Несуществующие зависимости в .do файле прописываются как вызов
redo-ifcreate с аргументами несуществующих файлов. В state
запоминается отсутствие файла. Его появление вызывает пересборку
текущей цели.
* Явный вызов redo target форсированно вызывает пересборку цели, даже
если зависимости не изменились (.PHONY-like цель).
Многие реализации дополнительно предлагают ещё такую команду как
redo-always: всегда пересобирать указанные зависимости. Некоторые
реализации используют ctime для понимания изменился ли файл, а некоторые
-- множество различных атрибутов файлов (mtime, size, inode number, file
mode, UID/GID, и т.д.). Некоторые просто считают криптографический хэш
от файла -- плюсом этого решения является уменьшение false-positive сборок.
Проверенные мной работающие реализации:
=> apenwarr/redo
Python
apenwarr/do -- из состава apenwarr/redo
POSIX shell
=> redo-c
C, есть проблема с пустыми целями
=> goredo
Go
=> baredo
C, проверяет только mtime