Определите рекурсивно расширенную переменную, используя переменную, имя которой вычисляется из просто расширенной переменной
Я столкнулся с трудностью определения общих правил для нерекурсивной системы make.
Фон
Для дальнейшего чтения, а не для того, чтобы я воспроизводил слишком много существующего материала, посмотрите этот ранее заданный вопрос, который довольно хорошо освещает вопрос и ранее помог мне при создании этой системы.
Для системы make, которую я создаю, я хочу определить зависимости между компонентами системы - например, компонент A зависит от компонента B - и затем покинуть систему make, чтобы убедиться, что любые продукты процесса сборки B собираются до того, как они понадобятся при сборке. шаги для A. Это немного расточительно из-за детализации (некоторые ненужные промежуточные звенья могут быть построены), но для моего варианта использования это соответствует удобному балансу между простотой использования и производительностью сборки.
Сложность, с которой должна столкнуться система, состоит в том, что порядок загрузки make-файла не может контролироваться - действительно, это не должно иметь значения. Однако из-за этого компонент, определенный в ранее загруженном make-файле, может зависеть от компонента, определенного в еще не прочитанном make-файле.
Чтобы разрешить применение общих шаблонов ко всем определяющим компоненты make-файлам, каждый компонент использует такие переменные, как: $(component_name)_SRC
, Это общее решение для нерекурсивных (но включенных в рекурсивную систему) систем.
Информацию о различных типах переменных GNU make смотрите в руководстве. В итоге: просто расширенные переменные (SEV) расширяются при чтении make-файла, демонстрируя поведение, аналогичное императивному языку программирования; Рекурсивно расширенные переменные (REV) раскрываются во время второй фазы make после того, как все make-файлы были прочитаны.
Эта проблема
Особая проблема возникает при попытке превратить список зависимых компонентов в список файлов, представляемых этими компонентами.
Я перевел свой код в этот работающий пример, который не учитывает многие детали реальной системы. Я думаю, что это достаточно просто, чтобы продемонстрировать проблему, не теряя своей сути.
rules.mk:
$(c)_src := $(src)
$(c)_dependencies := $(dependencies)
### This is the interesting line:
$(c)_dependencies_src := $(foreach dep, $($(c)_dependencies), $($(dep)_src))
$(c) : $($(c)_src) $($(c)_dependencies_src)
@echo $^
Makefile:
.PHONY: foo_a.txt foo_b.txt bar_a.txt hoge_a.txt
### Bar
c := bar
src := bar_a.txt
dependencies :=
include rules.mk
### Foo
c := foo
src := foo_a.txt foo_b.txt
dependencies := bar hoge
include rules.mk
### Hoge
c := hoge
src := hoge_a.txt
dependencies := bar
include rules.mk
Они будут работать, чтобы дать:
$ make foo
foo_a.txt foo_b.txt bar_a.txt
$
hoge_a.txt не включен в вывод, потому что в то время foo_dependencies
определяется как SEV, hoge_src
еще не существует
Расширение после того, как все make-файлы были прочитаны, является проблемой, которую должны решить REVs, и я уже пытался определить $(c)_dependencies_src
как REV, но это тоже не работает, потому что $(c)
затем раскрывается во время подстановки, а не во время определения, поэтому он больше не содержит правильное значение.
Если кому-то интересно, почему я не использую переменные, специфичные для цели, я обеспокоен тем, что применение переменной ко всем предварительным условиям цели, описанным в руководстве, приведет к нежелательному взаимодействию между правилами для различных компонентов.
Я хотел бы знать:
- Есть ли решение этой конкретной проблемы? (т.е. есть ли простой способ заставить эту линию достичь того, чего я хочу?)
- Есть ли более типичный способ создания такой системы? (т.е. один экземпляр make, загрузка компонентов из нескольких make-файлов и определение зависимостей между этими компонентами.)
- Если существует несколько решений, каковы компромиссы между ними?
Заключительный комментарий: когда я написал свой вопрос, я начал осознавать, что может быть возможным решение, использующее eval для построения определения REV, однако, поскольку я не смог найти эту проблему где-либо еще в SO, я счел целесообразным задавая вопрос в любом случае ради будущих искателей, плюс я бы хотел услышать мнения более опытных пользователей об этом или любых других подходах.
1 ответ
Короткий ответ: нет хорошего решения для вопроса, который вы задаете. Невозможно остановить расширение переменной на полпути и отложить ее на потом. Не только это, но и потому, что вы используете переменную в списке предварительных условий, даже если вы можете получить значение $(c)_dependencies_src
переменная, содержащая только те ссылки на переменные, которые вы хотели, в следующей строке они будут полностью развернуты как часть списка предварительных условий, так что вы ничего не получите.
Есть только один способ отложить расширение предварительных условий - использовать вторичную функцию расширения. Вы должны сделать что-то вроде:
$(c)_src := $(src)
$(c)_dependencies := $(dependencies)
.SECONDEXPANSION
$(c) : $($(c)_src) $$(foreach dep, $$($$@_dependencies), $$($$(dep)_src))
@echo $^
(Непроверенные). Это обходит проблему с $(c)_dependencies_src
просто не определяя его вообще и не помещая в список предварительных условий напрямую, а как вторичное расширение.
Как я уже писал в своем комментарии выше, я лично не разработал бы систему, которая работала бы так. Я предпочитаю иметь системы, в которых все переменные создаются заранее с использованием пространства имен (обычно перед именем цели), а затем в конце, после того, как все переменные были определены, включая один "rules.mk" или что-либо еще, что будет использовать все эти переменные для построения правил, скорее всего (если все ваши рецепты не очень просты), используя eval
,
Итак, что-то вроде:
targets :=
### Bar
targets += bar
bar_c := bar
bar_src := bar_a.txt
bar_dependencies :=
### Foo
targets += foo
foo_c := foo
foo_src := foo_a.txt foo_b.txt
foo_dependencies := bar hoge
### Hoge
targets += hoge
hoge_c := hoge
hoge_src := hoge_a.txt
hoge_dependencies := bar
# Now build all the rules
include rules.mk
А потом в rules.mk
вы бы увидели что-то вроде:
define make_c
$1 : $($1_src) $(foreach dep, $($1_dependencies), $($(dep)_src))
@echo $$^
endif
$(foreach T,$(targets),$(eval $(call make_c,$T)))
Вы даже можете избавиться от настроек для target
если вы осторожны с именами переменных, добавив что-то вроде этого в rules.mk
:
targets := $(patsubst %_c,%,$(filter %_c,$(.VARIABLES)))
Чтобы разрешить одинаковые компоненты в разных целях, вам просто нужно добавить больше в пространство имен, чтобы различать два разных компонента.