Search code examples
makefile

Make removes files as intermediate


When trying to organize the compilation output into a build directory, make keeps removing object files. The Makefile is:

MPI_INSTALLATION=/home/gkaf/Software/MPI
IDIR=$(MPI_INSTALLATION)/include
LDIR=$(MPI_INSTALLATION)/lib

CC=gcc
CFLAGS=-I$(IDIR)
LDFLAGS=-L$(LDIR) -Wl,-rpath=$(LDIR)

BUILD_DIR=build
OBJ_DIR=$(BUILD_DIR)/obj
BIN_DIR=$(BUILD_DIR)/bin

SRC_DIR=src

LIBS=-lmpi

.PHONY: all
all: test-mpi

.PHONY: test-mpi
test-mpi: prepare $(BIN_DIR)/test-mpi

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
        $(CC) -c -o $@ $< $(CFLAGS)

$(BIN_DIR)/%: $(OBJ_DIR)/%.o
        $(CC) -o $@ $^ $(LDFLAGS) $(LIBS)

prepare: $(BUILD_DIR) $(OBJ_DIR) $(BIN_DIR)

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

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

The object file build/obj/test-mpi.o generated during the compilation is deleted after the executable build/bin/test-mpi is created.

I believe that make treats build/obj/test-mpi.o as an intermediate file. However, I was expecting that build/obj/test-mpi.o would not be treated as an intermediate file since it appears explicitly in the target $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c and gnu make documentation states that "ordinarily, a file cannot be intermediate if it is mentioned in the makefile as a target or prerequisite".

This behavior has been reported in a similar issue, but I believe that in both cases the files should not be treated as intermediate since they appear in a target. Am I missing something?


Solution

  • I believe that make treats build/obj/test-mpi.o as an intermediate file.

    Yes, that looks right.

    However, I was expecting that build/obj/test-mpi.o would not be treated as an intermediate file since it appears explicitly in the target $(OBJ_DIR)/%.o: $(SRC_DIR)/%.c [...]

    Given that $(OBJ_DIR) expands to build/obj, the pattern $(OBJ_DIR)/%.o matches build/obj/test-mpi.o. That's the opposite of build/obj/test-mpi.o appearing explicitly.

    Even so, you have read the GNU make documentation correctly: make would not consider build/obj/test-mpi.o to be an intermediate file if it was mentioned as a target or prerequisite of some other rule. But it isn't. If make builds that file at all, it is entirely make's idea, notwithstanding the fact that you set the stage for it to come to that decision. This is exactly what it means to be an intermediate file.

    Am I missing something?

    Apparently you are missing what it means for a file to be "mentioned in" a makefile, as the GNU docs put it. It means that the file's name appears literally in the makefile text, after macro expansion, as a target or prerequisite of a rule. Example:

    $(BIN_DIR)/test-mpi: $(OBJ_DIR)/test-mpi.o
    

    or

    $(OBJ_DIR)/test-mpi.o: $(SRC_DIR)/test-mpi.c
    

    Matching a target or prerequisite pattern of an implicit ("pattern") rule does not suffice. In fact, it is exactly files that are generated as intermediates in chains of implicit rules that make aims to remove. Implicit rules defined in the makefile are not distinguished from make's built-in implicit rules in this regard.

    However, although files such as the one you asked about are definitely intermediate files as GNU make defines that term, make has an additional capability here that might serve your purposes. If you want to use a pattern to specify intermediate targets that you want to preserve, then you can do so by designating the pattern as a prerequisite of the special target .PRECIOUS, like so:

    .PRECIOUS: $(OBJ_DIR)/%.o
    

    Intermediate files matching such a pattern will be spared from the automatic deletion to which they otherwise they would be subject.