Makefile in linux
We offer you a brighter future with industry-ready online courses - Start Now!!
By the end of this article, you will what linux makefile is, why it is used, look at the alternatives of makefile, version, and types of make, and only then go to the practical part.
The fun part begins when we start looking at how to use a makefile. We will cover most of the ground as we discuss variables, conditional loops like if-else, functions like forereach function, call function, shell function and so many more coll topics. So let’s start!!!
What is a makefile and why do we use it?
Makefile is a command-line-based utility in Linux-based operating systems that helps us in deciding which part of gargantuan programs needs to be recompiled. It comes in handy when you want to run or update a task when certain files are updated.
The make file tool requires a file that either goes by the name “Makefile” or “makefile” with a set of tasks that need to be executed. Most open-source projects use ‘make’ to compile a final executable program which can later be installed by using “make install”.
In this tutorial, we will be prioritizing C and C++ as in the majority of the cases these programs are compiled as other languages have their own tools.
Are there alternatives to Make?
Of course, there are! Take C/C++ for example, they have alternate build systems like Scons, CMake, Bazel, Ninja, and more. Other code editors like VS Code (Visual Studio Code) have their own in-built tools.
Java also has its own tools, namely Ant, Maven, and Gradle. However, programming languages like Python, Ruby, and javascript don’t require analog Makefiles.
Versions of Makefile
There are various implementations of Make, but don’t worry, most of this tutorial will work on any version of Make. All the examples shown in this article work on all the versions 3 and 4. The version of Makefile I am using currently is 4.2.1.
If you want to see the version of your Make, simply type the command “make -version” in the terminal.
How does Make work?
The main goal of Makefile is to compile the files of your code that need to be compiled. It does this based on what files have changed. But when the files in the interpreted programming language change, nothing needs to be recompiled. You must keep in mind that when the program runs, the most recent version of the file is used.
Here is a diagram showing the example dependency graph that you might build with Make. You must not that the file will get recompiled If any file’s dependencies change:
Syntax of the makefile command in linux
Every MakeFile consists of a set of rules, and a rule looks like this:
<targets>: <prerequisite> <command> <command> <command>
Let us take a closer look at the fields in the syntax of writing a rule:
1. <targets>
The targets are nothing but file names, separated by spaces. Mostly, there is only one per rule.
2. <prerequisite>
The prerequisite file is also file names, separated by spaces. However, you need to keep in mind that the files you specified need to exist before the commands for the target are run. Prerequisites are also called dependencies.
3. <commands>
The commands are a set of actions or a series of steps typically used to make the target(s). You must always bear in mind that commands always start with a tab character and not spaces. If at all you give spaces instead of a tab, make will fail.
Printing “Hello World”
Before we jump into the action of Make, let us print the cliched “hello world” statement to understand how to write make and execute them. First of all, you need to have “Make” installed in your system, if you don’t you can simply use the command “sudo apt install make”.
We need a file to write the set of instructions right, well, there are countless ways of creating it, you can use, nano, vim, vi, gedit, cat and so many more, the only thing that is important is that the name of the file must either be “Makefile” or “makefile”. Once you create the file, write the following piece of code in it:
Once you have written it, save and close it. To execute it, simply type the command “make” in the terminal, and you will get the following output:
Beginner examples
Before we actually head-on and understand variables, conditionals, functions, and more, let us get our basics straight and understand how targets, prerequisites, and commands work by looking at some simple and small examples.
Let us create a Makefile that has 3 separate rules, when you run “make blah” in the terminal, it will build a program called “blah” in the following series of steps:
1. Since make is given “blah” as the target, it searched for this target
2. “blah” requires “blah.o”, so it searched for “blah.o”
3. “blah.o” requires “blah.c”. co it searches for this target.
4. However, “blah.c” has o dependencies, so it runs the “echo” command
5. Then the “cc -c” command is run as all of the “blah.o” dependencies are finished
6. Then the “cc” command as all of the “blah” dependencies are finished
7. And voila! “Blah” is now a fully compiled C program.
And here is the output, executed just like we anticipated!!
Let us look at a few more examples before proceeding further.
In the below-shown makefile, we only have a single target called “a_file”, so the default target is the first target, so the “a_file” will run.
And when we execute the Makefile, we will get the following output:
In the below-shown makefile, all we did is add a touch command to create “a_file”.
And we get the output as expected nothing special, we also created the file “a_file”, which we can see by running the “ls” command
But the special part is when you run the program again, Make is clever enough to not create another “a_file” but tells you that it is already created and up to date!
If we now make this program slightly more complex by depending the target “a_file” on “b_file”, the target will first look at its list of dependencies, and if any of them are older, it will first run the targets for those dependencies, and then run itself. However, the second time you execute this program, neither target will run because both targets exist.
So in the command shown below, first the target “b_file” will run followed by “a_file”. Anagin you can check if the file “b_file” is successfully created or not by using the ls command.
Now that we got the hang of how Makefiles work, and how the program executes, let us dive deeper and explore things like targste, variables, conditional statements, functions and more.
Targets
If you want to make multiple target and run all of them, you can simply make an all target
Multiple targets
When there are multiple targets for a rule, the commands will be run for each target specified. We can use the “$@” which is an automatic variable that contains the target name. Here is an example:
Infact, the above shown program is the same as the program shown below:
And we even get the same output:
Variables
In Make variables can only be strings, they can’t be integer, float. Booleans, etc, just plain strings. You typically use, “:=” but “=” also works. Let us look at an example to get a better understanding:
Here is the output of the program shown in the above screenshot:
Reference variables on the other hand use “${}” or “$()”, here is an example
And the output for the above shown code:
Flavors and modification
In make, there are 2 flavor variables:
- = (recursive) – It only looks for the variables when the command is used
- := (simply expanded) – only those defined so far get expanded
Here is an examples using both the Recursive and simply expanded variables:
And the output of the code shown above:
The simply expanded variable allows you to append a variable as shown:
Here we appended “hello” (as a variable) to “there” as seen below:
Another variable is “?=”, it only sets variables if they have not yet been set as shown in the below example:
If you think of giving spaces to your variables, you can do so, However, Spaces at the end of a line are not stripped, but those at the start are. Therefore, if you want to give spaces in a proper and professional way, create a variables called “$(nullstring)”
As you can see in every place we put the null string variables we get a blank space in the output as shown below:
If at all you are wondering what happens if we don’t define a variable? The answer is that they will simply be an empty string.
As you can see even though we used the variables “nowhere”, we did not define it, but it still acts as an empty string.
Command-line arguments and override
You can actually override the variables that come from the command line by using override. Here is an example:
List of commands and “define”
“Define” is actually just a list of commands, don’t confuse it with a function. Here is an example:
Target specific variables
You can also assign variables for specific targets as shown below:
Pattern specific variables
Variables can also be set for specific patterns as shown below:
Automatic variables
There are many automatic variables that we can use in makefiles, but only a few showup.
Wildcards
In make files we can use either “*” or “%” as wildcards, but they mean entirely different things. Let’s look at the difference between th 2.
1. *
This wildcard searches your filesystem for matching filenames
2. %
This wildcard is used for the following tasks:
a. matches one or more characters in a string. This match is called the stem.
b. takes the stem that was matched and replaces that in a string.
c. used in rule definitions and in some specific functions.
Fancy rules
In Make files we have the following fancy rules
1. Implicit rules
2. Static pattern rules
3. Static pattern rules and filter
4. Pattern rules
5. Double-colon Rules
Let us look at each one in detail along with an example:
Implicit rules
When make is compiling c programs, it uses a automatic rules, make calls these “implicit rules” here is a list of the Implicit rules:
Compiling a C program:
“n.o” is made automatically from “n.c” with a command of the form $(CC) -c $(CPPFLAGS) $(CFLAGS)
Compiling a C++ program
n.o is made automatically from n.cc or n.cpp with a command of the form $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)
Linking a single object file
n is made automatically from n.o by running the command $(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS)
Here is an example to build a C program without explicitly telling Make how to do the compilation:
Static pattern rules
Static [pattern rules is another way to write less in a Makefile!the syntax we use is shown below:
targets...: target-pattern: prereq-patterns ... commands
The basic idea is that the given target is matched by the target-pattern (through a % wildcard). Here is an example:
Static pattern rules and filters
The filter can be used in static pattern rules to match the correct files. In the example shown below, I made up the .raw and .result extensions.
Pattern rules
Pattern rules are best used for
1. Defining your own implicit rules
2. Used as a simpler form of static pattern rule.
Double – colon rules
Double colon rules are very seldom used. These rules allow multiple rules to be defined for the same target. You must be careful while using them and not get confused with single colons, as a warning would be printed and only the second set of commands would run.
Commands and execution
Command echoing/silencing
To stop a command from being printed, use the at the rate (@) symbol before the command. You can also run make with “-s “to add an at the rate symbol (@) before each line.
Command execution
Each command runs in a new shell
Default shell
Speaking of shell, every makefile’s default shell is the /bin/sh however, you can change this by using the variable “SHELL” at the beginning of the program as shown below:
Error handling
We handle different errors by using the following characters:
1. -k
We can use “-k” when running make to continue running even if it faces errors.
2. –
We can use “-” before a command to suppress the error
3. -i
We can use “-i” to make to have this happen for every command
Interrupting or Killing make
You can simply interrupt or kill make in the middle of something by pressing the key combination “ctrl” + “c”. It will also delete the newer targets it just made.
Recursively using make
If you want to recursively call a makefile, use the special variable “$(MAKE)” instead of “make”. Why? Because it will pass the make flag for you and won’t affect itself by them. Here is an example:
Using export for recursively calling a makefile
Another way to recursively call a makefile is by using export. The export directive takes a variable and makes it accessible to the command present in make, or most commonly called sub make commands.
If-else statement in Makefiles
We all know how the if-else statement works, the logic behind it is the same, the only thing that changes between various programming languages is the syntax. Let us look at one example to get a better idea of if-else statements in Makefiles:
The above program checks if the string is “DataFlair” or not. Let us now use this conditional statement in a few examples.
Checking if a variable is empty or not
Functions in Makefiles
In make there are the following function:
1. First functions
2. String substitution
3. Foreach function
4. If function
5. Call function
6. Shell function
Let us dive deeper and understand each one of them with the help of an example:
First functions
The primary purpose of functions is text processing, you can also make your own functions using the call built in function.
String substitution
The string substitution function finds whitespace separated word in the text that match pattern and replaces them with the replacement you specified.
Foreach function
The foreach function converts one list of words to another. It looks something like this:
$(foreach var, <list>, <text>)
If function
The if function checks if the first argument is non-empty or empty. If at all the first argument is not empty, it runs the second argument, otherwise the third.
Call function
As we glimpsed earlier, the inbuilt call function supports creating basic custom functions. It is you who defined the function just by creating a variable. You will have to use the parameters $(0), $(1), etc. The syntax you use to create your own function is:
$(call <variable>, <parameter>, <parameter>)
Shell function
The shell function calls the shell, but it replaces the newlines with spaces.
Bonus makefile features
Before we call it a day, let us look at some bonus features of makefile.
Multiline
The backslash (\) gives us the ability to use multiple lines when the commands are too long. Here is an example:
.phony
We can add “.PHONY” to a target to prevent make from confusing the phony target with a file name. In the example shown below, if the file “clean” is created, make clean will still be run.
.delete_on_error
Make stop running a rule if a command returns a nonzero exit status. This is where “.delete_on_error” comes in handy. It deletes the target of a rule if the rule fails in this manner. This happens for all targets, not just the one it is before like PHONY
Summary
We have now covered all of the basics and intermediate level practical commands related to make. As you have seen, makefile is a simple compiler of various programming languages.
You have now learned what makefile is, why it is used, the alternatives of makefile, how makefile works, and the syntax. In the practical part we kicked of by printing “hello world” and then going through a few beginner examples, we then went deep and understood things like targets, wildcats, variables, rules, commands and execution, if-else statement, functions and a few bonus features like PHONE, multiline and delete on error.
Did you know we work 24x7 to provide you best tutorials
Please encourage us - write a review on Google