Создавать каталоги с помощью make файла


101

Я новичок в make-файлах и хочу создавать каталоги с помощью make-файлов. Мой каталог проекта выглядит так

+--Project  
   +--output  
   +--source  
     +Testfile.cpp  
   +Makefile  

Я хочу поместить все объекты и вывод в соответствующую папку вывода. Я хочу создать структуру папок, которая была бы такой после компиляции.

+--Project
   +--output
     +--debug (or release)
       +--objs
         +Testfile.o
       +Testfile (my executable file)
   +--source
     +Testfile.cpp
   +Makefile

Я пробовал с несколькими вариантами, но безуспешно. Пожалуйста, помогите мне создать каталоги с помощью make file. Я отправляю на ваше рассмотрение свой Makefile.

#---------------------------------------------------------------------
# Input dirs, names, files
#---------------------------------------------------------------------
OUTPUT_ROOT := output/

TITLE_NAME := TestProj 

ifdef DEBUG 
    TITLE_NAME += _DEBUG
else
ifdef RELEASE
    TITLE_NAME += _RELEASE
endif
endif


# Include all the source files here with the directory tree
SOURCES := \
        source/TestFile.cpp \

#---------------------------------------------------------------------
# configs
#---------------------------------------------------------------------
ifdef DEBUG
OUT_DIR     := $(OUTPUT_ROOT)debug
CC_FLAGS    := -c -Wall
else
ifdef RELEASE
OUT_DIR     := $(OUTPUT_ROOT)release
CC_FLAGS    := -c -Wall
else
$(error no build type defined)
endif
endif

# Put objects in the output directory.
OUT_O_DIR   := $(OUT_DIR)/objs

#---------------------------------------------------------------------
# settings
#---------------------------------------------------------------------
OBJS = $(SOURCES:.cpp=.o)
DIRS = $(subst /,/,$(sort $(dir $(OBJS))))
DIR_TARGET = $(OUT_DIR)

OUTPUT_TARGET = $(OUT_DIR)/$(TITLE_NAME)

CC_FLAGS +=   

LCF_FLAGS := 

LD_FLAGS := 

#---------------------------------------------------------------------
# executables
#---------------------------------------------------------------------
MD := mkdir
RM := rm
CC := g++

#---------------------------------------------------------------------
# rules
#---------------------------------------------------------------------
.PHONY: all clean title 

all: title 

clean:
    $(RM) -rf $(OUT_DIR)

$(DIR_TARGET):
    $(MD) -p $(DIRS)

.cpp.o: 
    @$(CC) -c $< -o $@

$(OBJS): $(OUT_O_DIR)/%.o: %.cpp
    @$(CC) -c $< -o $@

title: $(DIR_TARGET) $(OBJS)

Заранее спасибо. Пожалуйста, помогите мне, если я тоже допустил ошибки.

Ответы:


89

Это сделало бы это - при условии, что среда похожа на Unix.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

${OUT_DIR}:
        ${MKDIR_P} ${OUT_DIR}

Это должно быть выполнено в каталоге верхнего уровня - или определение $ {OUT_DIR} должно быть правильным относительно того, где он запускается. Конечно, если вы будете следовать указаниям статьи Питера Миллера « Рекурсивное создание вредоносных программ », то вы все равно будете запускать make в каталоге верхнего уровня.

Я сейчас играю с этим (RMCH). Потребовалась небольшая адаптация к набору программного обеспечения, которое я использую в качестве тестовой площадки. В пакете есть дюжина отдельных программ, созданных с исходным кодом в 15 каталогах, некоторые из которых являются общими. Но с небольшой осторожностью это можно сделать. OTOH, это может не подойти новичку.


Как отмечено в комментариях, указывать команду «mkdir» как действие для «каталогов» неверно. Как также отмечалось в комментариях, есть другие способы исправить возникающую ошибку «не знаю, как сделать вывод / отладку». Один из них - удалить зависимость от строки «каталоги». Это работает, потому что mkdir -p не генерирует ошибок, если все каталоги, которые его просят создать, уже существуют. Другой - показанный механизм, который попытается создать каталог, только если он не существует. Версия «с поправками» - это то, что я имел в виду вчера вечером, но оба метода работают (и у обоих есть проблемы, если вывод / отладка существует, но является файлом, а не каталогом).


Спасибо, Джонатан. когда я попытался это сделать, я получил ошибку «make: *** Нет правила для создания целевых output/debug', needed by каталогов. Остановить». Но сейчас я не буду об этом беспокоиться. буду придерживаться основных правил. :). Спасибо за руководство. И я запускаю "make" только из каталога верхнего уровня.
— Jabez

Просто удалите $ {OUT_DIR} за каталогами :, тогда он должен работать.
— Док Браун,

Реализация этого требует, чтобы вы уловили все возможные случаи, когда вы используете каталоги из командной строки. Кроме того, вы не можете сделать какие-либо правила сборки файлов зависимыми, directoriesне заставляя их всегда перестраивать ..
— mtalexan

