demeshchuk
demeshchuk

Reputation: 742

Using a result of shell's find as a target in a Makefile

Say, there's a source directory with non-defined internal structure, and we want to grab all the files and declare the files they are supposed to be compiled into as targets.

Something like that:

SOURCES := $(shell find src -name \*.c)
OBJS := $(addprefix /tmp,$(addsuffix .o,$(basename $(notdir $(SOURCES)))))
$(OBJS): $(SOURCES)
    cc $(CFLAGS) -o $@ $^

However, it starts with the second line not working (it's just being empty). So, I ended up replacing it:

OBJS := $(shell find src -name \*.c -exec sh -c "basename {} | sed 's/^/\/tmp\/g' | sed 's/\.c/\./o/g'" \;)

I don't like this approach much, but otherwise it doesn't seem to be working anyway. Okay, now it's expected to be working. However, the targets don't work as expected, say, as if I declared them explicitly:

TARGETS := /tmp/foo.o /tmp/bar.o
$(TARGETS): $(SOURCES)
    ...

Instead, I just get an error No rule to make target /tmp/foo.o.

So, guess, I have two questions:

  1. Why the very first code snippet makes $(OBJS) empty?
  2. Is there a way to actually implement dynamic targets?

Upvotes: 2

Views: 7935

Answers (1)

Nikolai Popov
Nikolai Popov

Reputation: 5685

Testing the first snippet on my machine

$ uname -a
Linux - 3.2.0-4-686-pae #1 SMP Debian 3.2.54-2 i686 GNU/Linux

$ make -v
GNU Make 3.81

$ cat makefile
SOURCES := $(shell find ./ -name \*.c)
OBJS := $(addprefix /tmp,$(addsuffix .o,$(basename $(notdir $(SOURCES)))))
$(info $(OBJS))
$ mkdir test; touch test/1.c; touch test/2.c; make -f makefile

Output:

/tmp1.o /tmp2.o

The test shows that the first snippet is ok, except for the missing slash after /tmp.

Questions:

  • How do you test the emptiness of OBJS?
  • Are you sure that SOURCES is not empty?
  • What is the output of make with $(info $(SOURCES)) in makefile?
  • Are there any *.c files in the src directory?

Testing the second snippet on my machine

$ sed --version
GNU sed 4.2.1

$ cat makefile
SOURCES := $(shell find ./ -name \*.c)
OBJS := $(shell find ./ -name \*.c -exec sh -c \
          "basename {} | sed 's/^/\/tmp\/g' | sed 's/\.c/\./o/g'" \;)
$(info $(OBJS))
$ make -f makefile

Output:

sed: -e expression #1, symbol 10: unknown modifier for s'
sed: -e expression #1, символ 12: incomplete command s'
sed: -e expression #1, символ 12: incomplete command s'
sed: -e expression #1, символ 10: unknown modifier for s'

With that version of the second snippet, OBJS is not empty.

Note the removed '\' in the first sed command and the removed '/' in the second command:

OBJS := $(shell find ./ -name \*.c -exec sh -c \
          "basename {} | sed 's/^/\/tmp\//g' | sed 's/\.c/\.o/g'" \;)

Is there a way to actually implement dynamic targets?

Yep. It's easy, if you can store .o files in the source directories.

The next snippet does the trick:

sources := $(shell find ./ -name \*.c)
objects := $(sources:.c=.o)

$(objects): %.o: %.c
    $(cc) -c $<

But perhaps it's not what you want.

With using VPATH and a couple of additional strings, you can do better.

target   := ./a.out
obj_dir  := ./tmp/
sources  := $(shell find ./ -name \*.c)
objects  := $(addprefix $(obj_dir), $(notdir $(sources:.c=.o)))
dirs     := $(dir $(sources))
VPATH    := $(dirs)

$(target): $(objects)
    $(cc) -o $@ $^

$(objects): $(obj_dir)%.o: %.c
    $(cc) -o $@ -c $<

Upvotes: 4

Related Questions