Как стать автором
Обновить

Комментарии 111

Я бы все же добавил, что сила make в универсальности. И последний пример у меня выглядел бы примерно так:

TARGET = hello
PREFIX = /usr/local/bin
SRCS = main.c hello.c
OBJS = $(SRCS:.c=.o)

.PHONY: all clean install uninstall

all: $(TARGET)
$(TARGET): $(OBJS)
            $(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
 
.c.o:
            $(CC) $(CFLAGS)  -c $< -o $@

clean:
            rm -rf $(TARGET) $(OBJS)
install:
            install $(TARGET) $(PREFIX)
uninstall:
            rm -rf $(PREFIX)/$(TARGET)
А расскажите, пожалуйста, что делает следующий код? Где определены $(CC), $(CFLAGS), что такое %< и $@? Что делает и в каком месте вызывается таргет .c.o?
.c.o:
            $(CC) $(CFLAGS)  -c $< -o $@

Заранее спасибо.
Советую поглядеть вывод make -p, там очень много полезного. Переменная CC по умолчанию содержит имя C компилятора (т.е. на Linux это gcc, также есть и CXX и другие). CFLAGS по умолчанию пустой, но используется вот так:

$ CFLAGS=-g make
$ CFLAGS=-O2 make


Т.е. переменными окружения выбираем, что собирать — релизную или отладочную версию.

Шаблон .c.o указывает, каким образом из .c файлов получить .o файлы.

%< и $@ — это переменные, значение которых содержит имя цели или зависимости.

PS: где-то у меня был более универсальный Makefile с генерацией зависимостей и поиском исходников. Если найду, могу показать.
Года два назад проникся духом Make'а и решил запилить сборку проекта с юнит тестированием на нём (и генерацией зависимостей, есс-но).
По сути, Make — язык со своей парадигмой, на основе зависимостей, можно горы свернуть. Но в процессе написания чего-то сложного постоянно натыкаешься на нереализованные мелочи, отчего получаются хаки, вкрапления sed'а и прочие не очень понятные вещи.

pastebin.com/BVvCpWJV
pastebin.com/9FakyqTk

Избранные простые моменты:

SOMETHING_TEST_ROOT := $(dir $(lastword $(MAKEFILE_LIST)))


empty = 
space = $(empty) $(empty)
Как же так, CC — это C Compiler, а для C++ Compiler есть свои собственные CXX и CXXFLAGS. Нередко нереализованные мелочи — недопрочтённые мануалы:)

Что-то не помню я, чтобы приходилось для deps шаманства sed прикручивать, но к сожалению не могу найти исходник.

Для тестов есть make test и make check, что прекрасно поддерживается дистрибутивами.
Недопрочитанные мануалы, говорите? А ларчик просто открывался, да…

PS: тоже в процессе написания относительно универсального common.mk для собственного пользования. :)
В переменных окружения имеет смысл задать значения флагов по умолчанию, а для конкретного вызова make значения переменных можно передавать как параметр, это смотрится более аккуратно, что ли.

make CFLAGS=-g
make CFLAGS=-O2
Я точно не ручаюсь сейчас, но вроде бы make CFLAGS=-O2 и CFLAGS=-O2 make — будет по-разному интерпретироваться. Т.е. в первом случае CFLAGS будет принудительно перезаписан, вне зависимости от того что прописано в самом makefile, а во втором — только если разработчик разрешил.
Зачем гадать? :)
Всё именно так.
Есть ещё неплохая свободная книга Managing Projects with GNU Make. Там описано довольно много интересных рецептов для сборки проектов на C, C++, Java, отладки make-файлов и просто хаков.
.c.o, по-моему, не обязательно — можно обойтись .o и встроенными неявными правилами сборки .c. Ну и замена у вас по суффиксу (.c -> .o), .c.o: не будет работать.
Очень дельное замечание, но я бы добавил ещё два улучшения:

  • Удаление bin из PREFIX, что возвращает ему стандартный смысл (не путь к директории с исполняемыми файлами, а префикс дерева директорий с определённой структурой, содержащего в том числе и поддиректорию bin)
  • Использование ?= для определения PREFIX, что позволит менять его через окружение

Итого:

TARGET = hello
PREFIX ?= /usr/local
SRCS = main.c hello.c
OBJS = $(SRCS:.c=.o)

.PHONY: all clean install uninstall