@mtalexan Вы предоставили пару комментариев, объясняющих, что не так с некоторыми из этих ответов, но вы не предложили альтернативного ответа. С нетерпением жду вашего решения этой проблемы.
— Самуэль

@Samuel Я указал на проблемы, не предложив решения, потому что я искал то же самое и так и не нашел решения. В итоге я просто столкнулся с падением неидеального решения.
— mtalexan

135

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

Если вы нацеливаетесь на обычный или «шаблонный» файл, просто используйте makeвнутреннюю переменную $(@D), которая означает «каталог, в котором находится текущая цель» (cmp. With $@для цели). Например,

$(OUT_O_DIR)/%.o: %.cpp
        @mkdir -p $(@D)
        @$(CC) -c $< -o $@

title: $(OBJS)

Затем вы фактически делаете то же самое: создаете каталоги для всех $(OBJS), но вы будете делать это менее сложным способом.

Одна и та же политика (файлы - цели, каталоги - никогда) используется в различных приложениях. Например, gitсистема контроля версий не хранит каталоги.


Примечание. Если вы собираетесь его использовать, может быть полезно ввести вспомогательную переменную и использовать makeправила расширения.

dir_guard=@mkdir -p $(@D)

$(OUT_O_DIR)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -c $< -o $@

$(OUT_O_DIR_DEBUG)/%.o: %.cpp
        $(dir_guard)
        @$(CC) -g -c $< -o $@

title: $(OBJS)

12
Хотя связывание требований к каталогу с файлами, на мой взгляд, является лучшим вариантом, ваше решение также имеет существенный недостаток, заключающийся в том, что процесс mkdir будет вызываться файлом make для каждого отдельного файла, который будет перестроен, для большинства из которых не нужно делать каталог снова. При адаптации к системам сборки, отличным от Linux, таким как Windows, он фактически вызывает как неблокируемый вывод ошибки, поскольку нет эквивалента -p команде mkdir, так и, что более важно, гигантские накладные расходы, поскольку вызов оболочки не является минимально инвазивным.
— mtalexan

1
Вместо прямого вызова mkdir я сделал следующее, чтобы не пытаться создать каталог, если он уже существует: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
— Brady

26

Или ПОЦЕЛУЙ.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Это создаст все каталоги после анализа Makefile.


3
Мне нравится этот подход, потому что мне не нужно загромождать каждую цель командами для обработки каталогов.
— Кен Уильямс

1
Мне просто нужно было создать каталог, если он не существует. Этот ответ идеально подходит для моей проблемы.
— Джефф Пал

Мало того, это предотвращает запуск измененных временных меток каждого каталога ненужным этапом сборки. Это должен быть ответ
— Assimilater

6
Это лучше: $(info $(shell mkdir -p $(DIRS)))без символа $(info ...)вывод mkdirкоманды будет вставлен в Makefile , что в лучшем случае приведет к синтаксическим ошибкам. В $(info ...)гарантирует вызов , что а) ошибки (если таковые имеются) видны пользователю, и б) , что расширяется вызова функции ни к чему.
— cmaster - восстановить монику на работе

10

makein и off себя обрабатывает целевые объекты каталогов точно так же, как целевые файлы. Итак, легко написать такие правила:

outDir/someTarget: Makefile outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

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

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget
$ make
touch outDir/someTarget

Это определенно не то, что вам нужно. Всякий раз, когда вы касаетесь файла, вы также касаетесь каталога. И поскольку файл зависит от каталога, он, следовательно, кажется устаревшим, что вынуждает его перестроить.

Однако вы можете легко разорвать этот цикл, указав программе make игнорировать временную метку каталога . Это делается путем объявления каталога как предварительного сайта только для заказа:

# The pipe symbol tells make that the following prerequisites are order-only
#                           |
#                           v
outDir/someTarget: Makefile | outDir
    touch outDir/someTarget

outDir:
    mkdir -p outDir

Это правильно дает:

$ make
mkdir -p outDir
touch outDir/someTarget
$ make
make: 'outDir/someTarget' is up to date.

TL; DR:

Напишите правило для создания каталога:

$(OUT_DIR):
    mkdir -p $(OUT_DIR)

И цели для содержимого внутри зависят только от порядка каталогов:

$(OUT_DIR)/someTarget: ... | $(OUT_DIR)

На какой сломанной OS / FS вы видели touchизменение каких-либо статистических данных в родительском каталоге? Для меня это не имеет смысла. Время mtime каталога зависит только от содержащихся в нем имен файлов. Я не смог воспроизвести вашу проблему.
— Johan

@ JohanBoulé Debian.
— cmaster - восстановить

А вы за такое неработающее поведение залили баг?
— Johan

6

