Создавать каталоги с помощью 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)

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

Jabez
источник

Ответы:

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)
cmaster - восстановить монику
источник
На какой сломанной 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)
Stefanct
источник
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
SimplyKnownAsG
источник