all: $(TARGET)
$(TARGET): $(OBJS)
            $(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
 
.c.o:
            $(CC) $(CFLAGS)  -c $< -o $@

clean:
            rm -rf $(TARGET) $(OBJS)
install:
            install $(TARGET) $(PREFIX)/bin
uninstall:
            rm -rf $(PREFIX)/bin/$(TARGET)

Это нужно чтобы проект мог правильно и не требуя лишних телодвижений (как-то изменение Makefile или явное указание аргументов make) собраться и установиться у человека с настроенными в окружении компилятором, флагами и путями для установки (например, CC=clang CFLAGS=-g -Wall PREFIX=$HOME/custom_apps). То же самое нужно для сборки проекта через source-based пакетные системы, например порты FreeBSD (а скорее всего верно и для portage/aur/slackbuilds/brew/...).
make — это конечно хорошо… было на каком-то этапе эволюции. Советую обратить внимание на cmake — универсальность2.
Для сборки программ — конечно следует выбрать что-то вроде cmake/qmake/scons и т.д. Однако make создан не только для компиляции, это просто удобная программа, умеющая отслеживать зависимости. Например вы хотите сайт на markdown писать, make в помощь:

SRCS = index.md page1.md page2.md
HTMLS = $(SRCS:.md=.html)

.PHONY: all clean install uninstall

all: $(HTMLS)

%.html: %.md
        markdown < $^ > $@


Ну и любые подобные задачи. Так что make не перестает быть удобным и полезным инструментом. Хотя да, использую я его все реже и реже… :-)
Я с помощью make набранные в лилипонде ноты компилирую, смотрю, слушаю и архивирую:
SRC = a4.ly tenor1.ly tenor2.ly bass1.ly bass2.ly lyrics.ly header.ly ../lib/paper.ly
RESULT = a4.pdf all.midi
DIRNAME = $(realpath .)

.PHONY: preview publish archive dist play view help
preview pre view $(RESULT): $(SRC)
	lilypond a4.ly
	mv -f a4.midi all.midi
	see a4.pdf &
publish pub a4.publish.pdf: $(SRC)
	lilypond -dno-point-and-click -o a4.publish a4.ly
	mv -f a4.publish.midi all.midi
dist: $(SRC) a4.publish.pdf
	7z a -tzip -mx=9 $(DIRNAME).zip $(SRC) a4.publish.pdf all.midi Makefile
clean:
	rm -f *~ $(RESULT) a4.publish.pdf a4.ps a4.publish.ps
play: all.midi
	timidity all.midi
help:
	@echo Available goals:
	@echo '  preview - create and show PDF preview with debug info, make MIDI file'
	@echo '  publish - create final PDF file without debug info, make MIDI file'
	@echo '  play    - play MIDI file'
	@echo '  dist    - create archive with sources and result'
	@echo '  clean   - delete result and backup files'
	@echo '  help    - show this message'

Make удобно использовать с graphviz для генерации документации.

Cmake — это не замена make. Cmake — это замена autotools.
Даже не столько замена.
Autotools создают configure. Который создаёт Makefile. На целевой системе созданный configure самодостаточен (т.е. чтобы сконфигурировать сборку при наличии configure, никаких автотулзов уже не нужно).
А вот cmake сразу создаёт Makefile. И в нём уже ничего без cmake не подтюнишь.
Надо бы знать что CMake занимается ничем иным как генерацией Makefile'ов, поэтому надобность в make не пропала особенно при использовании CMake. Кроме того, для простых проектов (несколько исходных файлов, без внешних зависимостей — как в примере из статьи) CMake, пожалуй, будет overkill'ом и make останется лучшим выбором.
Начнем с того, что cmake занимается генерацией build-систем, не только для make, но для VS c XCode. Соответственно, надобность писать под какую-то конкретную build-систему исчезает, абстрактное описание build-процесса эффективнее конкретных реализаций.

Написание cmake скриптов легче, сами скрипты проще для понимания (естественно есть какой-то порог вхождения для любой технологии).

Ну и по поводу простых/сложных проектов проектов.

Для простого проекта в статье 19 строк makefile можно заменить 3-мя строками cmake:
cmake_minimum_required(VERSION 2.8)
set(TARGET "hello")
add_executable(${TARGET} hello.c main.c)


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

Но для
binary: binary.c
    ${CC} ${CFLAGS} binary.c -o binary

CMake на самом деле нафиг не сдался.
Все всякого сомнения не рационально использовать cmake в случаях, когда достаточно одной шелл команды:
gcc -O2 binary.c -o binary
Не надо передёргивать.

Альтернатива make -это ninjia.

Действительно просто, но не настраиваемо. Компилятор одной строкой не сменить, для добавления одного файла исходника нужно добавить две строки и одну изменить, подключение библиотек совсем не предусматривается.

Не упомянуты стандартные цели сборки, с ними всё может быть проще. К примеру:
Для его компиляции достаточно очень простого мэйкфайла:

hello: main.c
    gcc -o hello main.c


Если не так уж критично имя бинарника hello, можно сделать ещё более простой Makefile:

all: main


