Home > Software design >  Make offsetting file contents during build
Make offsetting file contents during build

Time:10-21

I'm trying to use Make to ... make modular Dockerfiles. Long story short, I want to centralize certain elements and make the composable and reusable, like classes and functions really, but the Dockerfile syntax does not - and according to the developers, will not - offer any facilities in the image of C's #include or similar composability solutions. Not to worry, #include and friends to the rescue!
Except...

I have the following Makefile in my project:

BUILD_DIR := ${CI_PROJECT_DIR}/build

TEMPLATE_FILES := $(shell find ${CI_PROJECT_DIR} -name '*.build')
TEMPLATE_FILENAMES := $(foreach file,$(TEMPLATE_FILES),$(BUILD_DIR)/$(notdir $(file)).built)
BUILT_TEMPLATES := $(TEMPLATE_FILENAMES:.build.built=.built)

DOCKER_FILES := $(shell find ${CI_PROJECT_DIR} -name '*.Dockerfile')
DOCKER_OBJS := $(foreach file,$(DOCKER_FILES),$(BUILD_DIR)/$(notdir $(file)))

all: $(BUILT_TEMPLATES) $(DOCKER_OBJS)

$(BUILD_DIR)/%.built: $(TEMPLATE_FILES) $(BUILD_DIR) # build any templated Dockerfiles
    cpp -E -P -o $(BUILD_DIR)/$(notdir $@) -I ${CI_PROJECT_DIR}/modules $<
    sed -i 's/__NL__ /\n/g' $(BUILD_DIR)/$(notdir $@)

$(BUILD_DIR)/%.Dockerfile: $(DOCKER_FILES) $(BUILD_DIR)
    cp $< $(BUILD_DIR)/$(notdir $(@))

$(BUILD_DIR):
    mkdir -p $(BUILD_DIR)

.PHONY: clean
clean:
    -rm -r $(BUILD_DIR)

The objective is to run the templated Dockerfiles through GCC to compile the #includes in them into proper Docker instructions, and just copy the rest of the files. Sounds simple enough.

Except that it looks like all the target files are "offset" from their sources - like the file names are correct, but the contents are from a file elsewhere in the list, and with no discernible order either.


One thing that I'm fairly sure is wrong - but even more wrong otherwise - is the line

$(BUILD_DIR)/%.built: $(TEMPLATE_FILES) $(BUILD_DIR) # build any templated Dockerfiles

By all manuals and documentation, it ought to be

$(BUILD_DIR)/%.built: %.build $(BUILD_DIR) # build any templated Dockerfiles

but that's even worse, because then Make just says make: *** No rule to make target '/docker/build/runner-dart-2-18-firebase.built', needed by 'all'. Stop.

I'm out of ideas here, along with my limited knowledge of Make. What am I missing to make Make make - sorry - my Dockerfiles?

CodePudding user response:

This line:

$(BUILD_DIR)/%.built: $(TEMPLATE_FILES) $(BUILD_DIR)

Says that if make wants to build a target that matches that pattern, and it can find all the prerequisites, then the pattern rule matches and the recipe can be used. Let's ignore BUILD_DIR (note that it's always a bad idea to list a directory as a prerequisite, but that's not causing this problem). Suppose TEMPLATE_FILES is set to the value ./foo/foo.build ./bar/bar.build. Now the above rule expands to:

./build/%.built: ./foo/foo.build ./foo/bar.build ./build

What is the recipe?

cpp -E -P -o $(BUILD_DIR)/$(notdir $@) -I ${CI_PROJECT_DIR}/modules $<

First it's always wrong to create a file that is not exactly $@ so you should use just $@ not $(BUILD_DIR)/$(notdir $@). But more importantly, what will $< be set to? It is always set to the first prerequisite, and the first prerequisite is always ./foo/foo.build. So every time you run this recipe, regardless of which .built file you're trying to create, you will always be preprocessing the first .build file.

Your idea that you want this instead:

$(BUILD_DIR)/%.built: %.build $(BUILD_DIR)

is correct, in general. Why do you get the error? Because if you are trying to build the target ./build/foo.built, then the stem (part that matches %) is foo. Then make will look to see if the prerequisite foo.build exists or can be created, because you said the prerequisite is %.build. That file does NOT exist and CANNOT be created (make doesn't know how to create it), because the file is ./foo/foo.build not foo.build which is a totally different file.

You have three options. You can either write separate rules for each source directory:

$(BUILD_DIR)/%.built: foo/%.build
    ...
$(BUILD_DIR)/%.built: bar/%.build
    ...

Or, you can change your generated files so they are not all in the same directory but instead keep the source directory structure; you would change this:

TEMPLATE_FILENAMES := $(foreach file,$(TEMPLATE_FILES),$(BUILD_DIR)/$(notdir $(file)).built)
BUILT_TEMPLATES := $(TEMPLATE_FILENAMES:.build.built=.built)

to just this:

BUILT_TEMPLATES := $(patsubst %.build,$(BUILD_DIR)/%.built,$(TEMPLATE_FILES))

then create the output directory as part of the recipe:

@mkdir -p $(@D)
cpp -E -P -o $@ -I ${CI_PROJECT_DIR}/modules $<
sed -i 's/__NL__ /\n/g' $@

Or finally, you could use VPATH to tell make what directories to look in to find the *.build files:

VPATH := $(sort $(dir $(TEMPLATE_FILES)))

(note, you should choose only one of these options).

  • Related