Advice on Makefile and build system structure with a C library - c

What I want to achieve:
I have a library that has submodules which depend on each other.
I want to have a Makefile for each submodule and then I combine the submodules into one .a file with another Makefile in the root of the repository. The submodules are in their won folders and will compile the .o files into their won obj directories.
What I have now
Makefile that combines the libs:
NAME = libcore.a
HEADERS = arr/inc mem/inc str/inc map/inc
FOLDER = src
MEM = mem
ARR = arr
STR = str
MAP = map
MEM_O = $(wildcard mem/obj/*.o)
ARR_O = $(wildcard arr/obj/*.o)
STR_O = $(wildcard str/obj/*.o)
MAP_O = $(wildcard map/obj/*.o)
OBJ = $(MEM_O) $(ARR_O) $(STR_O) $(MAP_O)
RM = rm -f
all: $(OBJ)
#make -C $(MEM)
#make -C $(ARR)
#make -C $(STR)
#make -C $(MAP)
#ar -rcs $(NAME) $(OBJ)
#echo "\\n\033[32;1mCORE ACTIVATED \033[0m \\n"
clean:
#make clean -C $(MEM)
#make clean -C $(ARR)
#make clean -C $(STR)
#make clean -C $(MAP)
#echo "\\n\033[32;1mCORE DEACTIVATED \033[0m \\n"
fclean: clean
#${RM} ${NAME}
#make fclean -C $(MEM)
#make fclean -C $(ARR)
#make fclean -C $(STR)
#make fclean -C $(MAP)
re: fclean all
.PHONY: all fclean clean re
Makefile for an individual submodule:
NAME = libarr.a
CC = clang
CFLAGS = -O3 -Wall -Wextra -Werror -Wpedantic -Wtype-limits \
-Wunreachable-code -Wpadded -Wshadow -fPIC -Wconversion
SRC_DIR = src/
INC_DIR = inc/
OBJ_DIR = obj/
SRC_BASE = arr_add.c \
arr_add_first.c \
arr_add_last.c \
arr_add_mult.c \
arr_new.c \
arr_put.c \
arr_free.c \
arr_del.c \
arr_del_first.c \
arr_del_last.c \
arr_get.c \
arr_get_first.c \
arr_get_last.c \
arr_iter.c \
arr_iter_range.c \
arr_join.c \
arr_read_file.c \
arr_take.c \
arr_take_first.c \
arr_take_last.c \
arr_search.c \
arr_find.c \
arr_parse.c \
arr_write.c \
arr_null.c \
arr_grow.c \
arr_copy.c \
arr_rotate.c \
SRC = $(addprefix $(SRC_DIR), $(SRC_BASE))
OBJ = $(addprefix $(OBJ_DIR), $(SRC_BASE:.c=.o))
all:
$(MAKE) -j $(NAME)
$(NAME): $(OBJ)
#echo "\\n\033[32;1mARRAY FUNCTIONS COMPILED\033[0m \\n"
$(OBJ_DIR):
#mkdir -p $#
$(OBJ_DIR)%.o: $(SRC_DIR)%.c | $(OBJ_DIR)
#$(CC) $(CFLAGS) -c $< -o $# -I $(INC_DIR)
clean:
#rm -rf $(OBJ_DIR);
fclean: clean
#rm -rf $(NAME);
re: fclean all
.PHONY: all fclean clean re
The problem
Test program compilation:
gcc -fsanitize=undefined -fsanitize=address -fsanitize=leak arr/tests/tests.c libcore.a
There's something wrong with my setup. When I run make for the main Makefile once, the linker won't find the functions in the array library and gives undefined reference. However when I run Make the second time I can then compile my test program correctly. I just can't figure out what I'm doing wrong. My idea is that I create the .o files in the submodule Makefiles and the just basically compile all those .o files with the main Makefile into one .a file.
Thanks in advance for any help on this!

The immediate cause of the error you see is here, in the major makefile:
MEM_O = $(wildcard mem/obj/*.o)
This gives you a list of all object files that exist when the variable MEM_O is expanded, which happens before Make executes any rule. So on the first pass the variable is empty (because there are no object files), the sub-makes build the object files, and the major Make puts nothing into the library (because the variable is empty).
The highest-lever error is that you wrote all of these Makefiles without adequate testing. When you write code, it behooves you to start with something small and simple that works perfectly, then build up. (I really ought to have that hotkeyed by now.)
The mid-level error is that recursive Make is clumsy. It has its uses, but if you handle it carelessly you get errors like this. I suggest a top-level makefile that includes the lower makefiles (by means of the include directive), and lower-level makefiles that can function at either level.

Related

VScode fails to recognize include path from Makefile

Bellow is my Makefile from which vscode should understand that the #include "philo.h" and #include "philo_bonus.h" are okay even though those .h files are not in the same folders (srcs and srcs_bonus) as my .c, thanks to the -I include/.
What is very weird is that it behaves in the expected way for my srcs/ files but no for my srcs_bonus/ files which give me the error : cannot open source file "philo_bonus.h"
It does compile well so i assume the issue is vscode's. I have tried reloading the window several times with no differences and this issue has come up accross different projects. Is this a glitch or am I missing something here ?
SRCS = philo.c \
thread.c \
util.c \
lib.c \
free.c \
init.c \
SRCS_BONUS = philo_bonus.c \
thread_bonus.c \
util_bonus.c \
lib_bonus.c \
free_bonus.c \
init_bonus.c \
OBJS = $(addprefix srcs/, $(SRCS:.c=.o))
OBJS_BONUS = $(addprefix srcs_bonus/, $(SRCS_BONUS:.c=.o))
INC = -I include/
CC = cc -pthread
AR = ar rcs
CFLAGS = -g3 -Wall -Wextra -Werror -fsanitize=address
RM = rm -f
NAME = philo
%.o: %.c
$(CC) $(CFLAGS) $(INC) -c $< -o $#
$(NAME): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(NAME)
all: $(NAME)
bonus: $(OBJS_BONUS)
$(CC) $(CFLAGS) $(OBJS_BONUS) -o $(NAME)
clean:
$(RM) $(OBJS) $(OBJS_BONUS)
fclean: clean
$(RM) $(NAME)
re: fclean all
.PHONY: all bonus clean fclean re
Here is my file tree :
include/
├─ philo.h
├─ philo_bonus.h
srcs/
├─ files.c
srcs_bonus/
├─ files_bonus.c
Makefile
I am on Ubuntu 22.04.1

How to use clean and fclean in Makefile?

I'm new to using Makefile and I'm struggling to make it work correctly.
I want to compile my files to an executable .o file and then remove all the other .o files that were created in the process.
SRC = ./main.c \
./rush00.c \
./ft_putchar.c
OBJ = $(SRC:.c=.o)
CFLAGS += -Wall -Werror -Wextra
NAME = rushB
all:$(NAME)
$(NAME):$(OBJ)
cc -o $(NAME) $(OBJ)
clean:
rm -f $(OBJ)
fclean: clean
rm -f $(NAME)
re: fclean $(NAME)
.PHONY: all clean fclean re
Is there a way to make an executable file without creating main.o, rush00.o and ft_putchar.o?

Unexpected prerequisite skip in GNU Make 4.2.1

I'm trying to create a generic Makefile to use with most of my projects. It should work as follows: only rebuild and link those .o files whose .c or .h dependency has changed. The .o and .d files are stored in a separate directory called 'build'.
With the help of the official GNU Make manual and some googling I've managed to achieve the desired behavior except for one thing: when I run make re I get the error:
Assembler messages: Fatal error: can't create build/ft_build_buffer.o: No such file or directory — the reason for this is that the 'build' directory only gets created whenever the .d files are generated, but for some reason the re rule simply skips this step and goes on to compile .o files straight away! Note: if I run make clean && make fclean && make all (which should be the exact same thing) everything works fine.
A few other things: I've tried using the -MMD option to generate dependencies on the fly but on my machine that causes the .d files to only contain .c dependencies. Of course I could just make all .c files depend on all .h files but that seems like a very sloppy solution.
Feel free to share any other advice/improvements that will make this file more clean and readable, thanks! :)
# Define the C compiler to use.
CC := gcc
# Define any compile-time flags.
CFLAGS := -I./include -Wall -Wextra -Werror -g
#CFLAGS := -I./include -march=native -O2 -pipe
# Define the executable file.
BIN := ft_hexdump
# Define build directory.
BUILD_DIR := build
# Define source files directory.
SRC_DIR := src
# Define the C source files.
SRCS := $(wildcard $(SRC_DIR)/*.c)
# Define the C object files.
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
# Define the prerequisite files.
DEPS := $(OBJS:%.o=%.d)
.PHONY: all clean fclean re
.DELETE_ON_ERROR:
all: $(BIN)
-include $(DEPS)
$(BIN): $(OBJS)
$(CC) $(CFLAGS) $^ -o $#
$(BUILD_DIR)/%.o: $(BUILD_DIR)/%.d
$(CC) $(CFLAGS) -c $(SRC_DIR)/$*.c -o $#
$(BUILD_DIR)/%.d: $(SRC_DIR)/%.c
#mkdir -p $(#D)
#set -e; rm -f $#; \
$(CC) $(CFLAGS) $(INCLUDE) -MM $< > $#.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $# : ,g' < $#.$$$$ > $#; \
rm -f $#.$$$$
clean:
-rm -rf $(BUILD_DIR)
fclean: clean
-rm -f $(BIN)
re: fclean all
Here is the modified working version as suggested by #M.M
# Define the C compiler to use.
CC := gcc
# Define any compile-time flags.
CFLAGS := -I./include -Wall -Wextra -Werror -g
#CFLAGS := -I./include -march=native -O2 -pipe
# Define the executable file.
BIN := ft_hexdump
# Define build directory.
BUILD_DIR := build
# Define source files directory.
SRC_DIR := src
# Define the C source files.
SRCS := $(wildcard $(SRC_DIR)/*.c)
# Define the C object files.
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(BUILD_DIR)/%.o)
# Define the prerequisite files.
DEPS := $(OBJS:%.o=%.d)
.PHONY: all clean fclean re
.DELETE_ON_ERROR:
all: $(BIN)
-include $(DEPS)
$(BIN): $(OBJS)
$(CC) $(CFLAGS) $^ -o $#
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
#mkdir -p $(#D)
$(CC) $(CFLAGS) -MMD -c $(SRC_DIR)/$*.c -o $#
clean:
-rm -rf $(BUILD_DIR)
fclean: clean
-rm -f $(BIN)
re:
$(MAKE) fclean
$(MAKE) all

How to know if a library exists or is up to date with makefile

Here is my Makefile:
NAME = fillit
FLAGS = -Wall -Wextra -Werror
LIB_NAME = libft.a
LIB_DIR = ../libft/
LIB_PATH = $(LIB_DIR)$(LIB_NAME)
OBJ_DIR_NAME = objects
OBJ_DIR = $(OBJ_DIR_NAME)/
HEADER_DIR = ../libft
SRC = main.c
OBJ = $(SRC:.c=.o)
all :
mkdir -p $(OBJ_DIR_NAME)
$(MAKE) $(NAME)
$(NAME) : $(OBJ) $(LIB_PATH)
gcc -o $(NAME) $(addprefix $(OBJ_DIR), $(OBJ)) -L$(LIB_DIR) -lft
$(LIB_PATH) : $(LIB_PATH)
$(MAKE) -C $(LIB_DIR) --no-print-directory
%.o : %.c
gcc $(FLAGS) -I$(HEADER_DIR) -c $<
mv $# $(OBJ_DIR)
clean :
rm -f $(addprefix $(OBJ_DIR), $(OBJ))
rm -rvf $(OBJ_DIR_NAME)
fclean : clean
rm -f $(NAME)
rm -f $(addprefix $(LIB_DIR), $(LIB_NAME))
re: fclean
make
When i write $(LIB_PATH) : $(LIB_PATH) i'm expecting that my Makefile check if my lib is updated or exist and if that's not the case compile it (like objects and sources) but when i do make it only argue about circular rules and compile anyway my library.
Then, what should i write for when i do make, it enter in the $(LIB_PATH): only when my library hasn't been compiled yet?
This is a common problem with hierarchical Makefiles. You don't want to have to specify the library dependencies twice (i.e., once to know when to launch the sub-makefile, and again in the sub-makefile to actually build the target) so you just have to call the sub-makefile each time to let it determine if it needs to rebuild.
Try:
.PHONY: $(LIB_PATH)
$(LIB_PATH):
[tab] $(MAKE) -C $(LIB_PATH) --no-print-directory
This should cause make to run in the $(LIB_PATH) directory every time this makefile evaluates a target that depends on $(LIB_PATH).
.PHONY is meant for targets that don't really represent files, like clean. It just marks the target as 'stale' even if a file happens to exist with that name and is fresh.

Trouble with Makefile compiling objects and moving them

This is my Makefile:
.PHONY : clean fclean re $(LIB_PATH)
NAME = fillit
FLAGS = -Wall -Wextra -Werror
LIB_NAME = libft.a
LIB_DIR = ../libft/
LIB_PATH = $(LIB_DIR)$(LIB_NAME)
OBJ_DIR_NAME = objects
OBJ_DIR = $(OBJ_DIR_NAME)/
HEADER_DIR = ../libft/
SRC = main.c func1.c
OBJ = $(SRC:.c=.o)
all :
mkdir -p $(OBJ_DIR_NAME)
$(MAKE) $(NAME)
$(NAME): $(OBJ_DIR)$(OBJ) $(LIB_PATH)
gcc -o $(NAME) $(addprefix $(OBJ_DIR), $(OBJ)) -L$(LIB_DIR) -lft -I$(HEADER_DIR)
$(LIB_PATH):
$(MAKE) -C $(LIB_DIR) --no-print-directory
$(OBJ_DIR)%.o : %.c
gcc $(FLAGS) -I $(HEADER_DIR) -c $<
-mv $(#F) $(OBJ_DIR)
clean :
-rm $(addprefix $(OBJ_DIR), $(OBJ))
-rm -rv $(OBJ_DIR_NAME)
fclean : clean
-rm $(NAME)
$(MAKE) -C $(LIB_DIR) fclean --no-print-directory
re: fclean
make
When i do make, i expect that the rule $(OBJ_DIR)%.o : %.c compiles the two objects (func1.c main.c) and move them to objects directory. But the rule only happens with the first source file written in SRC
compiles the two objects (reader.c main.c)
Did you mean func1.c instead of reader.c?
If that is the case, please check if the func1.c file is in the same directory as main.c. If it isn't in the same directory, you'll have to write another pattern rule. Something like this:
FUNC1_DIR = # The directory where your func1.c is located, maybe src/ or something alike
$(OBJ_DIR)%.o : $(FUNC1_DIR)%.c
gcc $(FLAGS) -I $(HEADER_DIR) -c $<
-mv $(#F) $(OBJ_DIR)
$(NAME): $(OBJ_DIR)$(OBJ) doesn't apply $(OBJDIR) to each item $(OBJ).
It literally just concatenates $(OBJDIR) onto the list. This is why only one instance of your pattern rule fires (only one .o has the path prefix that the pattern is trying to match).
Instead, use:
OBJ_WITH_PATH := $(foreach obj,$(OBJ),$(OBJ_DIR)$(obj))
Then update the dependency on your NAME rule to
$(NAME): $(OBJ_WITH_PATH) $(LIB_PATH)
As an aside, I find it is sometimes helpful to add rules like
.PHONY: DEBUG
DEBUG:
#echo MAKING SURE THIS WORKS: $(OBJ_DIR)$(OBJ)
#echo ALTERNATE: $(OBJ_WITH_PATH)
then you can run your DEBUG target:
$ make DEBUG
MAKING SURE THIS WORKS temp/a.o b.o
ALT temp/a.o temp/b.o

Resources