Рекомендую статью Эффективное использование GNU Make. И, конечно же, не останавливаться на простейшем. Читать документацию, смотреть на альтернативы — возможно вам CMake больше подойдёт.
Ах да, есть ещё один неплохой источник — Learn C The Hard Way. Во многом предполагает наличие самостоятельного чтения документации, по Make там два или три урока всего, но вещь полезная.
Полностью согласен.
Несколько основных целей я все же упомянул.
Вообще я задумывал статью для тех кто никак не соберется с силами начать освоение. В рунете инфы пруд- пруди.
Моей целью было как можно более просто приподнести базовые идеи. Главное начать.
Чтобы у тех, кто никак не соберётся силами, эти силы появились, думаю стоит в конце статьи выделить пару хороших ссылок из этого «пруд-пруди».
Где же вы были, автор, часов в десять по москве сегодня?! На работе было спокойно и я с утра думал, о чем бы почитать и какой бы инструмент изучить (или продолжить изучать). Начал с xmobar и закончил cmus, и даже в голову не пришло, что я в свое время недоучил make! Спасибо за статью, сейчас буду есть макарошки и чтить Столмана )
Спасибо :) Приятного прочтения!
Посмотрите на комментарий выше. Вот там хороший пример толкового Makefile, в котором новый исходник добавляется одной строкой.

С библиотеками немного более хитро, но сила в минимализме — построить можно что угодно.

Компилятор меняется одной строкой, например, так:

CC=clang make
У make есть один большой недостаток — непригодность для отладки мейкфайлов. И если в файле из 10 строк можно всё отдебажить методом пристального взгляда, то в мейкфайлы в больших и старых проектах содержат столько всего… кхм… удивительного, что бедные средства make оказываются вообще бесполезными. Единственное решение, которое я для себя нашёл — remake.
Тогда уж лучше сразу automake или cmake.
Вы не поняли. remake — это тот же самый мейк (сам мейкфал менять не надо), просто к нему прикрутили полезные дебажные возможности.

В проектах на несколько миллионов строк и возрастом под 20 лет нет возможности переехать на cmake, а дебажить как-то надо.

Хотя, если честно, в cmake кроме кроссплатформенности не увидел особых плюшек. Хотя может смотрел не достаточно хорошо.
Ну чтобы не писать миллионы строк в make народ и придумал automake. Для разных систем компилить без чего-то автоматизированного это просто не реально. Даже ключи у компиляторов разные, не говоря уже о путях библиотек.
Для этого есть всякие разные библиотеки для мейка.
Тогда лучше уж сразу configure.py
make --trace помог выудить крайне неуловимый баг в системе сборки (дело оказалось не в самом Make, а в среде). Для сложных проектов с мегатоннами унаследованного кода конечно он не подойдёт, но он серьёзно выручает для генерации всяких graphviz, tex и т. п., для которых не всегда IDE-то существует, а автоматизировать процесс надо. Даже собственный парсер так удобнее запускать.

cmake и autotools преимущественно решают проблему унаследованного ПО (зависимости и совместимость), проект, где из сторонних библиотек только STL, собирать командой make одно удовольствие.
Что такое make --trace? У мейка как раз нет --trace, он есть в remake, он этим и ценен.
А как cmake помагает с унаследованным ПО? Я не представляю как легко и просто перевести монструозный проект с огромным количеством включаемых друг в друга под разными условиями и разных последовательностях мейкфалов на cmake.

Кстати об STL. Есть libstdc++, есть libc++, они могут собираться с разными abi и иметь разные версии и разную степерь поддержки С++11. А ещё на макоси с 10.9 хидера STL теперь лежат не в системе, а в XCode, что добавляет приятных моментов. Так что даже с одним STL при необходимости написать хорошо портируемый кросс-платформенный мейк возникает куча внезапных нюансов, будь они не ладны.
> Что такое make --trace
Возможно, потому что у меня Linux и немного другой Mac Make?:) В GNU Make --trace есть и здорово помогает отладочными сообщениями в случае чего.

> Я не представляю как легко и просто перевести монструозный проект
Никак. Если сразу выбрали cmake — хорошо. Но в мире OpenSource не принято таскать за собой thirdparty, а как раз разруливать зависимости, уже имеющиеся в операционной системе, с чем cmake/autotools и справляется. Из крупных проектов, которые тащат за собой груз из унаследованного ПО: Qt, Chromium. При этом у них ещё и собственный велосипед — своя система сборки.

> они могут собираться с разными abi
Эти нюансы тоже должны прятаться внутри cmake/autotools. Перед сборкой проекта они проверяют возможности компиляторов, наличие библиотек и их возможности и в соответствии с этим генерируют Makefile.
GNU Make 3.82 из Fedora 18. Самый что ни на есть обычный мейк.

Подозреваю, что у вас remake вместо make подложен (и это, кстати, хорошо!). Вот что говорит о себе remake:
> remake --version
GNU Make 3.82+dbg0.9
На счёт autoconf — это тоже ещё те грабли. То есть это решает многие проблемы, но далеко не панацея. Плюс, на винде он бесполезен практически (при условии, что проект собирается в некотором шеле — или cygwin, или самописном/самопортированном). Сейчас ответ более или менее понятен — cmake, но лет 15-20-25 назад было всё иначе, а проекты бывают по стольку живут и здравсвуют.

