I'm trying to create targets that depend on a list of files in a directory with the name of the target:
bin/%.out: src/%/ $(shell find src/%/* -type f -iname '*.cpp' -o -iname '*.hpp')
# Build stuff here
However shell find src/%/* ...
ends up expanding to shell find src//* ...
. At first I thought it's because I could only have 1 target wildcard but even after removing the src/%/
dependency it ended up with this same problem.
Some more context: my directory contains a 'src' directory, which contains directories. Each sub-directory of 'src' I treat as a "project". When I build, all object files should go in out/src/projname
. I'm attempting to use find
to recursively get all source and header files per project. All binaries will go in bin/projname.out
and as such, the main dependencies are .out
files in bin
that have the same names as their project name. (if src/abc
exists, bin/abc.out
is a dependency of all
).
So for each sub-directory in src
, all source files are compiled and the object files are moved to out/projname
which eventually will be linked to bin/projname.out
.
- src
- proj0
- cpp/hpp files
- proj1
- cpp/hpp files
- proj0
- out
- src
- proj0
- object files
- proj1
- object files
- proj0
- src
- bin
- proj0.out
- proj1.out
Currently getting the list of projects and list of output files as follows:
SRCS := $(shell find src/* -maxdepth 0 -type d)
SRCS_OUT := $(patsubst src/%,bin/%.out,$(SRCS))
...
all: out/ bin/ $(SRCS_OUT)
Projects can contain sub-directories and such, which is why I'm using find
in the code at the very top.
I do not see how to do what you want with standard, portable, make features. But it is doable with advanced GNU make features:
We first define a make variable (
PROJECT_rule
) that will be used as a template for any of your projects; in this template,$(1)
will represent the name of your project. Then, we compute the list of projects (PRJS
) and, finally, we iterate over the projects and process our template for each of them (foreach-eval-call
).Notes:
I assumed that each object file of a project depends on all header files of the same project. If it is not the case you will have to rework this a bit.
What is important to understand is that:
PROJECT_rule
variable is first processed bycall
to substitute$(1)
with the project's name.Then it is expanded recursively and completely (including the recipes) by
eval
. Recursively means that if we have:$($(BAR))
is expanded asfoo
while$$(BAZ)
is expanded as$(BAZ)
. Completely means that all parts of thePROJET_rule
variable are expanded, even what is finally used as recipes (while when make parses a Makefile, it normally defers the expansion of the recipes to a later stage).The result will be instantiated as regular make constructs, that is, it will be expanded once more as regular make constructs (not the recipes), before being evaluated.
So, as there will be two expansions of
PROJECT_rule
, some$
signs must be escaped ($$
). Please remember this when writing your own<compile recipe>
and<link recipe>
. If you want to use the$@
or$^
automatic variables, for instance, do not forget to write$$@
. Example: if your compile recipe is:write:
in the definition of
PROJECT_rule
. The first expansion will transform it in:While, if you write:
the first expansion will transform it in something like:
Note that, unless
CPP
,CPPFLAGS
orINCLUDES
have specific target-dependent definitions, you can also write:because the first expansion will transform it in something like:
which is also correct.
A bit more detailed explanation: for each project (e.g.
proj0
),foreach
will produce:call
will substitute$(1)
withproj0
in thePROJECT_rule
definition, such that the parameter ofeval
will be:eval
will expand it recursively and completely, leading to:(each
$$
becomes$
, even in the recipes).eval
will then instantiate the result as regular make constructs and they will be parsed as regular make syntax. That is, the (simple) variables will be expanded immediately, the targets and prerequisites too, but not the recipes. The recipes will be expanded in a second phase, just before being passed to the shell and executed, if they are.The compile rule is a static pattern rule. If you do not know this already you can read this section of the GNU make documentation.
Exercise: in the definition of
PROJECT_rule
you could replace$$(shell...
by$(shell...
but not$$(patsubst...
by$(patsubst...
. Why?