Makefile pattern rule with multiple sources in different subdirectories - makefile

I want a pattern rule for a Makefile for turning several sources into a single object.
So instead of a filename like foo.c I have a folder name foo/.
Additionally, there's path of the directory stem src/bar/foo/ where not only the shared src/ needs to be ignored but also the variable bar/.
Directory Structure
I have several objects to create - let's call them this:
bin/lib1.so
bin/lib2.so
bin/lib3.so
The source directory has a structure like this with folders grouping the libs:
src/
foo/
lib1/
a.c
b.c
bar/
lib2/
d.c
e.c
lib3/
f.c
g.c
If the grouping of libs didn't exist it would be straightforward, but it does exist.
Makefile
The targets get built by make all which is given for this example explicitly (I actually use shell find as in this answer, because there's several of them, but for this example I keep it simple):
all: bin/lib1.so bin/lib2.so bin/lib3.so
Now I would like to clean up the Makefile by creating a pattern rule for making all of these targets, kind of like this:
bin/%.so: $$(wildcard src/*/%/*.c)
# testing the rule:
echo $< > $#
But this doesn't work.
I'm skipping the step of c->o for simplicity because the point is linking together several files.
There's this question which has a similar problem of subdirectories, but it's only about 1:1 mapping of files instead of n:1, and I couldn't get it to work for my problem.
Desired Result
To test the rule it should just show in each target file what sources went into it:
bin/lib1.so
src/foo/lib1/a.c
src/foo/lib1/b.c
bin/lib2.so
src/bar/lib2/d.c
src/bar/lib2/e.c
bin/lib3.so
src/bar/lib3/f.c
src/bar/lib3/g.c

Here is a working version that supports one or multiple C files as input:
bin/%.so: src/*/%/*.c
#echo "START"
#echo $^
#echo $#
#echo "END"
all: bin/lib1.so bin/lib2.so bin/lib3.so
Result:
$ make
START
src/foo/lib1/main.c
bin/lib1.so
END
START
src/bar/lib2/plip.c src/bar/lib2/plop.c
bin/lib2.so
END
START
src/bar/lib3/kilo.c
bin/lib3.so
END

Related

Building nested directories using makefile

I tried to build a nested directories following: Iterating through a list of directories in a Makefile
Here is my script
LAYER1 = a b
LAYER2 = 1 2
$(LAYER1)/foo%/run: $(DIR)
mkdir -p $#
DIR: $(addsuffix /foo$(LAYER2)/run, $(LAYER1))
The expected output is creating a/foo1/run a/foo2/run b/foo1/run b/foo2/run but the output is only 1 directory named a. How do I create the expected directory structure?
What does the rule:
$(LAYER1)/foo%/run: $(DIR)
expand to? It expands to this:
a b/foo%/run: $(DIR)
(I don't know what $(DIR) is supposed to be) which is an explicit rule that tells make how to build two things: a and b/foo%/run (this is not a pattern rule because not all the targets contain %).
Since a here is the first target in the makefile, it's the one that will be run by default when you run make.
What does $(addsuffix /foo$(LAYER2)/run, $(LAYER1)) do? It takes every word in $(LAYER1) and prefixes it by the string /foo$(LAYER2)/run. What is that string? It's /foo1 2/run. So the result of this is:
a/foo1 2/run b/foo1 2/run
which means DIR has the files a/foo1, 2/run, b/foo1, and 2/run as dependencies.
If LAYER1 has multiple words you need to loop through it, not just use it as-is.
See MadScientist's answer as to why your answer would not work. As far as something that will work, you likely want code that looks like this:
LAYER1 = a b
LAYER2 = 1 2
DIRS := $(foreach L1,$(LAYER1),\
$(foreach L2,$(LAYER2),\
$(L1)/foo$(L2)/run))
all: | $(DIRS)
$(DIRS):
mkdir -p $#
Notice the use of order-only dependencies as the prerequisite type for $(DIRS)

copying only changed files between directories using Makefile

I read this: Makefile: Copying files with a rule but couldn't do it.
To make it simple, suppose I have two directories dir1 and dir2. Under dir1 I have three files: rabbit.c, tiger.c and bus.c .
I made Makefile like this:
dir2/rabbit.c:dir1/rabbit.c
dir2/tiger.c:dir1/tiger.c
dir2/bike.c:dir1/bike.c
dir2/%:
cp -f $< $#
I specified the prerequisites in three separate lines and specified the unified recipe for the three targets. I expected when I touch any file under dir1, make will copy that file to dir2. But this happend only for rabbit.c. What is wrong?
ADD(after selecting an answer) :
After realizing what's wrong by Takkat's answer, I fixed it and later modified it further and I think this is the correct simplest Makefile for this case.
.PHONY:all
LIST:=rabbit.c tiger.c bike.c
DSTFILES:=$(addprefix dir2/, $(LIST))
all: $(DSTFILES)
dir2/%:dir1/%
cp -f $< $#
Make chooses a default target in your makefile and, unless you specify differently on the command line, it builds just that target (and all prerequisites required to build that target).
The default target in a makefile is, by default, the first explicit target listed.
So in your makefile the first rule is:
dir2/rabbit.c:dir1/rabbit.c
so the first explicit target is dir2/rabbit.c, so that's all make builds.
If you want to build multiple targets by default, you need a first target that lists all the "real" targets as prerequisites; put this line first in your makefile:
all: dir2/rabbit.c dir2/tiger.c dir2/bike.c
and it will work. It's often considered good practice to declare targets like this, which don't relate to real files on the disk, as phony:
.PHONY: all

Makefile's 'vpath' doesn't work when searching prerequisites with wildcards

My project includes .c and .s (asm) files. I compile both types with 'gcc' and put output .o files to separate directory './bin'. To do that I'm using single makefile rule like this
bin/%.o: %.[cs]
$(CC) $(CFLAGS) -o $# -c $<
(As far as I understand, using square brackets wildcard in such context is a little bit unconventional, but it's working and it looks neat, so...)
The other day I decided to move some of my .c files to dedicated directory './common', so I added
vpath %.c common
at the beginning of the makefile. And now each time I try to 'make', it stops and throws an error on a file I had moved. For example, for 'common/foo.c' I get
"*** No rule to make target bin/foo.o, needed by..."
as if I haven't specified 'vpath'. But when I modify the rule to compile only .c files
bin/%.o: %.c
... ...
magically it starts to operate properly again and checks './common' for sources.
Looks like 'vpath' mechanism and wildcards can not work together, but I'm still new to 'make' and eager to learn what's the exact reason of such behavior. Any ideas anyone? Thanks in advance.
(Tested with make–3.81 and make–4.1.)
UPD: Having all the files and 'bin' directory reside on the same level like so
|-bin/
|-foo.c
|-bar.s
|-baz.c
|-Makefile
here's MWE
ROOTS = foo.o bar.o
OBJS = baz.o
SS = $(addprefix bin/,$(ROOTS) $(OBJS))
all: ff.out
ff.out: $(SS)
ld -o $# $^
bin/%.o: %.[cs]
gcc -o $# -c $<
Now if I move, say, 'foo.c' to separate directory and specify 'vpath', build stops with "No rule to make target bin/foo.o, needed by ff.out".
I suggest careful reading of How Not to Use VPATH as you seem to be at Step Three of that by having the OBJDIR in some places but not others.
To be explict, using a static pattern rule doesn't get you away from needing either at least one rule per source directory, or at least one make invocation per source directory. So, the simple answer is add a new rule for the new common/ directory that's the same as the other one:
bin/%.o: common/%.[cs]
gcc -o $# -c $<
There are lots of more comprehensive, but complex, answers, see the followon article for some of them.
For simple projects, there is no reason not to just track what directories you have in your main Makefile by adding extra rules. Also, there's a reasonable case for not having that bin/ dir and splitting .o and .out locations. Distributors and others expect to be able to control where files are created running from a seperate directory anyway.
I've thrown up a git repo with branches based on your cut down example that may clarify things.

Makefile dependency-iterating generic rule

I've been looking through makefile syntax manuals and haven't found anything that really helps the usage case I'm trying to enact here.
What I have is a list of source files with varying directories under a common directory, like so:
src/a.h
src/b.h
src/dir/c.h
src/dir/dir/d.h
and would like make to use these individually as a dependency for a rule that ultimately creates:
build/a.h
build/b.h
build/c.h
build/d.h
which then are used as dependencies individually for more rules.
What I have so far:
LIST := src/a.h src/b.h src/dir/c.h src/dir/d.h
all : $(addprefix build/,$(notdir ${LIST}))
#echo 'All rule invoked'
What doesn't work:
$(LIST) : build/$(notdir %).h : %.h
#echo 'dst $* dat $# din $<'
target 'item' doesn't match the target pattern
build/%.h: %.h
no rule to make target 'build/a.h' needed by 'all'.
I'm guessing make got mad at me at this point, as the errors started telling me to stop.
Basically, I am reading in a list of files with a path prefix that is relevant for the search path and dependency, and want to dump each individual one only when the source file is updated. After this, these files in that single directory are used as dependencies for another batch of rules. How can I accomplish this?
Note: I've gotten it done by ignoring the dependency chain, but that's not going to work. I can also use make to run scripts that generate an explicit makefile that can do it properly, but that feels like overkill and a waste of resources, and make ought to be able to create a rule that does that by itself, as powerful as it is. I just don't know how to create generic rules that focus on the dependency variable for its text matching, rather than the target.
There's no good way of using a pattern rule here, as all the headers are (potentially) in different directories and you want to move them out to a common directory. If you're using GNU make, you can write a macro rule that expands to all the rules you need:
define copy_header_rule
build/$(notdir $(1)): $(1)
cp $$< $$#
endef
$(foreach hdr,$(LIST),$(eval $(call copy_header_rule,$(hdr))))
This goes through each of the headers in your $(LIST) a creates a rule to copy it to the build directory
You can make things pretty simple with vpath:
TARGS:= $(addprefix build/, $(notdir $(LIST)))
vpath %.h $(dir $(LIST))
all: $(TARGS)
build/%.h: %.h
#echo building $# from $<
...

Directory independent target in Makefile

I'm trying to make a rule that will generate files regarding their names but regardless of the directory.
I'm starting from this makefile (see a previous question of mine):
TARGETS:=$(patsubst %_tpl,%,$(wildcard *_tpl))
.PHONY: all
all: $(TARGETS)
.SECONDEXPANSION:
$(TARGETS): %: $$(wildcard %*_tpl)
./generate $#_tpl > $#
With this, I can do, for instance, make foo.xml. It looks if a set of foo.xml*_tpl files are there, consider them as prerequisites and call the generate script to generate the target.
What I would like to do is, for example, make ../ressources/foo.xml and have make use the rule to create foo.xml but creating it in the ../ressources/ directory, without having to explicitely specify this directory in the makefile.
What I have tried for the moment is adding this to the Makefile:
../ressources/%: $(notdir %)
mv $< $#
Which works, but I would like to avoid creating the file in the current directory before moving it to the destination folder. I would also like not having to specify the possible destination folders in the makefile (but this is less important).
But first of all, does this make any sense? Or is what I want to do just conceptually wrong?
EDIT: Some precisions regarding the _tpl files and the generate script to avoid confusions:
Each target has a main template ($#_tpl) that includes others ($#-part1_tpl, $#-part2_tpl...) and the generate script only takes the main template as argument. The templates are written with Jinja2 (the subparts included with the {% include %} jinja directive).
If you always want the targets in another directory, just say so.
TARGETS:=$(patsubst %_tpl,../resources/%,$(wildcard *_tpl))
.PHONY: all
all: $(TARGETS)
.SECONDEXPANSION:
$(TARGETS): ../resources/%: $$(wildcard %*_tpl)
./generate $#_tpl > $#
I'm not sure if you should have generate $^ >$# instead; superficially, this would make more sense.
If there are multiple *_tpl files for each target (i.e. there are more tpl files than xml files), the TARGETS definition isn't really correct; but we don't have enough information to actually fix it.
On the other hand, if the target directory can change a lot, the sane way forward might be to cd into the target directory and use make -f ../path/to/Makefile -- just make sure your VPATH is set up so that the source files can be found.

Resources