an introduction to make
#13611

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 $@ $&lt;

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 $@ $&lt;

.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 $&lt;s. thank you html

#13613

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 &quot;[C++] &quot; $@
    $(CC) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $&lt;

.c.o:
    @echo &quot;[C] &quot; $@
    $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $&lt;

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=&quot;-static-libgcc -static-libstdc++&quot; \
make rebuild

also more misuzu moment of quotes becoming &quot;

https://sig.flash.moe/signature.png
#13614
thank you for this post szy, i was hoping you would write something like this eventually and i hope you discuss more specific or complex examples in the future
//i.fii.moe/EEtDbYEICrUbLOFjkwKVQ7OUJWJR-ejl
#13630
flash: that is a pretty good makefile, does about as much as you'd want. one thing i dont understand though is why your "run" phony does a full rebuild instead of just depending on the final binary, wouldnt that kill the entire point of make, to not enable object file reuse? unless the concern is header files, in which case i recommend the listing-headers-as-dependencies trick

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
#13632

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

https://sig.flash.moe/signature.png
#13634
maybe relevant but not really https://github.com/64/cmake-raytracer
https://cockdickball.in/media/button.gif
#13965
Thanks to this tutorial, I was able to make (pun intended) a far simplified process for generating my website once I write a new blog post. Now that I can use make without accidentally deleting my entire project, I might try using it for my c code in the future.