Создавайте каталоги используя make file - PullRequest
84 голосов
/ 23 декабря 2009

Я очень новичок в makefiles, и я хочу создавать каталоги, используя makefile. Мой каталог проектов выглядит так

+--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)

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

Ответы [ 9 ]

124 голосов
/ 23 декабря 2009

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

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

$(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)
74 голосов
/ 23 декабря 2009

Это будет сделано - при условии Unix-подобной среды.

MKDIR_P = mkdir -p

.PHONY: directories

all: directories program

directories: ${OUT_DIR}

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

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

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


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

18 голосов
/ 12 июля 2017

Или, ПОЦЕЛУЙ.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

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

7 голосов
/ 28 июня 2018

make внутри и вне себя обрабатывает цели каталога точно так же, как цели файлов. Таким образом, легко написать такие правила:

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 голосов
/ 14 декабря 2016

Все решения, включая принятые, имеют некоторые проблемы, как указано в их комментариях. принятый ответ @ 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)
4 голосов
/ 22 октября 2010

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

3 голосов
/ 15 декабря 2016

Независимость от ОС имеет решающее значение для меня, поэтому 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
3 голосов
/ 23 декабря 2009

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

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

0 голосов
/ 22 апреля 2019

См. 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...