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

Я очень новичок в 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. Я отправляю свой 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 [email protected]

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

title: $(DIR_TARGET) $(OBJS)

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

Ответ 1

Это сделало бы это - предполагая 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' в качестве действия для 'directoryies' неправильно. Как также отмечено в комментариях, есть и другие способы исправить полученную ошибку "не знаю, как сделать вывод/отладку". Один из них - удалить зависимость от строки 'directoryies'. Это работает, потому что "mkdir -p" не генерирует ошибки, если все каталоги, которые ему предлагается создать, уже существуют. Другой показан механизм, который будет пытаться создать каталог, только если он не существует. Версия с исправлениями - это то, что я имел в виду прошлой ночью - но оба метода работают (и у обоих есть проблемы, если вывод/отладка существует, но это файл, а не каталог).

Ответ 2

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

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

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

title: $(OBJS)

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

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


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

[email protected] -p $(@D)

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

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

title: $(OBJS)

Ответ 3

Или, KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

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

Ответ 4

Все решения, в том числе принятые, имеют некоторые проблемы, как указано в их соответствующих комментариях. принятый ответ @jonathan-leffler уже неплохой, но не предполагает, что предпосылки не обязательно должны быть построены в порядке (например, в 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)

Ответ 5

make in и 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)

Ответ 6

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

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

ifndef $(dir $(1))_DIRECTORY_RULE_IS_DEFINED
$(dir $(1)):
    mkdir -p [email protected]

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

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

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

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

Ответ 7

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

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

Ответ 8

Независимость ОС важна для меня, поэтому 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 > [email protected]

w/h/a/t/things.dat: | $(TARGET_DIRS)
    echo whatever happens > [email protected]

Например, при запуске выше создайте:

$ 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

Ответ 9

См. Https://www.oreilly.com/library/view/managing-projects-with/0596006101/ch12.html.

REQUIRED_DIRS = ...
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
             do                               \
               [[ -d $$d ]] || mkdir -p $$d;  \
             done)

$(objects) : $(sources)

Поскольку я использую Ubuntu, мне также нужно было добавить это вверху моего Makefile:

SHELL := /bin/bash # Use bash syntax