I'm currently trying to create a Makefile for my C code where I can say make build
and it will build my code with ./src/main.c
automatically included or if I say make test
it will automatically include ./src/test.c
and build. But my lack of knowledge in Makefiles made this absurdly difficult to accomplish.
I tried whatever I could such as target-specific variables but they wouldn't pass down to the prerequisites which caused problems. I moved on then to custom functions but they would throw errors of their own.
Here's the full code
BINARY=app.bin
CC=clang
#DFLAGS=-MP -MD
#CFLAGS=-Wall -Wextra -g -O0 $(DFLAGS)
GLOBAL_INCLUDE_DIR=/usr/local/include
#GLOBAL_LIB_DIR=/usr/local/lib
BUILD_DIR=./build
LIB_DIR=./lib
INCLUDE_DIR=./include
SRC_DIR=./src/core
MAIN_DIR=./src
SRC_FILES=$(shell find $(SRC_DIR) -type f -name *.c)
RAW_FILES=$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(SRC_FILES:.c=.i))
IR_FILES=$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(SRC_FILES:.c=.s))
OBJ_FILES=$(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(SRC_FILES:.c=.o))
INCLUDE_FLAGS=-I$(GLOBAL_INCLUDE_DIR) -I$(INCLUDE_DIR)
#LIB_FLAGS=-L$(GLOBAL_LIB_DIR) -L$(LIB_DIR)
BUILD_ENTRY=$(MAIN_DIR)/main.c
TEST_ENTRY=$(MAIN_DIR)/test.c
all: build
.PHONY: build
build:
$(eval SRC_FILES += $(BUILD_ENTRY))
$(call preprocess,$(RAW_FILES),$(SRC_FILES))
define link
$(1): $(2)
@echo "link: $^"
$(CC) -o $@ $^
endef
define assemble
$(1): $(2)
@echo "assemble: $^"
$(CC) -c -o $@ $<
endef
define compile
$(1): $(2)
@echo "compile: $^"
$(CC) -S -o $@ $<
endef
define preprocess
$(1): $(2)
mkdir -p $(dir $@)
$(CC) -E $(INCLUDE_FLAGS) -o $@ $<
endef
clean:
rm -rf $(BINARY) $(RAW_FILES) $(IR_FILES) $(OBJ_FILES)
#install:
#
#uninstall:
#
echo:
@echo "[SRC_FILES]: $(SRC_FILES)"
@echo "[RAW_FILES]: $(RAW_FILES)"
@echo "[IR_FILES]: $(IR_FILES)"
@echo "[OBJ_FILES]: $(OBJ_FILES)"
which when I run make build
it throws
./build/insane.i ./src/main.i: ./src/core/insane.c ./src/main.c
make: ./build/insane.i: No such file or directory
make: *** [Makefile:32: build] Error 127
You are thinking about make and makefiles in a fundamentally incorrect way. A makefile is not a procedural program like a script, and makefile targets are not functions that you call from the command line.
A makefile is a textual representation of a dependency graph: each target is a node in the graph, and the prerequisites are child nodes. The recipe is a command list attached to the target node that says, "if you decide you need to build this target, then use this shell script to do it".
make will parse the entire makefile and construct the graph in memory from the contents. Then it will start at the node you asked on the command line (or the first target in the makefile if you don't specify) and walk the graph from there; at each node first it recursively tries to build each prerequisite then when all of them are done it looks to see if the current target is out of date: if so it runs that target's recipe.
A recipe has to be a shell script, so the attempt to put an entire rule into a recipe, like you do with $(call preprocess ...)
cannot work.
As John says in the comments is a bad idea to build the same target different ways. Make works by checking timestamps; it has no idea what was used to build the target last time, only whether files have been changed. If you build the same target one way then try to build it another way, make won't do anything the second time unless you change some code.
Also, it's wrong to list all the source files as prerequisites of all the targets. This means if ANY source file changes, ALL targets will be considered out of date and be updated. The main goal of using a makefile, instead of just a shell script that compiles everything, is to avoid unnecessary rebuilds.
You want something like this:
BINARY = app.bin
TEST = test.bin
# use := here so the find only runs once
SRC_FILES := $(shell find $(SRC_DIR) -type f -name *.c)
OBJ_FILES := $(patsubst $(SRC_DIR)/%,$(BUILD_DIR)/%,$(SRC_FILES:.c=.o))
# tell make where to look for source files
VPATH = $(sort $(dir $(SRC_FILES))) $(MAIN_DIR)
build: $(BINARY)
test: $(TEST)
# how to build the binaries
$(BINARY) $(TEST): $(OBJ_FILES)
@echo "link: $^"
$(CC) -o $@ $^
# add in the per-target specific objects
$(BINARY): $(BUILD_DIR)/main.o
$(TEST): $(BUILD_DIR)/test.o
# how to build one object file from one assembly file
$(BUILD_DIR)/%.o: $(BUILD_DIR)/%.s
@echo "assemble: $^"
$(CC) -c -o $@ $<
# how to build one assembly file from one preproc file
$(BUILD_DIR)/%.s: $(BUILD_DIR)/%.i
@echo "compile: $^"
$(CC) -S -o $@ $<
# how to build one preproc file from one source file
$(BUILD_DIR)/%.i: %.c
mkdir -p $(@D)
$(CC) -E $(INCLUDE_FLAGS) -o $@ $<
You can look in the GNU Make reference manual to learn more about pattern rules, VPATH, etc.