Make-файлы с исходными файлами в разных каталогах


135

У меня есть проект, в котором структура каталогов такая:

                         $projectroot
                              |
              +---------------+----------------+
              |               |                |
            part1/          part2/           part3/
              |               |                |
       +------+-----+     +---+----+       +---+-----+
       |      |     |     |        |       |         |
     data/   src/  inc/  src/     inc/   src/       inc/

Как мне написать make-файл, который будет частично / src (или где-то еще), который может частично дополнять / связывать исходные файлы c / c ++? / Src?

Могу я сделать что-нибудь вроде -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Если это сработает, есть ли более простой способ сделать это. Я видел проекты, где в каждой соответствующей части есть make-файл? папки. [в этом посте я использовал знак вопроса, как в синтаксисе bash]



1
В исходном руководстве по GNU ( gnu.org/software/make/manual/html_node/Phony-Targets.html ) в разделе Phony Targets есть образец recursive invocation, который выглядит довольно элегантно.
Фрэнк Нок

Какой инструмент вы использовали для создания этого текстового изображения?
Халид Хуссейн

Ответы:


114

Традиционный способ - иметь Makefileв каждом подкаталоге ( part1, part2и т. Д.), Что позволяет создавать их независимо. Кроме того, создайте Makefileв корневом каталоге проекта, в котором все строится. «Корень» Makefileбудет выглядеть примерно так:

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

Поскольку каждая строка в цели make запускается в своей собственной оболочке, нет необходимости беспокоиться о поиске резервной копии дерева каталогов или других каталогов.

Я предлагаю взглянуть на раздел 5.7 руководства GNU make ; это очень полезно.


26
Это должно быть +$(MAKE) -C part1и т. Д. Это позволяет управлять работой Make в подкаталогах.
ephemient

26
Это классический подход, который широко используется, но он неоптимален по нескольким причинам, которые ухудшаются по мере роста проекта. У Дэйва Хинтона есть указатель, которому нужно следовать.
dmckee --- котенок экс-модератора

89

Если у вас есть код в одном подкаталоге, зависящий от кода в другом подкаталоге, вам, вероятно, будет лучше с одним make-файлом на верхнем уровне.

Полное обоснование см. В разделе Рекурсивное создание считается вредным , но в основном вы хотите, чтобы у make была полная информация, необходимая для принятия решения о том, нужно ли перестраивать файл, и у него не будет этого, если вы скажете ему только о трети ваш проект.

Похоже, что ссылка выше недоступна. Тот же документ доступен здесь:


3
Спасибо, не знал об этом. Очень полезно знать «правильный способ» делать что-то, а не способы, которые «просто работают» или считаются стандартными.
tjklemz

3
Рекурсивный make считался вредным, когда он действительно не работал. В наши дни это не считается вредным, фактически, это то, как autotools / automake управляют более крупными проектами.
Эдвин Бак

36

Может пригодиться опция VPATH, которая сообщает программе make, в каких каталогах искать исходный код. Однако вам все равно понадобится опция -I для каждого пути включения. Пример:

CXXFLAGS=-Ipart1/inc -Ipart2/inc -Ipart3/inc
VPATH=part1/src:part2/src:part3/src

OutputExecutable: part1api.o part2api.o part3api.o

Это автоматически найдет соответствующие файлы partXapi.cpp в любом из указанных каталогов VPATH и скомпилирует их. Однако это более полезно, когда ваш каталог src разбит на подкаталоги. Для того, что вы описываете, как говорили другие, вам, вероятно, лучше иметь файл makefile для каждой части, особенно если каждая часть может стоять отдельно.


2
Не могу поверить, что этот простой идеальный ответ не набрал больше голосов. Это +1 от меня.
Николас Гамильтон,

2
У меня было несколько общих исходных файлов в более высоком каталоге для нескольких разрозненных проектов в подпапках, VPATH=..у меня сработало!
EkriirkE 02

23

Вы можете добавить правила в свой корневой Makefile, чтобы скомпилировать необходимые файлы cpp в других каталогах. Приведенный ниже пример Makefile должен стать хорошим началом для того, чтобы привести вас туда, где вы хотите быть.

CC = G ++
TARGET = cppTest
OTHERDIR = .. / .. / someotherpath / в / проект / ЦСИ

ИСТОЧНИК = cppTest.cpp
ИСТОЧНИК = $ (OTHERDIR) /file.cpp

