Matt Conway
Matt Conway

Reputation: 237

Make: How can I auto generate rules to build targets?

The question is a little vague, because I'm not entirely sure of the best way to ask what I'm trying to achieve in such a short summary.

To explain it best this is what I currently have....

common.mk

DESTDIR = ../../install/

tools.mk

CC = gcc
CFLAGS = -fPIC -Wall -Wextra -O2 -g -I.
LDFLAGS = -shared
RM = rm -f
MAKEDIR = mkdir -p

Makefile

include ../../builder/tools.mk
include ../../builder/common.mk

TEST_RUNNERS = test_foo test_bar

test_foo_TESTS = tests/test_foo.c
test_foo_SOURCES = foo.c

test_bar_TESTS = tests/test_bar.c
test_bar_SOURCES = bar.c


# Gather lists of ALL sources and objects required to build test_foo
test_foo_ALL_SOURCES = $(test_foo_TESTS) $(test_foo_SOURCES)
test_foo_ALL_OBJECTS = $(test_foo_ALL_SOURCES:.c=.o)

# Compile All the sources required for test_foo
$(test_foo_ALL_SOURCES:.c=.d):%.d:%.c
    $(CC) $(CFLAGS) -MM $< >$@
include $(test_foo_ALL_SOURCES:.c=.d)

# Build test_foo and clean up temporary build files
test_foo: $(test_foo_ALL_OBJECTS)
    $(CC) -L$(DESTDIR) -o $(strip $(DESTDIR))$(strip $@) $^
    -${RM} ${test_foo_ALL_OBJECTS} ${test_foo_ALL_SOURCES:.c=.d}


# Gather lists of ALL sources and objects required to build test_bar
test_bar_ALL_SOURCES = $(test_bar_TESTS) $(test_bar_SOURCES)
test_bar_ALL_OBJECTS = $(test_bar_ALL_SOURCES:.c=.o)

# Compile All the sources required for test_bar
$(test_bar_ALL_SOURCES:.c=.d):%.d:%.c
    $(CC) $(CFLAGS) -MM $< >$@
include $(test_bar_ALL_SOURCES:.c=.d)

# Build test_bar and clean up temporary build files
test_bar: $(test_bar_ALL_OBJECTS)
    $(CC) -L$(DESTDIR) -o $(strip $(DESTDIR))$(strip $@) $^
    -${RM} ${test_bar_ALL_OBJECTS} ${test_bar_ALL_SOURCES:.c=.d}

What I want to do is remove all the complexity in having to manually add rules for each target, and instead "auto-generate" these rules. It's fairly clean and simple in my own mind...

TEST_RUNNERS = test_foo test_bar

So for each TEST_RUNNER that is specified in the list, a list of SOURCES (the code under test) and a list of TESTS (the unit test sources) must be provided...

test_foo_TESTS
test_foo_SOURCES

I've been playing around with foreach but it's not the right approach, and I'm not entirely sure what I need to do to achieve my goal, so after playing around for a few hours I thought I'd try and ask some of you guys because there's some pretty clever guys here that hopefully may be able to help me!

Another idea I was playing around with was to creating templates that I could call upon to generate these rules:

$(foreach runner,$(TEST_RUNNERS),$(eval $(call COMPILE_ALL_TEST_RUNNER_SOURCES, runner)))
$(foreach runner,$(TEST_RUNNERS),$(eval $(call MAKE_TEST_RUNNER_TEMPLATE, runner)))

 define COMPILE_ALL_TEST_RUNNER_SOURCES
 $($(1)_ALL_SOURCES:.c=.d):%.d:%.c
     $(CC) $(CFLAGS) -MM $< >$@
 include $($(1)_ALL_SOURCES:.c=.d)
 endef


 define MAKE_TEST_RUNNER_TEMPLATE
 $(1): $($(1)_ALL_OBJECTS)
     $(CC) -L$(DESTDIR) -o $(strip $(DESTDIR))$(strip $@) $^
     -${RM} ${$(1)_ALL_OBJECTS} ${$(1)_ALL_SOURCES:.c=.d}
 endef

Upvotes: 1

Views: 865

Answers (2)

Matt Conway
Matt Conway

Reputation: 237

After spending a little more time reading the Make manual, I discovered this very useful page.

https://www.gnu.org/software/make/manual/html_node/Eval-Function.html

which has some really useful information on how I can construct my Makefile exactly how I want to. If anyone is interested, I'm using this as my basis for going forward...

TEST_RUNNERS = test_foo test_bar

test_foo_TESTS = tests/test_foo.c
test_foo_SOURCES = foo.c

test_bar_TESTS = tests/test_bar.c
test_bar_SOURCES = bar.c

.PHONY: test
test: unittest

#
# Template to create rules for each TEST_RUNNER defined within TEST_RUNNERS
#
# $(1) is the name of the test runner
#
define test_TEST_RUNNER_template
THE_$(1)_SOURCES = $$($(1)_TESTS)
.PHONY: unittest unittest_$(1)
unittest: unittest_$(1)
unittest_$(1):
    @echo "?(1)=" $(1) "?@=" $$@ " THE_$(1)_SOURCES="  $$(THE_$(1)_SOURCES)
endef

# Create a rule for all TEST_RUNNERS when "make test" is invoked...
$(foreach runner,$(TEST_RUNNERS),$(eval $(call test_TEST_RUNNER_template,$(runner))))

Upvotes: 1

Beta
Beta

Reputation: 99084

If you're willing to add one more line per target:

test_foo_TESTS = tests/test_foo.c
test_foo_SOURCES = foo.c
test_foo_ALL_OBJECTS := tests/test_foo.o foo.o

then a couple of pattern rules can handle all the rest:

%.o: %.c
    $(CC) $(CFLAGS) -MMD -c $< -o $@

-include *.d tests/*.d

.PHONY: $(TEST_RUNNERS)

$(TEST_RUNNERS): test_% : $(DESTDIR)test_%

$(DESTDIR)test_%:
    $(CC) -L$(DESTDIR) -o $@ $^

(Trust me, this approach to dependency handling is much better than what you had. See here for a detailed explanation.)

If you don't like writing three lines per target, notice that the first two aren't actually used for anything and can be omitted.

If you are fond of the first two and really dislike the the third, yes, you can automate the process of constructing the object list, but it isn't worth the effort for this question.

Upvotes: 0

Related Questions