Makefile creation for C++ Programs

In this document we study the basics of Makefile creation taking C++ as the programming language. We do all this in a Ubuntu machine. Makefile is to ease the compilation process. However, the way how we compile the C++ files remains the same inside.

1.0. Write sample C++ programs:

Let us write some sample programs to work on Makefile. I have refered a online tutorial at the link https://www.youtube.com/watch?v=aw9wHbFTnAQ. The files that I created are as below.

File: main.c

#include <iostream>

#include “function.h”

int main()

{

print_hello();

std::cout << std::endl;

std::cout << “The facorial of 5 is ” << factorial(5) << std::endl;

return 0;

}

File: function.h

void print_hello(void);

int factorial(int);

File: function1.cpp

#include “function.h”

int factorial (int n)

{

if(n!=1)

{

return (n*factorial(n-1));

}

else

{

return 1;

}

}

File: function2.cpp

#include <iostream>

#include “function.h”

void print_hello()

{

std::cout << “Hello World!”;

}

As you can see, there are 4 files main.cpp, function.h, function1.cpp and function2.cpp. Usually we compile such files as below,

g++ main.cpp function1.cpp function2.cpp -o hello

This gives out a file named “hello” which can be executed to see what the program does.

Here we are using a straight and simple program. But in real world programs are too complicated in structure and may involve many number of files. Hence, we can not use the above method of compilation with efficiency. So we go for creating “Makefile”s.

2.0 Creating Makefile:

Let me create a simple text file and name it as “Makefile”. It has contents as given below,

File: Makefile

# this is a sample Makefile

all:

g++ main.cpp function1.cpp function2.cpp -o hello

Note: The filename as Capital “M”. You can use the # symbol for including the comments in your Makefile

Here,

all:” refers to target. If a Makefile has multiple targets, then the first target will only get executed unless the first target is linked with other targets.

The second line I have copied the previous command directly. Definitely it will be used as a command and will get executed during “make” command.

Note: Make sure that you give “tab” before the command (i.e. before g++ in this case). Otherwise the command will never executed.

To compile the files, just issue command “make” in the terminal.

Output: “make” command

vijay@vijay-X55U:~/VKS_Module$ ls

function1.cpp function2.cpp function.h hello main.cpp Makefile

vijay@vijay-X55U:~/VKS_Module$ make

g++ main.cpp function1.cpp function2.cpp -o hello

vijay@vijay-X55U:~/VKS_Module$ ls

function1.cpp function2.cpp function.h hello main.cpp Makefile

vijay@vijay-X55U:~/VKS_Module$

Note: If there are multiple targets, then make sure that you use the target along-with make command like for example, make compile or make all.

2.1. Modifying Makefile:

Now, let us use multiple targets and modify the Makefile. The sample Makefile is as given below,

File: Makefile

# this is a sample Makefile

all: hello

hello: main.o function1.o function2.o

g++ main.o function1.o function2.o -o hello

main.o: main.cpp

g++ -c main.cpp

function1.o: function1.cpp

g++ -c function1.cpp

function2.o: function2.cpp

g++ -c function2.cpp

clean:

rm -rf *.o hello

Now, as you see in the above Makefile, you see a number of targets written namely all, main.o, function1.o, function2.o, clean. The targets have some parameters after : (colon) symbol separated by a space. These are called dependencies. Say for example, “main.o:” target has dependency main.cpp and if dependency is satisfied, then it will execute the corresponding command “g++ -c main.cpp”.

2.2. Using variables in Makefile:

As you can see in the above Makefile, “g++” is repeated multiple times. Also, if you want to change the compiler, then you have to modify the Makefile by replacing “g++” by other compiler names multiple times. To avoid this, you can declare variables as shown below,

File: Makefile

# this is a sample Makefile

#declare the variables

CC = g++

CFLAGS = -c -Wall

all: hello

hello: main.o function1.o function2.o

$(CC) main.o function1.o function2.o -o hello

main.o: main.cpp

$(CC) $(CFLAGS) main.cpp

function1.o: function1.cpp

$(CC) $(CFLAGS) function1.cpp

function2.o: function2.cpp

$(CC) $(CFLAGS) function2.cpp

clean:

rm -rf *.o hello

Here we have used two variables CC and CFLAGS. The “=” operator is used to assign values to these variables. If you use $(CC) or $(CFLAGS) then you can use the values wherever needed.

