I have prepared a small makefile for a small project, but I am trying to find what would be the best practices; up to now I have gathered these:

  • relatively idiomatic (typical structures, flows, var names for Least Astonishment)
  • define the products in terms of the inputs (if I have .c files and want an exec, why should I think about intermediate files?)
  • up-to-date with GNU make's and gcc's capabilities (auto-dependency tracking)
  • silent by default
  • but can be verbose (for Eclipse integration for example)
  • keeps object files in directories separate from sources
  • avoids implicit rules (explicit is clearer and anyway I wanted to mute the output)

Also a couple of make-quirk-addressing details like immediate assignment :=, order-only prerequisites…

So here's the makefile:

WARNINGS := -Wall -Wextra 
CFLAGS ?= -std=gnu99 -g $(WARNINGS)
OBJDIR := obj
SRCDIR := src
# silent by default
ifeq ($(VERBOSE),1)
    SILENCER := 
    SILENCER := @
# pass this environment variable to the C source
ifeq ($(DEBUG_BUILD),1)
# typical way to list files and build full paths
# list the sources, not the object files (nor includes)
_SRCS := uthreads.c main.c 
SRCS := $(patsubst %,$(SRCDIR)/%,$(_SRCS))
OBJS := $(patsubst %,$(OBJDIR)/%,$(_SRCS:c=o))
# generate phony deps during compilation
DEPS := $(patsubst %,$(OBJDIR)/%,$(_SRCS:c=d))
# can't include the deps before the first, default target has appeared!
# "all" is the classical default target
all: main
        $(SILENCER)mkdir -p $(OBJDIR)
main: $(OBJS) 
        #@echo " LINK $^"
        $(SILENCER)$(CC) $(CFLAGS) -o $@ $^ 
# put object and dependency files away from the sources
# create the dir before building into it
$(OBJDIR)/%.o: $(SRCDIR)/%.c | createdir
        #@echo " CC $<"
        $(SILENCER)$(CC) $(CFLAGS) -c -o $@ $< 
        $(SILENCER)$(RM) -f *~ core main
        $(SILENCER)$(RM) -r $(OBJDIR)
.PHONY: clean all
# dependencies won't turn into a default target here
-include $(DEPS)

I'll be grateful for any criticism or addition to either the list of "best practices" or to the makefile itself.



2 Answers 2


Generally good, clean and straightforward. Few notes however:

  • Too many comments.

  • Group CFLAGS assignments together.

  • I am kind of surprised that -g is set regardless of DEBUG_BUILD.

  • Speaking of debug, it usually helps to separate debug and release targets (different directories, different CFLAGS).

  • I recommend to abstain from relying on environment variables (or make command line arguments): you cannot express that the target depends on them. In other words,

    $ make
    $ export DEBUG_BUILD=1
    $ make
    make: `all` is up to date
  • \$\begingroup\$ The comments are mostly a "teaching aid", they won't stay - probably. It's interesting that your 3 points about debug builds do reinforce each other. I'll fix it. Thank you! \$\endgroup\$
    – hmijail
    Commented Sep 16, 2015 at 18:28

You should be able to simplify the definition of DEPS to

DEPS := $(OBJS:.o=.d)

For small projects that don't require complicated configuration schemes, it's often times easiest to get the list of source files by wildcard instead of manually maintaining an explicit list:

SRCS := $(wildcard $(SRCDIR)/*.c)
OBJS := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRCS))
DEPS := $(OBJS:.o=.d)

That way, you won't have to change the makefile when you add, delete, or rename source files.

Since all of your object files are going into the same output folder, you only need to create the output folder when it does not exist. You are currently calling mkdir for every C file that gets compiled. This won't break your build, but it's unnecessary overhead and doesn't scale well. You should be able to simplify to something like this:

$(OBJDIR)/%.o: $(SRCDIR)/%.c
    #@echo " CC $<"
    $(SILENCER)$(CC) $(CFLAGS) -c -o $@ $< 

$(OBJS): | $(OBJDIR)
    $(SILENCER)mkdir -p $(OBJDIR)

The second line of your recipe for "clean" should add the "-f" flag to avoid error messages when $(OBJDIR) doesn't exist.

  • \$\begingroup\$ "You are currently calling mkdir for every C file that gets compiled." -> This seems to be wrong. What I'm seeing is that mkdir is only called once, no matter how many .c files get modified. And in fact the make manual says that a multitarget rule like $(OBJS): | $(OBJDIR) "is equivalent to writing the same rule once for each target, with duplicated prerequisites and recipes." - so it's equivalent to the original. \$\endgroup\$
    – hmijail
    Commented Aug 7, 2021 at 8:54
  • 1
    \$\begingroup\$ @hmijailmournsresignees The difference is that the original uses the target createdir, which is not the name of an output object and not declared .PHONY. That means make will always think it needs to be built. My version uses the directory name directly so after it's created, make won't try to build it again. \$\endgroup\$
    – bta
    Commented Aug 9, 2021 at 22:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.