Русский
Русский
English
Статистика
Реклама

Make

Make на мыло, redo сила

02.09.2020 14:13:27 | Автор: admin
Приветствую! Хочу рассказать о главных, не всегда очевидных, недостатках системы сборки Make, делающих её часто не пригодной для использования, а также рассказать о прекрасной альтернативе и решении проблемы гениальнейшей по своей простоте, системе redo. Задумка известнейшего DJB, криптография которого где только не применяется. Лично меня, redo настолько впечатлил life-changing простотой, гибкостью и куда лучшим выполнением задач сборки, что я практически во всех своих проектах им полностью заменил Make (там где не заменил значит ещё руки не дошли), у которого я не смог найти ни одного преимущества или причины оставлять в живых.


Yet another Make?


Make много кого не устраивает, иначе не было бы десятков других систем сборки и десятков диалектов одного только Make. А redo это ещё одна очередная альтернатива? С одной стороны конечно же да только крайне простая, но способная решать абсолютно все те же задачи что и Make. С другой стороны а разве у нас есть какой-то общий для всех и единый Make?

Большинство альтернативных систем сборки рождалось потому что не хватало родных возможностей Make, не хватало гибкости. Многие системы занимаются только генерированием Makefile-ов, не производя сборку самостоятельно. Многие заточены под экосистему определённых языков программирования.

Ниже я постараюсь показать что redo является куда более заслуживающей внимания системой, не просто yet another решением.

Make всё равно всегда есть


Лично я всё равно всегда косо смотрел на всю эту альтернативу, ибо она или сложнее, или ecosystem/language-specific, или является дополнительной зависимостью которую нужно ставить и изучать как ею пользоваться. А Make это такая вещь, с которой плюс-минус все знакомы и умеют пользоваться на базовом уровне. Поэтому всегда и везде старался использовать POSIX Make, предполагая что это то, что в любом случае у каждого есть в (POSIX) системе из коробки, как например компилятор C. И задачи в Make выполнять только для которых он предназначен: распараллеливаемое выполнение целей (команд) с учётом зависимостей между ними.

В чём проблема просто писать на Make и быть уверенным что на любых системах это заработает? Ведь можно же (нужно!) писать на POSIX shell и не заставлять пользователей ставить какие-нибудь монструозные громадные GNU Bash. Проблема только в том, что работать будет только POSIX Make диалект, достаточно скудный даже для многих небольших простых проектов. Make в современных BSD системах более сложен и feature-full. Ну а с GNU Make мало с кем идёт в сравнение, хотя его возможностей по полной почти никто и не использует и не знает как ими пользоваться. Но GNU Make не поддерживает диалект современных BSD систем. А BSD системы не имеют GNU Make в своём составе (и их можно понять!).

Использовать BSD/GNU диалект значит потенциально заставлять пользователя всё равно ставить дополнительный софт, не идущий из коробки. В этом случае, возможное преимущество Make его наличие в системе, сводится на нет.

Использовать и писать на POSIX Make можно, но сложно. Лично у меня с ходу вспоминается два очень раздражающих случая:

  • Какие-то Make реализации при выполнении $(MAKE) -C переходят в директорию выполнения нового Make, а какие-то нет. Можно ли написать Makefile так, чтобы оно одинаково работало везде? Безусловно:

    tgt:    (cd subdir ; $(MAKE) -C ...)
    

    Удобно? Безусловно нет. И неприятно тем, что о подобных мелочах надо постоянно помнить.
  • В POSIX Make нет оператора выполняющего shell-вызов и его результат сохраняющий в переменную. В GNU Make до 4.x версии можно сделать:

    VAR = $(shell cat VERSION)
    

    а начиная с 4.x, а также в BSD диалектах можно выполнить:

    VAR != cat VERSION
    

    Не совсем аналогичным действием можно сделать:

    VAR = `cat VERSION`
    

    но оно буквально подставляет это выражение в ваши shell-команды описанные в целях. Этот подход применяют в suckless проектах, но это, конечно же, костыль.

Лично я в подобных местах часто писал Makefile-ы сразу под три диалекта (GNU, BSD и POSIX):

$ cat BSDmakefileGOPATH != pwdVERSION != cat VERSIONinclude common.mk$ cat GNUmakefileGOPATH = $(shell pwd)VERSION = $(shell cat VERSION)include common.mk

Удобно? Отнюдь! Хотя задачи крайне просты и распространены. Вот и выходит, что или:

  • Писать параллельно для нескольких диалектов Make. Размен времени разработчика на удобство пользователя.
  • Помня о множестве нюансов и мелочей, возможно с неэффективными подстановками (`cmd ...`), пытаться писать на POSIX Make. Лично для меня, с многолетним опытом с GNU/BSD Make, этот вариант самый трудозатратный (проще писать на нескольких диалектах).
  • Писать на одном из диалектов Make, заставляя пользователя ставить сторонний софт.

Технические проблемы Make


Но всё гораздо хуже от того, что любой Make не сказать что (хорошо) справляется с поставленными на него задачами.

  • mtime не даёт никаких гарантий, а Make оценивает свежесть целей исключительно по mtime, сравнивания его значение у выполненных целей. Если гранулярность временных штампов вашей файловой системы такая, что быстрый компьютер способен обновлять файлы быстрее, то Make будет бессилен понять изменение файлов. mtime не обязан монотонно возрастать! Обновлённый mtime также и не обязан быть ни больше, ни меньше, ни равным текущему времени! Как с mtime работают системы контроля версий по разному, но никаких гарантий об его обновлении не дают. FUSE файловые системы вообще могут отдавать mtime хоть всегда нулевого значения. mmap обновит ваш mtime когда-нибудь, или пока не вызван msync (это штатное POSIX поведение). А если у вас NFS? Всё это приводит к тому, что работать Make ожидаемо может только на системах: медленных (или хорошей гранулярностью времени на ФС), с всегда идущими вперёд часами, без FUSE/NFS/mmap/VCS.
  • Цели выполняются не атомарно. А должны? Make считает что не его забота. Но преобладающее большинство людей всё равно же захочет и будет писать цели вида:

    tgt-zstd:    zstd -d < tgt-zstd.zst > tgttgt-fetch:    fetch -o tgt-fetch SOME://URL
    

    Но внезапный сбой, перезапуск системы, убийство Make процесса с детьми, приведут к тому, что на файловой системе будут созданы целевые файлы и они не будут пересобираться, так как, с точки зрения Make, выполнены и актуальны.

    Решить эту проблему можно:

    tgt-zstd:    zstd -d < tgt-zstd.zst > tgt-zstd.tmp    fsync tgt-zstd.tmp    mv tgt-zstd.tmp tgt-zstd
    

    Но кому захочется в такие tmp/fsync/mv вызовы оборачивать каждое описание цели? Более того, в примере выше могут быть проблемы при параллельном запуске Make-ов, параллельно собирающих и записывающих в tgt.tmp.
  • Цели на зависят от своего описания. Если вы поменяли описание цели (её команды) Makefile, то будет ли Make производить пересборку этой изменённой цели? Нет. А если вы обновили какие-то переменные типа $(CFLAGS)? Тоже нет.

    Но никто же не мешает вам прописать зависимость цели от самого Makefile! Изменение описания целей будет приводить к пересборке. Но если у вас один Makefile на множество целей, то изменение только одной из них, приведёт к пересборке и всех остальных. Хотя, безусловно, уж лучше пересобрать что-то лишнее, чем недособрать что требуется.

    А что если описание целей вынести в свои отдельные Makefile и их просто:

    $ cat Makefileinclude tgt1.mkinclude tgt2.mk...
    

    Это решит проблему пересборки лишнего. Но удобно ли? Отнюдь!
  • Повезёт, если будут работать рекурсивные цели. Отличная небольшая статья Recursive Make Considered Harmful описывает простейшие случаи, когда рекурсивные Makefile-ы, где нижестоящие Makefile-ы зависят от каких-то целей описанных в сторонних, и, в зависимости от того как Make проходит граф зависимостей, могут легко возникать ситуации где всё это не будет работать корректно и пересобирать что надо. Один единственный большой Makefile на весь проект решение. Удобно? Отнюдь, иначе бы не писали рекурсивные Makefile.

    А если включить распараллеленную сборку? Тогда обход графа зависимостей снова будет нарушен, нередко и непредсказуемым образом. Например при сборке всяких FreeBSD портов, именно поэтому, по умолчанию, отключают распараллеливание, так как с ним часто может всё ломаться и вести себя непредсказуемым образом.
  • Динамически сгенерированные цели сделать нельзя. А ведь так хочется чтобы автоматически, если я написал #include tgt.h, .c файл зависел от tgt.h, ведь эту информацию можно узнать из .c выполнив какой-нибудь sed вызов.

    tgt.o: tgt.c `sed s/.../ tgt.c`
    

    сделать не выйдет. Иногда можно попытаться сгенерировать новый .mk Makefile с этими зависимостями и сделать его include. Будет ли это работать? Зависит от конкретной реализации Make, но скорее всего не так как ожидается: .mk честно пересоберётся, но проинтерпретирована будет его прошлая версия, прочитанная на этапе чтения всех Makefile-ов с include-ами.
  • Цели Makefile-ов не совсем обычный shell, а каждая строка запускается в отдельном интерпретаторе, из-за чего часто приходится или писать крайне некрасивые многострочные, но объединённые через \\$, скрипты, или выносить их в отдельные .sh файлы, вызываемые из Make. И сам Make это новый язык/формат, так и его shell это тоже не совсем привычный и удобный shell, в котором не забывать и про экранирование корректное придётся. Удобно?

