Определите рекурсивно расширенную переменную, используя переменную, имя которой вычисляется из просто расширенной переменной

Я столкнулся с трудностью определения общих правил для нерекурсивной системы 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) затем раскрывается во время подстановки, а не во время определения, поэтому он больше не содержит правильное значение.

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

Я хотел бы знать:

  1. Есть ли решение этой конкретной проблемы? (т.е. есть ли простой способ заставить эту линию достичь того, чего я хочу?)
  2. Есть ли более типичный способ создания такой системы? (т.е. один экземпляр make, загрузка компонентов из нескольких make-файлов и определение зависимостей между этими компонентами.)
  3. Если существует несколько решений, каковы компромиссы между ними?

Заключительный комментарий: когда я написал свой вопрос, я начал осознавать, что может быть возможным решение, использующее 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)))

Чтобы разрешить одинаковые компоненты в разных целях, вам просто нужно добавить больше в пространство имен, чтобы различать два разных компонента.

Другие вопросы по тегам