Compiling multiple executable files with a static pattern rule in Makefile

88 Views Asked by At

I am writing a matrix library in C for practice. To enforce good habits I am testing using the Unity Testing Framework, meaning I compile multiple test files and run them. Since I do not know how many test files I will end up with, I've been trying to write my Makefile such that any .c file in my tests/ directory will be found and used to make a corresponding executable.

Here is my project file structure:

├── bin
├── include
|   ├── fml.h
├── src
|   ├── fml.c
├── test-framework
|   ├── unity.c
|   ├── unity.h
|   ├── unity_internals.h
├── tests
|   ├── check_fml_at.c
|   ├── check_fml_set_at.c
|   ├── check_fml_mem_management.c
├── Makefile
├── Readme

And here is the Makefile I'm struggling with:

BIN_DIR = bin
SRC_DIR = src
TESTS_DIR = tests

FML = $(BIN_DIR)/fml.o
TEST_FRAMEWORK = $(BIN_DIR)/unity.o

CFLAGS = -Wall -Werror -g
CC = gcc

TESTS = $(patsubst %.c, %, $(patsubst $(TESTS_DIR)/%,  \
$(BIN_DIR)/%, $(wildcard $(TESTS_DIR)/*.c)))

$(info $$var is [${TESTS}])

$(TESTS): $(BIN_DIR)/%: $(TESTS_DIR)/%.c $(FML) $(TEST_FRAMEWORK)
    $(CC) $(CFLAGS) $^ -o $@ -Iinclude -Itest-framework
    
$(FML): src/fml.c include/fml.h
    $(CC) $(CFLAGS) -c $< -o $@ -Iinclude

$(TEST_FRAMEWORK): test-framework/unity.c test-framework/unity.h test-framework/unity_internals.h
    $(CC) $(CFLAGS) -c $< -o $@

memcheck:
    valgrind $(TESTS)

check:
    $(TESTS)

clean:
    rm $(TESTS) $(OBJS)

Each test file needs the library (fml) and the test framework (unity).

The expected behavior when running make is for check_fml_at, check_fml_set_at, and check_fml_mem_management to appear as executables in the bin/ file, along with fml.o and unity.o libraries. I've checked and the TESTS variable correctly evaluates to check_fml_at, check_fml_set_at, and check_fml_mem_management. But for some reason, only the first test is built, and when I run make I only see check_fml_at along with fml.o and unity.o in the bin dir.

Why is the makefile behaving this way? And what can I do to ensure every file in TESTS is built so I can run every test?

Thank you for any help! -- Tomas Dougan

1

There are 1 best solutions below

1
John Bollinger On

There does not appear to be anything inherently wrong with your static pattern rule. The reason that you are getting only one test built when you run make without arguments is that make then chooses a default target, not a default rule. If the first rule in the makefile has multiple targets then the one that appears first is the default target.

If you want the default to be to build all the tests, then insert a new first rule that specifies all the tests as prerequisites:

.PHONY: all
all: $(TESTS)

Do note, by the way, that your memcheck and check recipes aren't going to work as you appear to intend when more than one test is designated by $(TESTS). Instead of running each test, you will run just the first, with the names of the others passed as command-line arguments. Perhaps you want something more like

.PHONY: memcheck check

memcheck:
        for t in $(TESTS); do valgrind $$t; done

check:
        for t in $(TESTS); do $$t; done