Правило GNU Makefile, генерирующее несколько целей из одного исходного файла
Я пытаюсь сделать следующее. Существует программа, назовите ее foo-bin
, которая принимает один входной файл и генерирует два выходных файла. Правило Makefile для него было бы следующим:
file-a.out file-b.out: input.in
foo-bin input.in file-a.out file-b.out
Однако это не означает make
каким-либо образом, что обе цели будут генерироваться одновременно. Это нормально при запуске make
в серийном режиме, но, вероятно, вызовет проблему, если вы попытаетесь make -j16
или что-то подобное безумное.
Вопрос заключается в том, существует ли способ написать правильное правило Makefile для такого случая? Очевидно, что он будет генерировать DAG, но каким-то образом руководство GNU не укажет, как этот случай мог обрабатываться.
Выполнение одного и того же кода два раза и генерация только одного результата не может быть и речи, потому что для вычисления требуется время (подумайте: часы). Вывод только одного файла также будет довольно сложным, потому что часто он используется как вход для GNUPLOT, который не знает, как обрабатывать только часть файла данных.
Ответы
Ответ 1
Я решил бы это следующим образом:
file-a.out: input.in
foo-bin input.in file-a.out file-b.out
file-b.out: file-a.out
#do nothing
noop
В этом случае параллельный make будет "сериализовать" создание a и b, но так как создание b ничего не делает, это не занимает времени.
Ответ 2
Хитрость заключается в использовании правила шаблона с несколькими целями. В этом случае make будет предполагать, что обе цели создаются одним вызовом команды.
all: file-a.out file-b.out
file-a%out file-b%out: input.in
foo-bin input.in file-a$*out file-b$*out
Это различие в интерпретации между правилами шаблонов и нормальными правилами не имеет особого смысла, но полезно для таких случаев, и это задокументировано в руководстве.
Этот трюк может использоваться для любого количества выходных файлов, если их имена имеют некоторую общую подстроку для соответствия %
. (В этом случае общая подстрока "." )
Ответ 3
У Make нет интуитивного способа сделать это, но есть два достойных решения.
Во-первых, если задействованные цели имеют общую основу, вы можете использовать префиксное правило (с GNU make). То есть, если вы хотите исправить следующее правило:
object.out1 object.out2: object.input
foo-bin object.input object.out1 object.out2
Вы можете написать так:
%.out1 %.out2: %.input
foo-bin $*.input $*.out1 $*.out2
(Используя переменную типа шаблона $ *, которая обозначает совпадающую часть шаблона)
Если вы хотите быть переносимыми к реализациям, отличным от GNU Make, или если ваши файлы не могут быть названы в соответствии с шаблоном, существует другой способ:
file-a.out file-b.out: input.in.intermediate ;
.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
foo-bin input.in file-a.out file-b.out
Это говорит о том, что input.in.intermediate не будет существовать до запуска make, поэтому его отсутствие (или его временная метка) не приведет к ложному запуску foo-bin. И будь то файл-a.out или файл-b.out или оба являются устаревшими (относительно input.in), foo-bin будет запускаться только один раз. Вы можете использовать.SECONDARY вместо.INTERMEDIATE, который будет давать указание НЕ удалять гипотетическое имя файла input.in.intermediate. Этот метод также безопасен для параллельных сборок.
Точка с запятой на первой строке важна. Он создает пустой рецепт для этого правила, так что Make знает, что мы действительно будем обновлять файл-a.out и файл-b.out (спасибо @siulkiulki и другим, кто указал на это)
Ответ 4
Это основано на следующем ответе @deemer, который не полагается на правила шаблонов, и исправляет проблему, с которой я сталкивался с вложенными способами обхода.
file-a.out file-b.out: input.in.intermediate
@# Empty recipe to propagate "newness" from the intermediate to final targets
.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
foo-bin input.in file-a.out file-b.out
Я бы добавил это как комментарий к ответу @deemer, но я не могу, потому что я только что создал эту учетную запись и не имею никакой репутации.
Объяснение: Пустой рецепт необходим, чтобы позволить Make сделать правильную бухгалтерию, чтобы пометить file-a.out
и file-b.out
как перестроенную. Если у вас есть еще одна промежуточная цель, которая зависит от file-a.out
, тогда Make решит не строить внешний промежуток, требуя:
No recipe for 'file-a.out' and no prerequisites actually changed.
No need to remake target 'file-a.out'.
Ответ 5
Вот как я это делаю. Сначала я всегда отделяю предварительные требования от рецептов. Тогда в этом случае новая цель, чтобы сделать рецепт.
all: file-a.out file-b.out #first rule
file-a.out file-b.out: input.in
file-a.out file-b.out: dummy-a-and-b.out
.SECONDARY:dummy-a-and-b.out
dummy-a-and-b.out:
echo creating: file-a.out file-b.out
touch file-a.out file-b.out
Первый раз:
1. Мы пытаемся создать файл-a.out, но сначала нужно выполнить фиктивный-a-and-b.out, чтобы make запускал рецепт dummy-a-and-b.out.
2. Мы пытаемся создать файл-b.out, dummy-a-and-b.out в актуальном состоянии.
Второе и последующее время:
1. Мы пытаемся создать файл-a.out: make ищет предварительные условия, нормальные предварительные требования обновлены, вторичные предпосылки пропадают, поэтому игнорируются.
2. Мы пытаемся создать файл-b.out: make ищет предварительные условия, нормальные предварительные условия обновлены, вторичные предпосылки отсутствуют, поэтому игнорируются.
Ответ 6
В качестве дополнения к ответу @deemer я обобщил его в функцию.
sp :=
sp +=
inter = .inter.$(subst $(sp),_,$(subst /,_,$1))
ATOMIC=\
$(eval s1=$(strip $1)) \
$(eval target=$(call inter,$(s1))) \
$(eval $(s1): $(target) ;) \
$(eval .INTERMEDIATE: $(target) ) \
$(target)
$(call ATOMIC, file-a.out file-b.out): input.in
foo-bin input.in file-a.out file-b.out
Сломать:
$(eval s1=$(strip $1))
Убрать все начальные/конечные пробелы из первого аргумента
$(eval target=$(call inter,$(s1)))
Создайте целевую переменную с уникальным значением для использования в качестве промежуточной цели. В этом случае значение будет.inter.file-a.out_file-b.out.
$(eval $(s1): $(target) ;)
Создайте пустой рецепт для выходов с уникальной целью в качестве зависимости.
$(eval .INTERMEDIATE: $(target) )
Объявите уникальную цель как промежуточную.
$(target)
Завершите со ссылкой на уникальную цель, чтобы эту функцию можно было использовать непосредственно в рецепте.
Также обратите внимание, что использование eval здесь происходит потому, что eval расширяется до нуля, поэтому полное расширение функции - это просто уникальная цель.
Нужно отдать должное Атомным правилам в GNU Make, на которых основана эта функция.