Кстати, в LLVM (типичный большой проект, работающий на всём зоопарке архитектур и ОС) есть и autoconf, и cmake. Собираются постепенно отказаться от autoconf. Но оба решения несколько недопилены (ибо там чёрт ногу сломит местами).
make -dp --warn-undefined-variables | less

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

Насчёт распечатки не понял — Make по умолчанию печататает всё, что делает.
Если проект небольшой и свой, проблемы нет. Если проект большой или очень большой, да ещё и старый, там возникают нюансы. Во-первых, рекомендация про хороший стиль перестают работать. Во-вторых, там объективная необходимость в переменных, ибо кроссплатформенность и различные настройки и прочее. В-третьих, в большом проекте может быть много подпроектов, в которых свои мейкфайлы, которые включаются в главный. В-четвёртых, в большом проекте вся выдача подавлена — ибо по дефолту выдача нужна только если какие-то ворнинги/ошибки (и это правильно), поэтому отсутствие ключика --всё-равно-всё-печатать-и-не-волнует расстраивает при необходимости копнуть глубже.
Во-первых, рекомендация про хороший стиль перестают работать.


Очень интересно. Чтобы рекомендации не переставали работать, нужно применять code review.

Во-вторых, там объективная необходимость в переменных, ибо кроссплатформенность и различные настройки и прочее.


Я не предложил отказаться от переменных. Я говорю, что одна переменная — одно определение. Тогда нет необходимости понимать, где же ей было присвоено значение.

В-третьих, в большом проекте может быть много подпроектов, в которых свои мейкфайлы, которые включаются в главный.


You're doing it wrong. В частности, по причине того, что становится сложно отследить определение переменных. Советую почитать эту хорошую статью. Ну и эту, до кучи.

В-четвёртых, в большом проекте вся выдача подавлена — ибо по дефолту выдача нужна только если какие-то ворнинги/ошибки (и это правильно), поэтому отсутствие ключика --всё-равно-всё-печатать-и-не-волнует расстраивает при необходимости копнуть глубже.

Ключик реализуется своими силами элементарно:
define RUN
ifeq ($(VERBOSE),y)
  echo $(2);
  $(2);
else
  echo $(1);
  $(2);
endif

$(TARGET):
  $(call RUN, "CC $@", $(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS))
Прошу не принимать это как оскорбление, но вы идеалист-теоретик :)

Смысла в переменных, когда у них одно определение, нет никакого, ибо это не переменные, а константы. Например, при сборке на определённой ОС, определённой версии с определённой библиотекой, добавлять к ключам компиляции такой-то флажок, а с другой библиотекой, другой флажёк и так стопицот раз. Предлагаете каждый флажёк в отдельную переменную пихать, а потом всё слить воедино? А как же тогда стиль? Там не на один экран таких переменных набежит плотным текстом. Поэтому «CFLAGS +=», а вот откуда что пришло уже не так понятно.

Я, кстати, не про рекурсивный мейк говорил, а про включение в главный мейкфайл (include'ом). И это хорошая практика.

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

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

Поэтому вместо изобретения велосипедов проще взять remake.
Прошу не принимать это как оскорбление, но вы идеалист-теоретик :)

Это вы — любитель грязных решений. Я переписал сборку проекта из миллиона строк и тоже знаю, о чём речь.
Смысла в переменных, когда у них одно определение, нет никакого, ибо это не переменные, а константы. Например, при сборке на определённой ОС, определённой версии с определённой библиотекой, добавлять к ключам компиляции такой-то флажок, а с другой библиотекой, другой флажёк и так стопицот раз. Предлагаете каждый флажёк в отдельную переменную пихать, а потом всё слить воедино? А как же тогда стиль? Там не на один экран таких переменных набежит плотным текстом. Поэтому «CFLAGS +=», а вот откуда что пришло уже не так понятно.

Функциональные языки программирования (и, в частности, Haskell) передают привет.

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

Да, идеальной ситуации «одно определение» не получится всё равно. Но так проще, поэтому надо стремиться к этому.
Про вербоз своими силами — в вашем случае и --dry-run отлично подойдёт. В реальности там будет ещё несколько генераторов генераторов, запуск этих генераторов, несколько кастомных правил, которые делают разные манипуляуции с выдачей генераторов. Генерация и обратобка зависимостей опять же. И разные файлы с разными ключами собираются, конечно же. Ну, то есть всё под одну гребёнку крайне сложно.

--dry-run не подойдёт, потому что он меняет логику. И мы же говорим о журналировании, а не отдельном специальном запуске make. Наличие генераторов генераторов генераторов просто не лучшим образом говорит об архитектуре системы сборки.
И, заметьте, это всё при нормальном код ревью и поставленных процессах. Ибо без них вообще адъ, мрак и средневековье. Я по долгу службы на всякое насмотрелся. Действительно большие проекты всегда живут своей жизнью.

