LINUX.ORG.RU

make grouped targets в версиях ранее 4.3

 ,


0

0

В GNU make 4.3 появились grouped targets:

a b &: c
	my_awesome_script --outfile=$@

При это my_awesome_script будет вызван только один раз.

Как правильно перенести эту логику на make 4.1? Возможно ли это без дополнительной обработки на стороне my_awesome_script?

★★★★★

Ответ на: комментарий от xaizek

Наверное, так

Если не сохранять некий make_a_and_b артефакт на диск, то my_awesome_script будет выполняться при каждом запуске make, что очень дорого в моем случае. А если сохранять, то получится аналог того костыля, которым я это подпер сейчас.

b: a
a : c
	my_awesome_script --outlife=$@  # generates both a and b

other: a # b

Оно работает, но пришлось убрать у всех других целей b из зависимостей, т.к. иначе получаем

make: *** No rule to make target 'b', 'other'.  Stop.

Оно сейчас работает, но дизайн мне не нравится. Отсюда вопрос, как столько лет решалась подобная задача до make 4.3?

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

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

No rule to make target ‘b’, ’other

Странно, я такого не помню. У меня аналогичная конструкция работает нормально:

$(out_dir)/src/c/c11-parser.gen.hpp: $(out_dir)/src/c/c11-parser.gen.cpp
$(out_dir)/src/c/c11-parser.gen.cpp: src/c/c11-parser.ypp
	bison --defines=$(out_dir)/src/c/c11-parser.gen.hpp \
	      --output=$(out_dir)/src/c/c11-parser.gen.cpp $<
xaizek ★★★★★ ()
Ответ на: комментарий от xaizek

Странно, я такого не помню. У меня аналогичная конструкция работает нормально

Через -jN пробовал? У меня проблема всплыла только когда запустил в много потоков.

Не вижу у тебя файла c11-lexer.gen.hpp в зависимостях для других задач.

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

Странно, я такого не помню. У меня аналогичная конструкция работает нормально:

Фишка в том, что первая строчка не является правилом. Эта конструкция просто определяет дополнительные зависимости. Чтобы она стала правилом, рецепт должен синтаксически присутствовать — возможно пустой (из одной пустой строки), но он должен быть.

Чтобы строка засчиталась за рецепт, в её начале должен быть Tab. Или, как вариант, можно написать в конце заглавной строки точку с запятой:

a: b c
<--->

или

a: b c ;

Если у тебя работает как есть, то это тупо гонка. Make не будет проверять наличие правила для %.hpp, если к тому моменту, как он дойдёт до обработки этого файла, он уже будет существовать.

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

Hotfix:

В варианте

b: a ;
a : c
	my_awesome_script --outlife=$@  # generates both a and b

other: a b

обязательно нужна ; после пустого рецепта.

https://www.gnu.org/software/make/manual/make.html#Empty-Recipes

Иначе, первая строчка не является рецептом, а лишь добавляет зависимость a к b. При достаточно большом количестве одновременно выполняемых задач make начнет выполнять b до выполнения a и не сможет найти для него правило.

За решение спасибо @intelfx.

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

-j будет использовать максимальное количество потоков и у меня всё равно ничего не происходит.

Я просто не вижу даже возможности для гонки. Пока a не выполнится, b не появится в очереди задач на исполнение. Когда a выполнилось, b уже существует. Окна для гонки просто нет, либо я не о той гонке думаю.

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

Скорее зависимости как-то это решают.

Ну может быть, я не видел твоего мейкфайла целиком.

А вообще гонка имеет следующий вид:

all: a b

a: b
b: c
    my_awesome_command  # writes b and a

Если мейк запустить с -j1, то он может решить сначала сделать b, а уже потом пойти смотреть на a, а может решить наоборот. В первом случае сборка выполнится успешно, т. к. к моменту, когда make посмотрит на a, он уже будет существовать с правильным временем, несмотря на то, что мейкфайл написан неправильно. А во втором случае, или если запустить мейк с -jN (N > 1), то гонка проявится и сборка свалится с ошибкой.

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

Из моего комментария выше:

Я просто не вижу даже возможности для гонки. Пока a не выполнится, b не появится в очереди задач на исполнение. Когда a выполнилось, b уже существует.

Где здесь будет гонка, если a и b появляются одновременно? Если есть a без b, то да, может быть бок. Но это не гонка, а проблема с рецептом.

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

Окей, хорошо, не гонка, а нондетерминизм.

Из моего комментария выше:

Аналогично, из моего комментария выше:

Если мейк запустить с -j1, то он может решить сначала сделать b, а уже потом пойти смотреть на a, а может решить наоборот.

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

С чего бы собирать b до сборки a, если b зависит от a? Зависимости указывают не для красоты, а чтобы топологически отсортировать и выполнять цель только после всех её не up-to-date зависимостей. Нет b, нет a, значит будет собрано ab заодно) и только потом b будет «собираться».

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

Это ничего не меняет, так как ты сказал:

b, а уже потом пойти смотреть на a, а может решить наоборот.

Не может он взять и «решить наоборот». В этом весь смысл зависимостей.

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

Понял, что ты не понимаешь, зачем нужны зависимости и что такое топологическая сортировка их DAG, раз думаешь, что a и b на одном уровне (подсказка: уровни считаются не с начала цепочки зависимостей).

А ТСу лучше присмотреться к своему мэйкфайлу, пустой рецепт мог просто скрыть в нём какой-то бок.

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

А ТСу лучше присмотреться к своему мэйкфайлу, пустой рецепт мог просто скрыть в нём какой-то бок.

В документации, ссылку на которую привел в одном из сообщений выше, черным по белому описан данный случай.

Empty recipes can also be used to avoid errors for targets that will be created as a side-effect of another recipe: if the target does not exist the empty recipe ensures that make won’t complain that it doesn’t know how to build the target, and make will assume the target is out of date.

Пример:

ALL = 1.a 2.a 1.b 2.b

all: $(ALL)

%.b: %.a ;
%.a: c
	@touch $@
	@echo $@ | sed 's/a/b/g' | xargs touch
	
c:
	@touch c

clean:
	@rm $(ALL)

Убираем ; и сборка с -j ломается.

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

Ну, я решил не вываливать целиком свой Makefile в стартовом сообщении. Привел то, что показалось релевантным.

Хотя я и сейчас на не уверен, что без % не возникнет подобной ошибки. Если уж документация говорит писать ; чтобы превратить зависимость в рецепт, то проще всегда делать так и не ломать голову.

Ну а вообще лучше использовать make 4.3 и grouped targets.

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

Если уж документация говорит писать ; чтобы превратить зависимость в рецепт, то проще всегда делать так и не ломать голову.

Просто меня их описание не удовлетворяет. Но думаю, что я нашёл причину разницы в поведении:

10.5.6 Canceling Implicit Rules

You can override a built-in implicit rule (or one you have defined yourself) [...]

You can cancel a built-in implicit rule by defining a pattern rule with the same target and prerequisites, but no recipe. For example, the following would cancel the rule that runs the assembler:

    %.o : %.s

Т.е.

%.b: %.a

это вообще не правило, а удаление неявного правила. Это отличается от

1.b: 1.a

которое является правилом и добавление к нему ; ничего не меняет. И никакой гонки или недетерминизма.

xaizek ★★★★★ ()