Разработка

индекс
203,40

GNU Make может больше чем ты думаешь

Как только исходники проекта надо распространять, то возникает необходимость использовать систему сборке, вместо того что нагенерила любимая IDE. В мире unix (с подачи gnu) традиционно используется autotools, ему есть отличные альтернативы в виде cmake или scons. Но почему-то ядро Linux собирается при помощи GNU Make, а вся FreeBSD включая порты при помощи BSD Make. WTF?

Однажды намучившись с autotools, я решил провести эксперимент — насколько можно перелопатить Makefile, чтобы обеспечить себе более-менее удобную сборку.



Т.к. современные make сильно отличаются, я использовал GNU Make, т.к. он родной для моей основной системы — GNU/Linux.
И так, первую приятную вещь, что я обнаружил, так это отсутствие необходимости писать для каждого файлика правило. Т.е. вместо повторения для каждого файла блоков вроде:
foo.o: foo.c
	$(CC) -c foo.c

Можно просто, для конкретного бинарика указать список зависимых объектов и он их соберёт:
main_executable: foo.o bar.o test.o main.o
    $(CC) $^ -o $@ $(LDFLAGS)

Что за $^ и $@? Это автоматические переменные, здесь $@ раскрывается в имя цели (т.е. main_executable), а $^ в список всех зависимостей. Подробное описание всех автоматических переменных тут.

Вторая проблема которую я хотел решить это генерация зависимостей. Один файлик может зависеть от другого, а он от системного заголовка и т.д. Если прописывать их руками то отпадает всякий смысл в предыдущем открытии.
Как выяснилось, gcc умеет разбирать исходник и выдавать список заголовков от которых он зависит, делается это при помощи ключа -M: gcc -M foo.c. Вывод команды в формате Makefile, следовательно он может быть сохранён в файл и подключён в Makefile. Большинство build систем также использую gcc -M. Классически, генерация зависимостей запихивалась куда-нибудь в таргет: depends, но в мануале нашёлся способ обновлять все зависимости автоматом.
В совете используется один список объектов, но что если целей несколько? Я слишком ленив чтобы руками писать для каждой! По этому я решил организовывать Makefile по такой схеме:
* Есть список targets, содержащий все цели (бинарики). Правило для его сборки прописывается вручную.
* Для кажой цели есть список её объектов: цель_obj = foo.o bar.o и т.д.
* Есть цель all которая проходит по всем целям.
Исходя из этого, необходимо взять все цели, преобразовать их имена в вид цель_obj, из них уже вытащить объекты, переименовать их суффиксы в .dep (или в .d), и потом только подключить. Звучит сложно? Строчка которая делает это ещё более запутана: deps = $(foreach o,$(targets:=_obj),$($(o):%.o=.deps/%.dep)). Я даже не буду пытаться объяснить ньюансы, скажу лишь что она генерит список зависимостей для каждого объекта, в виде .deps/foo.dep. В директорию .deps только чтобы не гадить в директории с сорцами.

В заключение пример мэйкфайла:
CFLAGS=-Wall -pipe -ggdb

compiler_obj = compiler.o string_util.o tokenizer.o btree.o memory.o ast.o
vm_obj = vm.o

targets = compiler vm

all: $(targets)

.deps/%.dep: %.c
	@mkdir -p .deps
	@set -e; rm -f $@; \
		$(CC) -M $(CFLAGS) $< > $@; \
		sed -i 's,\($*\)\.o[ :]*,\1.o $@ : ,g' $@;

deps = $(foreach o,$(targets:=_obj),$($(o):%.o=.deps/%.dep))
-include $(deps)

echo-deps:
	@echo $(deps)

compiler: $(compiler_obj)
	@$(CC) $^ -o $@ $(LDFLAGS)

vm: $(vm_obj)
	@$(CC) $^ -o $@ $(LDFLAGS)

