Преимущества и недостатки 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