Давайте честно признаемся: как часто и сколько приходилось делать make clean или пересобирать без распараллеливания, потому что что-то недособралось или не пересобралось вопреки ожиданиям? В общем случае, безусловно, это связано не с идеально корректно, правильно и полно написанными Makefile-ами, что говорит о сложности их грамотного и работоспособного написания. Инструмент должен помогать.

Требования redo


Чтобы перейти к описанию redo, я для начала расскажу что он из себя представляет как реализация и что придётся выучить его пользователю (разработчику описывающему цели и зависимости между ними).

  • redo, в общем случае, вообще не привязан к какому-либо языку для описания целей. redo не заставляет изучать новый диалект или формат файлов. Знаний POSIX shell полностью достаточно. Но можно писать все цели на Python или вообще в виде исполняемых файлов. Минимальный порог входа: ни нового формата, ни языка, ни кучи команд.
  • redo реализаций много на разных языках: POSIX shell, GNU bash, Python, Haskell, Go, C++, Inferno Shell. Средний разработчик способен написать его реализацию за день.
  • Реализация с полноценными учётом состояния и распараллеливанием сборки на чистом C занимает менее тысячи строк кода, включая полную реализацию SHA256, а результирующий исполняемый файл у меня занимает 27KB. На чистом POSIX shell можно написать в 100 строк. Это также означает и то, что вы можете просто встроить POSIX shell реализацию redo в свои tarball-ы с софтом и пользователю ничего не придётся ставить дополнительно.
  • Он не имеет ни одной описанной выше проблемы Make-а, превосходно выполняя все его задачи (с распараллеливанием).

Обычные redo цели


