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

Я столкнулся с трудностью при определении общих правил для нерекурсивной системы make.

Фон

Для дальнейшего чтения, вместо того, чтобы воспроизводить слишком много существующего материала, см. " title="Ранее SO вопрос / обсуждение нерекурсивных подходов make">этот более ранний вопрос, который довольно хорошо освещает тему и ранее помог мне при построении этой системы.

Для системы make, которую я создаю, я хочу определить зависимости между компонентами системы, например. компонент A зависит от компонента B, а затем оставьте систему make, чтобы гарантировать, что любые продукты процесса сборки B будут построены до того, как они потребуются на этапах сборки для A. Это немного расточительно из-за детализации (могут быть построены некоторые ненужные промежуточные продукты). ), но для моего варианта использования он обеспечивает удобный баланс между простотой использования и производительностью сборки.

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

Чтобы разрешить применение общих шаблонов ко всем make-файлам, определяющим компоненты, каждый компонент использует такие переменные, как: $(component_name)_SRC. Это распространенное решение для нерекурсивных (но рекурсивно включенных) систем make.

Для получения информации о различных типах переменных GNU make см. руководство. Подводя итог: просто расширенные переменные (SEV) расширяются по мере чтения make-файла, демонстрируя поведение, подобное императивному языку программирования; рекурсивно расширяемые переменные (REV) расширяются во время второй фазы make после того, как все make-файлы были прочитаны.

Проблема

Конкретная проблема возникает при попытке превратить список зависимых компонентов в список файлов, которые эти компоненты представляют.

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

правила.мк:

$(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-файлы были прочитаны, — это проблема, которую должны решить REV, и ранее я пытался определить $(c)_dependencies_src как REV, но это тоже не работает, потому что $(c) затем расширяется во время замены, а не во время определения, поэтому больше не имеет правильного значения.

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

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

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

Последний комментарий: когда я написал свой вопрос, я начал понимать, что может быть возможное решение с использованием eval для построения определения REV, однако, поскольку я не мог найти эту проблему, описанную где-либо еще на SO, я подумал, что стоит в любом случае задавайте вопрос ради будущих искателей, плюс я хотел бы услышать мысли более опытных пользователей об этом или любых других подходах.


person Gerard Thornley    schedule 01.01.2017    source источник
comment
Почему вы используете := вместо = при настройке $(c)_dependencies_src? Если бы вы использовали здесь =, это решило бы вашу проблему.   -  person MadScientist    schedule 01.01.2017
comment
Лично я никогда бы не стал создавать систему, в которой используются глобальные переменные, такие как назначение c := foo, затем включение файлов правил, затем назначение c := bar и включение файлов правил. Я бы сделал это так: targets += foo, затем foo_c := foo, foo_src := foo.x и т. д., затем targets += bar, bar_c := bar, bar_src := bar.x и т. д. Это позволяет избежать установки одинаковых имен переменных и гарантировать их использование до их сброса.   -  person MadScientist    schedule 01.01.2017
comment
Вы пробовали это с примером, который я разместил? По крайней мере, у меня это не работает, как я уже сказал: ... REV должны иметь возможность решать, и я ранее пытался определить $(c)_dependencies_src как REV...   -  person Gerard Thornley    schedule 01.01.2017
comment
Спасибо, я понимаю, что это одно из доступных решений, однако мой пример более тривиален, чем реальная система. При этом, чтобы избежать конфликтов имен, '$(c)' может генерироваться во время обхода make-файла. (Некоторые компоненты совместно используются приложениями, и для обоих используются одни и те же make-файлы).   -  person Gerard Thornley    schedule 01.01.2017
comment
Я принял ответ MadScientist, потому что он правильно ответил на вопрос, который я задал.   -  person Gerard Thornley    schedule 27.01.2017
comment
С тех пор я усовершенствовал make-файлы и нашел решение, которое не зависит от вторичного расширения make: сначала я читаю все вспомогательные make-файлы, а затем перебираю их, чтобы создать цели. Это не сильно отличается от подхода вторичного расширения, за исключением того, что он больше находится под контролем make-файла. Переход к этому подходу в сочетании с несколькими другими изменениями привел к гибкому и простому (с точки зрения определения компонентов) решению. Я опубликую ссылку на github, когда закончу.   -  person Gerard Thornley    schedule 27.01.2017


Ответы (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)))

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

person MadScientist    schedule 01.01.2017
comment
Спасибо, что указали, что это было определение правила, которое расширяло переменную - я этого не осознавал. Я понимаю вашу неприязнь к используемому здесь не строго декларативному подходу, но я все же хотел бы посмотреть, есть ли способ заставить мой текущий подход работать, поэтому я подожду и посмотрю, не захочет ли кто-нибудь еще ответить на в настоящее время. Есть некоторая ценность в динамическом построении пространства имен, поскольку оно передает ответственность за управление им от автора компонента пользователю компонента, но я понимаю, что это компромисс, и эта проблема является одним из последствий. - person Gerard Thornley; 01.01.2017