[О блоге] [наверх] [пред] [2022-09-29 22:59:47+03:00] [17aa5a765f6ab4a81dc3e14bf8ecdcd5a88ac820]
Темы: [redo]

autoconf vs redo

Количество проектов на Си на работе плодится. И в них я использую redo
вместо Make и autoconf. Вообще конечно абсолютно некорректно сравнивать
redo и autoconf, ибо у них по сути не пересекающиеся задачи, но autoconf
это же ведь множество задач/целей, результат выполнения которых
используется, по сути, как зависимость для команд сборки. И кучу всяких
детектов как-раз можно описывать в виде redo целей. Но об этом уже писал
в 401c0f635a1cdfb01068a48a4cdf40791d3db458. Если в autoconf нужно знать
как этот сам framework, так ещё и основы M4, то в redo ничего не нужно
дополнительно: что хочешь, то и используй. Нужно Си программу
проверяющую работоспособность чего то? Делаешь цель, которая может
зависеть от уже имеющихся детектов флагов, компиляторов, других команд,
которая соберёт что нужно. Распараллеливание всех этих детектов из
коробки (ведь известно, что ./configure может выполняться существенно
дольше чем вообще вся сборка программы после него).

Сегодня понял про другой плюс: redo-log команда может показать вывод
связанный только и только с заданной целью. Если ./configure падает, то
это очень не тривиально найти место даже самого последнего падения в
config.log, ибо в конце там вывод далеко не последнего детекта.

Пока просматривал свою старую запись про замену autoconf, то удивился
что ещё проще у меня многое стало. Я полностью избавился от redo-stamp.
Я приводил пример с conf/cmd/cc.do целью, где основное содержание было:

    echo "${CC:-`command -v cc`}" > $3
    command -v redo-stamp > /dev/null || exit 0
    redo-stamp < $3

а теперь это conf/cmd/default.do:

    envname=`echo "$1" | tr a-z A-Z | tr - _`
    eval echo '${'$envname':-`command -v '$1'`}'

который применим к любой команде (это default цель), которую и через или
переменные окружения или config-файл в корне можно переопределить.

В моём прошлом примере было упоминание pkg-config-based цели. Но на
практике регулярно pkg-config нужен не только для определения одной
библиотеки и все эти цели идентичны и похожи, поэтому вместо
conf/flags/libcrypto.do:

    redo-ifchange ../../config ../cmd/pkg-config
    . ../../config
    read PKG_CONFIG < ../cmd/pkg-config
    cat <<EOF
    ${SSL_CFLAGS:-`$PKG_CONFIG --cflags libcrypto`}
    ${SSL_LDFLAGS:-`$PKG_CONFIG --libs-only-L libcrypto`}
    ${SSL_LDLIBS:-`$PKG_CONFIG --libs-only-l libcrypto`}
    EOF

результат которого мне надо читать чётко понимая что там три строчки
флагов, я делаю conf/flags/default.pc.rc.do:

    redo-ifchange ../../config
    . ../../config
    libname=$2
    prefix=`echo $libname | tr a-z A-Z | tr - _`
    eval cflags=\$${prefix}_CFLAGS
    eval ldflags=\$${prefix}_LDFLAGS
    eval ldlibs=\$${prefix}_LDLIBS
    eval pkgconf=\$${prefix}_PKGCONF
    cat <<EOF
    {
        read ${prefix}_CFLAGS
        read ${prefix}_LDFLAGS
        read ${prefix}_LDLIBS
        read ${prefix}_PKGCONF
    } <<EOF
    EOF
    export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}
    if [ -z "$cflags" -a -z "$ldflags" -a -z "$ldlibs" ] ; then
        redo-ifchange ../cmd/pkgconf
        read PKGCONF < ../cmd/pkgconf
        $PKGCONF --cflags $libname
        $PKGCONF --libs-only-L $libname
        $PKGCONF --libs-only-l $libname
        echo $libname
    else
        cat <<EOF
    $cflags
    $ldflags
    $ldlibs
    $pkgconf
    EOF
    fi
    echo EOF

Если мне нужно получить флаги для сборки whatever библиотеки, то
достаточно выполнить redo conf/flags/whatever.pc.rc. whatever станет
вторым аргументом при выполнении этого скрипта, и будет подставлятся по
все эти команды, создавая .rc-файл, который если за-source-ить, то на
руках получим WHATEVER_CFLAGS, WHATEVER_LDFLAGS, и т.д..

Подобные цели я таскаю из проекта в проект без изменений: они just
works. Как в autoconf есть свои библиотеки детектов, так и здесь
подобные цели аналогичную функцию выполняют.