Ну я не спорю, дерьма всегда можно понаписать. Особенно в больших проектах. С Make сделать это несложно.
Поэтому вместо изобретения велосипедов проще взять remake.

Вывод не следует из предпосылок, нет?

В remake всё, что добавлено — отладчик. Это, конечно, позволит попроще жить в уже существующей дерьмовой инфраструктуре, но первопричина в том, что инфраструктура дерьмовая, а не в том, что отлаживаться тяжело. Писать надо так, чтобы отлаживаться не приходилось.

Да, если легаси и всё уже плохо, может и remake поможет. Но вообще это полумера и костыль.
Это вы — любитель грязных решений. Я переписал сборку проекта из миллиона строк и тоже знаю, о чём речь.

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

Наличие генераторов генераторов генераторов просто не лучшим образом говорит об архитектуре системы сборки.

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

В remake всё, что добавлено — отладчик. Это, конечно, позволит попроще жить в уже существующей дерьмовой инфраструктуре, но первопричина в том, что инфраструктура дерьмовая, а не в том, что отлаживаться тяжело. Писать надо так, чтобы отлаживаться не приходилось.

Чтобы отлаживаться не приходилось, говорите? Хм. Такого даже в идеальном мире не бывает ;-)
Советую глянуть на mk-configure. Он, правда, работает с bmake, а не GNU make, но bmake есть практически везде на данный момент.
С ним этот мейкфайл будет выглядеть так:
PROG = hello

SRCS = main.c hello.c

.include <mkc.mk>
Впервые слышу о подобном.
Предлагаю альтернативой CMake: кросплатформенный, способный собрать проект в любой директории.

Без install:
set(sources main.c hello.c)
add_executable(hello ${sources})

Для install на пару строк больше выйдет.
CMake не альтернатива. У него уродский входной синтаксис, а мейкфайлы он генерит нечитабельные и неотлаживаемые.

Кстати, приведённый выше мейкфайл уже с install и прочими весёлыми штуками.
Хех, вот так и подсаживаешься.

Поставил Arch Linux на Paspberi Pi чтобы переключить ASIC с основного компьютера. Кое как в том числе тупо повторяя написанное с найденной страницы с инструкциями все это завел. В том числе и make cgminer.

Сейчас глядя на пример с hello.c осознал, что от написания софта меня по сути ничего не отделяет. Никаких навороченых комплексов. В редакторе написал, make и любуешься на результат. И это завораживает.
Вашу простейшую программу main.c можно собрать вообще без Makefile:

make main

Попробуйте.
Работает!
Я с трудом воспринимаю «раздутые» примеры и писал с учетом того, что бы людям вроде меня было проще усвоить материал.
Можно было сразу выкатить сложный пример и далее подробно разобрать его, но думаю это ударило бы по усвояемости материала.
Но тем не менее.

Вопрос неявных правил (Implicit Rules) и, в частности, встроенных правил, обойдён, по-моему, незаслуженно. Это распространённый источник недопонимания и желания писать в своих Makefile то, что уже и так встроено в Make.
и даже без make
gcc main.c
А если дописать в исходнике первой строкой #!/usr/local/bin/tcc -run, то можно и без gcc:

./main.c
может уже совсем не компилировать?
ну, в случае helloworld можно и не компилировать :)
А в случае прототипирования простейшей утилитки, состоящей из единственного файла бывает гораздо быстрее без make. Голый gcc или g++, а потом запускать ./a.out
Если прототипирование растягивается на десяток-другой запусков, то в определённый момент уже можно добавить Makefile (исключительно ради того, чтобы компилировать прямо из Vim командой :make
Не спорю, сам так иногда делаю :) но с чего то надо было начать, а по поводу простоты — хотелось чтобы статью можно было не напрягаясь усвоить за раз, а не добавлять в закладки чтобы в итоге никогда не прочесть
Семантика отличается.
make, при наличии в окружении переменных вроде CC и CFLAGS, применит их в процессе компиляции. И файл получится с тем же именем, что и исходник — это проще, когда в одной папке несколько исходников. Как раз при прототипировании.

Ну и мощь — во встроенных правилах:

➜ ls
rpn.y
➜ make rpn
yacc rpn.y
mv -f y.tab.c rpn.c
cc -c -o rpn.o rpn.c
cc rpn.o -o rpn
rm rpn.o rpn.c
В качестве отличного источника также рекомендую CMCrossroads (например, Painless non-recursive make) и в частности статьи John Graham-Cumming. Он же автор GNU Make Standard Library — в ней немало хороших решений задач, для которых хотелось бы нагородить своих хаков. И отладчик.
А вот скажите, а для чего в список исходников указывают вручную все С файлы, а не просто
SRC=$(wildcard *.c)