Все решения, включая принятое, имеют некоторые проблемы, указанные в соответствующих комментариях. Общепринятый ответ на @ Джонатан Леффлера уже довольно хорошо , но не принимает в том , что предпосылки не обязательно должны быть построены в порядке ( в течение make -j, например). Однако простое перемещение directoriesпредпосылки с allна programвызывает перестройку AFAICT при каждом запуске. Следующее решение не имеет этой проблемы, и AFAICS работает по назначению.

MKDIR_P := mkdir -p
OUT_DIR := build

.PHONY: directories all clean

all: $(OUT_DIR)/program

directories: $(OUT_DIR)

$(OUT_DIR):
    ${MKDIR_P} $(OUT_DIR)

$(OUT_DIR)/program: | directories
    touch $(OUT_DIR)/program

clean:
    rm -rf $(OUT_DIR)

4

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

define depend_on_dir
$(1): | $(dir $(1))

ifndef $(dir $(1))_DIRECTORY_RULE_IS_DEFINED
$(dir $(1)):
    mkdir -p $$@

$(dir $(1))_DIRECTORY_RULE_IS_DEFINED := 1
endif
endef

$(foreach file,$(ALL_TARGET_FILES),$(eval $(call depend_on_dir,$(file))))

Вот как это работает. Я определяю функцию, depend_on_dirкоторая принимает имя файла и генерирует правило, которое заставляет файл зависеть от каталога, в котором он находится, а затем определяет правило для создания этого каталога при необходимости. Затем я использую foreachдля callэтой функции на каждом имени файла и evalрезультата.

Обратите внимание, что вам понадобится поддерживающая версия GNU make eval, я думаю, это версии 3.81 и выше.


Создание переменной, содержащей «имя каждого файла, который будет создавать ваш make-файл», является довольно обременительным требованием - мне нравится определять цели верхнего уровня, затем то, от чего они зависят, и так далее. Наличие плоского списка всех целевых файлов противоречит иерархической природе спецификации make-файла и может быть (легко) невозможным, если целевые файлы зависят от вычислений во время выполнения.
— Кен Уильямс

3

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

Тем не менее, один из способов создания каталога, отличного от исходного каталога, - VPATH ; я предпочитаю шаблонные правила


3

Независимость от ОС для меня критична, поэтому mkdir -pэто не вариант. Я создал эту серию функций, которые используются evalдля создания целевых каталогов с предварительным условием для родительского каталога. Это имеет то преимущество, что make -j 2будет работать без проблем, поскольку зависимости определены правильно.

# convenience function for getting parent directory, will eventually return ./
#     $(call get_parent_dir,somewhere/on/earth/) -> somewhere/on/
get_parent_dir=$(dir $(patsubst %/,%,$1))

# function to create directory targets.
# All directories have order-only-prerequisites on their parent directories
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types
TARGET_DIRS:=
define make_dirs_recursively
TARGET_DIRS+=$1
$1: | $(if $(subst ./,,$(call get_parent_dir,$1)),$(call get_parent_dir,$1))
    mkdir $1
endef

# function to recursively get all directories 
#     $(call get_all_dirs,things/and/places/) -> things/ things/and/ things/and/places/
#     $(call get_all_dirs,things/and/places) -> things/ things/and/
get_all_dirs=$(if $(subst ./,,$(dir $1)),$(call get_all_dirs,$(call get_parent_dir,$1)) $1)

# function to turn all targets into directories
#     $(call get_all_target_dirs,obj/a.o obj/three/b.o) -> obj/ obj/three/
get_all_target_dirs=$(sort $(foreach target,$1,$(call get_all_dirs,$(dir $(target)))))

# create target dirs
create_dirs=$(foreach dirname,$(call get_all_target_dirs,$1),$(eval $(call make_dirs_recursively,$(dirname))))

TARGETS := w/h/a/t/e/v/e/r/things.dat w/h/a/t/things.dat

all: $(TARGETS)

# this must be placed after your .DEFAULT_GOAL, or you can manually state what it is
# https://www.gnu.org/software/make/manual/html_node/Special-Variables.html
$(call create_dirs,$(TARGETS))

# $(TARGET_DIRS) needs to be an order-only-prerequisite
w/h/a/t/e/v/e/r/things.dat: w/h/a/t/things.dat | $(TARGET_DIRS)
    echo whatever happens > $@

w/h/a/t/things.dat: | $(TARGET_DIRS)
    echo whatever happens > $@

Например, выполнение вышеуказанного приведет к созданию:

$ make
mkdir w/
mkdir w/h/
mkdir w/h/a/
mkdir w/h/a/t/
mkdir w/h/a/t/e/
mkdir w/h/a/t/e/v/
mkdir w/h/a/t/e/v/e/
mkdir w/h/a/t/e/v/e/r/
echo whatever happens > w/h/a/t/things.dat
echo whatever happens > w/h/a/t/e/v/e/r/things.dat

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