LINUX.ORG.RU

Предварительный этап в Makefile

 ,


1

1

Приветствую.

Есть следующая структура каталогов:

.
├── interrupts
│   ├── handlers
│   │   ├── file.a65
│   │   └── Makefile // Вложенный
│   └── Makefile // Вложенный
├── defines.mk
├── common.mk
└── Makefile // Корневой

В первую очередь, файл defines.mk:

PROJECT_NAME = xxx

PROG_AS ?= ca65
PROG_LD ?= ld65
PROG_DOX ?= doxygen
PROG_RM ?= rm -f
PROG_MV ?= mv -f
PROG_MKDIR ?= mkdir -p
PROG_FIND ?= find
PROG_MAKE ?= make

DIR_BUILD ?= build

Файл common.mk

TARGET_SUBMAKE = $(shell \
	$(PROG_FIND) \
	. \
	-mindepth 2 \
	-type f \
	-name Makefile \
	-printf '%h\n' \
)

TARGET_SRC = $(shell \
	$(PROG_FIND) \
	. \
	-maxdepth 1 \
	-type f \
	-name '*.a65' \
	-printf '%f\n' \
)

TARGET_OBJ = $(patsubst \
	%.a65, \
	$(DIR_BUILD)/$(PROJECT_NAME)/%.o, \
	$(TARGET_SRC) \
)

.PHONY: all $(PROJECT_NAME) $(TARGET_SUBMAKE)

$(DIR_BUILD)/$(PROJECT_NAME)/%.o : %.a65
	$(PROG_MKDIR) \
	    $(DIR_BUILD)/$(PROJECT_NAME)
	$(PROG_AS) \
	    -o $@ \
	    $<

$(TARGET_SUBMAKE):
	$(PROG_MAKE) \
	    -C $@ \
	    MAKEFILE_DEF=$(MAKEFILE_DEF) \
	    MAKEFILE_COMMON=$(MAKEFILE_COMMON) \
	    DIR_BUILD=$(DIR_BUILD)

$(PROJECT_NAME): $(TARGET_SUBMAKE) $(TARGET_OBJ)

all: $(PROJECT_NAME)

Корневой Makefile

MAKEFILE_DEF    := $(CURDIR)/defines.mk
MAKEFILE_COMMON := $(CURDIR)/common.mk

include $(MAKEFILE_DEF)
include $(MAKEFILE_COMMON)

.PHONY: doc clean

all: doc

doc:
	$(PROG_DOX) Doxygen

