Gautitho
Gautitho

Reputation: 623

Bash : use variable in variable (nested loop)

I have a makefile and I want to compile each of my vhdl files in the correct library. And there is my code :

    $(DEBUG)for core_lib in $(CORE_LIB_LIST); \
        do for core_lib_src_vhd in $($$core_lib.VHDL_SRC_FILES_LIST); \
            do $(COMPILER_VHDL) $(CC_VHDL_OPTIONS) $(COVER_OPTIONS) -work $$core_lib $(BLOCK_PATH)/cores/$$core_lib_src_vhd; \
        done; \
    done;

But $($$core_lib.VHDL_SRC_FILES_LIST) is unrecognized.

Upvotes: 0

Views: 100

Answers (1)

Renaud Pacalet
Renaud Pacalet

Reputation: 29137

I guess that in $($$core_lib.VHDL_SRC_FILES_LIST) core_lib is a shell variable and you want make to expand it first, and then expand the make variable which name is ${core_lib}.VHDL_SRC_FILES_LIST. This is not how make works. You cannot expect make to expand shell variables.

Instead you should rely on make variables only. Assuming:

  • make variable CORE_LIB_LIST is the list of libraries,
  • for each library LIB there is a make variable LIB.VHDL_SRC_FILES_LIST listing the source files,
  • the source files are in $(BLOCK_PATH)/cores/,

you could try this:

.PHONY: compile-all-libs

# $(1): library
define COMPLIB_rule
.PHONY: compile-$(1)

compile-$(1):
    $$(DEBUG)$$(COMPILER_VHDL) $$(CC_VHDL_OPTIONS) $$(COVER_OPTIONS) -work $(1) $$(addprefix $$(BLOCK_PATH)/cores/,$$($(1).VHDL_SRC_FILES_LIST))

compile-all-libs: compile-$(1)
endef

$(foreach LIB,$(CORE_LIB_LIST),$(eval $(call COMPLIB_rule,$(LIB))))

Explanation: the define COMPLIB_rule ... endef is just another way to define a make variable named COMPLIB_rule. The $(foreach ... construct must be put flat in the Makefile (not in a recipe). It iterates over the words in the definition of make variable CORE_LIB_LIST. For each word LIB, it replaces $(1) by LIB in the definition of COMPLIB_rule (it also replaces every $$ by a single $) and it instantiates the result as a regular make rule. If the make variable CORE_LIB_LIST evaluates as a b, for instance, the result will be the same as:

.PHONY: compile-a

compile-a:
    $(DEBUG)$(COMPILER_VHDL) $(CC_VHDL_OPTIONS) $(COVER_OPTIONS) -work a $(addprefix $(BLOCK_PATH)/cores/,$(a.VHDL_SRC_FILES_LIST))

compile-all-libs: compile-a

.PHONY: compile-b

compile-b:
    $(DEBUG)$(COMPILER_VHDL) $(CC_VHDL_OPTIONS) $(COVER_OPTIONS) -work b $(addprefix $(BLOCK_PATH)/cores/,$(b.VHDL_SRC_FILES_LIST))

compile-all-libs: compile-b

So, if you type make compile-all-libs, make will try to build compile-a and compile-b, the two pre-requisites of compile-all-libs. In order to build compile-a it will execute the recipe:

$(DEBUG)$(COMPILER_VHDL) $(CC_VHDL_OPTIONS) $(COVER_OPTIONS) -work a $(addprefix $(BLOCK_PATH)/cores/,$(a.VHDL_SRC_FILES_LIST))

which will compile in library a all source files listed in make variable a.VHDL_SRC_FILES_LIST and found in directory $(BLOCK_PATH)/cores. Same with compile-b.

But of course, it would be much better if you were recompiling only what's needed (that is, source files that changed since the last time they were compiled). This can be done with empty tag files that keep track of the last time a source file was compiled:

.PHONY: compile-all-libs

# $(1): library
# $(2): source file basename
define COMPLIB_rule
$$(BLOCK_PATH)/cores/$(1).$(2).tag: $$(BLOCK_PATH)/cores/$(2)
    $$(DEBUG)$$(COMPILER_VHDL) $$(CC_VHDL_OPTIONS) $$(COVER_OPTIONS) -work $(1) $$< && \
    touch $$@

compile-all-libs: $$(BLOCK_PATH)/cores/$(1).$(2).tag
endef

$(foreach LIB,$(CORE_LIB_LIST),$(foreach FILE,$($(LIB).VHDL_SRC_FILES_LIST),$(eval $(call COMPLIB_rule,$(LIB),$(FILE)))))

clean:
    $(DEBUG)rm -f $(BLOCK_PATH)/cores/*.tag

Explanation: there, the foreach-foreach-eval-call iterates over library/source file pairs. For each LIB-FILE pair, it replaces $(1) by LIB and $(2) by FILE in the definition of COMPLIB_rule (it also replaces every $$ by a single $) and it instantiates the result as a regular make rule. All this declares all LIB.FILE.tag files as pre-requisites of target compile-all-libs and declares the rule to build the tag by compiling FILE in LIB and touching the tag file. This is just like if, for each source FILE of library LIB, you added this to your Makefile:

$(BLOCK_PATH)/cores/LIB.FILE.tag: $(BLOCK_PATH)/cores/FILE
    $(DEBUG)$(COMPILER_VHDL) $(CC_VHDL_OPTIONS) $(COVER_OPTIONS) -work LIB $< && \
    touch $@

compile-all-libs: $(BLOCK_PATH)/cores/LIB.FILE.tag

Just type make compile-all-libs and see: make will build all tag files, that is, compile each source file in its own library and touch the tag file. As the VHDL source file is a pre-requisite of the tag file, it is only if the VHDL source file is more recent than the tag file that the recipe will be executed. This is the same as the .o / .c dependency for C programs. The only difference is that we do not use the compilation result itself (.o) because we do not really know what it is with Modelsim. Instead, we create a tag file, just for this purpose. Side effect: it would be exactly the same with a different VHDL compiler/simulator.

This would even give you the possibility to declare dependencies between your source files: if $(BLOCK_PATH)/cores/foo.vhd must be compiled in library FOO_LIB before $(BLOCK_PATH)/cores/bar.vhd can be compiled in library BAR_LIB, you could add:

$(BLOCK_PATH)/cores/BAR_LIB.bar.vhd.tag: $(BLOCK_PATH)/cores/FOO_LIB.foo.vhd.tag

to your Makefile. And there are also many possible improvements like, for instance, per-library goals...

Upvotes: 2

Related Questions