Правила сборки цели являются обычным POSIX shell скриптом в файле имя-цели.do. Напомню в последний раз, что это может быть и любой другой язык (если добавить shebang) или просто исполняемый бинарный файл, но по умолчанию это POSIX shell. Скрипт запускается с set -e и тремя аргументами:

  • $1 имя цели
    $2 базовое имя цели (об этом ниже)
    $3 имя файла результата

    Вот треть redo я уже и описал. Результатом выполнения цели является или весь выловленный stdout или созданный $3 файл. Почему так? Где-то удобнее не возиться с промежуточными файлами, а где-то не все программы умеют свой результат писать сразу в stdout. Например приводимые для примера цели в начале статьи в redo:

    $ cat tgt-zstd.dozstd -d < $1.zst$ cat tgt-fetch.dofetch -o $3 SOME://URL
    

    Предполагаем, что fetch не умеет писать в stdout. stdout сохраняется во временном файле, как и $3. После завершения работы скрипта, выполняется его fsync и переименование в название цели. Тем самым гарантируя атомарность её выполнения! И только при успешном выполнении, только при отработке fsync и записи результат в директории только тогда будет успешно выполнена цель.

    Некоторые цели, типа (make) clean, как правило, не генерируют никаких результатов. Большинство redo реализаций не создают пустой файл, что удобно. По умолчанию, большинство реализаций выполняют all цель.

    default цели


    Очень часто многие цели собираются одними и теми же командами. В POSIX Make можно делать такие правила сборки всех .c:

    .c:    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
    

    в redo для этого используются default.do файлы, а точнее default.ВОЗМОЖНО-КАКИЕ-ТО-РАСШИРЕНИЯ.do. Аналогом Make выше будет:

    $ cat default.c.do$CC $CLFAGS $LDFLAGS -o $3 $1
    

    Часто хочется узнать имя цели без расширения для этого используется $2 аргумент, совпадающий с $1 в обычных redo целях. В default-ных он будет:

    a.b.c.do       -> $2=a.b.cdefault.do     -> $2=a.b.cdefault.c.do   -> $2=a.bdefault.b.c.do -> $2=a
    

    Цели можно указывать спокойно и в любых директориях, что уровнем ниже, что выше. Вместо cd dir; redo tgt можно писать redo dir/tgt. Цель всегда будет выполняться в той же директории что и .do файл. Поэтому использование относительных путей будет надёжно работать, раз всегда известно где будет рабочая директория во время выполнения цели.

    Если файла имя-цели.do не найдено, то ищется default.do файл. А с учётом возможных расширений, поиск .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
    

    Вот уже 2/3 всей redo системы описано.

    Зависимости


    Зависимости для цели задаются путём вызова в ней redo-ifchange команды:

    $ cat hello-world.doredo-ifchange hello-world.o ../config. ../config$CC $CFLAGS -o $3 hello-world.o$ cat hello-world.o.doredo-ifchange hw.c hw.h ../config. ../config$CC $CFLAGS -c -o $3 hw.c$ cat ../configCC=ccCFLAGS=-g$ cat ../all.do# этот файл в корне проекта для красоты, чтобы, набрав <em>redo</em>, он собрал# hw/hello-world программу по умолчаниюredo-ifchange hw/hello-world# Очистка проекта для красоты$ cat ../clean.doredo hw/clean$ cat clean.dorm -f *.o hello-world
    

    Именно тут коренное отличие redo и кроется: у него есть state. Для каждой цели в нём сохраняются зависимости и информация чтобы понять их свежесть. redo-ifchange запишет, что при выполнении такой-то цели, ей требовались такие-то зависимости, а также проверит не изменились ли они, все ли они свежи, а если нет, то запустить их сборку. Зависимость от .do файла автоматическая. В примере выше, изменение config файла приведёт к пересборке всего что касается hello-world программы.

    Где хранится state? Зависит от реализации. Кто-то хранит в виде TSV-like файла имя-цели.do.state, кто-то аналогично, но в .redo директории, кто-то в SQLite3 СУБД .redo директории.

    stderr целей кем-то не перехватывается, а кем-то сохраняется в state, чтобы пользователь мог дать команду покажи ка мне лог сборки такой-то цели.

    Что сохраняется в state? Автор redo предлагает вообще хранить криптографический хэш от зависимости: если хоть бит изменится, независимо от FUSE/mmap/NFS/VCS, то гарантированно это изменение будет обнаружено. Некоторые реализации хранят набор из ctime, inode number, размера и кучи всего другого с хэшом тогда не придётся его пересчитывать, если мы всё равно увидели что размер обновился.

    Наличие state на файловой системе позволяет иметь и lock-и и поэтому проблем аналогичных рекурсивному Make нет. Есть общий для всех (в пределах проекта) state с lock-ами и всеми известными зависимостями. Это также безопасно позволяет запускать сколько угодно параллельных процессов сборки.

    Динамика


    Может показаться, что redo-ifchange это как-раз и есть такой новый формат, который всё же придётся иметь в виду. Но нет это обычный вызов команды. redo-ifchange можно вызвать внутри скрипта абсолютно когда угодно, хоть пост фактум после сборки цели:

    redo-ifchange $2.cgcc -o $3 -c $2.c -MMD -MF $2.depsread deps < $2.depsredo-ifchange ${deps#*:}
    

    В него можно подставлять любые пути, хоть автоматически сгенерированные на основе include-ов:

    $ cat default.o.dodeps=`sed -n 's/^#include "\(.*\)"$/\1/p' < $2.c`redo-ifchange ../config $deps[...]
    

    Зависеть от всех собранных *.c?

    for f in *.c ; do echo ${f%.c}.o ; done | xargs redo-ifchange
    

    Без проблем можно и сгенерировать .do (....do.do цель) файл на лету и иметь от него зависимость. А чтобы не писать в куче .do файлах одни и те же $CC $CFLAGS..., то можно это сохранить в отдельном файле компиляции кода:

    $ cat tgt.doredo-ifchange $1.c cc./cc $3 $1.c$ cat cc.doredo-ifchange ../config. ../configcat > $3 <<EOF#!/bin/sh -e$CC $CFLAGS $LDFLAGS -o \$1 \$@ $LDLIBSEOFchmod +x $3
    

    Хочется сгенерировать compile_flags.txt для интеграции с Clang LSP демоном?

    $ cat compile_flags.txt.doredo-ifchange ../config. ../configecho "$PCSC_CFLAGS $TASN1_CFLAGS $CRYPTO_CFLAGS $WHATEVER_FLAGS $CFLAGS" |    tr " " "\n" | sed "/^$/d" | sort | uniq
    

    А как получить все эти $PCSC_CFLAGS, $TASN1_CFLAGS? Конечно же, используя pkg-config, без громоздких и медленных autotools!

    $ cat config.docat <<EOF[...]PKG_CONFIG="${PKG_CONFIG:-pkgconf}"PCSC_CFLAGS="${PCSC_CFLAGS:-`$PKG_CONFIG --cflags libpcsclite`}"PCSC_LDFLAGS="${PCSC_LDFLAGS:-`$PKG_CONFIG --libs-only-L libpcsclite`}"PCSC_LDLIBS="${PCSC_LDLIBS:-`$PKG_CONFIG --libs-only-l libpcsclite`}"TASN1_CFLAGS="${TASN1_CFLAGS:-`$PKG_CONFIG --cflags libtasn1`}"TASN1_LDFLAGS="${TASN1_LDFLAGS:-`$PKG_CONFIG --libs-only-L libtasn1`}"TASN1_LDLIBS="${TASN1_LDLIBS:-`$PKG_CONFIG --libs-only-l libtasn1`}"[...]EOF
    

    Если кому-то всё же любо видеть единственный .do файл, аналогичный такому Makefile:

    foo: bar baz    hello world.c:    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
    

    то это легко сделать:

    $ cat default.docase $1 infoo)    redo-ifchange bar baz    hello world    ;;*.c)    $CC $CFLAGS $LDFLAGS -o $3 $1    ;;esac
    

    но при этом не забывать, что изменение default.do приведёт к пересборке всех целей им созданных. А если надо ровно для одного .o файла сделать особые команды или дополнительные зависимости? Ну так и написать для него special.o.do, а остальные будут fallback делать до default.o.do и default.do правил.

    Заключение


    Про redo я слышал наверное ещё лет десять назад, но не придал ему значения и даже повёл носом от того, что мне что, придётся каждую цель в отдельном файле описывать!? (про default я не знал). Но решил попробовать, опять же, без уверенности что переезд будет безболезненным и, тем более, стоящим. А я большой любитель минималистичных и suckless подходов (уж извините, но CMake, собирающийся дольше чем многие GCC, с документацией более ёмкой чем pure-C реализация redo это перебор).

    • Гора ручной работы убрана из-за возможности автоматизации и динамического создания зависимостей.
    • Полностью решённые проблема с совместимостью на разношёрстных системах (*BSD vs GNU) POSIX shell работает везде одинаково, совершенно разные (Python, C, shell) реализации redo вели себя одинаково.
    • Минус целый язык/формат Makefile-ов.
    • Гарантированно работающее распараллеливание сборок.
    • Небывалая и невиданная точность задания зависимостей (потому что легко и просто) и, соответственно, система сборки честно пересобирает только то что связано и изменилось.
    • Каждая цель в своём файле оказалось очень удобным, так как можно узнать все доступные цели для выполнения, сделав l **.do.

    О чём я пожалел и есть ли всё же помехи/недостатки?

    • Жалею только об огромном количестве потраченных часов на борьбу с Make за все эти годы, абсолютно ничем не компенсирующиеся.
    • Мне потребовался не один месяц чтобы отучиться от рефлекса делать redo clean, так как уже привычка после Make, что что-нибудь обязательно да не (пере)соберётся.

    Рекомендую документацию apenwarr/redo реализации, с огромным количеством примеров и пояснений.

    Сергей Матвеев, шифропанк, Python/Go/C-разработчик, главный специалист ФГУП НТЦ Атлас.
Подробнее..

Отладка Makefile часть 1

21.12.2020 02:23:17 | Автор: admin

Отладка makefile это что-то из черной магии. К несчастью, не существует такой вещи как makefile отладчик, чтобы изучить ход выполнения конкретного правила или как разворачивается переменная. Большую часть отладки можно выполнить с помощью обычных printов и проверкой makefile. Конечно, GNU make немного помогает своими встроенными методами и опциями командной строки. Один из лучших методов отладки makefile это добавить отладочные перехваты (hooks) и использовать техники безопасного программирования, на которые можно будет опереться, когда дела пойдут совсем плохо. Далее представлено несколько основных техник отладки и практик безопасного программирования, которые будут, на мой взгляд, наиболее полезными.

Отладочные возможности make

Очень полезная для отладки не работающего makefile функция warning . Так как функция warningразворачивается в пустую строку, ее можно использовать везде в makefile: на верхнем уровне, в имени цели, в списке зависимостей и в командных скриптах. Это позволяет печатать значения переменных там, где это наиболее подходит для их проверки. Например:

$(warning A top-level warning)FOO := $(warning Right-hand side of a simple variable)barBAZ = $(warning Right-hand side of a recursive variable)boo$(warning A target)target: $(warning In a prerequisite list)makefile $(BAZ)   $(warning In a command script)   ls$(BAZ):

Дает вывод:

$ makemakefile:1: A top-level warningmakefile:2: Right-hand side of a simple variablemakefile:5: A targetmakefile:5: In a prerequisite listmakefile:5: Right-hand side of a recursive variablemakefile:8: Right-hand side of a recursive variablemakefile:6: In a command scriptlsmakefile

Заметим, что выполнение функции warning следует нормальному ходу алгоритма make для немедленных и отложенных вычислений. Также, присваивание к BAZ содержит warning и сообщение не печатается, пока BAZ не будет развернут в списке зависимостей.

Возможность вставки warning вызов куда угодно делает его очень полезным инструментом отладки.

Опции командной строки

Есть три очень полезные опции командной строки для отладки: --just-print (-n), --print-data-base (-p) и --warn-undefined-variables.

--just-print

Первое испытание новой цели в makefile это вызвать make с опцией --just-print (-n). Будучи вызванным с этой опцией make прочитает makefile и напечатает каждую команду, которую в обычном режиме он бы выполнил для обновления цели. Для удобства, GNU make также выведет команды помеченные собачкой (@) - заглушающим модификатором.

