Как сделать SIMPLE C++ Makefile?
Мы должны использовать Makefile, чтобы собрать все вместе для нашего проекта, но наш профессор никогда не показывал нам, как это сделать.
У меня есть только один файл, a3driver.cpp
, Драйвер импортирует класс из местоположения "/user/cse232/Examples/example32.sequence.cpp"
,
Вот и все, все остальное содержится с .cpp
,
Как бы я сделал простой Makefile для создания исполняемого файла a3a.exe
?
7 ответов
Скопировано из поста вики, который я написал для аспирантов по физике.
Так как это для Unix, исполняемые файлы не имеют расширений.
Стоит отметить, что root-config
утилита, которая обеспечивает правильные флаги компиляции и компоновки; и правильные библиотеки для создания приложений против root. Это просто деталь, связанная с первоначальной аудиторией этого документа.
Сделай меня, детка
или ты никогда не забудешь первый раз, когда тебя сделали
Вводное обсуждение make и как написать простой make-файл
Что такое марка? И почему я должен заботиться?
Инструмент под названием make является менеджером зависимостей сборки. Таким образом, он заботится о том, чтобы знать, какие команды нужно выполнить и в каком порядке взять ваш программный проект из коллекции исходных файлов, объектных файлов, библиотек, заголовков и т. Д. И т. Д. - некоторые из которых могли недавно измениться --- и превращение их в правильную актуальную версию программы.
На самом деле вы можете использовать make и для других вещей, но я не буду об этом говорить.
Тривиальный Makefile
Предположим, что у вас есть каталог, содержащий: tool
tool.cc
tool.o
support.cc
support.hh
, а также support.o
которые зависят от root
и должны быть скомпилированы в программу под названием tool
и предположим, что вы взломали исходные файлы (что означает tool
сейчас устарела) и хочу скомпилировать программу.
Чтобы сделать это самостоятельно, вы могли бы
1) проверьте, есть ли support.cc
или же support.hh
новее чем support.o
и, если это так, выполните команду, как
g++ -g -c -pthread -I/sw/include/root support.cc
2) проверить, если либо support.hh
или же tool.cc
новее чем tool.o
и, если это так, выполните команду, как
g++ -g -c -pthread -I/sw/include/root tool.cc
3) проверить, если tool.o
новее чем tool
и, если это так, выполните команду, как
g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
-Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl
Уф! Что за хлопот! Есть много чего вспомнить и несколько шансов ошибиться. (Кстати, особенности представленных здесь командных строк зависят от нашей программной среды. Эти работают на моем компьютере.)
Конечно, вы можете просто запустить все три команды каждый раз. Это бы сработало, но не подходит для существенного программного обеспечения (такого как DOGS, которое компилируется с нуля на моем MacBook более 15 минут).
Вместо этого вы можете написать файл с именем makefile
как это:
tool: tool.o support.o
g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
-Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl
tool.o: tool.cc support.hh
g++ -g -c -pthread -I/sw/include/root tool.cc
support.o: support.hh support.cc
g++ -g -c -pthread -I/sw/include/root support.cc
и просто введите make
в командной строке. который выполнит три шага, показанных выше, автоматически.
Здесь строки с отступом имеют форму "target: dependencies" и говорят make, что соответствующие команды (строки с отступом) должны выполняться, если какая-либо из зависимостей новее, чем target. То есть строки зависимости описывают логику того, что необходимо перестроить, чтобы учесть изменения в различных файлах. Если support.cc
изменения, которые означают, что support.o
должен быть восстановлен, но tool.o
можно оставить в покое. когда support.o
изменения tool
должен быть восстановлен.
Команды, связанные с каждой линией зависимостей, выделяются с помощью вкладки (см. Ниже), которая должна изменить цель (или, по крайней мере, коснуться ее, чтобы обновить время модификации).
Переменные, встроенные правила и другие полезности
На этом этапе наш make-файл просто запоминает работу, которая должна быть выполнена, но нам все еще приходилось разбираться и вводить все необходимые команды в полном объеме. Так не должно быть: make - это мощный язык с переменными, функциями для работы с текстом и целым рядом встроенных правил, которые могут сделать это для нас намного проще.
Сделать переменные
Синтаксис для доступа к переменной make: $(VAR)
,
Синтаксис для назначения переменной make: VAR = A text value of some kind
(или же VAR := A different text value but ignore this for the moment
).
Вы можете использовать переменные в правилах, таких как эта улучшенная версия нашего make-файла:
CPPFLAGS=-g -pthread -I/sw/include/root
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
-lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz \
-Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root \
-lm -ldl
tool: tool.o support.o
g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)
tool.o: tool.cc support.hh
g++ $(CPPFLAGS) -c tool.cc
support.o: support.hh support.cc
g++ $(CPPFLAGS) -c support.cc
который немного более читабелен, но все же требует много печатать
Сделать функции
GNU make поддерживает множество функций для доступа к информации из файловой системы или других команд в системе. В этом случае нас интересует $(shell ...)
который расширяется до вывода аргумента (ов), и $(subst opat,npat,text)
который заменяет все экземпляры opat
с npat
в тексте.
Воспользовавшись этим, мы получаем:
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
tool: $(OBJS)
g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)
tool.o: tool.cc support.hh
g++ $(CPPFLAGS) -c tool.cc
support.o: support.hh support.cc
g++ $(CPPFLAGS) -c support.cc
который легче набирать и гораздо более читаемый.
Заметить, что
- Мы все еще явно указываем зависимости для каждого объектного файла и конечного исполняемого файла
- Мы должны были явно ввести правило компиляции для обоих исходных файлов
Неявные и шаблонные правила
Как правило, мы ожидаем, что все исходные файлы C++ должны обрабатываться одинаково, и make предоставляет три способа указать это
- суффиксные правила (считаются устаревшими в GNU make, но сохраняются для обратной совместимости)
- неявные правила
- шаблонные правила
Неявные правила встроены, и некоторые из них будут обсуждаться ниже. Шаблонные правила указываются в виде
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
Это означает, что объектные файлы генерируются из исходных файлов c, выполнив команду, показанную, где переменная "automatic" $<
расширяется до имени первой зависимости.
Встроенные правила
Make имеет целый набор встроенных правил, которые означают, что очень часто проект может быть скомпилирован очень простым make-файлом.
В GNU make встроено правило для исходных файлов c, которое показано выше. Точно так же мы создаем объектные файлы из исходных файлов C++ с правилом вроде $(CXX) -c $(CPPFLAGS) $(CFLAGS)
Отдельные объектные файлы связаны с помощью $(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
, но это не будет работать в нашем случае, потому что мы хотим связать несколько объектных файлов.
Переменные, используемые встроенными правилами
Встроенные правила используют набор стандартных переменных, которые позволяют вам указывать информацию о локальной среде (например, где найти включаемые файлы ROOT) без переписывания всех правил. Наиболее вероятными для нас являются:
CC
- c компилятором использоватьCXX
- использовать компилятор C++LD
- компоновщик для использованияCFLAGS
- флаг компиляции для исходных файлов cCXXFLAGS
- флаги компиляции для исходных файлов C++CPPFLAGS
- флаги для c-препроцессора (обычно включают пути к файлам и символы, определенные в командной строке), используемые c и C++LDFLAGS
- флаги компоновщикаLDLIBS
- библиотеки для ссылки
Основной Makefile
Используя преимущества встроенных правил, мы можем упростить наш make-файл для:
CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
tool.o: tool.cc support.hh
support.o: support.hh support.cc
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) tool
Мы также добавили несколько стандартных целей, которые выполняют специальные действия (например, очистка исходного каталога).
Обратите внимание, что когда make вызывается без аргумента, он использует первую цель, найденную в файле (в данном случае все), но вы также можете назвать цель, которую хотите получить, что и делает make clean
удалите объектные файлы в этом случае.
У нас все еще есть жестко запрограммированные зависимости.
Некоторые Таинственные Улучшения
CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)
SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))
all: tool
tool: $(OBJS)
$(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)
depend: .depend
.depend: $(SRCS)
$(RM) ./.depend
$(CXX) $(CPPFLAGS) -MM $^>>./.depend;
clean:
$(RM) $(OBJS)
distclean: clean
$(RM) *~ .depend
include .depend
Заметить, что
- Для исходных файлов больше нет строк зависимости!?!
- Существует некоторая странная магия, связанная с.depend и зависимость
- Если вы делаете
make
затемls -A
вы видите файл с именем.depend
который содержит вещи, которые выглядят как линии зависимости
Другое Чтение
- GNU make manual
- Рекурсивная сборка считается вредной для обычного способа написания make-файлов, который не является оптимальным, и того, как его избежать.
Знайте ошибки и исторические заметки
Язык ввода для make чувствителен к пробелам. В частности, строки действий, следующие за зависимостями, должны начинаться с вкладки. Но ряд пробелов может выглядеть одинаково (и действительно есть редакторы, которые будут молча преобразовывать вкладки в пробелы или наоборот), в результате чего файл make выглядит правильно и все еще не работает. Это было определено как ошибка на ранней стадии, но ( история продолжается) не была исправлена, потому что уже было 10 пользователей.
Я всегда думал, что это было легче изучить на подробном примере, так что вот как я думаю о make-файлах. Для каждого раздела у вас есть одна строка, которая не имеет отступа, и показывает название раздела, за которым следуют зависимости. Зависимости могут быть либо другими разделами (которые будут выполняться до текущего раздела), либо файлами (которые при обновлении приведут к повторному запуску текущего раздела при следующем запуске). make
).
Вот краткий пример (имейте в виду, что я использую 4 пробела, где я должен использовать вкладку, переполнение стека не позволяет мне использовать вкладки):
a3driver: a3driver.o
g++ -o a3driver a3driver.o
a3driver.o: a3driver.cpp
g++ -c a3driver.cpp
Когда вы печатаете make
, он выберет первый раздел (a3driver). a3driver зависит от a3driver.o, поэтому он перейдет в этот раздел. a3driver.o зависит от a3driver.cpp, поэтому он будет работать, только если a3driver.cpp изменился с момента последнего запуска. Предполагая, что он был (или никогда не был запущен), он скомпилирует a3driver.cpp в файл.o, затем вернется к a3driver и скомпилирует окончательный исполняемый файл.
Поскольку существует только один файл, его можно даже уменьшить до:
a3driver: a3driver.cpp
g++ -o a3driver a3driver.cpp
Причина, по которой я показал первый пример, заключается в том, что он показывает силу make-файлов. Если вам нужно скомпилировать другой файл, вы можете просто добавить другой раздел. Вот пример с secondFile.cpp (который загружается в заголовок с именем secondFile.h):
a3driver: a3driver.o secondFile.o
g++ -o a3driver a3driver.o secondFile.o
a3driver.o: a3driver.cpp
g++ -c a3driver.cpp
secondFile.o: secondFile.cpp secondFile.h
g++ -c secondFile.cpp
Таким образом, если вы измените что-то в secondFile.cpp или secondFile.h и перекомпилируете, он будет перекомпилировать только secondFile.cpp (не a3driver.cpp). Или, альтернативно, если вы что-то измените в a3driver.cpp, он не будет перекомпилировать secondFile.cpp.
Дайте мне знать, если у вас есть какие-либо вопросы по этому поводу.
Также традиционно включать раздел с именем "all" и раздел с именем "clean". "all" обычно собирает все исполняемые файлы, а "clean" удаляет "артефакты сборки", такие как файлы.o и исполняемые файлы:
all: a3driver ;
clean:
# -f so this will succeed even if the files don't exist
rm -f a3driver a3driver.o
РЕДАКТИРОВАТЬ: я не заметил, что вы на Windows. Я думаю, что единственная разница заключается в изменении -o a3driver
в -o a3driver.exe
,
Почему всем нравится перечислять исходные файлы? Простая команда поиска может позаботиться об этом легко.
Вот пример простого C++ Makefile. Просто поместите его в каталог, содержащий .C
файлы, а затем введите make
...
appname := myapp
CXX := clang++
CXXFLAGS := -std=c++11
srcfiles := $(shell find . -name "*.C")
objects := $(patsubst %.C, %.o, $(srcfiles))
all: $(appname)
$(appname): $(objects)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)
depend: .depend
.depend: $(srcfiles)
rm -f ./.depend
$(CXX) $(CXXFLAGS) -MM $^>>./.depend;
clean:
rm -f $(objects)
dist-clean: clean
rm -f *~ .depend
include .depend
Старый вопрос, я знаю, но для потомков. У вас было два варианта.
Вариант 1: самый простой makefile = NO MAKEFILE.
Переименуйте "a3driver.cpp" в "a3a.cpp", затем в командной строке напишите:
nmake a3a.exe
И это все. Если вы используете gnu-make, используйте "make", "gmake" или что-то еще.
Вариант 2: 2-строчный make-файл.
a3a.exe: a3driver.obj
link /out:a3a.exe a3driver.obj
Вуаля.
Я использовал ответ Фридмада. Я изучил это некоторое время, и, похоже, это хороший способ начать. Это решение также имеет четко определенный метод добавления флагов компилятора. Я ответил снова, потому что внес изменения, чтобы он работал в моей среде, Ubuntu и g++. Иногда больше примеров - лучший учитель.
appname := myapp
CXX := g++
CXXFLAGS := -Wall -g
srcfiles := $(shell find . -maxdepth 1 -name "*.cpp")
objects := $(patsubst %.cpp, %.o, $(srcfiles))
all: $(appname)
$(appname): $(objects)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)
depend: .depend
.depend: $(srcfiles)
rm -f ./.depend
$(CXX) $(CXXFLAGS) -MM $^>>./.depend;
clean:
rm -f $(objects)
dist-clean: clean
rm -f *~ .depend
include .depend
make-файлы кажутся очень сложными. Я использовал один, но он генерировал ошибку, связанную с отсутствием связи в библиотеках g++. Эта конфигурация решила эту проблему.
Я предлагаю:
tool: tool.o file1.o file2.o
$(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@
или же
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o
Последнее предложение немного лучше, так как при повторном использовании GNU создают неявные правила. Однако для работы исходный файл должен иметь то же имя, что и конечный исполняемый файл (то есть: tool.c
а также tool
).
Обратите внимание, что нет необходимости объявлять источники. Промежуточные объектные файлы создаются с использованием неявного правила. Следовательно, это Makefile
работать на C и C++ (а также на Fortran и т. д.).
Также обратите внимание, по умолчанию, использование Makefile $(CC)
в качестве компоновщика. $(CC)
не работает связывать объекты C++. Мы модифицируем LINK.o
только из-за этого. Если вы хотите скомпилировать код C, вам не нужно принудительно LINK.o
значение.
Конечно, вы также можете добавить свои флаги компиляции с переменной CFLAGS
и добавьте свои библиотеки в LDLIBS
, Например:
CFLAGS = -Wall
LDLIBS = -lm
Примечание с одной стороны: если вам нужно использовать внешние библиотеки, я предлагаю использовать pkg-config для правильной установки CFLAGS
а также LDLIBS
:
CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)
Внимательный читатель заметит это Makefile
не перестраивается должным образом, если один заголовок изменен. Добавьте эти строки, чтобы решить проблему:
override CPPFLAGS += -MMD
include $(wildcard *.d)
-MMD
позволяет создавать файлы.d, содержащие фрагменты Makefile о зависимостях заголовков. Во второй строке просто используйте их.
Конечно, хорошо написанный Makefile должен также включать clean
а также distclean
правила:
clean:
$(RM) *.o *.d
distclean: clean
$(RM) tool
Обратите внимание, $(RM)
эквивалентно rm -f
но это хорошая практика, чтобы не звонить rm
непосредственно.
all
Правило также приветствуется. Для того, чтобы работать, это должно быть первое правило вашего файла:
all: tool
Вы также можете добавить install
правило:
PREFIX = /usr/local
install:
install -m 755 tool $(DESTDIR)$(PREFIX)/bin
DESTDIR
по умолчанию пусто Пользователь может установить его для установки вашей программы в альтернативную систему (обязательно для процесса кросс-компиляции). Сопровождающие пакета для многократного распространения могут также измениться PREFIX
для того, чтобы установить ваш пакет в /usr
,
И последнее слово: не помещайте исходные файлы в подкаталоги. Если вы действительно хотите это сделать, оставьте это Makefile
в корневом каталоге и используйте полные пути для идентификации ваших файлов (т.е. subdir/file.o
).
Подводя итог, ваш полный Makefile должен выглядеть так:
LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
PREFIX = /usr/local
override CPPFLAGS += -MMD
include $(wildcard *.d)
all: tool
tool: tool.o file1.o file2.o
clean:
$(RM) *.o *.d
distclean: clean
$(RM) tool
install:
install -m 755 tool $(DESTDIR)$(PREFIX)/bin
Ваш make-файл будет иметь одно или два правила зависимости в зависимости от того, будете ли вы компилировать и ссылаться одной командой или одной командой для компиляции и одной командой для ссылки.
Зависимости - это дерево правил, которые выглядят так:
main_target : source1 source2 etc
command to build main_target from sources
source1 : dependents for source1
command to build source1
После команд для цели должна быть пустая строка, и не должно быть пустой строки перед командами. Первая цель в make-файле - это общая цель, остальные цели создаются только в том случае, если от них зависит первая цель.
Так что ваш make-файл будет выглядеть примерно так.
a3a.exe : a3driver.obj
link /out:a3a.exe a3driver.obj
a3driver.obj : a3driver.cpp
cc a3driver.cpp