2016-12-15 4 views
2

Я пытаюсь написать Makefile, где несколько исходных файлов (в моем случае они являются уценкой) создают несколько целевых файлов (pdf). Однако создаваемые целевые файлы содержат дополнительные символы в имени файла, которые невозможно предсказать (это, скорее всего, номер версии, закодированный в источнике), но в идеале Makefile не должен был бы считывать сам источник.Makefile, где неизвестно имя цели

Так, например:

file1.md => file1-v1.pdf 
file2.md => file2-v2.pdf 
... 

я могу вычислить имя источника заданное целевое имя (исключая ничего после дефиса и добавление .md), но не может рассчитать целевое имя, данное источник.

Возможно ли создать файл Makefile, который создает только те цели, где источник был обновлен?

+0

Готовы ли вы иметь дополнительные файлы маркеров? – Beta

+0

Меня интересовали бы решения с и без. Очевидно, что не было бы аккуратнее, но если это невозможно ... – wannymahoots

ответ

2

Это будет уродливо, но оно будет работать.

Как это часто бывает с замыкающими, наша задача разбивается на эти две проблемы:
1. конструкт список целей
2. построить их

Предположим, что у нас есть пять Md файлов, отображающих в PDF файлах (имена которых мы заранее не знаем):

file1.md => file1-v1.pdf 
file2.md => file2-v1.pdf 
file3.md => file3-v1.pdf 
file4.md => file4-v1.pdf 
file5.md => file5-v1.pdf 

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

file1-dummy.pdf: file1.md 
    zap file1.md 

Когда Make выполняет это правило, он производит файл file1-v1.pdf. Тот факт, что он не создает файл с именем file1-dummy.pdf, вызывает беспокойство, но не представляет серьезной проблемы. Мы можем превратить это в шаблонное правило:

%-dummy.pdf: %.md 
    zap $< 

Тогда все, что мы должны сделать, это включить список существующих входных файлов (file1.md, file2.md, ...) в список ложных целей (file1-dummy.pdf, file2-dummy.pdf, ...), и построить их. Все идет нормально.

Но предположим, что некоторые из выходных файлов уже существуют.Если file2-v2.pdf уже существует - и является новее, чем file2.md - тогда мы предпочли бы, чтобы Make не перестраивал его (пытаясь построить file2-dummy.pdf). В этом случае мы предпочли бы, чтобы file2-v2.pdf быть в целевом списке, с помощью правила, которые работали так:

file2-v2.pdf: file2.md 
    zap $< 

Это не легко превратить в шаблонное правило, потому что Make не обрабатывает символы очень ну и не может справиться с несколькими подстановочными знаками в одной фразе, не без большой неуклюжести. Но есть способ написать одно правило, которое будет охватывать оба случая. Прежде всего заметим, что мы можем получить часть переменной перед дефисом с этим ляп:

$(basename $(subst -,.,$(VAR))) 

вооруженных с этим, и с secondary expansion, мы можем написать шаблонное правило, которое будет работать с обоих случаях, и построить цель список, который будет использовать его:

# There are other ways to construct these two lists, but this will do. 
MD := $(wildcard *.md) 
PDF := $(wildcard *.pdf) 

PDFROOTS := $(basename $(subst -,.,$(basename $(PDF)))) 
MDROOTS := $(filter-out $(PDFROOTS), $(basename $(MD))) 

TARGETS:= $(addsuffix -foo.pdf, $(MDROOTS)) $(PDF) 

.SECONDEXPANSION: 
%.pdf: $$(basename $$(subst -,., $$*)).md 
    # perform actions on $< 
1

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

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

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

Таким образом, у вас должна быть хотя бы одна пара целевых/обязательных условий, которая выводима (для неявных правил) или состояний (для явных правил), то есть известная в то время, когда вы пишете файл makefile. Если вы этого не сделаете, маркерный файл станет вашей единственной альтернативой. Обратите внимание, что вы можете добавлять дополнительные сгенерированные непроизводные предварительные условия (например, в компиляторах вы можете добавлять заголовочные файлы в качестве предварительных условий, не связанных с именем исходного файла), в дополнение к известному предварительным требованиям.

1

@ ответ Beta является информативным и полезным, но мне нужно решение (с помощью GNU Make 4.1), которая работала, когда имя файл назначение не имеет никакого сходства с входным файлом, например, если он генерируется из его содержимого. Я придумал следующее, которое принимает каждый файл, соответствующий *.in, и создает файл, читая содержимое исходного файла, добавляя .txt и используя его в качестве имени файла для создания. (Например, если test.in существует и содержит foo, то Makefile создаст foo.txt файл.)

SRCS := $(wildcard *.in) 
.PHONY: all 

all: all_s 

define TXT_template = 
$(2).txt: $(1) 
    touch [email protected] 
ALL += $(2).txt 
endef 

$(foreach src,$(SRCS),$(eval $(call TXT_template, $(src), $(shell cat $(src))))) 

.SECONDARY_EXPANSION: 
all_s: $(ALL) 

Объяснение:

define блок определяет рецепт, необходимый, чтобы сделать текстовый файл из файла .in , Это функция, которая принимает два параметра; $(1) - это файл .in. и $(2) - это его содержимое или база выходного имени файла. Замените touch тем, что делает выход. Мы должны использовать [email protected], потому что eval будет расширять все один раз, но мы хотим, чтобы [email protected] оставил после этого расширения. Поскольку мы должны собрать все сгенерированные цели, чтобы мы знали, что все делает, линия ALL накапливает цели в одну переменную. Строка foreach проходит через каждый исходный файл, вызывает функцию с исходным именем файла и содержимым файла (то есть, что мы хотим быть именем цели, здесь вы обычно используете любой скрипт, генерирующий желаемое имя файла) и затем оценивает полученный блок, динамически добавляя рецепт в make. Благодаря Beta для объяснения .SECONDARY_EXPANSION; Мне нужно было это по причинам, не вполне ясным для меня, но он работает (установка all: $(ALL) вверху не работает). all: наверху зависит от вторичного расширения all_s: внизу, и как-то эта магия заставляет его работать. Комментарии приветствуются.

Смежные вопросы