Но зная о default цели, мы же не знаем какие именно команды
предполагается вызывать в проекте? Раньше я делал что-то-там.list файлы
со списком возможных сгенерированных команд. Вот только их приходилось
дублировать в .gitignore файле. Теперь я подобные списки храню в
.gitignore полностью, ведь чем он хуже остальных файлов? А clean.do цель
в директории может подчистить файлы просто делая:
rm -f `sed "s#/##" < .gitignore`

./configure может выдать полный список опций/флагов который пользователь
может задать и переопределить. Если цели для определения команд и флагов
написаны аккуратно, то это и в redo-based проектах выполняется очень
просто, например в conf/vars.list.do:

    redo-ifchange fn2env.sh cmd/.gitignore flags/.gitignore
    fn2env() { tr a-z A-Z | tr - _ }
    {
        sed "s#^/##" cmd/.gitignore | ./fn2env.sh
        sed -n 's#^/\(.*\)\.pc\.rc$#\1_CFLAGS \1_LDFLAGS \1_LDLIBS \1_PKGCONF#p' \
            flags/.gitignore | ./fn2env.sh
        perl -ne 'print "$1\n" if /\$\{(\w+):?.*\}/' *.do flags/*.do | ./fn2env.sh
    } | tr " " "\n" | sort | uniq | fmt

и сделав redo conf/vars.list && cat conf/vars.list можно получить
красивый список типа:

    AR CC CFLAGS CLANG_FORMAT CLANG_TIDY DTRACE XXX_CFLAGS XXX_LDFLAGS
    XXX_LDLIBS XXX_PKGCONF INCDIR INCLUDE_WHAT_YOU_USE INFODIR LDFLAGS
    LDLIBS LIBDIR LIBTAP_CFLAGS LIBTAP_LDFLAGS LIBTAP_LDLIBS MAKEINFO
    PKG_CONFIG_DIR PKG_CONFIG_PATH PKGCONF PLANTUML PREFIX

Детектирование glibc например делаю так:

    redo-ifchange ../../config ../cmd/cc
    . ../../config
    GLIBC_CFLAGS="-D_POSIX_C_SOURCE=200112L"
    GLIBC_CFLAGS="$GLIBC_CFLAGS -D_DEFAULT_SOURCE"
    read CC < ../cmd/cc
    src=`mktemp`
    trap "rm -f $src $src.c" HUP PIPE INT QUIT TERM EXIT
    cat > $src.c <<EOF
    #include <features.h>
    int main(int argc, char **argv) { return 0; }
    EOF
    $CC -o /dev/null $src.c > /dev/null 2>&1 && printf "%s\n" "$GLIBC_CFLAGS" || echo

где факт компилирования с features.h вполне годится. А в других целях я
из этого файла возьму опциональные флаги нужные для ОС с glibc.
Аналогично я делаю Си программы которые уже что-то выводят (xxx.rc.do):

    redo-ifchange ../../config ../cmd/cc common.rc xxx.pc.rc
    read CC < ../cmd/cc
    . ./common.rc
    . ./xxx.pc.rc
    src=`mktemp`
    trap "rm -f $src $src.c" HUP PIPE INT QUIT TERM EXIT
    cat > $src.c <<EOF
    #include <stdio.h>
    #include <xxx.h>
    int main(int argc, char **argv) {
        puts("{\\nread XXX_FOO_CTX_SIZE\\nread XXX_BAR_CTX_SIZE\\n} <<EOF");
        printf("%zu\\n%zu\\nEOF\\n", XXX_FOO_CTX_SIZE, XXX_BAR_CTX_SIZE);
        return 0;
    }
    EOF
    $CC $XXX_CFLAGS $CFLAGS -o $src $src.c $LDFLAGS $XXX_LDFLAGS $LDLIBS $XXX_LDLIBS
    $src

И этот детект зависит и от default.pc.rc и cmd/default.do целей.

    [оставить комментарий]
    комментарий 0:
    From: kmeaw
    Date: 2022-10-01 16:17:18Z
    
    > clean.do цель в директории может подчистить файлы
    
    Если используется git, то можно сделать git clean -Xf - тогда будет
    использоваться полноценный парсер .gitignore.
    
    комментарий 1:
    From: Sergey Matveev
    Date: 2022-10-02 09:28:15Z
    
    *** kmeaw [2022-10-01 19:16]:
    >Если используется git, то можно сделать git clean -Xf - тогда будет
    >использоваться полноценный парсер .gitignore.
    
    Хм, не приходила эта мысль раньше. Выглядит здраво и красиво! И именно
    сегодня как-раз появились в одном redo-driven проекте .gitignore-ы
    которые действую на целой иерархии директорий, где просто sed/whatever
    уже не подошёл бы. git clean как раз решает и эту проблему. Спасибо!