How do I make multiple targets from a set of templates for the source?

I have targets identified by a version number (7.0 7.1 7.2)

I have some templates that have a placeholder that needs to be replaced for each version such that I end up with a directory for each version with all the files in each directory having the appropriate replacements.

So, if the template directory is :


I want to end up with :


with the the tag in the template\Dockerfile being replaced with the version number.

This is a simplified example. There are actually 4 files in the template folder (and 2 are in a subdirectory).

A pipeline will run make build-targets and then the appropriate docker commands to build the containers and push them to the repository - that's the goal.

Whilst it would be nice to have an answer to all of this, I would like to learn, but I can't find anything about how to deal with sets of sources and sets of targets.

Any advice would be appreciated.

More details in response to comments

The versions are currently just 1 line in the Makefile: VERSIONS := 7.0 7.1 7.2 latest

The code to run will be a series of sed commands to take a template and replace a #version# tag in the file with the version number (except for latest in which case it will just remove the tag.

The template sources are all in the templates directory. As part of the sed command, the resulting filename will have the part templates replaced with the version (including latest).

A start ... (no idea if this is good or not - still learning)

VERSIONS  := 7.0 7.1 7.2 latest
SOURCEDIR := ./templates
DESTDIR   := ./
TEMPLATES := $(shell find $(SOURCEDIR) -type f)

# For each file in $(TEMPLATES) for each version in $(VERSIONS), create a corresponding file in $(DEST_DIR)/$(VERSION)
# Replace `#version#` with $(VERSION) in each file, except for the `latest` target where the `#version#` removed.

Based upon solutions provide below, my final code is:

VERSIONS := 7.0 7.1 7.2 latest
SOURCE_DIR := ./templates
TEMPLATES := $(shell find $(SOURCE_DIR) -type f)
TEMPLATES := $(patsubst $(SOURCE_DIR)/%,%,$(TEMPLATES))
DEST_DIR := ./

.PHONY: all

# $(1): version
# $(2): template file (without the $(SOURCE_DIR)/ stem)
define generate_target_file
$(DEST_DIR)/$(1)/$(2): $(SOURCE_DIR)/$(2)
    @echo "Making $$@"
    @mkdir -p $$(dir $$@)
    @if [ "$(1)" == "latest" ]; then \
        sed 's/#version#//g' $$< > $$@; \
    else \
        sed 's/#version#/$(1)-/g' $$< > $$@; \

all: $(DEST_DIR)/$(1)/$(2)

$(foreach version,$(VERSIONS),$(foreach template,$(TEMPLATES),$(eval $(call generate_target_file,$(version),$(template)))))

    @echo 'The following targets are available :'
    @$(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | \
        awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | \
        sort | \
        egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | \
        xargs -0

1 answer

  • answered 2018-10-12 11:41 Renaud Pacalet

    If you are using GNU-make, you can easily create loops with make's foreach function. And you can nest them. The eval function can be used to programmatically instantiate make constructs and the call function allows you to create kind of macros:

    V := 7.0 7.1 7.2 latest
    S := ./templates
    T := $(shell find $(S) -type f)
    T := $(patsubst $(S)/%,%,$(T))
    D := ./destdir
    .DEFAULT_GOAL := all
    .PHONY: all
    # $(1): version
    # $(2): template file (without the $(S)/ stem)
    # $(3): replacement string
    define MY_rule
    $(D)/$(1)/$(2): $(S)/$(2)
        mkdir -p $$(dir $$@)
        sed 's/#version#/$(3)/g' $$< > $$@
    all: $(D)/$(1)/$(2)
    $(foreach v,$(V),\
      $(foreach t,$(T),\
        $(eval $(call MY_rule,$(v),$(t),$(patsubst latest,,$(v))))))

    The GNU make manual explains how this works. Beware the double expansion of the MY_rule macro, reason why some $ signs must be doubled.