Development

GNU Make Tutorial

GNU Make is a tool that helps to generate executable programs from source code and also process other non-source files from the project. Make gets the logic of building the executables and processing other non-source files from a file called a makefile or a Makefile.

Why Make?

  • Make is the de-facto tool for building executable programs from source code in the world of open source.

  • Make enables end users to build executable programs without knowing technical details of how to build them.

  • All the details of how to build executables and process non-source files are listed in the makefile – so the process becomes repeatable by all people or systems trying to build a project.

  • If a codebase is very big then it is time consuming and problematic building an executable from from scratch when the change in source code is very small. Make takes care of that. It tracks which files are changed and resolve dependency accordingly to build rebuild the specific part of the program.

  • Make is programming language agnostic. It does not care what programming language or which dialect of them you are using. Makefile is a text file of shell commands organized structurally with dependency and other logic for building the program and managing other non-source files. As it is a bunch of shell commands, it can run anywhere shell command runs. Windows do not run linux shell commands by default, but you can do it with specialized version of it for Windows.

  • During building executable programs many intermediate files are created that need not be there when the building is finished. Make deletes those files automatically. It helps keep the environment clean and saves a lot of valuable time.

Installing Make

Make alone is not enough to build programs. To build programs from source you need to have compilers and other tools installed on your system. So, we need the full suite of development tools for our purpose.

To compile sources on Linux, there is a package called “build-essential” on Debian-based systems (e.g. Ubuntu, Linux Mint, etc) and “Development Tools” on Red Hat and CentOS.

To install this on Debian based systems:

apt-get install build-essential

To install this on CentOS and Red Hat run:

yum groupinstall "Development Tools"

Getting Started With Makefile

Let’s get started by writing a hello world program with the C programming language.

The main function of our C program will reside inside hellomain.c. The content of the file should look like the following:

#include <hellofun.h>

int main() {
  my_fun();

  return 0;
}

This code includes the header file hellofun.h that contains the declaration of a function called hello_fun(). The content of hellofun.h is:

void my_fun();

The definition of my_fun() lives inside hellofun.c:

#include <stdio.h>
#include <hello.h>

void my_fun() {

  printf("Hello World!\n");

  return;
}

This is a very simple program and we could compile it with gcc with just one line of command. But, real life programs are not as simple and as small as this. Things get complex really soon.  Below, I am going to write the necessary makefile script for compiling this hello world program. I will explain different parts of it in subsequent sections.

hellomain: hellomain.c hellofun.c
    gcc -o hello hellomain.c hellomain.c -I.

Keep this code in a file called makefile (without any file extension). Put the file in the directory where the C files are. Point your command line in this directory. On the command line write make and press enter. An executable file called hello will be generated in the current directory. You can verify the result by running the executable with the following command.

./hello

Outputs:

Hello World!

Rules, Targets And Dependencies

A makefile script is a collection of rules. The rules instruct Make how to build a target or output from source or other files. The rule also specifies dependencies of the target. The dependency rules must be executed first depending on whether that is already processed by looking at the time stamps. In our example makefile above, we have a rule with target named hellomain and its dependencies. The target name is separated by a colon from the dependency list. The shell commands that will be executed are listed in the following line. The shell commands must start with a tab character.

If you specify no parameter with the make command then the first target is executed. In our example we did not specify any parameter and we had hellomain as the first and only target.

Variables

Variable are great way of writing value once and using them numerous times without repeating the value over and over again. It helps us keep our code DRY (Do Not Repeat Yourself). If you ever need to change some value all over the script, you just need to change that in one place to reflect the change everywhere if you are using variable.

In our example we used gcc as the compiler, but we may need to change the compiler to something else. So, we can keep the compiler name in a variable. Also, we can keep the compiler flags in another variable to reuse that. For setting a value to a variable we use equal sign (=) and to read that variable we use $(variable_name).

CC=gcc
CFLAGS=-I.

hellomain: hellomain.c hellofun.c
    $(CC) -o hello hellomain.c hellomain.c $(CFLAGS)

Cleaning The Environment

We often may need to clean our environment. If we want every piece of our project to be rebuilt from scratch, we need to clean it. In our simple example the only file that is generated is the hello executable. Without deleting that manually, we can delete that with make. So, we can create a rule for that and name the target as clean.

CC=gcc
CFLAGS=-I.

hellomain: hellomain.c hellofun.c
    $(CC) -o hello hellomain.c hellomain.c $(CFLAGS)

clean:
    rm hello

The shell command in the clean target is just the age old shell command rm. Now, from the command line execute:

make clean

Look at the current directory to see that the hello executable file is gone.

Expanding Our Example To Solve More Problems

In our simple hello world compilation example we have a problem that we have not solved yet. hellomain target looks at hellomian.c and hellofun.c files’ time stamps the next time you try to recompile it with make or make hellomain. So, if you change any of those two files, they will be recompiled. But, if you change hellofun.h then it will not recompile. That’s unexpected!

Again, we have skipped an intermediate level. We did not generate the object files and directly generated the executable. But, behind the scene the object files are created in a temporary directory and deleted. We want to generate the object files before building the executable. This time we are naming the main target as all

all: hellomain.o hellofun.o
    gcc hellomain.o hellofun.o -o hello

hellomain.o: hellomain.c hellofun.h
    gcc -I. -c hellomain.c

hellofun.o: hellofun.c hellofun.h
    gcc -I. -c hellofun.c

clean:
    rm -rf *.o
    rm hello

Run the make command again to see if your program builds successfully or not. Run the hello executable to verify the result. Look at the current directory and you will see that objects files are created. We have added one more line to the clean target to clean up object files. This makefile script will help recompile the hello world program even if the hellofun.h file is modified.

Conclusion

Make is one of the most essential tools for linux users and programmers. If you are an end user then knowledge of make will help you fix many broken things in your world of linux. If you are a programmer then you just cannot walk away by writing the code and let your users figure out how to compile that source code to executables. You must create a makefile script for the end users, so that they do not play some guess game to compile your source to executables.

References

GNUMake Project Homepage
GNU Make Documentation

About the author

Sabuj Sarker

Software Developer & Backend Web Developer, Python, Go, C/C++, Java, PHP and not-so-many languages & technology.

https://twitter.com/SabujXi
https://www.facebook.com/SabujXiP
https://sabuj.me/