clean:
	$(PROG_RM) $(DIR_BUILD)/$(PROJECT)/*.o

Вложенные Makefile (все одинаковые)

include $(MAKEFILE_DEF)
include $(MAKEFILE_COMMON)

Итак, в чём вопросы:

1

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

$(DIR_BUILD)/$(PROJECT_NAME)/%.o : %.a65
	$(PROG_MKDIR) \
	    $(DIR_BUILD)/$(PROJECT_NAME)
	...

). Что я пытался сделать: а) Вынести mkdir в отдельный сегмент и вызывать как $(DIR_BUILD)/$(PROJECT_NAME)/%.o : %.a65 | build-dir
б) Вынести mkdir в отдельный сегмент и вызывать тут: $(PROJECT_NAME): build-dir $(TARGET_SUBMAKE) $(TARGET_OBJ)

Оба варианта приводят к тому, что при вызове корневого make он просто создаёт директорию и даже не пытается заходить в дочерние Makefile.

2

Приветствуется общая критика. Я не эксперт в make, соответственно буду рад услышать, что я делаю не так.

P.S. Сразу уточню насчёт build директории. «Корневой» makefile на самом деле тоже не корневой и его вызывают выше с

DIR_BUILD := $(CURDIR)/build

make -C code/xxx DIR_BUILD=$(DIR_BUILD)
★★★★★

Я так сделал (я не предлагаю это копипастить если что, это просто пример предварительного этапа)

mkdirs:
        if [ -n "$(BINDIR)" ]; then mkdir -p "$(BINDIR)"; fi
        if [ -n "$(OBJDIR)" ]; then mkdir -p "$(OBJDIR)"; fi

# for bsd make (will be ignored by gnu make)
.BEGIN: mkdirs

# for gnu make (will create empty vars on bsd make)
dummyvar1:=$(shell if [ -n "$(BINDIR)" ]; then mkdir -p "$(BINDIR)"; fi)
dummyvar2:=$(shell if [ -n "$(OBJDIR)" ]; then mkdir -p "$(OBJDIR)"; fi)

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от wandrien

Не понимаю, как мне это адаптировать у себя:

  • $(PROJECT_NAME): описана в common
  • В родительском описанные правила конкатенируются справа к тем, которые описаны выше (т.е в common)

Т.е. если я напишу (в корневом):

$(PROJECT_NAME): build-dir

То оно не добавит в начало, а добавит в конец:

$(PROJECT_NAME): $(TARGET_SUBMAKE) $(TARGET_OBJ) build-dir

Что сломает порядок.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от wandrien

А если ещё раз ввести make что будет? По-моему оно будет всё заново пересобирать т.к. таргет «mkdir_dst» посчитает опять несуществующим, сделает mkdir и всё что от него зависит по цепочке.

Если вместо mkdir_dst указать настоящее имя директории, то тоже плохо: второй запуск обнаружит что её таймстамп больше чем у почти всех файлов и тоже запустит всё заново.

firkax ★★★★★
()
Ответ на: комментарий от PPP328

По-моему он предлагает создание директории дописывать к каждому правилу отдельно а не к проекту целиком. Но см. выше, это в любом виде будет ломать инкрементальные сборки.

firkax ★★★★★
()
Ответ на: комментарий от PPP328

Ты говоришь, что тебе нужен предварительный этап.

Это значит, что у тебя должна быть цель сборки, которая означает «предварительный этап», и любые реальные файлы или фиктивные цели, которые относятся к основному этапу, должны зависеть от неё. Тогда она будет выполняться раньше основного этапа.

wandrien ★★★
()
Последнее исправление: wandrien (всего исправлений: 2)
Ответ на: комментарий от wandrien

Если сделать вот так в common, то тоже не работает, и я об этом писал еще в основном посте:

build-dir:
	$(PROG_MKDIR) \
	    $(DIR_BUILD)/$(PROJECT_NAME)

$(PROJECT_NAME): build-dir $(TARGET_SUBMAKE) $(TARGET_OBJ)

Оно просто создает директорию и больше ничего не делает

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

Make не поддерживает такое

Ты не умеешь в make. Я тебе привёл пример выше, который работает.

Впрочем, ничего удивительного:

PPP328 ★★★★★ (07.07.25 22:11:32 KRAT) Ставит фейспальмы в ответ на исправление его ошибок

wandrien ★★★
()
Ответ на: комментарий от wandrien

Тогда наверно .preparation надо поместить как раз в создаваемую директорию, чтобы при её затирании он тоже пропадал. Такой вариант лучше моего в том плане что он не полагается на нестандартные gnu или bsd фичи, но с другой стороны всё-таки создаёт лишний файл только чтобы проверить существование директории.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Тогда наверно .preparation надо поместить как раз в создаваемую директорию, чтобы при её затирании он тоже пропадал

Да, так лучше.

Я хотел дополнить таким вариантом, но стало лень.

Впрочем, ТСу всё равно не в коня корм, не в первый раз.

wandrien ★★★
()
Ответ на: комментарий от wandrien

Хотя нет, есть ещё недостаток - твоя схема местами требует составлять предварительные списки файлов, а в make принято заранее их не знать и генерить на лету исходя из задачи. Мне кажется у PPP328 как раз их нет и поэтому ему некуда вставить зависимость от директории.

firkax ★★★★★
()
Ответ на: комментарий от firkax

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

SRC = $(wildcard src/*.txt)

DST = $(patsubst src/%,dst/%,$(SRC))

all: $(DST)

$(DST): | dst

dst:
	mkdir -p $@

dst/%.txt: src/%.txt
	cp $< $@

wandrien ★★★
()
Ответ на: комментарий от wandrien

Я уже показывал, что при вложенных цепочечных make с общими правилами | не работает.

Как обычно, читать не будем, будем орать и тыкать всех пальцами «во тупые»

а) Вынести mkdir в отдельный сегмент и вызывать как $(DIR_BUILD)/$(PROJECT_NAME)/%.o : %.a65 | build-dir б) Вынести mkdir в отдельный сегмент и вызывать тут: $(PROJECT_NAME): build-dir $(TARGET_SUBMAKE) $(TARGET_OBJ)

Я буквально это в стартовом посте перечислил.

PPP328 ★★★★★
() автор топика
Последнее исправление: PPP328 (всего исправлений: 1)
Ответ на: комментарий от firkax

твоя схема местами требует составлять предварительные списки файлов, а в make принято заранее их не знать и генерить на лету исходя из задачи

Нет. Обычная практика, это иметь в переменной список объектных файлов для сборки, полученный тем или иным образом.

Ну сам подумай: от чего у тебя будет зависеть цель линковки бинарника, если у тебя нет списка объектных файлов?

wandrien ★★★
()
Ответ на: комментарий от wandrien

Это НЕ работает:

$(DIR_BUILD)/$(PROJECT_NAME):
	$(PROG_MKDIR) \
	    $(DIR_BUILD)/$(PROJECT_NAME)

$(DIR_BUILD)/$(PROJECT_NAME)/%.o : %.a65 | $(DIR_BUILD)/$(PROJECT_NAME)
	$(PROG_AS) \
	    -o $@ \
	    $<

После создания директории не происходит больше ничего

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

Это НЕ работает:

А ты отличий не видишь с моим примером? Точно-точно нет никаких отличий?

Почему же у меня тогда работает?

wandrien ★★★
()
Последнее исправление: wandrien (всего исправлений: 1)
Ответ на: комментарий от wandrien

Почему же у меня тогда работает?

Потому что вы сделали единичный make без вызова сложенных make с одним шаблоном на всех, очевидно же, ну. Одиночный make и у меня работает.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от wandrien
$(TARGET_OBJ): | dst

dst:
	$(PROG_MKDIR) \
	    $(DIR_BUILD)/$(PROJECT_NAME)

$(DIR_BUILD)/$(PROJECT_NAME)/%.o : %.a65
	$(PROG_AS) \
	    -o $@ \
	    $<

make[1]: вход в каталог «/home/alex/development/NES/lina/code/lepus»
mkdir -p \
    /home/alex/development/NES/lina/build/lepus
make[1]: выход из каталога «/home/alex/development/NES/lina/code/lepus»

И всё. Больше НИЧЕГО не происходит

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

Ваши варианты не работают.

Перепутал ты маленько. Наши работают. При чем так, как описано в документации на сабж. Не работают твои.

Больше НИЧЕГО не происходит

Наверное нужно понять, почему так происходит? Ты предлагаешь нам ошибку в твоём коде поискать, не имея даже доступа к полному примеру?

Ведь изолировать проблему до наглядного образца ты не потрудился.

А если бы потрудился, то может и решение бы нашел в процессе.

wandrien ★★★
()
Ответ на: комментарий от wandrien

не имея даже доступа к полному примеру?

У вас полная слепота или что? Все файлы есть в самом первом посте.

Перепутал ты маленько. Наши работают. При чем так, как описано в документации на сабж. Не работают твои.

С вами всё ясно, ваше самомнение всегда наглядно.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

У вас полная слепота или что? Все файлы есть в самом первом посте.

А знаешь чего нет? Нет твоей среды, твоего каталога, в котором ты запускаешь команды, нет истории команд. Ты же даже не указываешь, как выглядит команда, из какого каталога ты её запускаешь, что лежит в целевой директории и т.п.

Телепаты в отпуске.

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

Вот главный вопрос:

Больше НИЧЕГО не происходит

ПОЧЕМУ ничего не происходит.

Может ничего не должно происходить, потому что все файлы собраны?

Может у тебя зависимости криво описаны?

Может ты вообще не в том каталоге сборку запускаешь?

Если что-то не работает, то разработчик локализует проблему до тех пор, пока не увидит конкретную причину. Ты какие действия предпринял, чтобы локализовать проблему?

wandrien ★★★
()
Ответ на: комментарий от wandrien

Нет твоей среды, твоего каталога, в котором ты запускаешь команды, нет истории команд. Ты же даже не указываешь, как выглядит команда, из какого каталога ты её запускаешь, что лежит в целевой директории и т.п.

Очевидно, что запускается просто make на верхнем уровне каталога.

Может ничего не должно происходить, потому что все файлы собраны?

Я уже в который раз вообще говорю, что зависимости описаны корректно и всё собирается, если записать как в ОП-посте - mkdir внутри %.o. Если сделать через | или добавить как отдельный шаг (неважно в виде build-dir или $(DIR_BUILD)/$(PROJECT_NAME)) - то на этом этапе всё заканчивается, дальше рекурсивно ничего не вызывается.

И это уже третий раз, когда я это повторяю персонально вам.

PPP328 ★★★★★
() автор топика

меня не устраивает, что оно на каждый файл вызывает создание целевого каталога

  1. Тебе нужен GNU make.
  2. Тебе нужны order-only зависимости:
SRC_DIR := ./src
OBJ_DIR := ./tmp

CC = gcc
COMPILE = $(CC) -o $@ -c $< $(CFLAGS)

OBJECTS :=
OBJECTS +=  $(OBJ_DIR)/source1.o
OBJECTS +=  $(OBJ_DIR)/source2.o

$(OBJECTS) : $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
	$(COMPILE)

$(OBJ_DIR):
	mkdir $@

Приветствуется общая критика.

Лучше переписать на один общий Makefile.

Примеры один, два.

LamerOk ★★★★★
()
Ответ на: комментарий от PPP328

Если сделать через | или добавить как отдельный шаг (неважно в виде build-dir или $(DIR_BUILD)/$(PROJECT_NAME)) - то на этом этапе всё заканчивается, дальше рекурсивно ничего не вызывается.

Тебе нужно взять элементарный пример, который ничего не компилирует, а просто копирует исходный файл в целевой, ну или копирует с заменой символов через sed - в общем, что-то элементарное.

Далее описать для него элементарный Makefile в соответствии с мануалом на GNU make.

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

А то, что ты словами вот это описал, тут нечего обсуждать: тут нет diff-а «вот так работало, а вот так не работает».

wandrien ★★★
()
Ответ на: комментарий от LamerOk

$(OBJECTS) : $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)

Ну ёпт, ну я уже в который раз в этом треде говорю, что с вложенными make это не срабатывает?

Considered Harmful

Минус параллелизм, плюс конфликты в глобальном make, плюс невозможность собрать каждый модуль отдельно. Класс. goto тоже considered harmful.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от firkax
Обновление целей результата...
Обработка целевого файла «build-dir».
 Файл «build-dir» не существует.
 Обновление целей, от которых зависит целевой файл «build-dir», завершено.
Необходимо пересобрать цель «build-dir».
mkdir -p \
    /home/alex/development/NES/lina/build/lepus
Помещение потомка 0x561399292710 (build-dir) PID 63859 в цепочку потомков.
Незавершённый потомок 0x561399292710 (build-dir) PID 63859 
Подбирается удачно завершившийся потомок 0x561399292710 PID 63859 
Удаляется потомок 0x561399292710 PID 63859 из цепочки.
Целевой файл «build-dir» успешно пересоздан.
make[1]: выход из каталога «/home/alex/development/NES/lina/code/lepus»

Вот так выглядит при вариантах через | или при указании зависимости до основного билда.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

Минус параллелизм,

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

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Поясняю, для тех, кто считает, что читать умеет. make -j с рекурсивными вызовами может распараллелить сборку нескольких вложенных make, которые при этом параллелятся от того же -j.

Но самый главный недостаток конкретно в моём случае - что когда мне нужно будет собрать с другим CHR банком - я просто в makefile нужного банка добавлю флаг, который будет собираться только если он глобально установлен. А в случае глобального makefile нужно вести тонну списков файлов самостоятельно, нельзя при этом сразу делать по рекурсивному wildcard, потому что он подцепит все банки сразу. Модульный же makefile позволяет в нужных каталогах делать wildcard, если выбор не нужен и полагаться на конкретные короткие списки, если выбор нужен.

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от firkax

У него $(TARGET_OBJ) пустой. Вот воспроизведение его кейса. Пришлось таки телепатом поработать.

vadim@aquila:/tmp/4$ cat Makefile 
TARGET_OBJ = x

$(TARGET_OBJ): | dst

dst:
	echo $@

vadim@aquila:/tmp/4$ make -d --no-builtin-rules
GNU Make 4.4.1
Эта программа собрана для x86_64-pc-linux-gnu
Copyright (C) 1988-2023 Free Software Foundation, Inc.
Лицензия GPLv3+: GNU GPL версии 3 или новее <https://gnu.org/licenses/gpl.html>
Это свободное программное обеспечение: вы можете свободно изменять его и
распространять. НЕТ НИКАКИХ ГАРАНТИЙ вне пределов, допустимых законом.
Чтение make-файлов...
Чтение make-файла «Makefile»...
Обновление make-файлов....
 Обработка целевого файла «Makefile».
  Поиск неявного правила для «Makefile».
  Не найдено неявного правила для «Makefile».
 Обновление целей, от которых зависит целевой файл «Makefile», завершено.
 Нет необходимости пересобирать цель «Makefile».
Обновление целей результата...
Обработка целевого файла «x».
 Файл «x» не существует.
 Поиск неявного правила для «x».
 Не найдено неявного правила для «x».
 Обработка целевого файла «dst».
  Файл «dst» не существует.
 Обновление целей, от которых зависит целевой файл «dst», завершено.
 Необходимо пересобрать цель «dst».
Makefile:6: причина обновления цели «dst»: цель не существует
echo dst
Помещение потомка 0x5603114946b0 (dst) PID 131430 в цепочку потомков.
Незавершённый потомок 0x5603114946b0 (dst) PID 131430 
dst
Подбирается удачно завершившийся потомок 0x5603114946b0 PID 131430 
Удаляется потомок 0x5603114946b0 PID 131430 из цепочки.
 Целевой файл «dst» успешно пересоздан.
Обновление целей, от которых зависит целевой файл «x», завершено.
Необходимо пересобрать цель «x».
Целевой файл «x» успешно пересоздан.
vadim@aquila:/tmp/4$ cat Makefile 
#TARGET_OBJ = x

$(TARGET_OBJ): | dst

dst:
	echo $@

vadim@aquila:/tmp/4$ make -d --no-builtin-rules
GNU Make 4.4.1
Эта программа собрана для x86_64-pc-linux-gnu
Copyright (C) 1988-2023 Free Software Foundation, Inc.
Лицензия GPLv3+: GNU GPL версии 3 или новее <https://gnu.org/licenses/gpl.html>
Это свободное программное обеспечение: вы можете свободно изменять его и
распространять. НЕТ НИКАКИХ ГАРАНТИЙ вне пределов, допустимых законом.
Чтение make-файлов...
Чтение make-файла «Makefile»...
Обновление make-файлов....
 Обработка целевого файла «Makefile».
  Поиск неявного правила для «Makefile».
  Не найдено неявного правила для «Makefile».
 Обновление целей, от которых зависит целевой файл «Makefile», завершено.
 Нет необходимости пересобирать цель «Makefile».
Обновление целей результата...
Обработка целевого файла «dst».
 Файл «dst» не существует.
Обновление целей, от которых зависит целевой файл «dst», завершено.
Необходимо пересобрать цель «dst».
Makefile:6: причина обновления цели «dst»: цель не существует
echo dst
Помещение потомка 0x559dfd1d8520 (dst) PID 131480 в цепочку потомков.
Незавершённый потомок 0x559dfd1d8520 (dst) PID 131480 
dst
Подбирается удачно завершившийся потомок 0x559dfd1d8520 PID 131480 
Удаляется потомок 0x559dfd1d8520 PID 131480 из цепочки.
Целевой файл «dst» успешно пересоздан.
vadim@aquila:/tmp/4$ 
wandrien ★★★
()
Ответ на: комментарий от firkax

Всё остальное - это попытки make поискать Makefile с разным регистром или расширением файла. Там несколько сотен строчек типа «Попытка применения неявной зависимости «SCCS/s.Makefile.m»»

PPP328 ★★★★★
() автор топика
Ответ на: комментарий от wandrien

У него $(TARGET_OBJ) пустой.

Голова у вас пустая. Ещё раз, для особо одарённых - при убирании mkdir из отдельного таргета ВСЁ работает.

Но я вывел специально для вас:


$(info $(TARGET_OBJ))

 /home/alex/development/NES/lina/build/lepus/lep_handler_irq.o  /home/alex/development/NES/lina/build/lepus/lep_handler_nmi.o  /home/alex/development/NES/lina/build/lepus/lep_handler_vars.o  /home/alex/development/NES/lina/build/lepus/lep_handler_reset.o


PPP328 ★★★★★
() автор топика
Ответ на: комментарий от PPP328

при убирании mkdir из отдельного таргета ВСЁ работает.

Ты нам diff между рабочим и нерабочим Makefile-ом покажешь наконец или так и будешь клоунадой заниматься?

Не, меня-то всё устраивает, так что продолжай. Это же тебе надо проблему решить, а не мне.

wandrien ★★★
()
Ответ на: комментарий от wandrien

Ты нам diff между рабочим и нерабочим Makefile-ом покажешь наконец или так и будешь клоунадой заниматься?

Вы особо одарённый или как? Я блджд, В САМОМ ПЕРВОМ ПОСТЕ написал, какие варианты были проверены.

PPP328 ★★★★★
() автор топика