Опция предназначена для полного подавления выполнения всех команд. Однако, есть исключения. В то время как make не выполняет командные скрипты целей, он выполнит вызовы shell функции, которые будут в немедленном контексте выполнения. Для примера:

REQUIRED_DIRS = ..._MKDIRS := $(shell for d in $(REQUIRED_DIRS); \             do                               \                [[ -d $$d ]] || mkdir -p $$d; \             done)$(objects) : $(sources)

Смысл переменной _MKDIRS в инициировании создания нужных директорий. Если выполнить это с --just-print опцией, команда оболочки будет выполнена как обычно в момент чтения makefile. Затем, make выведет (без исполнения) каждую команду компиляции необходимую для обновления списка файлов $(objects).

--print-data-base

Еще одна опция, которую нужно использовать почаще. С ней, после обычного "прогона" makefile, make выдаст в конце его внутреннюю базу данных. Данные сгруппированы по группам: переменные, директории, неявные правила, переменные шаблонов, файлы (явные правила) и vpath путь поиска. Давайте остановимся на этих секциях подробнее.

Секция Variables выводит список переменных с описательным комментарием:

# automatic<D = $(patsubst %/,%,$(dir $<))# environmentEMACS_DIR = C:/usr/emacs-21.3.50.7# defaultCWEAVE = cweave# makefile (from `../mp3_player/makefile', line 35)CPPFLAGS = $(addprefix -I ,$(include_dirs))# makefile (from `../ch07-separate-binaries/makefile', line 44)RM := rm -f# makefile (from `../mp3_player/makefile', line 14)define make-library libraries += $1 sources += $2 $1: $(call source-to-object,$2) $(AR) $(ARFLAGS) $$@ $$^endef

Авто-переменные не выводятся, но выводятся другие, зависящие от них, полезные переменные, такие как $(<D). В комментарии пишется вывод функции origin (см. make manual). Если переменная определена в файле, будет выведено имя файла и номер строки объявления. Простые и рекурсивные переменные определяются по оператору присваивания. Вычисленное значение простых переменных также печатается в правой части выражения.

Следующий раздел Directories более полезен разработчикам make, а не пользователям make. Представляет собой список папок просмотренных make, включая SCCS и RCS под-папки, которые могут быть, но обычно отсутствуют. Для каждой папки выводится детали реализации: номер устройства, inode и статистика по совпадениям шаблонов файлов.

Секция Implicit rules содержит все встроенные и пользовательские шаблоны в базе данных make. Опять же, для тех правил, которые определены в файле, выводится имя файла и строка определения:

%.c %.h: %.y# commands to execute (from `../mp3_player/makefile', line 73):   $(YACC.y) --defines $<   $(MV) y.tab.c $*.c   $(MV) y.tab.h $*.h%: %.c# commands to execute (built-in):   $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@%.o: %.c# commands to execute (built-in):   $(COMPILE.c) $(OUTPUT_OPTION) $<

Изучение этой секции даст быстрое понимание разнообразия и структуры встроенных правил make. Конечно, не все встроенные правила реализованы как шаблонные. Если нужное правило не находится, его еще можно поискать в разделе Files, где выводятся суффиксные правила в старом стиле.

Следующая секция это каталог зависящих от конкретного шаблона переменных определенных в makefile. Описание этих переменных это их значения, которые будут подставлены во время выполнения при выполнении соответствующих им правил. Например, для переменной определенной как:

%.c %.h: YYLEXFLAG := -d%.c %.h: %.y $(YACC.y) --defines $< $(MV) y.tab.c $*.c $(MV) y.tab.h $*.h

будет выведено:

# Pattern-specific variable values%.c :# makefile (from `Makefile', line 1)# YYLEXFLAG := -d# variable set hash-table stats:# Load=1/16=6%, Rehash=0, Collisions=0/1=0%%.h :# makefile (from `Makefile', line 1)# YYLEXFLAG := -d# variable set hash-table stats:# Load=1/16=6%, Rehash=0, Collisions=0/1=0%# 2 pattern-specific variable values

Далее следует Files секция, которая выводит все явные и суффикс- правила, которые относятся к конкретным файлам:

# Not a target:.p.o:# Implicit rule search has not been done.# Modification time never checked.# File has not been updated.# commands to execute (built-in): $(COMPILE.p) $(OUTPUT_OPTION) $<lib/ui/libui.a: lib/ui/ui.o# Implicit rule search has not been done.# Last modified 2004-04-01 22:04:09.515625# File has been updated.# Successfully updated.# commands to execute (from `../mp3_player/lib/ui/module.mk', line 3): ar rv $@ $^lib/codec/codec.o: ../mp3_player/lib/codec/codec.c ../mp3_player/lib/codec/codec.c ../mp3_player/include/codec/codec.h# Implicit rule search has been done.# Implicit/static pattern stem: `lib/codec/codec'# Last modified 2004-04-01 22:04:08.40625# File has been updated.# Successfully updated.# commands to execute (built-in): $(COMPILE.c) $(OUTPUT_OPTION) $<

Промежуточные файлы и суффикс-правила обозначены как "Not a target"; остальное цели. Каждый файл включает комментарии, показывающие как make будет обрабатывать это правило. У файлов, которые найдены через обычный vpath поиск, показан найденный путь до них.

Последняя секция называется VPATH Search Paths и перечисляет значение VPATH и все vpath шаблоны.

Для makefile'ов, которые обильно используют пользовательские функции и eval для создания сложных переменных и правил, исследование этого вывода - единственный путь проверить, что разворачивание макросов дает ожидаемые значения.

--warn-undefined-variables

Эта опция заставляет make выводить предупреждение при вычислении неопределенной переменной. Так как неопределенные переменные вычисляются в пустую строку, зачастую, опечатки остаются необнаруженными долгое время. Проблема с этим ключом в том, что многие встроенные правила используют неопределенные переменные, которые нужны для перехвата пользовательских значений. Поэтому запуск make с этой опцией неизбежно выведет много предупреждений, которые не являются ошибками и не связаны с makefile'ом пользователя, Например:

$ make --warn-undefined-variables -nmakefile:35: warning: undefined variable MAKECMDGOALSmakefile:45: warning: undefined variable CFLAGSmakefile:45: warning: undefined variable TARGET_ARCH...makefile:35: warning: undefined variable MAKECMDGOALSmake: warning: undefined variable CFLAGSmake: warning: undefined variable TARGET_ARCHmake: warning: undefined variable CFLAGSmake: warning: undefined variable TARGET_ARCH...make: warning: undefined variable LDFLAGSmake: warning: undefined variable TARGET_ARCHmake: warning: undefined variable LOADLIBESmake: warning: undefined variable LDLIBS

Тем не менее, эта опция может быть крайне полезна в поиске ошибок такого типа.

--debug опция

Когда нужно узнать как make анализирует твой граф зависимостей, используй --debug опцию. Она предоставляет самую детальную доступную информацию без запуска отладчика. Есть пять опций вывода отладки и один модификатор: basic, verbose, implicit, jobs, all, и makefile, соответственно.

Если опция указана в форме --debug, используется basic - краткий вывод для отладки. Если опция указана в виде -d, используется all. Для выбора других комбинаций можно использовать список разделенный запятыми: --debug=option1,option2, где option может быть одно из следующих значений (на самом деле, make смотрит только на первую букву):

  • basic
    Наименьшая детализированность. Когда включена, make выводит каждую цель, которая нуждается в обновлении и статус действия обновления.

  • verbose
    Эта опция включает basic вывод и дополнительную информацию о проанализированных файлах и о зависимостях, которые не нуждаются в обновлении.

  • implicit
    Эта опция включает basic вывод и дополнительную информацию о неявных правилах, просмотренных в поиске нужного для каждой выполняемой цели.

  • jobs
    Эта опция выводит детали о запущенных make'ом подпроцессах. Она не включает basic вывод.

  • all
    Включает все выше перечисленное и является значением по умолчанию для -d опции.

  • makefile
    Обычно, отладочная информация включается после того, как все makefileы будут обновлены. Обновление включает в себя и обновление всех импортированных файлов, таких как файлы со списками зависимостей. С этим модификатором make выведет выбранную опциями информацию в ходе обновления makefile'ов и импортированных файлов. Он включает basic информацию, а также включается при указании all опции.

