How to create our own Makefile

Updated on 21 January 2017 5 min Read
linux makefile creation

Although this blog is not especially oriented to the area of development, but there are several concepts that are convenient to know, whether in the area of systems or development. Knowledge is always useful, and although it is impossible to know everything, there are things which are advisable to know, even if it is heard or passed. One of those concepts which, in my opinion, it is useful to bear in mind, is the creation of our own Makefile. Both to create one in the future and to understand its functioning.

For starters, it is important to know the utility of this file, but for which the best we can do is put ourselves in a hypothetical situation. Generally, when we download a compressed package that we want to install on Linux, what we do is unzip it, enter it and execute the make and make install commands. This is not because the make command installed on the computer does “magic”, but because that command takes the reference of Makefile file that is present in the folder in question, which makes a long and complicated process, is really simple to do, but … What does this file do? What if we did not have the file? The Makefile performs the compilation tasks that normally we should execute by hand, tedious tasks that should be done one by one. In addition, the lack of a Makefile also makes that in the case of sharing the code with another person, it would have to compile every file by file; Task that can be very uncomfortable for anyone.

The best is to understand this with a simple example; imagine that we have these three pieces of code:

A file called test.h:

#ifndef H_TEST
#define H_TEST
void  test ( void );
#endif

A file called test.c:

#include <stdio.h>
#include <stdlib.h>
void  test ( void )
{
    printf  ( "Hello World \ n " );
}

And finally a file called main.c:

#include <stdio.h>
#include <stdlib.h>
#include "test.h"
int  main ( )
{
Test ( ) ;
return  0 ;
}

These files are completely separate and are currently useless, as the code cannot be executed at this time. It is necessary that we compile it to obtain from them a binary (which in many cases is considered as a program) that executes the said code. This process would be carried out as follows if we did it by hand:

gcc  -o  test.o  -c  test.c
gcc  -o  main.o  -c  main.c
gcc  -o  test  test.o main.o

Then we would just have to run the file as a normal program:

. / test
Hello World

This process itself has been very simple since we only had to deal with 3 files. But let’s imagine that they are 20, or 100, or 1000. And not only that, but we also want that code to be used by more People. That is not practical, but crazy as well and that is where the Makefile becomes very important. Now… How can we create one? For example: How can we create a Makefile based on the above case? The Makefile in question would look like this (It’s very important that lines starting with gcc, start with a tab stop):

Test: test.o main.o
        gcc  -o  test  test.o main.o

Test.o: test.c
        gcc  -o  test.o  -c  test.c

Main.o: main.c test.h
        gcc  -o  main.o  -c  main.c
What does this little section mean? Let’s analyze the lines to have a clear perception of what they represent:
  • For the binary test, we need the test.o and main.o files and the compilation would be performed by the command: “gcc -o test test.o main.o”.
  • For the file test.o it is necessary to have the file test.c, with which the command “gcc -o test.o -c test.c” would be executed.
  • For the file main.o, there need to be both main.c as test.h. When these conditions are fulfilled, we would execute the command: “gcc -o main.o -c main.c”.

The Makefile that we created is perfectly functional and if we had a directory with the three files from before and the Makefile file that we created, by simply executing the make command, we would have our binary. This file is a Simple and therefore the ideal would be to understand a more complex and real file, although with what we have seen and understood its essence. Generally, a makefile includes two special “rules” called all and clean that are executed when we write “Make clean” or “Make all”. In addition, files of a certain size usually use variables similar to those used in bash, so we will enhance the previous example so that, on the one hand, there are two new rules, and on the other Side use some variables.

CC = gcc
EXEC: test

all:  $ {EXEC}

$ {EXEC} : I test.o main.o
        $ {CC}  -o  test  test.o main.o

Test.o: test.c
        $ {CC}  -o  test.o  -c  test.c

Main.o: main.c test.h
        $ {CC}  -o  main.o  -c  main.c

Clean:
        rm  -rf  * .o

The variables created are simply two: One to choose the command that is used for the compilation and in the case of making a change, it is done globally (gcc) and another to choose the name that will adopt the binary. Also, there are two new “rules”, as I said before that we would add whose function would be:

all: Performs all necessary actions to compile the project; Usually not necessary because make already does, but sometimes it is convenient to use it to be safe.

clean: Cleans all .o files are generated during the compilation process.

With this, we would already have a much more complete and realistic Makefile, being able to not only create a Makefile but above all to understand it so that when we present one whose behavior does not convince us, we can understand and modify their behavior.

I hope you have found it useful.

The Author

I am an experienced technical content writer with a proven ability to translate complex information into clear, concise prose. I have extensive experience writing for various technical audiences, including software engineers, IT professionals, and product managers. I approaches each writing project with a customer-centric mindset, focusing on understanding the audience's needs and pain points.