Following this tutorial...
I have 2 source files and 1 header file. I want to have them in separate directories like in the tutorial.
So I set this project up:
.
├── include
│ └── hellomake.h
├── Makefile
└── src
├── hellofunc.c
└── hellomake.c
Makefile:
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
gcc -o $@ $^ $(CFLAGS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
The error I generate says:
gcc -o hellomake -I../include
gcc: fatal error: no input files
compilation terminated.
make: *** [hellomake] Error 4
What's happening?
A make file simply saves if a, b, c, if newer than x then build x. is essentially the structure. So remove the header file is ok as long as you do not modify the header file.
Since these files normally appear in the source directory, they should always appear in the source directory, not in the build directory. So Makefile rules to update them should put the updated files in the source directory.
Use cd ./dir && make && pwd inside Makefile . The && was exactly what I needed to change a directory and execute a command there, then drop back to the main folder to finish the build.
The only way to include the header file is to treat the filename in the same way you treat a string. Makefiles are a UNIX thing, not a programming language thing Makefiles contain UNIX commands and will run them in a specified sequence.
Your tutorial promotes old and bad practices, you should avoid it IMHO.
In your rule here:
$(ODIR)/%.o: %.c $(DEPS)
You're telling make to look for sources in the current directory while they actually reside in the src
directory, thus this pattern is never used and you have no suitable one.
Make sure you organize your project directory like this :
root
├── include/
│ └── all .h files here
├── lib/
│ └── all third-party library files (.a/.so files) here
├── src/
│ └── all .c files here
└── Makefile
Then let's take the process step by step, using good practices.
Firstly, don't define anything if you don't need to. Make has a lot of predefined variables and functions that you should use before trying to do it manually. In fact, he has so many that you can compile a simple file without even having a Makefile in the directory at all!
List your source and build output directories:
SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin # or . if you want it in the current directory
Name your final target, that is, your executable:
EXE := $(BIN_DIR)/hellomake
List your source files:
SRC := $(wildcard $(SRC_DIR)/*.c)
From the source files, list the object files:
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
# You can also do it like that
OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC))
Now let's handle the flags
CPPFLAGS := -Iinclude -MMD -MP # -I is a preprocessor flag, not a compiler flag
CFLAGS := -Wall # some warnings about bad code
LDFLAGS := -Llib # -L is a linker flag
LDLIBS := -lm # Left empty if no libs are needed
(CPP
stands for C PreProcessor here, not CPlusPlus! Use CXXFLAGS
for C++ flags and CXX
for C++ compiler.)
The -MMD -MP
flags are used to generate the header dependencies automatically. We will use this later on to trigger a compilation when only a header changes.
Ok, time to roll some recipes now that our variables are correctly filled.
It is widely spread that the default target should be called all
and that it should be the first target in your Makefile. Its prerequisites shall be the target you want to build when writing only make
on the command line:
all: $(EXE)
One problem though is Make will think we want to actually create a file or folder named all
, so let's tell him this is not a real target:
.PHONY: all
Now list the prerequisites for building your executable, and fill its recipe to tell make what to do with these:
$(EXE): $(OBJ)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
(CC
stands for C Compiler.)
Note that your $(BIN_DIR)
might not exist yet so the call to the compiler might fail. Let's tell make that you want it to check for that first:
$(EXE): $(OBJ) | $(BIN_DIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(BIN_DIR):
mkdir -p $@
Some quick additional notes:
$(CC)
is a built-in variable already containing what you need when compiling and linking in C$(LDFLAGS)
before your object files and $(LDLIBS)
after
$(CPPFLAGS)
and $(CFLAGS)
are useless here, the compilation phase is already over, it is the linking phase hereNext step, since your source and object files don't share the same prefix, you need to tell make exactly what to do since its built-in rules don't cover your specific case:
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
Same problem as before, your $(OBJ_DIR)
might not exist yet so the call to the compiler might fail. Let's update the rules:
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
$(BIN_DIR) $(OBJ_DIR):
mkdir -p $@
Ok, now the executable should build nicely. We want a simple rule to clean the build artifacts though:
clean:
@$(RM) -rv $(BIN_DIR) $(OBJ_DIR) # The @ disables the echoing of the command
(Again, clean is not a target that needs to be created, so add it to the .PHONY
special target!)
Last thing. Remember about the automatic dependency generation? GCC and Clang will create .d
files corresponding to your .o
files, which contains Makefile rules for us to use, so let's include that in here:
-include $(OBJ:.o=.d) # The dash is used to silence errors if the files don't exist yet
Final result:
SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin
EXE := $(BIN_DIR)/hellomake
SRC := $(wildcard $(SRC_DIR)/*.c)
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
CPPFLAGS := -Iinclude -MMD -MP
CFLAGS := -Wall
LDFLAGS := -Llib
LDLIBS := -lm
.PHONY: all clean
all: $(EXE)
$(EXE): $(OBJ) | $(BIN_DIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
$(BIN_DIR) $(OBJ_DIR):
mkdir -p $@
clean:
@$(RM) -rv $(BIN_DIR) $(OBJ_DIR)
-include $(OBJ:.o=.d)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With