I'm trying to compile my project with the Criterion framework for unit testing. Here's how my project is organized:
.
├── include
│ └── mysh.h
├── Makefile
├── obj
├── src
│ ├── arrays_handler.c
│ ├── builtin_env.c
│ ├── delete_from_env.c
│ ├── display_env.c
│ ├── display_prompt.c
│ ├── doubly_linked_list.c
│ ├── env_handler.c
│ ├── execute_command.c
│ ├── mysh.c
│ ├── my_strcmp.c
│ ├── my_strcpy.c
│ ├── my_strdup.c
│ ├── my_strlen.c
│ ├── my_strncmp.c
│ ├── my_strndup.c
│ ├── my_strrchr.c
│ ├── my_str_to_word_array.c
│ ├── name_slice.c
│ ├── parse_command.c
│ ├── sanitize.c
│ ├── shell_handler.c
│ └── strbind.c
└── tests
├── Makefile
└── sanitize_test.c
To compile my project, I use a first Makefile (the one at the root), which works very well. Here are its contents:
INCLUDE_DIR := include
OBJ_DIR := obj
SRC_DIR := src
NAME := mysh
CC := gcc
CFLAGS := -Wall -Wextra -Werror -ggdb3
CPPFLAGS := -I$(INCLUDE_DIR)
SRC_FILES := $(wildcard $(SRC_DIR)/*.c)
OBJ_FILES := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC_FILES))
all: $(NAME)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
@$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
$(NAME): $(OBJ_FILES)
@$(CC) $(CFLAGS) -o $@ $^ $(LDLIBS)
clean:
@$(MAKE) -C ./tests tests_clean
@rm -rf $(OBJ_DIR)/*.o
fclean: clean
@$(MAKE) -C ./tests tests_fclean
@rm -rf $(NAME)
tests_run: fclean
@$(MAKE) -C ./tests tests_run
re: fclean all
.PHONY: all clean fclean tests_run re
The problem seems to lie with my second Makefile (in the test folder), which is unable to compile the tests_run rule. Here are its contents:
CC := gcc
CFLAGS := -Wall -Wextra -Werror -ggdb3
CPPFLAGS := -I../include
LDFLAGS := -Lcriterion/lib
LDLIBS := -lcriterion
NAME = ../unit-tests
OBJ_DIR = ../obj
SRC = $(shell find ../src -name '*.c' ! -name 'mysh.c')
TEST_SRC = $(shell find . -name '*.c')
OBJ = $(patsubst ../src/%.c,$(OBJ_DIR)/%.o,$(SRC))
TEST_OBJ = $(patsubst ./%.c,$(OBJ_DIR)/%.o,$(TEST_SRC))
$(OBJ_DIR)/%.o: %.c
@mkdir -p $(OBJ_DIR)
@$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
tests_run: $(OBJ) $(TEST_OBJ)
@$(CC) $(CFLAGS) $(LDFLAGS) $(filter-out ../obj/mysh.o,$(TEST_SRC) $(SRC)) $(LDLIBS) -o $(NAME)
@./$(NAME)
tests_clean:
@rm -rf $(OBJ)
@rm -f ../*.gcda
@rm -f ../*.gcno
tests_fclean: tests_clean
@rm -f $(NAME)
.PHONY: tests_run tests_clean tests_fclean
I get the following error when calling tests_run:
make[1]: enter the "[...]/tests" directory
make[1]: exit the "[...]/tests" directory
make[1]: enter the "[...]/tests" directory
make[1]: exit the "[...]/tests" directory
make[1]: enter the "[...]/tests" directory
make[1]: *** No rule to make target "../obj/display_prompt.o", required for "tests_run". Stop.
make[1]: exit directory "[...]/tests".
make: *** [Makefile:29: tests_run] Error 2
Can you help me correct this Makefile?
This error ...
... is emitted when
makeis processing your second makefile, but it is about../obj/display_prompt.o, which is the responsibility of your first makefile. One of the drawbacks of recursivemake, such as you are using, is that the separatemakeruns are largely independent. There are ways for them to communicate, but they do not share rules or dependency information, so although the top-level makefile knows how to buildobj/display_prompt.o(relative to its own directory), the second level makefile does not know how build the same file, which for it is../obj/display_prompt.o.The second-level makefile needs all the main object files, so if you want this recursive
makearrangement to work then the top level makefile needs to ensure that they are built before recursing to the second-level makefile. But it doesn't. In fact, it does the exact opposite. The top-leveltests_runtarget hasfcleanas a prerequisite, which in turn hascleanas a prereq, which unconditionally deletes all the object files.Preliminary
I see no reason at all for the top-level
tests_runtarget to delete the object files. It is counter-productive. Perhaps your idea is to perform a clean build of these files for the tests, but if your makefile correctly captures all their dependencies then that should not be necessary. Moreover, if ever you do want to perform a clean build before testing then you can request it easily enough:make fclean; make tests_run.Additionally, your second-level makefile should have a separate rule for building the
unit-testsexecutable. That build should not be buried in a recipe for running the same.Solution 1
If you continue with the recursive
makeapproach, then instead of removing the object files, the top-leveltests_runrule must ensure, directly or indirectly, that they are built. That might look like this:Solution 2
I'm not a big fan of recursive
make, though I do use it in certain situations. Here, though, I would use a single makefile, non-recursively, to build everything. If you want, you can keep it somewhat modular by redesigning the second-level Makefile to beincluded by the top-level one, maybe something like this:In the top-level makefile, you would want to remove
run_testsrule (tests_runwill instead be used directly), and addinclude tests Makefilesomewhere near the end. (Though if it were me, I would also rename the second-level file totests.mkor similar, to clarify that it is not (any longer) a standalone makefile.