- What is Make?
- What are Makefiles?
- What constitute a makefile?
- Explicit Rules
- Implicit Rules
- How Make command is used?
- 1 What is Make?
- 2 What are Makefiles?
- 2.1 Variables
- 2.2 Rules
- 2.3 Directives
- 3 How Make command is used?
What is Make?
Most of the times when the complexity of a software development increases(handling tens or hundreds of files), we often should have a mechanism to automate and build the source code based on several configurations(selectively include or exclude parts of the code tree). It quickly becomes very tedious to manually figure out the required compiler settings under each of those configurations. This is where Make command comes in handy. Make command will automate the process of building a software though its configuration files called Makefiles. Based on the instructions(see below) provided in the Makefile, Make will figure out what should to be built. For example, if a software development have 10 components and not all of them are modified during each compilation, Then make will know exactly which of those modified components should be built for the next build. It also helps in streamlining the development process where less time is spent on configurations. In just single sentence make automates the build process(and more).
What are Makefiles?
As we saw in the previous section Make command cannot work on its own, It does require instructions like, what should be done with the code base, how each component depends on others, What the final build result should be etc. All of this information and more is provided in Makefiles. Like any other programming language Makefiles also have it own syntax to specify these instructions. So effectively the input to Make command is Source Code + Makefiles. In the following sections we will explore what comprises of a makefile and its syntax.
Any makefile will start will declaration of variables. Unlike major programming languages make syntax has multiple types of variable assignment. Purposefully these different assignments help in handling different scenarios in the build process.
name := Vineel wishes := Hello $(name)
:= is used for immediate assignment. Immediate assignment indicates that the right side of the assignment will be evaluated immediately after the assignment. Since wishes is a immediate assignment the value stored in wishes will be Hello Vineel . By the way a variable is referenced using $() syntax.
name := Vineel wishes = Hello $(name) $(salute) salute := Good Morning $(warning $(wishes))
= is used for Lazy assignment. Lazy assignment indicates that the right side of the assignment will be evaluated only when the variable is actually used. Even though salute is not defined at the time of declaring wishes, we still get Hello Vineel Good Morning printed by the warning function(debug function). This happens because the value of wishes gets evaluated only when it is used. This type of evaluation is also called as Recursive Assignment. A more practical example of using Lazy Assignment is in function declaration as shown below
#the below statement creates a lazy assignment which also simulates function #it takes the argument as $(1) and sorts it using builtin sort function and #get the last word from the sorted list using words and word builtin functions. #words give number of words in $(1) and word give the specified word from Sorted list lastsortedword = $(word $(words $(1)), $(sort $(1)) ) $(warning $(call lastsortedword, pqr xyz abc))
The above makefile print xyz
Conditional Lazy Assignment(?=)
name := Vineel wishes ?= Hello $(name) $(salute) salute := Good Morning $(warning $(wishes))
?= is used for Conditional Lazy assignment. Conditional Lazy assignment is exactly same as Lazy assignment expect it assigns the right hand side expression to left side only when left hand side variable does not have any value already
name := Vineel wishes += Hello $(name) $(salute) salute := Good Morning $(warning $(wishes))
+= is used for Concatenation assignment. Concatenation assignment as it name indicates concatenate the value of a variable(wishes) with the right hand side of the assignment. But one interesting feature of this assignment is, It can be an Immediate assignment or Lazy assignment depending on the type of the variable present on the left side of the assignment.
The main goal of variables in makefiles is to hold data upon which make command will act. So in real life example, The data could be in various forms, like the files name to be compiled, name of the compiler to be used, Its command line flags, etc.
Up until now we saw creating Makefiles purely from declaration point of view, None of the above information has instructions to make about what should be done with the files in the code base. This is where rules come in to picture. Rules help make command what should be done with the files. Rules fall into two categories, Explicit and Implicit.
An explicit rule has mainly three components to it.
targets... : prerequisites.... recipe1 recipe2 ....
targets: What should be created finally?
prerequisites: What are required to create the above target?
recipe: How to create the target? – commands to create the target
Note: every recipe should be indented with a single tab.
main : main.o cc -o main main.o
The beauty of rules in makefiles is, recipes gets executed only when target(main) is not already found or any of the prerequisites(main.o) are newer than the target. Not all of the three parts are required all the time. One important point about rules in general are the prerequisites can contain other targets in which case make invoke that target rule first before continuing with the current rule. A simple example will demonstrates this.
main : create-obj cc -o main main.o create-obj : main.c cc -c main.c
The above example has two rules main and create-obj since we specify the dependency of main with create-obj make invokes create-obj first and then continues with main. This is all done by make command, by first creating a dependency graph of all the rules present in the makefile and then recursively invoke the proper rule required.
Implicit rules tell make how to use customary techniques so that you do not have to specify them in detail when you want to use them. For example, there is an implicit rule for C compilation. File names decide which implicit rules are run. For example, C compilation typically takes a ‘.c’ file and makes a ‘.o’ file. So make applies the implicit rule for C compilation when it sees this combination of file name endings
foo : foo.o bar.o cc -o foo foo.o bar.o
looking at foo.o in the prerequisites, If the file(foo.o) does not exists make will implicitly write the following rule because it know how to create a foo.o from foo.c
foo.o : foo.c cc -c foo.c
Makefiles, Apart from containing instructions to make command about what should be done with the codebase, it also has instructions about itself. For example, How to include other Makefiles, How to export a make variable to sub make process, How to conditionally include or exclude parts of the makefile etc. All of these functionality will be specified using directives. Following are some of the most important directives used in makefiles.
- Including other Makefiles
- Conditionally include or exclude parts of the makefile
- Exporting current make variable to a sub make process(child process)
Including other Makefiles
The above statement will instruct make command to include commands from Makefile2 before continuing with the current makefile.
Conditionally include or exclude parts of the makefile
Makefiles should also have a mechanism to include or exclude some part of their code based on some condition. This facility is providied by conditional directives. The conditional directives are equivalent of if else in other programming languages. It comes in following two forms (if stmt else stmt endif or if stmt else if stmt else stmt endif)
conditional-directive text-if-true else text-if-false endif or: conditional-directive-one text-if-one-is-true else conditional-directive-two text-if-two-is-true else text-if-one-and-two-are-false endif
Before dealing with a practical example for the above we should look at different forms in which the conditional directive(if part) can be specified.
ifeq (arg1, arg2) #true when arg1 equals arg2 ifneq (arg1, arg2) #true when arg1 not equals arg2 ifdef variable-name #true when variable is defined ifndef variable-name #true when variable is not defined
Practical Example for conditional directives
files := test.c test2.c libs_for_gcc := -lgnu ifeq ($(CC),gcc) $(CC) $(files) $(libs_for_gcc) else $(CC) $(files) endif
Exporting current make variable to a sub make process(child process)
export libs_for_gcc := -lgnu
export directive will export libs_for_gcc to the environment so that any child make process will have access to it.
How Make command is used?
Once the required makefile(s) is created, invoking build process with make is just running make command in the directory where the Makefile is present. Make command will automatically read the file named Makefile and start executing the commands as described above. We can also force make to read some other make file with different name using -f flag. For the most part, Rules will contribute to the majority of the Makefile because they actually contain the recipes to create the required targets.
The purpose of this article is not to add existing content to the web, Instead, Put together, the gist of what it takes to create a Makefile. I hope the purpose is served. Now is the right time to start googling for creating sample makefiles. There is still lot of information about Make and Makefiles to be covered! Please refer to the official manual in the reference for more information.