1

My directory structure is as followed:

tests\
        test1.c
        test2.c
        test3.c
        build\
                 test1
                 test2
                 test3

Concretely, I want to build test1.c, test2.c and test3.c and output the executables in tests\build.

To achieve that I have the following Makefile snippet:

TSTS = $(wildcard $(TEST_DIR)/*.c)
EXE = $(patsubst $(TEST_DIR)/%.c,$(TEST_BUILD)/%, $(TSTS))
TEST_DIR = tests
TEST_BUILD = tests/build

$(EXE): $(TSTS)
        $(CC) $< $(CFLAGS_OUT) $@ (CFLAGS_LIBS_FLAG)$(LIB_NAME)

Which gives me something like that:

gcc -L/home/projects/loc/build -Iinclude tests/test1.c -o tests/build/test1 -lsalmalloc
gcc -L/home/projects/loc/build -Iinclude tests/test1.c -o tests/build/test2 -lsalmalloc
gcc -L/home/projects/loc/build -Iinclude tests/test1.c -o tests/build/test3 -lsalmalloc

The output paths are correct, but the source files are all tests/test1.c!

Ignore the include and other lib related files because I have removed the flags to make it simple. Essentially, make is building test cases from a single file, that is, for a single source file, test1.c, three builds are being generated with different names, instead of each test have its own build. I have looked at here, here, here and here but cannot find the problem.

How can I fix this?

Community
  • 1
  • 1
user1343318
  • 2,093
  • 6
  • 32
  • 59

3 Answers3

3

Your target

$(EXE): $(TSTS)

expands to

tests/build/test1 tests/build/test2 tests/build/test3: tests/test1.c tests/test2.c tests/test3.c

which, reading the manual about Multiple Targets in a Rule,

A rule with multiple targets is equivalent to writing many rules, each with one target, and all identical aside from that. The same recipe applies to all the targets, but its effect may vary because you can substitute the actual target name into the recipe using ‘$@’. The rule contributes the same prerequisites to all the targets also.

Similar recipes work for all the targets. The recipes do not need to be absolutely identical, since the automatic variable ‘$@’ can be used to substitute the particular target to be remade into the commands (see Automatic Variables). For example:

bigoutput littleoutput : text.g
   generate text.g -$(subst output,,$@) > $@

is equivalent to

bigoutput : text.g
   generate text.g -big > bigoutput
littleoutput : text.g
   generate text.g -little > littleoutput

is the same as

tests/build/test1: tests/test1.c tests/test2.c tests/test3.c
tests/build/test2: tests/test1.c tests/test2.c tests/test3.c
tests/build/test3: tests/test1.c tests/test2.c tests/test3.c

so when you use $< in the recipe you always get tests/test1.c.

What you want instead is a pattern rule.

Thus, a rule of the form

%.o : %.c ; recipe…

specifies how to make a file n.o, with another file n.c as its prerequisite, provided that n.c exists or can be made.

So you want

TSTS = $(wildcard $(TEST_DIR)/*.c)
EXES = $(patsubst $(TEST_DIR)/%.c,$(TEST_BUILD)/%, $(TSTS))

all: $(EXES)

$(TEST_BUILD)/%: $(TEST_DIR)/%.c
        $(CC) $< $(CFLAGS_OUT) $@ (CFLAGS_LIBS_FLAG)$(LIB_NAME)
Community
  • 1
  • 1
Etan Reisner
  • 77,877
  • 8
  • 106
  • 148
  • Thank you @Etan. One question though, what is the role of `all: $(EXES)` here since this target is not being used subsequently. – user1343318 Aug 25 '15 at 15:04
1

That's simply not how multiple targets work. When you have:

test1 test2 test3 : test1.c test2.c test3.c
    $(CC) $< ...

That gets expanded to:

test1 : test1.c test2.c test3.c
    $(CC) $< ...

test2 : test1.c test2.c test3.c
    $(CC) $< ...

test3 : test1.c test2.c test3.c
    $(CC) $< ...

Since $< grabs the first prequisite, that's why you see test1.c everywhere. What you want to do instead is a static pattern:

$(EXE) : $(TEST_BUILD)/% : $(TEST_DIR)/%.c
    $(CC) $< ...
Barry
  • 286,269
  • 29
  • 621
  • 977
1

Your rule is equivalent to

tests/build/test1 tests/build/test2 tests/build/test3: \
    tests/test1.cc tests/test2.cc tests/test3.cc
        $(CC) $< $(CFLAGS_OUT) $@ (CFLAGS_LIBS_FLAG)$(LIB_NAME)

$< means the first prerequisite, so tests/test1.cc in this case. You probably meant $^, the names of all prerequisites, however that would not help you, because then you would try to compile all .cc files together into one object file, three times.

What you probably want instead is a pattern rule:

$(TEST_BUILD)/%: $(TEST_DIR)/%.cc
        $(CC) $< $(CFLAGS_OUT) $@ (CFLAGS_LIBS_FLAG)$(LIB_NAME)

You also may need to add a dummy target

all: $(EXE)

depending on what else you have in that makefile.

ex-bart
  • 1,352
  • 8
  • 9
  • Why do I need a dummy target? – user1343318 Aug 25 '15 at 16:13
  • @user1343318 It depends on how you invoke the makefile. If you call make without arguments, it will look for the first rule and build that. However, it does not consider pattern rules when doing so. I proposed to replace a normal rule by a pattern rule, and if you do that make will not longer know what to build (unless you specify that explicitly). The typical way to explicitly tell make what to build by default is to define a rule with target `all` and the default target as dependencies as the first normal rule in the makefile. Ideally, all should also be phony. – ex-bart Aug 25 '15 at 16:31