Створіть каталоги за допомогою файлу make


101

Я дуже новачок в 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. Я розміщую свій 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)

Заздалегідь спасибі. Підкажіть, будь ласка, чи я теж помилявся.

Відповіді:


88

Це зробить це - припускаючи, що схоже на 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" лише з каталогу топлевелів.
Джабез

Просто видаліть $ {OUT_DIR} за каталогами :, тоді він повинен працювати.
Док Браун

Реалізуючи це, потрібно зафіксувати кожен можливий випадок, коли ви використовуєте каталоги з командного рядка. Крім того, ви не можете зробити жоден файл, що генерує правила складання, залежно від того, щоб не змушувати directoriesїх завжди перебудовувати ..
mtalexan

@mtalexan Ви надали пару коментарів, де пояснюється, що не так у деяких із цих відповідей, але ви не запропонували альтернативної відповіді. Хочеться почути ваше рішення для цієї проблеми.
Самуїл

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

135

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

Якщо ви орієнтуєтесь на звичайний або "візерунковий" файл, просто використовуйте 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)

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

1
Замість того, щоб безпосередньо викликати mkdir, я зробив наступне, щоб уникнути спроб створити каталог, якщо він уже існує: $ (shell [! -D $ (@ D)] && mkdir -p $ (@ D))
Брейді

26

Або, KISS.

DIRS=build build/bins

... 

$(shell mkdir -p $(DIRS))

Це створить усі каталоги після розбору Makefile.


3
Мені подобається такий підхід, тому що мені не потрібно захаращувати кожну ціль командами для обробки каталогів.
Кен Вільямс

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

Мало того, це заважає зміненим часовим позначкам кожного каталогу не викликати зайвого кроку збирання. Це має бути відповідь
асимілятор

6
Це краще: $(info $(shell mkdir -p $(DIRS)))без цього $(info ...)вихідна mkdirкоманда буде вставлена ​​в Makefile , що приводить до синтаксичних помилок у кращому випадку. У $(info ...)гарантує виклик , що а) помилки (якщо такі є) видно користувачеві, і б) , що розширюється виклику функції ні до чого.
cmaster - відновити моніку

10

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)

На якому зламаній ОС / FS ви бачили touchзміни будь-яких статистичних даних у батьківському каталозі? Це для мене не має сенсу. Час роботи dir залежить лише від файлів, які він містить. Я не міг відтворити вашу проблему.
Йохан Буле

@ JohanBoulé Debian.
cmaster - відновити

А ти заповнив помилку за таку порушену поведінку?
Йохан Буле

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яка містить ім'я кожного файлу, з якого буде складено ваш makefile. Потім використовуйте наступний код:

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 і вище.


Створення змінної, яка містить "ім'я файлу кожного файлу, з якого буде створений ваш makefile", є своєрідною вимогою. Мені подобається визначати свої цілі верхнього рівня, то те, від чого вони залежать, і так далі. Наявність плоского списку всіх цільових файлів суперечить ієрархічному характеру специфікації makefile і може не бути (легко) можливим, коли цільові файли залежать від обчислень часу виконання.
Кен Вільямс

3

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

з цього приводу, одним із способів побудови в каталозі, відмінному від вихідного каталогу, є 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.