2.2. Variable Assignment in Makefile:

Whenever you want to set values to variables declared in Makefile, you have Four choices. Each type is explained before.

To demonstrate the above types of assignments, let us write some sample Makefiles,

  • Lazy Set: This is a normal way of setting the values to any variable. If this kind of assignment is used, then the values will be expanded recursively when the variable is used, not when it’s declared.

    Syntax: VARIABLE = value

    # This is a test Makefile

    # Lazy Set

    HELLO = World

    HELLO_WORLD = $(HELLO) World!

    HELLO = Hello

    all:

    echo $(HELLO_WORLD)

    # Output we got is “Hello World!”

    Here, HELLO_WORLD is assigned value as “$(HELLO) World!”, but inspite of assignment “HELLO = Hello”, the expansion $(HELLO) took the latest value used. So it displayed “Hello World!”. So, expansion took place when we used “echo” command, not the when it was assigned.

  • Immediate Set: Setting of a variable with simple expansion of the values inside – values within it are expanded at declaration time.

    Syntax: VARIABLE := value

    # This is a test Makefile

    # Immediate Set

    HELLO = World

    HELLO_WORLD := $(HELLO) World!

    HELLO = Hello

    all:

    echo $(HELLO_WORLD)

    # Output we got is “Hello World!”

    Here, HELLO_WORLD is assigned value as “$(HELLO) World!”. Due to the “:=” assignment, the variable $(HELLO) gets expanded and assigned immediately. So, even if you change the value as “HELLO = Hello”, you will still see the final “echo” command displaying the old value that is “World World!”.

  • Set If Absent: Setting of a variable if it doesn’t have a value.

    Syntax: VARIABLE ?= value

# This is a test Makefile

# Set if Absent

HELLO =

HELLO ?= Value

all:

echo $(HELLO)

  • Append: Appending the supplied value to the existing value (or setting to that value if the variable didn’t exist and creating for the first time)

    Syntax: VARIABLE += value

    # This is a test Makefile

    # Append Set

    HELLO = Hello

    HELLO += World

    all:

    echo $(HELLO)

    # Displays Output as “Hello World”

    Here, the initial value is Hello. So when I used the += assignment, it will append the new value to the old value. So, the output that I got was “Hello World”.

3.0 Examples of C++ :

In this example, we deal with classes in C++ program and finally write a Makefile to compile all files wriiten in C++. The fles are included below with explanations.

File: main.cpp

#include “message.h”

using namespace std;

int main (void)

{

message m;

m.printMessage();

return 0;

}

Here, we are using a custom header file “message.h” for some of the functions used inside the main(). Then we have used “using namespace std” which indicates that we are using some namespaces where the “cout” functions, for example, are defined.

Then, as usual there is a main entry point called main() function. The “message m” creates object of the class “message” with object name “m”. Then when you call “m.printMessage()”, you are invoking a function inside the object. Then “return 0” used since the main function has a return type int.

File: message.h

#ifndef MESSAGE_H

#define MESSAGE_H

class message

{

public:

void printMessage();

};

#endif // MESSAGE_H

Here the #ifndef, #define and #endif are used to protect the header files being included multiple times.. Then, there is a class declaration section where it includes one public funtion “printMessage()”.

File: message.cpp

#include “message.h”

#include <iostream>

using namespace std;

void message::printMessage()

{

cout << “Makefile Message\n”;

}

Here, we need to include the header file “message.h” where the declarion of the class is included. The header file “iostream” is to enable us to use “cout”. Note that we are using namespace “std” here too. Now, we are adding a “printMessage()” function to the class using “::” symbol which is known as scope resolution operator and it tells to which namespace or the class the particular function or variable belong to. The “cout” statement is to print out the message.

Before we start writing Makefile, let us understand how we are going to compile the files.

  • message.cpp → message.o

  • main.cpp → main.o

  • Then link main.o and message.o together to get final “output file”

So, my Makefile will look like,

File: Makefile

CC = g++

CFLAGS = -c -Wall

all: main.o message.o

$(CC) main.o message.o -o output

main.o: main.cpp

$(CC) $(CFLAGS) main.cpp

message.o: message.cpp

$(CC) $(CFLAGS) message.cpp

clean:

rm -f *.o output

Leave a Reply

Your email address will not be published. Required fields are marked *