Мы обязаны использовать Make-файл для сплачивания всего для нашего проекта, но наш преподаватель никогда не показывал нам как.
У меня только есть один файл, a3driver.cpp
. Драйвер импортирует класс из местоположения, "/user/cse232/Examples/example32.sequence.cpp"
.
Именно. Все остальное содержится с .cpp
.
Как я пошел бы о создании простого Make-файла, который создает названный исполняемый файл a3a.exe
?
Поскольку это для Unix, исполняемые файлы не имеют расширений.
Следует отметить, что root-config
- это утилита, которая предоставляет правильные флаги компиляции и связывания; и нужные библиотеки для создания приложений против root. Это всего лишь деталь, относящаяся к исходной аудитории этого документа.
или Ты никогда не забудешь, когда тебя впервые сделали
Вводное обсуждение make и как написать простой make-файл
Что такое Make? И почему меня это должно волновать?
Инструмент под названием Make является диспетчером зависимостей сборки. То есть он заботится о том, чтобы знать, какие команды необходимо выполнить, в каком порядке взять ваш программный проект из коллекции исходных файлов, объектных файлов, библиотек, заголовков и т. Д. И т. Д. - некоторые из которых могли быть изменены недавно --- и превратив их в правильную актуальную версию программы.
На самом деле, вы можете использовать Make и для других целей, но я не буду об этом говорить.
Простой файл Makefile
Предположим, у вас есть каталог, содержащий: tool
tool.cc
tool.o
support.cc
support.hh
и support.o
, которые зависят от root
и должны быть скомпилированы в программу под названием tool
, и предположим, что вы взламывали исходные файлы ( что означает, что существующий инструмент
устарел) и вы хотите скомпилировать программу.
Чтобы сделать это самостоятельно, вы можете
проверить, является ли support.cc
или support.hh
новее, чем поддержка .o
, и если да, запустите команду типа
g ++ -g -c -pthread -I / sw / include / root support.cc
Проверьте, есть ли support.hh
или tool.cc
новее, чем tool.o
, и если это так, запустите команду типа
g ++ -g -c -pthread -I / sw / include / root tool.cc
Убедитесь, что 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, что связанные команды (строки с отступом) должны выполняться, если какая-либо из зависимостей новее, чем цель.То есть строки зависимостей описывают логику того, что необходимо перестроить, чтобы учесть изменения в различных файлах. Если support.cc
изменяется, это означает, что support.o
необходимо перестроить, но tool.o
можно оставить в покое. При изменении support.o
инструмент
необходимо перестроить.
Команды, связанные с каждой строкой зависимости, выделены вкладкой (см. Ниже), которая должна изменить цель (или, по крайней мере, прикоснуться к ней, чтобы обновить время модификации).
На этом этапе наш make-файл просто запоминает работу, которую необходимо выполнить, но нам все равно нужно было вычислить и ввести каждую необходимую команду целиком. Так быть не должно: Make - мощный язык с переменными, функциями манипулирования текстом и целым рядом встроенных правил, которые могут значительно упростить нам задачу.
Make Variables
Синтаксис для доступа к make-переменной: $ (VAR)
.
Синтаксис для присвоения переменной Make: VAR = какое-то текстовое значение
(или VAR: = другое текстовое значение, но пока игнорируйте это
).
Вы можете использовать переменные в правилах, подобных этой улучшенной версии нашего 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
, который немного более читабелен, но по-прежнему требует много ввода
Make Functions
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 $(OBJS) $(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 предоставляет три способа заявить об этом:
Неявные правила встроены, и некоторые из них будут рассмотрены ниже. Правила шаблона указываются в форме, подобной
%.o: %.c
$(CC) $(CFLAGS) $(CPPFLAGS) -c $<
, что означает, что объектные файлы создаются из исходных файлов C путем выполнения показанной команды, где «автоматическая» переменная $ <
заменяется на имя первой зависимости. .
Встроенные правила
Make имеет целый ряд встроенных правил, которые означают, что очень часто проект действительно может быть скомпилирован с помощью очень простого make-файла.
Выше показано встроенное правило GNU make для исходных файлов C. Аналогичным образом мы создаем объектные файлы из исходных файлов C ++ с помощью правила типа $ (CXX) -c $ (CPPFLAGS) $ (CFLAGS)
.
Файлы с одним объектом связаны с помощью $ (LD) $ (LDFLAGS) no $ (LOADLIBES) $ (LDLIBS)
, но в нашем случае это не сработает, потому что мы хотим связать несколько объектов файлы.
Переменные, используемые встроенными правилами
Встроенные правила используют набор стандартных переменных, которые позволяют вам указать информацию о локальной среде (например, где найти файлы ROOT include) без перезаписи всех правил. Наиболее вероятно, что нам будут интересны:
CC
- компилятор C для использования CXX
- компилятор C ++ для использования LD
- компоновщик использовать CFLAGS
- флаг компиляции для исходных файлов C CXXFLAGS
- флаги компиляции для исходных файлов 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
Обратите внимание, что
make
, то ls -A
вы увидите файл с именем .зависит
, который содержит вещи, похожие на строки зависимостей make Прочие материалы
Извещение об ошибках и исторические заметки
Язык ввода для Make чувствителен к пробелам. В частности, строки действий, следующие за зависимостями, должны начинаться с вкладки . Но ряд пробелов может выглядеть одинаково (и действительно, существуют редакторы, которые незаметно преобразуют табуляцию в пробелы или наоборот), в результате чего Make-файл выглядит правильно и по-прежнему не работает. Это было идентифицировано как ошибка на раннем этапе, но ( история гласит ) она не была исправлена, потому что уже было 10 пользователей.
(Это скопировано из вики-сообщения, которое я написал для аспирантов-физиков.)
Я всегда думал, что это легче изучить на подробном примере, поэтому вот что я думаю о make-файлах. Для каждого раздела у вас есть одна строка без отступа, в которой отображается имя раздела, за которым следуют зависимости. Зависимости могут быть либо другими разделами (которые будут запускаться перед текущим разделом), либо файлами (которые, если они обновлены, приведут к повторному запуску текущего раздела при следующем запуске make
).
Вот быстрый пример (имейте в виду, что я использую 4 пробела там, где я должен использовать вкладку, Stack Overflow не позволяет мне использовать вкладки):
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» удаляет «артефакты сборки», такие как файлы .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
.
Ваш Make-файл будет иметь одно или два правила зависимости в зависимости от того, компилируете ли вы и связываете с помощью одной команды или одной команды для компиляции и одной для связь.
Зависимости - это дерево правил, которое выглядит следующим образом (обратите внимание, что отступ должен быть TAB):
main_target : source1 source2 etc
command to build main_target from sources
source1 : dependents for source1
command to build source1
Там должна быть пустая строка после команд для target, и перед командами не должно быть , а не . Первая цель в make-файле - это общая цель, а другие цели создаются только в том случае, если от них зависит первая цель.
Итак, ваш make-файл будет выглядеть примерно так.
a3a.exe : a3driver.obj
link /out:a3a.exe a3driver.obj
a3driver.obj : a3driver.cpp
cc a3driver.cpp