## Определение конечных источников
ВКЛЮЧИТЬ = -I./ $ (AN_INCLUDE_DIR)  
ВКЛЮЧИТЬ = -I. $ (OTHERDIR) /../ inc
## end more включает

VPATH = $ (OTHERDIR)
OBJ = $ (join $ (добавляет суффикс ../obj/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .o))) 

## Исправить назначение зависимости, чтобы оно было ../.dep относительно каталога src
DEPENDS = $ (join $ (добавляет суффикс ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Правило по умолчанию выполнено
все: $ (TARGET)
        @правда

## Чистое правило
чистый:
        @ -rm -f $ (ЦЕЛЬ) $ (OBJ) $ (ЗАВИСИТ)


## Правило создания фактической цели
$ (ЦЕЛЬ): $ (OBJ)
        @echo "============="
        @echo "Связывание цели $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Ссылка завершена -

## Общее правило компиляции
% .o:% .cpp
        @mkdir -p $ (директория $ @)
        @echo "============="
        @echo "Компиляция $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Правила для объектных файлов из файлов cpp
## Объектный файл для каждого файла помещается в каталог obj
## на один уровень выше фактического исходного каталога.
../obj/%.o:% .cpp
        @mkdir -p $ (директория $ @)
        @echo "============="
        @echo "Компиляция $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Правило для "другого каталога" Вам понадобится один для "другого" каталога
$ (OTHERDIR) /../ obj /%. O:% .cpp
        @mkdir -p $ (директория $ @)
        @echo "============="
        @echo "Компиляция $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Создание правил зависимости
../.dep/%.d:% .cpp
        @mkdir -p $ (директория $ @)
        @echo "============="
        @echo Создание файла зависимостей для $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Правило зависимости для "прочего" каталога
$ (OTHERDIR) /../. Dep /%. D:% .cpp
        @mkdir -p $ (директория $ @)
        @echo "============="
        @echo Создание файла зависимостей для $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (OTHERDIR) /../ obj / $ *. o ^"> $ @ '

## Включите файлы зависимостей
-include $ (ЗАВИСИТ)


7
Я знаю, что это уже довольно давно, но разве SOURCE и INCLUDE не перезаписаны другим назначением на одну строку позже?
skelliam

@skelliam да, это так.
наброян 06

1
Такой подход нарушает принцип DRY. Дублировать код для каждого «другого каталога»
Jesus H

20

Если источники распределены во многих папках и имеет смысл иметь отдельные файлы Makefile, то, как предлагалось ранее, рекурсивный make - хороший подход, но для небольших проектов мне легче перечислить все исходные файлы в Makefile с их относительным путем. в Makefile вот так:

# common sources
COMMON_SRC := ./main.cpp \
              ../src1/somefile.cpp \
              ../src1/somefile2.cpp \
              ../src2/somefile3.cpp \

Затем я могу установить VPATHэто так:

VPATH := ../src1:../src2

Затем строю объекты:

COMMON_OBJS := $(patsubst %.cpp, $(ObjDir)/%$(ARCH)$(DEBUG).o, $(notdir $(COMMON_SRC)))

Теперь правило простое:

# the "common" object files
$(ObjDir)/%$(ARCH)$(DEBUG).o : %.cpp Makefile
    @echo creating $@ ...
    $(CXX) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<

А создание вывода еще проще:

# This will make the cbsdk shared library
$(BinDir)/$(OUTPUTBIN): $(COMMON_OBJS)
    @echo building output ...
    $(CXX) -o $(BinDir)/$(OUTPUTBIN) $(COMMON_OBJS) $(LFLAGS)

Можно даже VPATHавтоматизировать генерацию:

VPATH := $(dir $(COMMON_SRC))

Или используя тот факт, что sortудаляет дубликаты (хотя это не должно иметь значения):

VPATH := $(sort  $(dir $(COMMON_SRC)))

это отлично подходит для небольших проектов, где вы просто хотите добавить несколько пользовательских библиотек и поместить их в отдельную папку. Большое спасибо
zitroneneis 08

6

Я думаю, что лучше отметить, что использование Make (рекурсивного или нет) - это то, чего обычно вы можете избегать, потому что по сравнению с современными инструментами его сложно изучать, поддерживать и масштабировать.

Это замечательный инструмент, но в 2010+ его следует считать устаревшим.

Если, конечно, вы не работаете в особой среде, например, с унаследованным проектом и т. Д.

Используйте IDE, CMake или, если у вас жесткий ядро, Autotools .

(отредактировано из-за отрицательных голосов, Тай Хонза за указание)


1
Было бы неплохо получить разъяснения по поводу отрицательных голосов. Раньше я тоже делал свои Makefile.
IlDan

2
Я поддерживаю предложение "не делайте этого" - KDevelop имеет очень графический интерфейс для настройки Make и других систем сборки, и хотя я сам пишу Makefile, KDevelop - это то, что я даю своим коллегам по работе, но я не думаю, что ссылка Autotools помогает. Чтобы понять Autotools, вам нужно понимать m4, libtool, automake, autoconf, shell, make и, по сути, весь стек.
ephemient

7
Вероятно, отрицательные голоса вызваны тем, что вы отвечаете на свой вопрос. Вопрос не в том, следует ли использовать Makefile. Речь шла о том, как их писать.
Honza

8
Было бы неплохо, если бы вы могли в нескольких предложениях объяснить, как современные инструменты облегчают жизнь, чем написание Makefile. Обеспечивают ли они абстракцию более высокого уровня? или что?
Абхишек Ананд

1
xterm, vi, bash, makefile == правят миром, cmake для людей, которые не умеют взламывать код.
μολὼν.λαβέ

2

Сообщение RC было СУПЕР полезным. Я никогда не думал об использовании функции $ (dir $ @), но она сделала именно то, что мне нужно.

В parentDir создайте несколько каталогов с исходными файлами: dirA, dirB, dirC. Различные файлы зависят от объектных файлов в других каталогах, поэтому я хотел иметь возможность создать один файл из одного каталога и сделать так, чтобы он создавал эту зависимость, вызывая make-файл, связанный с этой зависимостью.

По сути, я сделал один Makefile в parentDir, который имел (среди прочего) общее правило, подобное RC:

%.o : %.cpp
        @mkdir -p $(dir $@)
        @echo "============="
        @echo "Compiling $<"
        @$(CC) $(CFLAGS) -c $< -o $@

Каждый подкаталог включал этот make-файл верхнего уровня, чтобы унаследовать это общее правило. В Makefile каждого подкаталога я написал собственное правило для каждого файла, чтобы я мог отслеживать все, от чего зависит каждый отдельный файл.

Когда мне нужно было создать файл, я использовал (по сути) это правило для рекурсивного создания любых / всех зависимостей. Отлично!

ПРИМЕЧАНИЕ: есть утилита под названием «makepp», которая, кажется, выполняет эту задачу даже более интуитивно, но ради переносимости и не в зависимости от другого инструмента я решил сделать это таким образом.

Надеюсь это поможет!


2

Рекурсивное использование Make

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

Это позволяет makeразделить на задания и использовать несколько ядер.


2
Как это лучше, чем делать что-то подобное make -j4?
devin

1
@devin, на мой взгляд, не лучше , просто позволяет make вообще использовать контроль заданий. При запуске отдельного процесса make он не подлежит контролю заданий.
Михаил Панков

1

Я искал что-то подобное, и после некоторых попыток и неудач я создал свой собственный make-файл, я знаю, что это не «идиоматический способ», но это начало понимания make, и это работает для меня, может, вы могли бы попробовать в своем проекте.

PROJ_NAME=mono

CPP_FILES=$(shell find . -name "*.cpp")

S_OBJ=$(patsubst %.cpp, %.o, $(CPP_FILES))

CXXFLAGS=-c \
         -g \
        -Wall

all: $(PROJ_NAME)
    @echo Running application
    @echo
    @./$(PROJ_NAME)

$(PROJ_NAME): $(S_OBJ)
    @echo Linking objects...
    @g++ -o $@ $^

%.o: %.cpp %.h
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS) -o $@

main.o: main.cpp
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS)

clean:
    @echo Removing secondary things
    @rm -r -f objects $(S_OBJ) $(PROJ_NAME)
    @echo Done!

Я знаю, что это просто, и для некоторых людей мои флаги неверны, но, как я уже сказал, это мой первый Makefile, который скомпилирует мой проект в нескольких каталогах и связывает все вместе, чтобы создать мою корзину.

Принимаю предложения: D


-1

Предлагаю использовать autotools:

//## Поместите сгенерированные объектные файлы (.o) в тот же каталог, что и их исходные файлы, чтобы избежать конфликтов при использовании нерекурсивного make.

AUTOMAKE_OPTIONS = subdir-objects

просто включив его Makefile.amс другими довольно простыми вещами.

Вот учебник .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.