since flash is scared of knowledge, he will be forcefed. i have written this simple makefile tutorial so ctori can exist one day. this tutorial presumes basic knowledge of C compilation and unix systems.
Compiling software is an endeavour that consists of turning files into other files. The point of Make was to automate all the recipes of file transformation, while optimizing compile times by only recompiling what has to be recompiled.
As such, the Make file (called the Makefile) consists of these recipes. A recipe consists of a target (the file that the recipe will create), prerequisites (the ingredients needed for that file), as well as commands describing the actual process. The indents before the command MUST be \t tabs.
target: prerequisites
command
command
However, instead of going by dry descriptions, let's work on an example. Here's our theoretical C application that we will be compiling:
meow.c
meow.h
util.c
log.c
net.c
It consists of a couple C files, some main logic, some helpers. meow.h is the header file all other files depend upon. The application is clearly named meow.
If we compiled this application manually, or with a shell script, the process would look like this:
gcc -c -o meow.o meow.c
gcc -c -o util.o util.c
gcc -c -o log.o log.c
gcc -c -o net.o net.c
gcc -o meow meow.o util.o log.o net.o -lm
There's a clear hierarchy of what files create other files. Let's turn this into a basic makefile:
meow: meow.o util.o log.o net.o
gcc -o meow meow.o util.o log.o net.o -lm
meow.o: meow.c meow.h
gcc -c -o meow.o meow.c
util.o: util.c meow.h
gcc -c -o util.o util.c
log.o: log.c meow.h
gcc -c -o log.o log.c
net.o: net.c meow.h
gcc -c -o net.o net.c
It's so far sligtly more verbose than the shell script, but we already get cool features. First of all, partial recompiles. One of the main jobs of make is comparing the age of output files to input files. When the input file is newer than the output file, that means the output is out of date, and the recipe is run again. Note how all object files depend on meow.h, so they all will be recompiled if the common header file is changed.
Now let's add some variables to the mix. Compilers like flags, and we all like -Wall. However, instead of shitting -Wall all over the place, let's separate out our wanted flags into variables. The traditional naming is CFLAGS for compiler flags and LDFLAGS for linker flags. We'll also make the compiler a variable.
CC = gcc
CFLAGS = -Wall -g
LDFLAGS = -lm
meow: meow.o util.o log.o net.o
$(CC) -o meow meow.o util.o log.o net.o $(LDFLAGS)
meow.o: meow.c meow.h
$(CC) -c $(CFLAGS) -o meow.o meow.c
util.o: util.c meow.h
$(CC) -c $(CFLAGS) -o util.o util.c
log.o: log.c meow.h
$(CC) -c $(CFLAGS) -o log.o log.c
net.o: net.c meow.h
$(CC) -c $(CFLAGS) -o net.o net.c
Our makefile is still very repetitive though. First, let's use macros, so we don't have to type each filename 50 times. $@ stands for current target, $^ for all prerequisites. yeah, i just look them up every time i need one
CC = gcc
CFLAGS = -Wall -g
LDFLAGS = -lm
meow: meow.o util.o log.o net.o
$(CC) -o $@ $^ $(LDFLAGS)
meow.o: meow.c meow.h
$(CC) -c $(CFLAGS) -o $@ meow.c
util.o: util.c meow.h
$(CC) -c $(CFLAGS) -o $@ util.c
log.o: log.c meow.h
$(CC) -c $(CFLAGS) -o $@ log.c
net.o: net.c meow.h
$(CC) -c $(CFLAGS) -o $@ net.c
That's a bit better, espcially for the linking rule, but for true deduplication we're going to use pattern rules. They are simply rules which instead of using specific filenames, specify patterns of filenames they work on. Notice a new macro, $<, which refers to the source file the pattern was matched to.
CC = gcc
CFLAGS = -Wall -g
LDFLAGS = -lm
meow: meow.o util.o log.o net.o
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c meow.h
$(CC) -c $(CFLAGS) -o $@ $<
Our makefile is now pretty much done! however, let's run through some more features first. Phony rules are rules which dont create files, but can still be requested from the make program. Here we'll create two traditional ones, all and clean. You can also run shell commands and capture their outputs with backticks. Here we'll use this to get the sdl2-config program to help with setting the right compiler flags. Finally, you can run make with -B, to forcibly rerun all rules, as if all files changed.
CC = gcc
CFLAGS = -Wall -g `sdl2-config --cflags`
LDFLAGS = -lm `sdl2-config --libs`
OBJS = meow.o util.o log.o net.o
DEPS = meow.h
all: meow
clean:
rm -f meow $(OBJS)
meow: $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c $(DEPS)
$(CC) -c $(CFLAGS) -o $@ $<
.PHONY: all clean
There's obviously much more to make, such as implied rules, fancier compilation processes, and whatever the fuck some GNU extensions are. However, this is enough for your daily C project. Notice, that nothing we did here actually depended on the language being C. You can use make for any automation, for example for generating your project's documentation or even resizing icons. Hell, as a recent school project i abused make into a static site generator, together with some glue C and python code, automating with it thumbnail generation and ffmpeg media compression. Everything that reduces to creating files out of files can be done with a makefile.
misuzu alert: all $<s in code snippets have turned into $<s. thank you html
lol oops i didn't see this until late last night, i feel bad for not seeing it earlier now but this does look pretty understandable and different sections of the forum are in need of High Quality content anyhow
it turned out tha my understanding of the compilation process was rudimentary at best and because of that i didn't really understand the role Make had in all of it, after actually bothering to look into it a bit more it makes a lot more sense
ended up constructing This hot mess:
TARGET ?= satori
SRCDIR := ./src
INCDIR := ./include
BINDIR := ./bin
C_SRC := $(wildcard $(SRCDIR)/*.c) $(wildcard $(SRCDIR)/**/*.c)
CXX_SRC := $(wildcard $(SRCDIR)/*.cxx) $(wildcard $(SRCDIR)/**/*.cxx)
CC := $(CXX)
OBJ := $(C_SRC:.c=.o) $(CXX_SRC:.cxx=.o)
OUT := $(BINDIR)/$(TARGET)
CFLAGS := -Wall -g -O2 $(CFLAGS)
CXXFLAGS := $(CXXFLAGS)
CPPFLAGS := -I$(INCDIR) $(CPPFLAGS)
LDFLAGS := $(LDFLAGS)
LDLIBS := -lstdc++ $(LDLIBS)
all: $(OUT)
$(OUT): $(OBJ)
mkdir -p $(BINDIR)
$(CC) $(LDFLAGS) $(CPPFLAGS) -o $@ $^ $(LDLIBS)
.cxx.o:
@echo "[C++] " $@
$(CC) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $<
.c.o:
@echo "[C] " $@
$(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<
clean:
rm -f $(OBJ) $(OUT)
rebuild: clean all
run: rebuild
@echo
$(OUT)
.PHONY: all clean rebuild
.SUFFIXES: .c .cxx
supports both C and C++ and is recursive because i don't want to bother maintaining a secondary list of files to compile
also left some openings to allow preliminary retargeting to win32, who knows how much sense it makes but it sure does work
#!/bin/sh
TARGET=satori-amd64.exe \
CXX=x86_64-w64-mingw32-g++ \
LDFLAGS="-static-libgcc -static-libstdc++" \
make rebuild
also more misuzu moment of quotes becoming "
reemo: well, i actually dont have much more than this, other than a couple of other small things like separating binaries into a directory (which flash has already shown off), that is about as much as i use makefiles
however, if you want to show off your approach and understanding of cmake, which i never managed to start daring to attempt grasping, or the GNU horrorshow that is autoconf, please do write something
oh that's a leftover from throwing things at the wall to try and convince it to actually build my cxx files, i should probably get rid of that yeah