Разумеется, если не нужна какая-то индивидуальная сборка отдельных файлов
Затем, что если кто-то положит в ту же папку свой исходник (возможно, случайно), сборка может сломаться с странными ошибками.
НЛО прилетело и опубликовало эту надпись здесь
В makefile есть функции wildcard и shell.
С помощью них можно построить дерево файлов по маске.
Ну, а дальше компилить.
Конечно же, мейк изящнее.
НЛО прилетело и опубликовало эту надпись здесь
Изящество в том, что:
— данный вспомогатильный код пишется быстрее, больше времени на payload
— не нужно ломать голову что происходит, человекопонятное описание
— эти бездушные «огромные» makefile достаточного качества, чтобы их даже не открывать :-)
НЛО прилетело и опубликовало эту надпись здесь
Видимо есть маленькое отличие между маленьким хобби проектом и промышленным программированием. Я не могу проверить на своих последних проектах данный makefile потому как:

— кросскомпиляция
— внешние зависимости
— внутренние зависимости
— различные цели: so, bin
НЛО прилетело и опубликовало эту надпись здесь
Это делается одной командой include_directories.
Ваш тоже не работает, у вас при изменении заголовочных файлов не пересоберутся объектники зависящие от них, и одной строкой вы это никак не исправите.
НЛО прилетело и опубликовало эту надпись здесь
Достаточно того что в CMake вы описываете суть проекта на высоком уровне — «взять все исходные файлы» и «собрать из них бинарник», а не цепочку трансформаций списка файлов, суть которой нельзя понять не вчитываясь и которую придётся переписывать целиком если структура изменится (скажем, добавится ещё один уровень каталогов) — две гибкие и понятные строки всяко изящнее десятка строк лапши.

А так, среди всего прочего, с CMake вы получаете из коробки контроль за зависимостями по include'ам (без этого вообще нельзя), поддержку переопределения компилятора/флагов/префикса/destdir и т.д. что необходимо для переносимой сборки, генерацию solution'ов для IDE и много чего ещё, это даже не затрагивая того что может потенциально понадобиться позже при развитии проекта и что на make двумя строчками сделать может и не получиться (поиск зависимостей, установка, интегрированное тестирование, кросс-компиляция), причём переносимо и без необходимости помнить об особенностях каждой целевой системы.
НЛО прилетело и опубликовало эту надпись здесь
У вас полная перекомпиляция всего и вся на каждый вызов make происходит. Вам make вообще не нужен, простого shell-скрипта достаточно.
НЛО прилетело и опубликовало эту надпись здесь
Если изменить константу в заголовочном файле, приведёт ли это к пересборке проекта?
НЛО прилетело и опубликовало эту надпись здесь
НЕ происходит

Да, я не сразу заметил зависимость all от объектников. К слову, я занимался переводом Managing Projects with GNU make Just For Fun (до сих пор валяется на гитхабе). А билд систем перепробовал ну наверное штук 15, для всех мейнстримовых (и не очень) языков.

P.S. Я не минусовал. Но ваш мейкфайл очень легко может привести к некорректной сборке, которая засегфолтится в самый неподходящий момент. Для домашнего проекта который не меняется после первых экспериментов это может быть ок. Для серьёзного проекта этот мейкфайл очень, очень далёк от приемлимого.
Дойду до работы — покажу вам скрипт.
пример простого шелл-скрипта можно привести?

Вот аналог:
#!/bin/bash
project=hobo
outdir=build
sources=`find -name '*.cpp'`
incdirs=`ls -d */ | grep -v "${outdir}/" | sed 's/^/-I/'`

mkdir -p ${outdir}
 
for src in $sources; do
    obj=${outdir}/${src}.o
    mkdir -p `dirname $obj`
    if [ ! -f "$obj" -o "$src" -nt "$obj" ]; then
        echo Compiling $src into $obj
        g++ ${incdirs} -c "$src" -o "$obj"
    fi
done

echo Linking ${project}
g++ ${incdirs} `find ${outdir} -name '*.o'` -o ${outdir}/${project}

На bash пишу раз в год, поэтому возможны ошибки и улучшения. Есть небольшое отличие от вышеупомянутого мейкфайла — объектники складываются в отдельный каталог, структура которого повторяет структуру дерева исходников, что на практике гораздо удобнее.
НЛО прилетело и опубликовало эту надпись здесь
знаете, как в моем makefile-е обработать изменения в заголовочных файлах

Знаю, но доводить это до ума у меня нет никакого желания. Это написано везде миллион раз, всё делается через хаки с седом.

В этом проблема Make — из коробки он подходит для чего угодно кроме сборки С/С++ проектов. Чтобы оно хоть как-то работало, люди либо пишут собственные решения, в которых в 99% невозможно разобраться, либо бездумно копипастят иероглифы из проверенных источников.

В реальной жизни нужен трекинг зависимостей, трегинг ключей сборки, конфигурации сборки (Debug/Release/...), отделение бинарников от исходников, удобная линковка библиотек, определение доступных внешних библиотек и многое другое. Всего этого в make из коробки нет и достигается лишь хаками. Пусть лучше GYP или CMake генерят эти хаки за меня, у меня есть дела поважнее. Очередной makefile имеет смысл писать только если у вас поистине уникальный проект вроде ядра linux.