Пишем код, удобный для отладки

Как мы видим, средств отладки для makefile не так уж и много, вывод внутреннего состояния и несколько функций отчетов. Когда дела обстоят именно так, мы сами вынуждены делать наши makefileы такими, чтобы и минимизировать шанс ошибок и иметь инструментарий для отладки этих ошибок.

Правила в этом разделе, в несколько произвольном виде, основаны на практиках кодирования, защитном программировании и техник отладки. Тогда как определенные правила, такие как проверка статуса выхода команд, можно поместить как в хорошие практики кодирования так и в защитное программирование, эти три категории отражают общую шкалу. Кодируй хорошо свои makefile, но без излишеств в срезании углов. Используй защитное программирование, чтобы защитить makefile от неожиданных событий и изменчивого внешнего окружения. В конце концов, когда баги все-таки будут возникать, используй каждый приём, доступный тебе, чтобы их размозжить.

Принцип KISS залог всех хороших систем. makefile очень быстро могут становится сложными, даже для повседневных задач, таких как генерация графа зависимостей. Борись с тенденцией включать все больше и больше функционала в твою систему сборки. Ибо ты проиграешь, но не так плохо, если будешь при каждом случае добавлять функционал.

Хорошие практики кодирования

По моему опыту, большая часть программистов не рассматривает написание makefile'ов как программирование, и, из-за этого, не уделяют такое же внимание, как при разработке на C++ или Java. Но ведь язык make это полный по Тьюрингу декларативный язык! И если надежность и легкость сопровождения твоей системы сборки важна, пиши ее тщательно и используй все доступные тебе лучшие практики.

Один из важнейших аспектов в программировании безотказных makefile это проверка статуса выхода команд. Конечно, make проверяет для простых команд это дело сам, но makefile часто содержит составные команды, которые могут не сработать молча:

do:   cd i-dont-exist; \   echo *.c

При выполнении этот makefile не прервется с ошибкой, тогда как ошибка несомненно произойдет:

$ makecd i-dont-exist; \echo *.c/bin/sh: line 1: cd: i-dont-exist: No such file or directory*.c

И далее, выражение подстановки не найдет никаких файлов .c, и молча вернет выражение подстановки. Ой. Способ по-лучше, это использовать возможности командной оболочки по проверке и предотвращению ошибок:

SHELL = /bin/bashdo:   cd i-dont-exist && \   shopt -s nullglob &&   echo *.c

Теперь ошибка cd правильно передастся make, команда echo никогда не исполнится и make прервётся со статусом ошибки. В дополнение, после установки nullglob опции bashа подстановка вернет пустую строку если не будут найдены такие файлы. (Конечно, в твоем конкретном случае могут быть другие предпочтения.)

$ makecd i-dont-exist && \echo *.c/bin/sh: line 1: cd: i-dont-exist: No such file or directorymake: *** [do] Error 1

Другой хорошей практикой является форматирование своего кода для максимальной читабельности. Большая часть makefile'ов плохо отформатированы, и как следствие, их трудно читать. Что тут кажется легче прочитать?

_MKDIRS := $(shell for d in $(REQUIRED_DIRS); do [[ -d $$d \]] || mkdir -p $$d; done)

или:

_MKDIRS := $(shell                           \             for d in $(REQUIRED_DIRS);      \             do                              \               [[ -d $$d ]] || mkdir -p $$d; \             done)

Если ты как все люди, то тебе будет труднее прочитать первое, точку с запятой сложно найти, и количество операндов сосчитать еще сложнее. И это не шутки. Огромный процент синтаксических ошибок, которые встречаются в командных скриптах будут из-за пропущенных точек с запятой, символов экранирования, других разделителей, таких как разделитель программного конвейера или логические операторы.

Замечательно, что не все пропущенные разделители дадут ошибку. Для справки, ни одна из следующих ошибок не даст синтаксической ошибки оболочки окружения:

TAGS:        cd src \        ctags --recursedisk_free:        echo "Checking free disk space..." \        df . | awk '{ print $$4 }'

С форматированием команд для читабельности такого рода ошибки станет легко ловить. Делай отступы при работе в пользовательских функциях. Если же пробелы станут проблемой при работе макроса, форматирование можно обернуть через вызов strip функции. Форматируй длинные списки значений по одному на строку. Добавь комментарий перед каждой целью, дай краткое пояснение, и задокументируй параметры.

Следующей хорошей практикой является использование переменных для хранения общих значений. Как и в любой программе, неограниченное использование буквальных значений создает дублицирование кода и приводит к проблемам поддержки и багам. Еще одно большое преимущество переменных make умеет их выводить в ходе работы в целях отладки. Ниже в разделе способы отладки будет приведена полезная команда.

Защитное программирование

Защитный код это код, который выполняется когда одно из твоих предположений или ожиданий неверно if проверяет то, что всегда истинно, assert функция никогда не упадет, и конечно, значимость такого кода в том, что внезапно (обычно когда меньше всего этого ждешь), он выполняется и дает предупреждение или ошибку, или ты включаешь трассировку кода, чтобы увидеть внутреннюю работу make.

Валидация это отличный пример защитного программирования. Следующий код проверяет что текущая запущенная версия make 3.80:

NEED_VERSION := 3.80$(if $(filter $(NEED_VERSION),$(MAKE_VERSION)),,             \ $(error You must be running make version $(NEED_VERSION).))

Для приложений Java полезно включить проверку файлов в CLASSPATH.

Код валидации также может просто проверять что что-то истинно.

Другой отличный пример защитного программирования это использование assert функций, например таких:

# $(call assert,condition,message)define assert   $(if $1,,$(error Assertion failed: $2))endef# $(call assert-file-exists,wildcard-pattern)define assert-file-exists   $(call assert,$(wildcard $1),$1 does not exist)endef# $(call assert-not-null,make-variable)define assert-not-null   $(call assert,$($1),The variable "$1" is null)endef

Небольшое количество assert вызовов в makefile будет дешевым и эффективным путём обнаружения пропавших переменных или переменных с опечатками в названии, а также и других ожиданий.

Также можно определить пару функций для трассировки разворачивания пользовательских функций:

# $(debug-enter)debug-enter = $(if $(debug_trace),\                $(warning Entering $0($(echo-args))))# $(debug-leave)debug-leave = $(if $(debug_trace),$(warning Leaving $0))comma := ,echo-args = $(subst ' ','$(comma) ',\              $(foreach a,1 2 3 4 5 6 7 8 9,'$($a)'))

Можно добавить вызовы этих макросов в свою функцию и оставить их выключенными до тех пор, пока они не потребуются для отладки. Для включения их нужно установить debug_trace в любое не пустое значение:

$ make debug_trace=1

И еще одно средство защитного программирования это легкое и простое выключение действия @ командного модификатора, если использовать его через переменную, а не буквально:

QUIET := @target:   $(QUIET) some command

Используя эту технику можно увидеть ход выполнения заглушенных команд просто переопределив переменную в командной строке:

$ make QUIET=
Подробнее..

Перевод Отладка Makefile часть 2

21.12.2020 22:14:07 | Автор: admin

Методы отладки


В этой части поговорим об общих методах и проблемах отладки. В конечном итоге, отладка это солянка из всего, что работает в данной конкретной ситуации. Эти методы работают для меня и мне приходится полагаться на них даже в случае проблем с простейшими makefile. Может быть, они помогут и тебе тоже.


Отладка Makefile /часть 1/


