I'm using GNU makefiles to build a C project. I want to keep all build artifacts on an isolated build tree in order to minimize clutter. The project looks like this:
$prefix/
include/$tree/program.h
source/$tree/program.c
build/
objects/$tree/program.o
dependencies/$tree/program.d
Where $prefix
represents the directory of the project and $tree
represents an arbitrary folder structure.
I wanted to match source files in the source/
directory to their object and dependency file counterparts in the build/
tree. So, I wrote the following rules:
# Remove the built-in rules
%.o : %.c
$(objects_directory)/%.o : $(source_directory)/%.c $(dependencies_directory)/%.d
$(compiler_command_line) $(compiler_option_output) $@ $<
$(build_directory)/$(target) : $(objects)
$(compiler_command_line) $(compiler_option_output) $@ $^
Make correctly figures out the compilation target
and the object files needed to build it. However, make stops at this point with the error:
No rule to make target
'build/objects/project/program.o'
, needed by'build/program.dll'
.
So why is this happening, and how do I fix it?
I investigated the problem by running make --print-data-base
, the output of which included:
# Not a target:
build/objects/project/program.o:
# Implicit rule search has been done.
# File does not exist.
# File has not been updated.
Which suggests that the prerequisite is not matching the implicit rule as intended. However, I verified that it does match when I tried to work my way backwards by writing:
object := build/objects/project/program.o
$(object:$(objects_directory)/%.o=$(source_directory)/%.c)
$(object:$(objects_directory)/%.o=%)
These lines result in source/project/program.c
and project/program
, which means the stem is being correctly computed.
I have studied the GNU make documentation and I don't remember reading anything that suggests that this kind of pattern matching can't happen in implicit rule definitions.
Here are the variable definitions:
include_directory := include
source_directory := source
build_directory := build
objects_directory := $(build_directory)/objects
dependencies_directory := $(build_directory)/dependencies
sources := $(wildcard $(source_directory)/**/*.c)
objects := $(sources:$(source_directory)/%.c=$(objects_directory)/%.o)
# Take the name of the project's directory
target := $(notdir $(CURDIR)).dll
compiler_dependency_file = $(patsubst $(source_directory)/%.c,$(dependencies_directory)/%.d,$<)
compiler_options = -I $(include_directory) -MMD -MF $(compiler_dependency_file)
CC = gcc
compiler_command_line = $(CC) $(compiler_options) $(CFLAGS)
compiler_option_output = -o