This is the current Makefile that I use with my C projects. Here is what I would like to be reviewed:

  1. Reusability - is this Makefile easy to use for multiple separate projects with minimal modification?

  2. Organization - is this Makefile organized in a logical and readable way?

  3. Dynamics - are there Make features (or compiler options/warnings) that I am not taking advantage of that could make my Makefile more powerful?

  4. Miscellaneous - what other things could I improve?

CXX = g++-4.9
CC = gcc-4.9
DOXYGEN = doxygen
CFLAGS = -fdiagnostics-color=always -std=gnu11 -s -c -g3 -O3 -time
WARNINGS = -Werror -Wall -Wextra -pedantic-errors -Wformat=2 -Wno-import -Wimplicit -Wmain -Wchar-subscripts -Wsequence-point -Wmissing-braces -Wparentheses -Winit-self -Wswitch-enum -Wstrict-aliasing=2 -Wundef -Wshadow -Wpointer-arith -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wnested-externs -Winline -Wdisabled-optimization -Wunused-macros -Wno-unused
LIBRARIES = -lcurl
SOURCES = main.c test.c


    $(CC) $(LDFLAGS) $(OBJECTS) -o $@ $(LIBRARIES)

debug: CFLAGS += -DDEBUG -g

    $(CC) $< -o $@ $(CFLAGS) $(WARNINGS)

.PHONY: doc clean       
    $(DOXYGEN) doxygen.config    
    rm -rf $(EXECUTABLE) $(OBJECTS)
  • \$\begingroup\$ Question is why would you use and (more importantly) create by hand Makefiles? For any project that exceeds few files (and spans over more than one directory) Makefiles become very cumbersome and it seems to manage them sanely you need some other software (to i.e. generate them from some more sane description). I can really recommend scons.org, way more flexible than any other build system I have ever had a chance to use. And should work on any platform that Python works on. \$\endgroup\$
    – elmo
    Apr 15, 2014 at 8:36
  • 1
    \$\begingroup\$ Writing good Makefiles by hand is really not a simple task, especially when you are aiming for Reusability and Portability. I know that, when you are at the point you are at the moment, you don't want to hear about "meta-generators" like the one elmo suggested, but they really are a big help. While elmo pointed to SCONS, I familiarized myself with CMake, and did put together some boilerplate of my own, named JAWS. Check it out, it does what you want (including the Doxygen part), and much more... \$\endgroup\$
    – DevSolar
    Apr 15, 2014 at 9:51
  • \$\begingroup\$ @DevSolar If you expanded on that a bit in an answer, that would be something that I would give an upvote, and maybe even a reward bounty (but that would have to give an in-depth explanation of some stuff :))! \$\endgroup\$
    – syb0rg
    Apr 15, 2014 at 13:24
  • \$\begingroup\$ @syb0rg: I'd rather work on some polish for JAWS if you don't mind. ;-) Anything specific you think is missing? \$\endgroup\$
    – DevSolar
    Apr 15, 2014 at 13:57
  • \$\begingroup\$ @DevSolar I'm still going through it, I'm somewhat new to the world of the GNU build system. If you would join me in our chat room to talk about some stuff, that would be awesome! \$\endgroup\$
    – syb0rg
    Apr 15, 2014 at 14:07

4 Answers 4


I would make source file discovery dynamic:

 SOURCES   = $(wildcard *.c)

In addition to all I usually add debug and release versions.

CFLAGS = -fdiagnostics-color=always -std=gnu11 -s -c -time
all:     CFLAGS += -DTYPE=ALL
debug:   CFLAGS += -DTYPE=DEBUG    -g3
release: CFLAGS += -DTYPE=RELEASE  -O3

I have a generic build file that I have all my rules built-into.


Note: It is not perfect or great (but does what I need).
But have a look and pull anything you need.

This allows my actual makefiles to be very simple:

# The Target I want to build
# My generic Makefile build apps and lib based
# on the extension extension of the target.
TARGET                  = myLib.slib

# Then include the Generic Makefile
# In uses THORSANVIL_ROOT as the root of where you are
THORSANVIL_ROOT         = $(realpath ../)
include ${THORSANVIL_ROOT}/build/tools/Makefile

Dependencies: right now your makefile will let changes in .h to remain unnoticed.

DEPS := $(SOURCES:.c=.d)

    $(CC) -o $< -MM $(CFLAGS)

-include $(DEPS)

PS: it is highly recommended to remove -c from CFLAGS and mention it explicitly in the .c.o rule.


The first rule should just be


In turn, $(EXECUTABLE) will require $(OBJECTS), and each object will require its corresponding .c file.

Also, all should be a .PHONY target, since you are not actually going to produce a file named all.

Both of the remarks above apply equally to the debug target as well.


Why is there a definition of CXX when there's no C++ sources?

That very long WARNINGS = line is hard to read. Split it over several lines, using +=.

Remove the flags that are implied by -Wall (namely, -Wchar-subscripts -Wimplicit, -Wmain, -Wmissing-braces, -Wparentheses, -Wsequence-point) or by -Wpedantic (-Wmain, -Wpointer-arith).

I don't see the need for all to depend on $(SOURCES). However, all should be a dependency of .PHONY.

We're missing .DELETE_ON_ERROR. Without that, a partial file from a failed or interrupted operation may languish and satisfy dependencies when it shouldn't.

You shouldn't need to write your own rule for this:

    $(CC) $< -o $@ $(CFLAGS) $(WARNINGS)

The built-in rule (as evidenced by make -print-data-base, assuming GNU Make) is

        $(COMPILE.c) $(OUTPUT_OPTION) $<

The command expands to

$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c -o $@ $<

So you just need to include WARNINGS into your CFLAGS (and remove the -c that doesn't belong there).

Similarly, use the default rule for linking:

    $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@

This means you should add your libraries to LDLIBS rather than LIBRARIES (the rule expands $(LOADLIBES) for historical reasons - you can safely ignore that).

Be careful with these:



You can easily end up with a mix of debug and release objects this way. I find it better to have separate directories containing the different build products. For example:

.PHONY: release debug
debug: CFLAGS += -g -O0
release debug:
    +$(MAKE) -C $@ -f ../Makefile

The clean target doesn't remove the Doxygen artefacts. You should test that after make clean there's nothing that isn't in your version control.

Ensure that headers are considered to be dependencies of their including object files. We can get this as a side-effect of compilation by adding -MMD to CFLAGS.

Modified makefile

CC = gcc-4.9
DOXYGEN = doxygen
CFLAGS += -std=gnu11
CFLAGS += -fdiagnostics-color=always -time

WARNINGS += -Wall -Wextra
WARNINGS += -pedantic-errors
WARNINGS += -Wbad-function-cast
WARNINGS += -Wcast-align
WARNINGS += -Wcast-qual
WARNINGS += -Wdisabled-optimization
WARNINGS += -Werror
WARNINGS += -Wformat=2
WARNINGS += -Winline
WARNINGS += -Winit-self
WARNINGS += -Wmissing-declarations
WARNINGS += -Wmissing-prototypes
WARNINGS += -Wnested-externs
WARNINGS += -Wold-style-definition
WARNINGS += -Wredundant-decls
WARNINGS += -Wshadow
WARNINGS += -Wstrict-aliasing=2
WARNINGS += -Wstrict-prototypes
WARNINGS += -Wswitch-enum
WARNINGS += -Wundef
WARNINGS += -Wunused-macros
WARNINGS += -Wwrite-strings
WARNINGS += -Wno-import
WARNINGS += -Wno-unused

OBJECTS = main.o test.o
LDLIBS += -lcurl

all: release


    $(DOXYGEN) doxygen.config    
    $(RM) -r release debug

debug: CFLAGS += -g -O0
release: CFLAGS += -O3 -s
release debug: VPATH += ..
release debug:
    +$(MAKE) -C $@ -f ../Makefile $(EXECUTABLE)

-include $(DEPFILES)

.PHONY: all doc clean release debug

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.