я его просто удаляю сейчас, когда меняю что-то в header-е

Нужно удалять ВСЕ объектники, в которые инклюдят хедер, а не один.о файл. Единственный способ _корректной_ сборки без знания детальных зависимостей — каждый раз пересобирать всё с нуля. Если вам хочется держать зависимости в голове — пожалуйста.

Вдобавок, у Вас отсутствует target clean

rm -rf build всего на 2 символа длиннее чем make clean. Если сделать алиас на rm -rf, будет даже короче.
Разрешение и использование зависимостей конкретного файла исходного кода это НЕ задача GNU make.

Если у Вас инкрементная сборка проекта на C/C++, то Вы можете покурить опции запуска Вашего любимого тулчейна. Я использую GCC, посему у меня хватает для сборки каждого объектника "-MMD -MF {template}" воткнуть и подцепить директивой include. Хаки с sed-ом хороши ровно до тех пор, пока это Ваш внутритумбочный проект — в дальнейшем рекомендую отказаться от них в пользу by-design решений.

Тот факт, что в так любимых Вами генераторах генераторов генераторов Makefile есть всяческие вкусные плюшки из коробки, ровным счётом тоже не проблема GNU make. Тот же самый out-of-tree build, на который молятся программисты (и я в том числе), вполне себе достигается GNU make без implicit rules, и отнюдь не хаками — просто надо читать документацию.

PS: впрочем да, от головотяпных зависимостей GNU make не спасёт отца русской демократии компиляции.
использование зависимостей конкретного файла исходного кода это НЕ задача GNU make

А чья?

в пользу by-design решений

Можно поподробнее?

отнюдь не хаками — просто надо читать документацию

Надо просто запускать make из target-каталога, и всё сразу становится очень просто. Но ведь все привыкли делать это из каталога с makefile-ом…

в так любимых Вами генераторах генераторов генераторов Makefile

Честно говоря, CMake мне противен. GYP концептуально приятней, но он очень сырой. На работе для сборки используем чистый мейк с кучей собственных макросов и правил, которых 95% разработчиков никогда не видели. Всё благодаря талантливым билд-инженерам, спасибо им большое.

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

Хаки на sed зачастую неплохо заменяются на встроенные функции; зачастую, но не всегда. Скиньте в ЛС реальный пример такого использования sed, которое на порядки лучше built-in вызовов, мне просто интересно.

Запускать make из build directory актуально только для собственно конечных целей, а промежуточные итоги (объектники, прекомпилированные заголовочники, временные файлы от link-time optimization, et cetera) и так хорошо собираются.

По двум последним абзацам Вашего комментария я могу судить о том, что у Вас разработка ПО — собственно работа, посему затраты на сторонние задачи (обслуживание билд-системы) редко оправданы или же вне компетенций. У меня это хобби, поскольку работаю в другой сфере IT. Сейчас сижу и пилю Makefile только потому, что GYP не хочет сделать мне красиво в одном хорошем опенсорсном проекте. Сыроват он, согласен.

Спасибо за беседу, желаю Вам успехов!
НЛО прилетело и опубликовало эту надпись здесь
Если Вы претендуете на профессионализм, то хотя-бы удосужьтесь проверить самостоятельно то, что пишете

Какое именно из процитированных вами утверждений неверно? Не могу найти никаких неточностей или противоречий.

Если вас устраивает помойка в каталоге с исходниками — прекрасно. Меня не устраивает. Хотя бы потому, что в текстовом редакторе нужно набирать больше букв, чтобы он понял, какой именно файл я хочу открыть. Когда появляется желание помещать сгенерированные файлы в отдельный каталог, файлы зависимостей нужно немного преобразовать. И обычно в дело пускают иероглифы вроде
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
    -e '/^$$/ d' -e 's/$$/ :/' < $(df).d >> $(df).P;

Источник: make.paulandlesley.org/autodep.html

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

Когда захочется контролировать уровень детальности вывода билд-системы, собирать модули в отдельные библиотеки (например, для юнит-тестирования), использовать ключи компиляции в качестве реквизитов и прочих радостей, нужно будет набрать определённое кол-во букв, набирать которые раз за разом утомляет. Примеры реализации некоторых из описанных радостей можете найти в мейкфайле ядра.
НЛО прилетело и опубликовало эту надпись здесь
Единственный способ корректной сборки без знания детальных зависимостей — каждый раз пересобирать всё с нуля.

.d файлы, которые генерятся компилятором, и есть те самые детальные зависимости. Вы утверждали, что удаляли объектный файл при изменении соответствующего заголовочного файла, а именно
мне на практике более удобно иметь объектник в той-же директории — я его просто удаляю сейчас, когда меняю что-то в header-е

