Как сделать ПРОСТОЙ Make-файл C++

Мы обязаны использовать Make-файл для сплачивания всего для нашего проекта, но наш преподаватель никогда не показывал нам как.

У меня только есть один файл, a3driver.cpp. Драйвер импортирует класс из местоположения, "/user/cse232/Examples/example32.sequence.cpp".

Именно. Все остальное содержится с .cpp.

Как я пошел бы о создании простого Make-файла, который создает названный исполняемый файл a3a.exe?

285
задан Peter Mortensen 21 October 2019 в 19:31
поделиться

3 ответа

Поскольку это для Unix, исполняемые файлы не имеют расширений.

Следует отметить, что root-config - это утилита, которая предоставляет правильные флаги компиляции и связывания; и нужные библиотеки для создания приложений против root. Это всего лишь деталь, относящаяся к исходной аудитории этого документа.

Сделай меня младенцем

или Ты никогда не забудешь, когда тебя впервые сделали

Вводное обсуждение make и как написать простой make-файл

Что такое Make? И почему меня это должно волновать?

Инструмент под названием Make является диспетчером зависимостей сборки. То есть он заботится о том, чтобы знать, какие команды необходимо выполнить, в каком порядке взять ваш программный проект из коллекции исходных файлов, объектных файлов, библиотек, заголовков и т. Д. И т. Д. - некоторые из которых могли быть изменены недавно --- и превратив их в правильную актуальную версию программы.

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

Простой файл Makefile

Предположим, у вас есть каталог, содержащий: tool tool.cc tool.o support.cc support.hh и support.o , которые зависят от root и должны быть скомпилированы в программу под названием tool , и предположим, что вы взламывали исходные файлы ( что означает, что существующий инструмент устарел) и вы хотите скомпилировать программу.

Чтобы сделать это самостоятельно, вы можете

  1. проверить, является ли support.cc или support.hh новее, чем поддержка .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, что связанные команды (строки с отступом) должны выполняться, если какая-либо из зависимостей новее, чем цель.То есть строки зависимостей описывают логику того, что необходимо перестроить, чтобы учесть изменения в различных файлах. Если 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

, который легче печатать и гораздо удобнее читать.

Обратите внимание, что

  1. Мы все еще явно указываем зависимости для каждого объектного файла и конечного исполняемого файла
  2. Нам пришлось явно ввести правило компиляции для обоих исходных файлов

Неявные и шаблонные правила

Обычно мы ожидаем, что все исходные файлы C ++ должны обрабатываться одинаково, и Make предоставляет три способа заявить об этом:

  1. суффиксные правила (считаются устаревшими в GNU make, но сохранены для обратной совместимости)
  2. неявные правила
  3. ] правила шаблона

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

%.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

Обратите внимание, что

  1. Больше нет строк зависимостей для исходных файлов!?!
  2. Существует некоторая странная магия, связанная с .depend и зависимостью
  3. Если вы сделаете make , то ls -A вы увидите файл с именем .зависит , который содержит вещи, похожие на строки зависимостей make

Прочие материалы

Извещение об ошибках и исторические заметки

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

(Это скопировано из вики-сообщения, которое я написал для аспирантов-физиков.)

530
ответ дан 23 November 2019 в 01:50
поделиться

Я всегда думал, что это легче изучить на подробном примере, поэтому вот что я думаю о 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 .

54
ответ дан 23 November 2019 в 01:50
поделиться

Ваш 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
6
ответ дан 23 November 2019 в 01:50
поделиться
Другие вопросы по тегам:

Похожие вопросы: