Как сделать SIMPLE C ++ Makefile? - PullRequest
267 голосов
/ 20 марта 2010

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

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

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

Как мне создать простой Makefile, который создает исполняемый файл с именем a3a.exe?

Ответы [ 7 ]

503 голосов
/ 20 марта 2010

Скопировано из вики-поста, который я написал для аспирантов по физике.

Так как это для unix, исполняемые файлы не имеют расширений.

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

Make Me Baby

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

Вводное обсуждение 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, что соответствующие команды (строки с отступом) должны выполняться, если какая-либо из зависимостей новее, чем цель. То есть строки зависимости описывают логику того, что нужно перестраивать, чтобы приспособить изменения в различных файлах. Если 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 $(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) n.o $(LOADLIBES) $(LDLIBS), но в нашем случае это не сработает, потому что мы хотим связать несколько объектных файлов.

Переменные, используемые встроенными правилами

Встроенные правила используют набор стандартных переменных, которые позволяют вам указывать информацию о локальной среде (например, где искать включаемые файлы ROOT) без переписывания всех правил. Наиболее вероятными для нас являются:

  • 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 вы увидите файл с именем .depend, который содержит вещи, которые выглядят как линии создания зависимости

Другое чтение

Знайте ошибки и исторические заметки

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

50 голосов
/ 20 марта 2010

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

32 голосов
/ 23 февраля 2015

Почему всем нравится перечислять исходные файлы? Об этом может позаботиться простая команда поиска.

Вот пример простого 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
12 голосов
/ 16 апреля 2014

Старый вопрос, я знаю, но для потомков. У вас было два варианта.

Вариант 1: самый простой make-файл = НЕТ 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

Вуаля.

6 голосов
/ 20 марта 2010

Ваш файл 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
5 голосов
/ 20 апреля 2016

Я предлагаю:

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 rule:

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
5 голосов
/ 22 августа 2015

Я использовал ответ фридмада. Я изучил это некоторое время, и, похоже, это хороший способ начать. Это решение также имеет четко определенный метод добавления флагов компилятора. Я ответил снова, потому что я внес изменения, чтобы он работал в моей среде, 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 ++. Эта конфигурация решила эту проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...