Я сказал, что удалять один объектный файл недостаточно, нужно удалять ВСЕ объектные файлы, в модули которых включается изменённый объектный файл. Если этого не делать, сборка будет некорректной и есть вероятность сегфолта или ещё хуче — незаметной некорректной работы программы. Если вы считаете это утверждение неверным и самонадеянным, я умываю руки.

Смешно, что никто не смог ответить на мой, достаточно элементарный вопрос

Ваши вопросы больше походят на топики SO, а их тон лично мне не приятен. Начали с «а вам слабо», показали мейкфайл, абсолютно непригодный для какого-либо production, начали оскорблять аудиторию и ждёте адекватной реакции на вашу викторину? Ок.
в модули которых включается изменённый объектныйзаголовочный файл. Если

ещё хучже
НЛО прилетело и опубликовало эту надпись здесь
приведите «хаки с седом» в моей последней версии?

Их нет, потому что все .d файлы высераются в дерево с исходниками. Для меня это неприемлимо.

топики SO

SO = StackOverflow.com

народ просто _не умеет_ им пользоваться

Повторюсь, ваш мейкфайл тривиален потому, что он непригоден для production. Ну либо у меня слишком высокие запросы, но опыт говорит об обратном.

Правильный ответ был-бы — «не знаю»

Ух… смело. Я могу в деталях объяснить, как именно работает ваш код, завязанный на весьма специфическое поведение make при нахождении отсутствующих инклюд-файлов. Я знал о нём ещё 5 лет назад, когда начал переводить Managing Project with GNU Make (Глава 8).
К слову, весь материал из статьи покрывается в первой главе этой книги.
НЛО прилетело и опубликовало эту надпись здесь
Потрудитесь объяснить, в чем заключается «весьма специфическое поведение make»?

GNU make рассматривает все файлы, которые в него инклюдят, как неявные цели. Когда он прочитывает основной мейкфайл, он пытается обновить все включаемые файлы с помощью явных или неявных правил и рестартует, если получилось.

Но если ещё немного подумать, то это поведение здесь действительно не при чём. Оно нужно только для решения, описанного в руководстве www.gnu.org/software/make/manual/html_node/Automatic-Prerequisites.html.

И без sed-а можно обойтись, если компилятор — gcc или поддерживает все его флажки, как clang. Сам переводил, а уже не помню. Ок, тут вы правы.
make не особо кстати и минималистичен, в нем просто сотнями встроенных правил срабатывающих по умолчанию. причем даже с этими встроенными правилами пользоватся makeом достаточно муторно: сделать простейшую вещь — отделить бинарники от исходников (out of source tree build) еще то занятие.
Если и смотреть на минималистичные системы сборки — то пожалуй это ninja (http://martine.github.io/ninja/), мало того что он проще так он еще и работает ощутимо быстрее (на инкрементальных сборках более-менее больших проектов).
Причем в последних CMake есть возможность использовать ninja для генерации собственно билд скриптов и получается девелопер может получить все лутшее от обоих миров — высокий уровень CMake плюс скорость сборки ninja. Чем сейчас и пользуюсь. )
Спасибо за наводку, попробую. Встречал в выводе cmake, но не обратил внимания на эту ninja.
Одно время думал перейти на tup, но как-то не сложилось. Да и проект загибается, видимо.
tup не быстрее ninja но при этом сильно ограничен в том что может делать. tup не позволяет (покрайней мере раньше не позволял и автор не собирался это исправлять хоть я и предлагал модель как это можно сделать) запускать команды которые правят несколько файлов в разных папках, как по мне абсолютно фатальное ограничение для данной билд тулзы.
Одно время я пытался использовать tup — даже засабмитил 3 или 4 фикса чтобы он нормально заработал под win32 но:
а) выше описанное ограничение по выходам команд (нельзя запустить патч сорцов во время билда, или генерацию сложных ресурсов)
б) win32 порт сильно отстает от *nix (на win32 даже нет поддержки out of source build)
в) сложная имплеметнация как на *nix так и на win32 которая часто глючит — tup через хуки мониторит процессы запущенные их под него чтобы обнаружить все зависимости и входы-выходы
г) сильно ограниченные возможности скриптов, язык tup даже не прячет особенности win32 vs *nix
в результате после прототипа на tup и обраружения всех этих недостатков перевел проекты над которыми работал на cmake + ninja.
очень понятно, спасибо
Извините, если есть выше, но я не увидел.
Здесь же на хабре есть хорошая статья Чем плох GNU make
Для ее поддержки make сопоставляет время изменения целей и их реквизитов (используя данные файловой системы), благодаря чему самостоятельно решает какие правила следует выполнить, а какие можно просто проигнорировать:

В частности одна из причин указанных там, эта неудачная зависимость от точного времени, что затруднительно на серверах.
Посмотрите по ссылке, там есть ещё довольно много аргументов.
Спасибо за простую, написанную доступным языком и, главное, последовательную статью.

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

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории