GNU Makefile sample version 2.0

Hello everyone,

After a bit of experimentation, I have created the ideal Makefile that works flawlessly for me and my needs.

For now I will write it down and I will explain it to you bit by bit later on.

CXX = g++

CXXFLAGS = -Wall
CXXFLAGS += -pedantic
CXXFLAGS += -std=c++14
CXXFLAGS += -O2

SRC = src
HEADERS = include

INC = -I $(HEADERS)

OBJDIR := obj
BINDIR := bin

TARGET = $(BINDIR)/hw\_demo

SOURCES = $(wildcard $(SRC)/\*.cpp)
TMPOBJ = $(patsubst %.cpp, %.o, $(notdir $(SOURCES)))
OBJECTS = $(addprefix $(OBJDIR)/, $(TMPOBJ))

all : $(TARGET)

$(TARGET) : $(OBJECTS)
    $(CXX) -o $@ $(OBJECTS)

build:
    mkdir -p $(OBJDIR) $(BINDIR)

$(OBJECTS): $(OBJDIR)/%.o: $(SRC)/%.cpp
    $(CXX) $(CXXFLAGS) $(INC) -c $< -o $@

clean :
    @echo "Cleaning target and object files..."
    @rm -rf $(TARGET) $(OBJDIR) $(BINDIR)
    $(shell find . -type f -iname "\*.pch" -delete)
    @echo "All clear!"

full : clean build all

.PHONY : clean build all

.DEFAULT_GOAL := full

So, what does it do?

First of all, we set our CXX variable to work with g++; you can choose clang for example, but don't ask me how it works as I'm not using it. Sorry people :/

Then we set our C++ variable CXXFLAGS according to our needs: things like warnings, how strict our checking should be, what standard should we use if we want to be explicit about it (since GCC after 5.x series has gnu++11 enabled by default), and what kind of code optimization could we use, again according our needs.

Now, the real show begins! Before I learned all this, I used to have my source files, both headers and actual implementation files, exposed in project's root directory and that was no handy at all, as it was generating lots of objects and sometimes some humongous precompiled files.

That's why I decided to create src and include subfolders inside project's root directory to know exactly where each file should be and where to look for generated objects and or precompiled files at a later time.

Later, INC comes to complement our inclusion variable that would point our parser to the right direction for header files with -I $(HEADERS).

Right after, I have decided to create two different subfolders, named obj and bin, that would exist along src and headers to make my life easier and a lot cleaner without all these objects, binaries, and precompiled files scattered all over the project.

What's next? Assigning to my TARGET variable the binary name it should generate; just give it something useful to make sense according to project's name; so now my TARGET would contain something like bin/hw_demo.

Immediately follows SOURCES variables that uses wildcard function that comes with make by default and collect all source files' names inside our variable with their extension.

Then we have TMPOBJ that holds our objects; long story short, without this temporary variable, I couldn't make it work. It would insist on carry with it the full path of each object file and that would have caused problems with our later assignment to OBJDIR which you will see very soon; I had to find a work around and gladly I did (by mistake, but shhhh...don't tell anyone)!

I used notdir function to extract just the file names from SOURCES and then I would substitute each file's extension with .o with the help of patsubstr function; what follows immediately after is our OBJECTS variable that would take our objects, newly prefixed with our OBJDIR. This way it will point to obj/foo.o obj/bar.o and so on, this time without any problem.

Then it's our targets: all, build, clean, and full.

  • all builds our objects.
  • build makes our two directories right inside our project's root directory.
  • clean removes our object and binary files and then deletes the two directories we have built with build target.
  • full basically runs a cleanup process, then creates our folders, and then builds everything anew.

Last but not least, it's .DEFAULT_GOAL. By setting this special variable to the target of our choice, it executes it first by force. I chose to do so, because my code is rather super tiny and I don't have to worry about compilation time. If a project were larger, I wouldn't have set it.

So, that was it everyone.

I hope this silly script make your life a lot easier and prettier with automation.

If anyone wants to say hi, please feel free.

Cheers.