Один из очень раздражающих багов в make 3.80 был в сообщении об ошибке в makefile, где make указывал номер строки, и обычно этот номер строки был неверный. Я не удосужился исследовать из-за чего эта проблема возникает: из-за импортируемых файлов, присваиваний многострочных переменных или из-за пользовательских макросов. Обычно, make дает номер строки больше чем должен был бы. В сложных makefile бывает что номер не совпадает на 20 строк.


Часто наиболее простой путь увидеть значение переменной это напечатать его в ходе выполнения цели. И хотя распечатать легко можно с помощью warning, в долгой перспективе поможет сэкономить много времени немного усилий на добавление общей цели debug для вывода переменных. Вот примерный код цели debug:


debug:       $(for v,$(V), \         $(warning $v = $($v)))

Для того чтобы использовать её, нужно перечислить имена переменных которые надо распечатать в командной строке и собрать debug цель:


$ make V="USERNAME SHELL" debugmakefile:2: USERNAME = Ownermakefile:2: SHELL = /bin/sh.exemake: debug is up to date.

Если уж совсем делать всё волшебно, то можно использовать MAKECMDGOALS переменную, чтобы избежать присвоения переменной V:


debug:       $(for v,$(V) $(MAKECMDGOALS), \         $(if $(filter debug,$v),,$(warning $v = $($v))))

Теперь можно выводить переменные просто перечислив их в командной строке. Однако, я не рекомендую этот способ, так как предупреждения make о невозможности обновлении переменных (так как они указаны как цели) могут сбить с толку:


$ make debug PATH SHELLmakefile:2: USERNAME = Ownermakefile:2: SHELL = /bin/sh.exemake: debug is up to date.make: *** No rule to make target USERNAME. Stop.

В то время как make выводит команды из сценариев цели до их выполнения, он не выводит команды выполняющиеся в shell функции. Часто эти команды сложные и неуловимые в том плане, что могут выполняться как незамедлительно, так и в отложенной манере, если они были вызваны в значении рекурсивной переменной. Один из способов увидеть эти команды включить отладку в самой оболочке:


DATE := $(shell date +%F)OUTPUT_DIR = out-$(DATE)make-directories := $(shell [ -d $(OUTPUT_DIR) ] || mkdir -p $(OUTPUT_DIR))all: ;

Если это запустить с опцией отладки sh, мы увидим:


$ make SHELL="sh -x"+ date +%F+ '[' -d out-2004-05-11 ']'+ mkdir -p out-2004-05-11

Можно заметить, что также выводятся значения всех переменных и выражений.


Часто встречаются сильно вложенные выражения, например, для оперирования с именами файлов:


FIND_TOOL = $(firstword $(wildcard $(addsuffix /$(1).exe,$(TOOLPATH))))

Ничего хорошего в отладке таких выражений нет. Один из разумных подходов будет их разворот и печать каждого подвыражения:


$(warning $(TOOLPATH))$(warning $(addsuffix /$(1).exe,$(TOOLPATH)))$(warning $(wildcard $(addsuffix /$(1).exe,$(TOOLPATH))))

Весьма нудно, но без настоящего отладчика это лучший путь (иногда единственный) для определения значений различных подвыражений.


Общие сообщения об ошибках


В руководстве make есть замечательный раздел со списком сообщений об ошибках make и их причин. Мы рассмотрим немного из наиболее часто встречаемых. Некоторые описанные проблемы, строго говоря, не являются ошибками make, такие как синтаксические в командных сценариях, но все же обычными проблемами для разработчиков. Полный список смотри в руководстве.


Сообщение make об ошибке имеет стандартный формат:


    makefile:n: *** message. Stop

или:


    make:n: *** message. Stop.

где makefile строка это имя файла или импортированного файла в котором произошла ошибка. Следующая часть номер строки, в которой произошла ошибка, далее следуют три звездочки, и, наконец, само сообщение.


Заметим, что это задача make запускать другие программы и таким образом, если при этом возникают ошибки, скорее всего проблемы в твоём makefile вызвали ошибки в этих других программах. Для примера, ошибки оболочки могут быть из-за плохо сформированных командных сценариев, или ошибок компилятора из-за некорректных аргументов командной строки. Выяснение того, какая программа выдала сообщение об ошибке первоочередная задача при решении проблемы. К счастью, сообщения make довольно очевидны.


Синтаксические ошибки


Обычно это типографические ошибки: пропущенные скобки, пробелы после запятых в параметрах функции, и так далее.


Одна из наиболее частых ошибок для новых пользователей make это опускание скобок вокруг имен переменных:


foo:     for f in $SOURCES; \     do                 \                       \     done

Скорее всего, make развернёт переменную $S в ничего, и оболочка выполнит цикл только раз со значением OURCES в f. В зависимости от того, что ты собрался делать с f, можно получить забавные сообщения оболочки:


    OURCES: No such file or directory

но можно и не получить сообщения вовсе. Помни имена переменных обрамляются скобками.


missing separator


Сообщение:


    makefile:2:missing separator. Stop.

или (в GNU make пер.):


    makefile:2:missing separator (did you mean TAB instead of 8 spaces?). Stop.

обычно означает make искал разделитель, такой как :, =, или табуляцию и не нашел ни одного. Вместо этого, он нашел что-то что он не понял.


commands commence before first target


Эта ошибка появляется в основном в середине makefile, когда строка вне командного сценария начинается с отступа (пробелы или символ табуляции). make сделает все возможное, чтобы устранить неоднозначность этой ситуации, но если строка не может быть идентифицирована как присваивание значения, условное выражение или многострочное определение макроса, make решит что это неправильно размещенная команда.


unterminated variable reference


Это простая, но распространённая ошибка. Она означает, что ты забыл закрыть имя переменной или вызов функции правильным количеством скобок. С сильно вложенными вызовами функций и именами переменных make файлы становятся похожими на Lisp! Избежать этого поможет хороший редактор, который умеет сопоставлять скобки, такой как Emacs.


Ошибки в командных сценариях


Есть три типа частых ошибок в командных сценариях: пропущенная точка с запятой в многострочных командах, незаконченная или неверная переменная пути, или просто команда, которая просто обнаружила проблему в ходе выполнения.


Мы обсуждали пропущенные точки с запятыми в разделе "лучшие практики", поэтому не будем на этом останавливаться здесь.


Классическое сообщение:


    bash: foo: command not found

выводится, когда оболочка не смогла найти команду foo. Так, оболочка поискала в каждой папке из переменной PATH исполняемый файл и не нашла совпадений. Чтобы исправить такую ошибку, нужно обновить PATH переменную, обычно в .profile (Bourne shell), .bashrc (bash) или .cshrc (C shell). Конечно, можно также установить PATH в самом makefile, и экспортировать PATH из make.


Если же команда завершилась с ошибкой, она выходит с ненулевым статусом выхода. В этом случае, make отчитается об ошибке со следующим сообщением:


$ maketouch /foo/bartouch: creating /foo/bar: No such file or directorymake: *** [all] Error 1

Здесь touch команда не сработала, что напечатало своё собственное сообщение объясняющее сбой. Следующая строка это итоговая ошибка make. Упавшая цель в makefile указана в квадратных скобках, а затем статус выхода упавшей программы. Если программа вышла по сигналу, а не с ненулевым статусом выхода, то make напечатает более подробное сообщение.


Заметим также, что команды под знаком @ также могут упасть. В этом случае сообщение об ошибке может возникнуть как будто оно из ниоткуда.


В обоих случаях ошибка происходит из программ запускаемых make, нежели от самого make.


No Rule to Make Target


Это сообщение имеет две формы:


    make: *** No rule to make target XXX. Stop.

и:


    make: *** No rule to make target XXX, needed by YYY. Stop.

Это означает, что make решил обновить файл XXX, но make не смог найти ни одного правила для выполнения работы. make ищет во всех явных и неявных правилах в его базе данных прежде чем сдаться и вывести это сообщение.


