Makefile

Makefile Rules and Dependencies: A Step-by-Step Tutorial

A makefile is a special type of text file that provides the setup instructions for a whole project or application. These guidelines describe how to assemble a desired executable file from its dependencies. You won’t get your ultimate result if you don’t understand the makefile guidelines. Therefore, it’s necessary to consider the guidelines while creating a makefile. In this tutorial, the essential components of makefiles – rules, targets, and dependencies – will be thoroughly presented.

Basic Structure of a Makefile Rule

Target, dependencies, and commands are the three core components of a makefile rule. It is important to realize that these fundamental components are referred to as “makefile rules” as a whole. When a single element is absent, the makefile rule is useless. The following definitions belong to these three components:

  • Target: A file to be generated after the execution of a makefile.
  • Dependencies: The documents that the target file relies on are known as dependencies.
  • Commands: The command-line instructions that are utilized to build the target are referred to as commands.

The basic structure of defining a rule in makefile is depicted in the following image:

Example 1: Makefile Rules

Let’s dive into the explanation of defining a rule in a basic makefile with the help of a code example. For example, we want to execute our very basic C++ program via the makefile. For this, we create two basic files: a C++ “main.cpp” file and a makefile.

C++ Program:

Open both files in an editor of your choice. Let’s say we use the Notepad++. Within the “main.cpp” file, we add the program code that we want to execute through the use of makefile. The C++ program code starts with the addition of the “iostream” header which is specific for input and output flow. The main() function is the necessity of any program to start its execution. It starts by defining the “val” integer variable. Now, the standard “cout” stream is employed to ask for the user input while the standard “cin” stream gets that input from a user and adds it to the “val” variable. The standard “cout” stream is once again employed to display the value of “val” variable that is inputted by a user.

#include <iostream>
int main(){
    int val;
    std::cout << Enter a value: ";
    std::cin >> val;
    std::cout << val << std::endl;
    return 0;
}

Define the Makefile Rule:

Let’s define a basic rule in the makefile to get a target executable file. As mentioned in the “Basic Structure of Makefile Rule” section, a rule should consist of a target, a dependency, and a command. So, starting from the first line of a makefile, we create a target file named “main” using the C++ code file which is “main.cpp”.

Target -> main

The target file name should be followed by the “:” sign and the name of a dependency. In this case, our C++ code file is a dependency file and without it, the target will not be built.

Dependency -> main.cpp

At the second line of a makefile, we add a “build” command to create a target using a dependency. The command starts with the compiler name, i.e. “g++”, followed by the compiler flags, i.e. “-o”, that are used to specify the name of an output target file, i.e. “main”. Lastly, you need to specify the source file which is “main.cpp” from which a target file can be generated. The G++ compiler compiles the “main.cpp” file and helps the makefile to create an updated target executable “main” file every time the makefile is executed.

Command -> g++ -o main main.cpp

main: main.cpp
    g++ -o main main.cpp

After saving the makefile, run the CMD and navigate within the project working folder. In our case, it’s “D:\Work” and it has only two files: main.cpp and makefile.

> ls

You will see the “build” command displayed on your CMD screen. The target executable “main.exe” file is generated in the same working directory. Running the target file displays the same outputs as running a “main.cpp” file separately via the G++ compiler.

> make
> ls
> main.exe

Example 2: Makefile Dependencies

It’s very necessary to handle the makefile dependencies correctly. When you work on makefiles, it is possible to use the nested dependencies; one source file depends on the other to get the required result. For instance, we create two C++ files and one makefile in our current directory.

The “main.cpp” file is the main source file. Within its header, we include the reference to the other C++ “define.cpp” file, i.e. the “main.cpp” file depends on the “define” file. The implementation of a program begins with the “main” function that displays an integer value of “5”. Then, it calls the “show” function that is defined in the “define” file. Another function which is “cube” is called by passing the “v” variable as a parameter. The returned result of the “cube” function is overridden in the “v” variable and displayed at the end.

#include <iostream>
#include "define.h"
using namespace std;
int main(){
    cout << "We are in main file! " << endl;
    int v = 5;
    cout << "Number = " << v << endl;
    show();
    cout << "Cube of " << v << " is : ";
    v = cube(v);
    cout << v << endl;
    return 0;
}

The other dependency “define” file displays the message via the “show” function and calculates the cube of a “y” value that is passed as an argument by the main file within the integer variable of “r”. The value of variable of “r” is returned to the main() function.

#include <iostream>
#include <cmath>
using namespace std;
void show() {
    cout << "We are in outside file! " << endl;
}
int cube(int y) {
    int r = y * y * y;
    return r;
}

Within the makefile, we define a rule to create a target “main” file using two files. The “main” files is dependent on the output of the “define” file. The command at the second line instructs the G++ compiler to create an executable target “main” file using the “main.cpp” source file.

main: main.cpp define.h
    g++ -o main main.cpp

Now, running the make instruction executes the main file first and then the “define” file as a second dependency. The target “main” executable file is created. Upon running the target file, we get the customized output according to both dependency files.

> make
> ls
> main.exe

Conclusion

While creating makefiles, it is necessary to take into consideration the targets and their dependencies while defining the rules. Within this guide, we discussed the basic structure of defining a rule in makefile followed by examples that illustrate the basic steps to create a target. In the end, we discussed the use of multiple dependency source files in makefile to make it more efficient.

About the author

Saeed Raza

Hello geeks! I am here to guide you about your tech-related issues. My expertise revolves around Linux, Databases & Programming. Additionally, I am practicing law in Pakistan. Cheers to all of you.