makefiles, part II

Like I said with just one file it doesn’t make sense to create a makefile. Let’s create something more complex.
Let’s create four files: main.cpp, class1.cpp, class1.h and common.h

// common.h
#pragma once

enum class EAnswer
{
	NO = 0,
	YES
};

======================================================

// class1.h
#pragma once

#include <common.h>

class Class1
{
private:
	EAnswer m_answer{ EAnswer::NO };
};

======================================================

// class1.cpp
#include <class1.h>

======================================================

// main.cpp
#include <class1.h>

int main()
{
	Class1 cl;

	return 0;
}

Now let’s create makefile to build it:

main: main.o class1.o
	g++ -o main main.o class1.o

main.o: main.cpp class1.h
	g++ -std=c++11 -c main.cpp -I.

class1.o: class1.cpp class1.h common.h
	g++ -std=c++11 -c class1.cpp -I.

clean: 
	$(RM) *.o main

tar:
	tar cfv sources.tar main.cpp class1.cpp class1.h common.h

Let’s analyze what’s going on here. We have two source files and two header files. We would split compilation and linking into separate tasks, so we would build only part which were changed and not the whole project. So our target is final program, which depends on two object files and command is to invoke linker. Then we have two more target by the number of object files we have, each depends on corresponding cpp files and some header files, and command is to compile only. At the end we have two more targets which you may find useful: one to clean up, which we saw in part I and the second is to create an archive with all our source file (so you can send them to someone).

Couple of more notes. You may noticed that class1.o depends on one source file and TWO header files, you have to specify manually all header files you code depends on, make utility doesn’t analyze you code and if your header/source file includes other header files they have to be explicitly added as dependent.
To compile use use new parameter -I. – we use it to tell the compiler where to look for header files: in the current directory.

If you were paying attention to previous post you may noticed that I skipped couple of parameters: -g and -Wall. Let’s add them to our makefile… only we have to add them on two separate lines, not very convenient. Is there a way to be efficient? Yes – macros. Macros are in the form name=value, in our example we would create macros with compiler parameters:

CFLAGS=-c -g -Wall -I. -std=c++11

main: main.o class1.o
	g++ -o main main.o class1.o

main.o: main.cpp class1.h
	g++ $(CFLAGS) main.cpp

class1.o: class1.cpp class1.h common.h
	g++ $(CFLAGS) class1.cpp
...

This time if we will need to remove debug information we need to update just one line.

What else? We can specify which compiler to use, linker flags and debug parameters as well:

CC=g++
DEBUG=-g
CFLAGS=-c $(DEBUG) -Wall -I. -std=c++11
LFLAGS=$(DEBUG) -Wall

main: main.o class1.o
	$(CC) $(LFLAGS) -o main main.o class1.o

main.o: main.cpp class1.h
	$(CC) $(CFLAGS) main.cpp

class1.o: class1.cpp class1.h common.h
	$(CC) $(CFLAGS) class1.cpp
...

What if we have multiple source file? Can we simplify creating of makefile? Yes, here is an example:

CC=g++
DEBUG=-g
CFLAGS=-c $(DEBUG) -Wall -I. -std=c++11
LFLAGS=$(DEBUG) -Wall
DEPS=class1.h common.h
OBJS=main.o class1.o

main: $(OBJS)
	$(CC) $(LFLAGS) -o $@ $^

%.o: %.cpp $(DEPS)
	$(CC) $(CFLAGS) -o $@ $<

clean: 
	$(RM) *.o main

tar:
	tar cfv sources.tar main.cpp class1.cpp class1.h common.h

Don't be scared we just used three more macros:
$@ - put what is on the left side of :
$< - put what is the first item in the dependencies $^ - put what is on the right side of :

Leave a Reply