clean:
	rm -f *.o .deps/*.dep $(targets)

ctags:
	@ctags *.c *.h


P.S. В данном случае есть ещё куча недочётов, но опыт работы с GNU Make, дал мне понять, что при желании, на ней можно сделать полноценную билд систему. Возможно в будущем, напишу и выложу более generic библиотеку мэйкфайлов.
+25
22 декабря 2008, 15:41
33

комментарии (58)

+3
dohlik #
Вы в скриптах тоже допускаете грамматические АшЫПки, или просто не любите русский язык?

ЗЫ «ньюансы» стали уже фишкой Хабра наверное
–4
redchrom #
Я не люблю русский язык, в школе любовь отбили.
+6
dohlik #
Ну, мне (и Вам, судя по всему) почему-то школьная информатика не отбила интереса к ИТ ;)
–3
redchrom #
Наверное по тому, что информатику не вели gramar nazi.
+5
dals #
Какбе «gramMar nazi».
–3
ilya_ost #
You is one of them!
+5
gearss #
Какбы «You ARE one of them!» :)
0
ilya_ost #
Да я знаю. Как бы шутка. И, кстати, КАК БЫ.
+1
gearss #
Я вас отлично понял (с первого раза) и поддержал.
Мне вообще следовало написать «какбе», подражая <~dals>
+2
Holy_Cheater #
Если вам без слова «как бы»,
обойтись никак нельзя,
то умом вы значит слабы
и учились как бы зря.
(ц) не помню кто :)
0
ctrlok #
Хорошая статья. Правда как то не могу понять её практического применения… Вернее я не могу понять зачем это надо? Если спортивный интерес — однозначно вам плюс. А если нет — раскажите мне, так как любопытно =)
0
redchrom #
В первую очередь конечно спортивный интерес. Практическая польза: иметь заготовку для небольших программ, где лень возиться в autotools.
0
vilgeforce #
Я для мелочи юзаю qmake. Создает make-файл, которым и пользуюсь :-)
0
redchrom #
Если бы я работал с Qt то тоже наверное бы пользовал.
0
lostdj #
Qt и не нужно, чтобы использовать qmake standalone.
0
stoune #
А не проще и практичнее чем тратить время на бодание с граблями make делать сборку с помощью scons, а секономленное время потратить на разработку.
Проблемы make извесны и в рамках его идеологии и архитектуры неизлечимы(иначе порушится обратная совместимость) так может тогда выбрать инструмент который будет помагать, а не ставить подножки.
0
redchrom #
Нарисовалась свободная минутка, имею право потратить так как хочу, кто-то в игры играет, кто-то в курилке тусит, а я с gnu make поковырялся.
0
strem #
Исправьте, пожалуйста:

+1
strem #
В мире unix (с под*о*чи gnu)

+1
redchrom #
Поправил, спасибо.
0
strem #
Прошу прощения — какие-то непонятные явления у меня с комментариями. Виноват.
+2
kmmbvnr #
0
redchrom #
Нет, но спасибо, почитаю.
+5
Yfka #
Для большинства достаточно крупных проектов используются именно Мэйкфайлы, а не autotools, причём написанные людями Мэйкфайлы. Это хорошо ложится в практику ежедневных билдов.

Вы будете смеяться, но в Windows мэйк используется ещё шире, чем юниксах. 95% известным мне проектов собираются в Виндах с помощью микрософтского nmake.

Но чем сложнее становится проект, тем сложнее становятся Мэйкфайлы. Всё больше уровней включений (include) становится в Мэйкфайлах.

Постепенно, с ростом проекта, поддерка Мэйкфайлов становится всё более и более утомительной, затратной. И тут уже действительно недостатки мэйка становятся видны невооруженным глазом. Но есть другая система, которая написана специально, чтобы преодолеть недостатки мэйка (Особенно зависимости, обслуживание зависимостей). Я говорю об Apache Ant. ant.apache.org/

Да, корни Анта из Жавы, но он применим для сборки систем на любых языках программирования, на любых ОС.

Тем кто планирует писать немаленькие программы/системы настоятельно рекомендую обратить внимание на Ant.
–2
redchrom #
Хм, а я думал им только яву собирать. Нодо будит посмотреть.
НЛО прилетело и опубликовало эту надпись здесь
0
shai_xylyd #
Аналогом Ant в мире .NET является NAnt=)
0
stoune #
Вообще-то для промышленного C/C++ под Windows доминирует Visual C++ и у него своя система MSBuild, но под C++ используется VCBuild, его я считаю использовать целесообразнее в Windows чем Ant. В 2010 планируют польностью перейти к MSBuild без использования VCBuild.

А для личных маленьких проэктов я использую scons. XML-хорош для машины но для человека он ужасен. Человекочитабельный формат значительно легче в дальнейшем сопровождать, да и после обучения легче логическую ошибку найти.
0
redchrom #
xml уныл и для машины, s-expressions рулит.
0
Frosty #
Было бы интересно, если бы кто нибудь написал обзор о том, как можно собирать с помощью make на N ядрах одновременно. Хотя, думаю это не так то и просто, раскидать N исходников на N ядер с учетом зависимостей и очередности сборки.
+1
redchrom #
make -j4
0
redchrom #
Это по два трэда на ядро будет. Проблемы встречал только на автотулз, да и то в редких ситуациях.
+1
Frosty #
Встроенная возможность make, отлично! Спасибо!
Протестировал на последней ревизии mplayer-а:
make: real 4m12.391s
make -j4: real 1m17.964s
make -j:real 1m13.463s

Это на Intel® Core(TM)2 Quad CPU Q9550 @ 2.83GHz
0
tass #
-j без параметра АФАИР это количество используемых потоков = количество ядер+1
0
Frosty #
что количество потоков вроде как равно количеству ядер, но для чего +1?
0
ptiss #
например для того, чтобы n+1й работал, когда n-й пишет на диск. чисто теоретически.
0
alex3d #
хм, мой make вот что говорит

$ (make -h 2>&1 && make -v) | grep -e "Make\|-j "
-j [N], --jobs[=N] Allow N jobs at once; infinite jobs with no arg.
GNU Make 3.81
–5
AlexcYeCu #
сборке опасносте?
–1
walker #
>можно хакнуть Makefile

т.е. теперь чтение документации называется хаком?
+2
Frosty #
Использование чего либо не совсем очевидным образом и есть хак. Может в этом случае и громковато сказано, но человек проявил терпение и смекалку, за что и получил от меня плюс. Осилить документацию — иногда это тот еще труд :)
0
walker #
не совсем очевидным? о_О

это именно стандартное поведение make, и я удивился, когда прочел, что автор писал все зависимости руками.
0
redchrom #
А мне например очевидно как использовать find и я про неё статьи не пишу. Если уж задел тебя использованием словом «хак» то извини, обидеть не хотел.
0
walker #
да, ты четко подметил разницу — если убрать из статьи фразу про хак, она воспринимается совсем иначе.

а так, словно из журнала хакер…
0
redchrom #
Ок, убрал. Всё-таки в англоизычных интернетах как-то более классически это слово воспринемают :\
0
walker #
здорово, что прислушиваешься к мнению других. :)
0
redchrom #
Можешь провести аналогию с «грязный хак» — воткнуть костыль чтобы работало. В данном контексте примерно тоже самое.
+2
grep0 #
Кстати, есть более стандартный способ не гадить в директории с сорцами — VPATH
0
redchrom #
Да, спасибо за совет. Как-то работал над проектом, где была куче сорцов в одной дире и несколько директорий для сборки (собирались разные проекты на одном ядре). Т.е. там нельзя было все .o держать в одном месте и Makefile генерился своим скриптом. Если занесёт на подобную организацию сборки — обязательно VPATH заюзаю.
+1
optio #
Когда передо мной встал выбор какую систему сборки использовать — я остановился на scons. К тому моменту я неплохо владел make и в ужасе думал о сложном Makefile под большой проект: слишком много нужно прописать руками, слишком нечеловеческий синтаксис для простых операций.
Я ценю шелл-скрипт, но
%.o: %.c defines.h
             $(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

для большого проекта, среди сотен подобных строчек просто снесёт голову…

А в случае со scons мы имеем полноценное python-окружение, внятные директивы и умный сборщик…

0
redchrom #
scons конечно хорошо но зачем правила то руками писать?
%.o: %.c defines.h
$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<
— так не нужно. Зависимость файлов от defines.h сама сгенерится. Также для специфичных целей, можно указать специфичные CFLAGS и т.д. Хотя конечно лучше scons использовать, если есть возможность, я просто в защиту make :)
0
optio #
Зависимость файлов от defines.h сама сгенерится. Генерацию зависимостей таки придётся ручками прописывать: $(CC) -M и всё такое… :)

P.S. Я тоже не наезжаю на make, хорошая система. Но стоит использовать более современные аналоги :)
0
nuit #
-include .config
-include ../mk/common.mk

all:

dir  := ls
exe  := program
src  := main.c
misc := ls/stdafx.h.gch
src := $(addprefix $(dir)/, $(src))

$(eval $(call make-program, $(exe), $(misc), $(src)))

-include ../mk/build-rules.mk


Вот примерно такие у меня мэйкфайлы для новых проектов.
Все зависимости файлов итп итд сгенерируются(одновременно с компиляцией сишного файла, а не как в этой статье). Можно так же наделать много make-program/library/archive/итд (обычно я их выношу в отдельные .mk файлики и делаю инклуд в основной). Если нужно будет что-то посложнее — всё легко реализуется.
+1
paul7 #
Можно просто, для конкретного бинарика указать список зависимых объектов и он их соберёт


Не поверите, make это умеел еще тогда, когда ни вас, ни GNU в проекте не было.
–3
parta #
«В мире unix (с подачи gnu) традиционно используется autotools..» — «мир юникс» не начинается и не заканчивается гнутым подельем.
«Но почему-то ядро Linux собирается при помощи GNU Make, а вся FreeBSD включая порты при помощи BSD Make.» — еще не хватало чтобы бсд собиралась гнутьем.

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

никогда не забуду как линупсятнеги радовались своему LVM'у и даже собрались портировать во FreeBSD. а бсдятники недоуменно пальцем у виска крутили… в конечном счете гнушники не осилили портануть, а потом обиделись когда узнали что в бсд с незапамятных времен есть vinum который умеет все что лвм и еще немного сверху…
+2
paul7 #
Вас обидел кто-то?
+1
VolCh #
Офтоп: что лучше Windows или Linux? Третьего не дано :)
–1
redchrom #
Бздун такой бздун.
+2
maashaa #
>> опыт работы с GNU Make, дал мне понять, что при желании, на ней можно сделать полноценную билд систему

Мой работы с C++ и Java дал мне понять, что при желании, на них можно полноценно программировать. :-)

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