Есть три причины для этой ошибки:


  • В твоем makefile отсутствует необходимое правило для обновления файла. В этом случае тебе необходимо добавить правило с описанием как построить цель.
  • В makefile опечатка. Или make ищет неверный файл или в правиле построения этого файла указан неверный файл. Если в makefile используются переменные, то опечатки становится еще труднее отыскать. Иногда единственный путь быть точно уверенным в значении сложного имени файла это напечатать его или печатая переменную напрямую или исследуя внутреннюю базу данных make.
  • Файл должен быть, но make не находит его или из-за того, что его нет, или make не знает где его искать. Конечно, иногда make абсолютно прав. Файла нет похоже мы забыли его скачать из VCS. Еще чаще, make не может найти файл из-за того, что исходник расположен где-то еще. Иногда исходник в другом дереве исходников, или может файл генерируется другой программой и создался в папке артефактов сборки.


    Overriding Commands for Target


    make позволяет только один командный сценарий для цели (за исключением :: правил, которые редко используются). Если встретится больше чем один командный сценарий для одной цели, make выведет предупреждение:


    makefile:5: warning: overriding commands for target foo
    

    Также он может вывести сообщение:


    makefile:2: warning: ignoring old commands for target foo
    

    Первое предупреждение показывает строку, на которой был найден второй сценарий команд; тогда как второе предупреждение указывает на позицию исходного переопределённого командного сценария.



В сложных makefile цели часто определены несколько раз, каждый раз добавляя еще собственные требования. Одно из этих определений цели обычно содержит командный сценарий, но в ходе разработки или отладки очень легко добавить еще один и не заметить, что существующий набор команд уже переопределен.


Например, мы могли бы определить общную цель во включаемом файле:


# Create a jar file.$(jar_file):        $(JAR) $(JARFLAGS) -f $@ $^

и позволим нескольким отдельным makefile добавить свои собственные требования. Мы могли бы записать в makefile:


# Set the target for creating the jar and add prerequisitesjar_file = parser.jar$(jar_file): $(class_files)

Если непреднамеренно добавить командный сценарий в такой makefile, make выдаст предупреждение переопределения.

Подробнее..

Как готовить Cake, используя только Frosting

26.12.2020 10:13:54 | Автор: admin

Итак, Cake. Многие слышали, многие хотели попробовать, но откладывали. Конечно, если ты все время работал на TeamCity или на Jenkins и продолжаешь, то зачем переизобретать то, что уже отлично работает? Люби свою жизнь и радуйся. Но вот, допустим, в твоей любимой жизни появился новый проект, новый дедлайн, минимум сторипойнтов до релиза, а опыта с новым сборщиком нет? Мне в этом случае и пригодился Cake.

Я сразу оговорюсь, что эта статья не подтолкнет сразу на использование Cake, как меня, и многих моих коллег не подтолкнули статьи, которые выходили ранее. По большей части потому что на него нет смысла переходить в проекте, который не приносит боль и который работает стабильно. Собираете в своем любимом Jenkins и все идет нормально. Но пусть после этой статьи в голове отложится, что Cake существует. Он в очередной раз никуда не делся, он умеет уже многое и работать с ним все проще. Гораздо проще, чем было раньше.

На что похож Cake? Наверное, любой разработчик, не погрязший в мире .Net, найдет свою аналогию: gradle, gulp, golang make. Make-системы не откровение в 2020 году. Это всегда было удобно, а значит нужно и правильно. Мир .Net долгое время был обделен такими средствами. Фактически был и есть до сих пор MSBuild, но у него есть очень-очень много недостатков. Основной - кто вообще умеет им пользоваться из рядовых разработчиков? И какова целесообразность его освоения? Какие-то базовые и нужные всем вещи явно проще делать на билд-сервере. Наверное, кому-то он и удобен, но я уверен, что значимая часть коммьюнити предпочтет MSBuild'у освоить новый билд-сервер. Один раз написать конфиг и забыть как страшный сон.

А что если бы существовала make-система с DSL на C#, автокомплитом и прочими фишками типизированного языка? Да, я про Cake. В частности сейчас пойдет разговор про библиотеку Cake.Frosting, являющуюся одним из раннеров make-системы.

Подробней про доступные раннеры можно прочитать тут: Cake Runners

С Frosting все привычно самодокументирующийся Api с которым почти сразу находишь общий язык. Методы расширения, загружаемые из Nuget на любой случай жизни, структура проекта, похожая на смесь тестов или бенчмарков и хоста Asp. Все решения угадываются сразу, все как дома.

Frosting от остальных раннеров Cake отличается тем, что существует не в виде тулза, а в виде отдельного проекта, который можно докинуть в solution и хранить вместе с ним в репозитории. Это невероятно упрощает работу с системой. Фактически стоит просто создать новый проект, подключить к нему зависимость Cake.Frosting, сконфигурировать Build-хост и можно запускать этот проект командой.

dotnet run

Чтобы нам стало еще проще, существует темплейт проекта. К нему в комплект даже идут шелл-скрипты для Mac OS, Linux и Windows, подгружающие SDK, если его нет в окружении. Через них стоит вызывать сборку вместо dotnet CLI, если в этом есть необходимость.

Тут можно почитать подробнее об этом: Frosting Bootstraping

После того, как проект создан, можно начинать конфигурировать процесс сборки. Из основных мест для нашего кода мы сразу обращаем внимание на прогрев и очистку системы перед сборкой это соответствующие методы класса Lifetime: Setup и TearDown. В них привычно делать уборку артефактов до и после сборки.

Вторая интересующая нас часть проекта папка Tasks. Тут хранятся все шаги сборки в виде классов -наследников от FrostingTask<Context>.

Задачи автоматически регистрируются в IoC контейнере, как мы привыкли в Asp. Более того, Frosting реализует точно такой же паттерн с DI через IServiceCollection, к которому мы все привыкли.

Порядок выполнения билд-шагов определяется их зависимостями. Анализ зависимостей начинается с корня графа, по умолчанию это задача Default. Для того, чтобы уведомить систему, что эта задача зависит от другой, ей можно установить атрибут

[Dependency(typeof(MyPreviousTask))]

Где MyPreviousTask это задача, которая должна завершиться ранее помеченной.


Список задач может быть любым, в том числе привычный нам:

  1. Восстановление пакетов.

  2. Билд.

  3. Прогон unit-тестов.

  4. Publish.

  5. Поставка артефактов.

В качестве поставки артефактов мы можем делать как привычную нам архивацию и отправку в среду исполнения приложения, так и упаковку приложения в образ docker, словом все, что мы можем написать на C#.

Как уже было сказано выше, на данный момент существует масса пакетов с расширениями контекста выполнения, понимающего, благодаря им, множество команд сборки. Копирование файлов, удаление, логирование и т.д.

Единственный минус такого похода захламление IntelliSense окна чудовищным количеством методов, но когда это нас останавливало?

По случаю хотелось бы напомнить про относительно свежую фичу .Net core self-contained приложения. В этом способе публикации надо явно задать версию рантайма, в результате чего формируется не библиотека, исполняемая в контексте dotnet, а запускаемое приложение, содержащее рантайм, так сказать, в себе. Она может пригодиться при упаковке в образ без установленного рантайма, если по каким-то причинам установить последний нельзя. Нет никаких причин не делать этого в Cake.

Когда все готово, настроено и залито в репозиторий, мы делаем в TS или Jenkins всего одну команду

dotnet run ./Build/Build.csproj

Путь до проекта у вас будет свой (Ваш Кэп) и смотрим, как происходит медитативный процесс сборки. Frosting пишет события сборки в стандартный вывод, который читает билд-сервер, так что никакие данные не пропадут.

Конечно, это минимальный и самый простой сценарий использования системы. Билд-сервер может передать в dotnet аргументы при вызове билда Frosting. Например, чтобы в дальнейшем установить версию сборки или образа или тип рантайма или папки назначения для публикации.

В общем полный простор фантазии.

Мотивация

  1. Это удобно. Вы пишете на своем основном языке и не зависите от выразительности скриптов и настроек/плагинов билд-сервера;

  2. Это мобильно. Вы заливаете код в репозиторий и он универсально запускается на любом билд-сервере. И никакого вендор-лока.

  3. Это версионно. Код сборки хранится в репозитории. Вместе с самим релизом.

  4. Это позволяет экономить. Если тарифный план билд-сервера не позволяет делать много билд-конфигов, вам достаточно одного, запускающего разные проекты. Тот же результат без лишних трат.

  5. Это легко. IntelliSense, автокомплит, разберется даже обленившийся senior.

Проблемой же может стать отсутствие в Cake необходимых интеграций с линтерами, сонарами и т.д. Этот вопрос следует уточнить перед использованием системы. Или дописать свои расширения, залить в Nuget и быть очень-очень хорошим человеком.

Бонусом пример использования Cake.Frosting на github. Для затравки так сказать: Link

Ссылка на сайт проекта Cake

Подробнее..

Делать или делать как работают слова do и make в английском

27.04.2021 18:13:12 | Автор: admin

В английском языке много мелких нюансов, которые нужно знать. Есть слова, которые вроде как синонимы и означают одно и то же, но при этом их нужно использовать в строго определенных случаях.

Сегодня расскажем о паре do-make. Эти слова изучают уже на первых занятиях по английскому, но нюансов в них столько, что ошибки допускают и студенты с уровнем Advanced. Поехали.

Что же делать с do и make

Сложность в том, что оба слова do и make переводятся на русский как делать. Но при этом у них есть большое количество дополнительных значений и грамматических конструкций, в которых они используются.

И как вишенка на торте в английском вообще есть фраза to make do with something, которая переводится как довольствоваться чем-то и со значением делать вообще не связана.

Но если разобраться, то все становится не таким уж и сложным. Они только на первый взгляд кажутся взаимозаменяемыми.

Главное различие между do и make в том, что:

  • предложение с do акцентируют внимание на процессе действия

  • а предложения с make на полученном результате.

Do как основной глагол в большинстве случаев указывает на действие. Do the best (постараться), do the dishes (мыть посуду), do your homework (делай свою домашку). Во фразах наподобие do the shopping глагол обращает внимание именно на процесс делать покупки.

Make позволяет обратить внимание не на сам процесс, а на его результат. Make a mistake (сделать ошибку), make money (делать деньги). То есть, акцент не на том, как именно человек сделал ошибку, а на самом факте, что она сделана.

Вот только далеко не всегда можно с легкостью определить, где именно нужно сделать акцент на процессе или результате. Поэтому вспомним частные случаи, где нужно использовать do и make.

А если вам удобнее воспринимать материал в видео-формате, то смотрите наше видео DO или MAKE - в чем разница? Английский для начинающих.

Please, do

Пройдемся по всех особенностям использования слова do.

1. Как вспомогательное слово в простых негативных формах.

Простое правило без особых сложностей.

I dont speak French. Я не говорю на французском.

I dont play football. Я не играю в футбол.

Вспомним несравненную Гвен Стефани и ее песню Dont speak.

2. Как анафорическая форма глагола.

Не пугайтесь, на практике звучит куда проще, чем называется. В английском это одно простое слово pro-verb. И в этом случае оно означает слово, которое в предложении позволяет избежать повторения основного глагола

I got better marks in the exam than my brother did. Я получил лучшие оценки на экзамене, чем мой брат.

Хороший способ избежать тавтологии и сделать речь более приятной. Опустить вспомогательный глагол вообще можно с точки зрения грамматики, но не стоит носители языка предпочитают конструкции с двумя грамматическими основами.

3. Как дополнительный глагол, который усиливает основной.

Когда студенты с уровнем Pre-Intermediate или Intermediate видят эту фишку впервые, она вызывает у них ступор. Потому что зачем два глагола рядом?

Jane, I do like your new dress. Where did you get it? Джейн, мне действительно нравится твое новое платье. Где ты его достала?

В этом контексте слово do можно переводить как действительно не ошибетесь.

И раз уж примеры пошли музыкальные, то вспомним Триш Толидо, которая поет I do love you в одноименной песне.

4. Как дополнительный императивный глагол.

Если простого приказания не хватает, то можно еще больше усилить императивный тон предложения.

Your music is too loud. Do turn it off! Твоя музыка слишком громкая. Давай выключай ее!

Просто Turn it off может не передать, насколько сильно человек хочет, чтобы музыку выключили все еще зависит от интонации. А Do turn it off сразу с помощью грамматики оглашает, что прямо жаждет и страждет.

Re-make

Со словом make все проще. Во всяком случае, у него только две особенности использования. И одна большая подлянка, но о ней чуть позже.

1. Лексическое использование слова, когда оно выступает основным глаголом.

Если не считать, что кроме сделать у глагола make еще примерно 50 значений, то все довольно просто.

This pie is so delicious. Have you made it by yourself? Этот пирог такой вкусный. Ты сама его сделала?

Путаница с do случается как раз здесь. И памятка про процесс-результат действительно помогает с этим разобраться.

Еще раз:

Do важен процесс

Make важен результат

Make также может быть и существительным, но нечасто. И значений у него тоже достаточно: марка, модель, образец, конструкция, вид.

I need you to get the make and serial number of that monitor. Мне нужна марка и серийные номера мониторов.

2. В качестве казуативного глагола.

Здесь тоже все проще, чем звучит: это дополнительный глагол, который можно перевести как заставить или быть причиной.

I made him tell all about his hobbies. Я заставила его все рассказать о его хобби.

То есть made здесь выступает как причина какого-то действия, а не само действие. И хоть переводить его как заставлять не совсем правильно с точки зрения лексики, но в 95% случаев этот перевод подходит лучше всего.

Вот как в треке Skrillex - Make It Bun Dem. Если что, это переиначенное make it burn them пусть это их зажжет.

И еще один случай тот самый подлый и бесячий. Со словом make существует огромное количество фразовых глаголов. Да еще и в разных ситуациях один и тот же фразовый глагол может иметь совершенно разные значения.

К примеру, make out. У него целых четыре основных значения.

Первое: увидеть, понять, разобраться.

I was able to reconstruct this photo enough to make out some black van. У меня получилось восстановить это фото в достаточной мере, чтобы разобрать на нем какой-то черный фургон.

Второе: справляться

Im sure you can make out with this test. Я уверен, что ты справишься с этим тестом.

Третье: притворяться, делать вид, показаться

You're not so calm and collected as you're trying to make out. Ты не такая тихая и спокойная, как хочешь показаться.

Четвертое: целоваться, обжиматься, заниматься любовью

I watched you both make out. Я видел, как вы двое целовались.

Четыре кардинально разных значения только у одного фразового глагола. А их со словом make больше 15.

Самое страшное: догадаться, что значит конкретный глагол практически невозможно. Даже контекст во многих случаях не помогает можно угадать только общий смысл, но не значение. И то не всегда.

Вот что значит глагол make the cut? Большинство переведет дословно и посчитает, что это сделать разрез. Но на самом деле это пройти отбор. К примеру, в спортивную команду. Догадаться, что это значит, с помощью логики, практически невозможно.

Do и make это только одна пара слов, которая путает студентов, изучающих английский как второй. А их еще очень и очень много, даже без учета фразовых глаголов. Если хотите разобраться с ними всеми просто и с удовольствием, регистрируйтесь на бесплатный пробный урок с преподавателем.

И если вам понравился такой формат статьи, напишите об этом в комментариях. Если запросов будет достаточно, сделаем полноценную рубрику.

Онлайн-школа EnglishDom.com вдохновляем выучить английский через технологии и человеческую заботу

Только для читателей Хабра первый урок с преподавателем в интерактивном цифровом учебнике бесплатно! А при покупке занятий получите до 3 уроков в подарок!

Получи целый месяц премиум-подписки на приложение ED Words в подарок. Введи промокод april21 на этой странице или прямо в приложении ED Words. Промокод действителен до 01.06.2021.

Наши продукты:

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru