Originally Published: Wednesday, 29 March 2000 Author: Derek Barber
Published to: develop_articles_tutorials/Development Tutorials Page: 1/1 - [Printable]

Getting Started with Make - Part 2: Makefiles, Variable Definitions, and Rules

In the last issue, we ventured into the world of make, and wrote our first makefile in the process. In this issue, we will delve deeper into the topics covered last issue, and touch upon a few new concepts.

   Page 1 of 1  

Getting Started with Make Series
1. The Basics
2. Makefiles, Variable Definitions, and Rules
3. Directives

In the last issue, we ventured into the world of make, and wrote our first makefile in the process. Various concepts about make were introduced in our basic makefile, including rules, targets, dependencies, and commands. In this issue, we will delve deeper into these topics, and explore some new concepts.

If you've ever downloaded the source for a big project like the Linux kernel, and have taken a look at the makefiles for it, you've probably been a little bit overwhelmed. There is a great deal to the seemingly innocent utility known as make, but with a clear understanding of the basic concepts, you can accomplish much with it.

Last time, we went through a broad overview of make's features, touching lightly upon a number of concepts. In this issue, as well as the next few issues, a different approach to make will be taken. We will look in detail at each of make's features and concepts one by one, until we have touched upon all the main ones. At the conclusion of this series, we will bring all the concepts together into a cohesive example.

Makefiles

To start things off, let's examine makefiles< in complete detail, starting with what exactly a makefile is. A makefile is simply a text file containing instructions which are passed to the make utility. A makefile can be compared to a shell script or a C source code file, and can be created using any simple text editor.

When you create a makefile, you must give it a name. There are three common names that are generally used for makefiles and the make utility knows them by default. When make is invoked without any arguments on the command line, it immediately searches the current directory for a makefile that is named one of the three defaults. The default names that it searches for are, in this order: GNUmakefile, makefile, and Makefile. Most people recommend using the filename Makefile for makefiles, because it is the most commonly used name throughout the free software world. If none of these default names appeal to you, you can tell make to use any filename by appending either the -f filename or --file=filename argument to make (filename being the name of your makefile).

Now that you know the naming possibilities that are available for your makefiles, what exactly can you put inside them? Although makefiles can be complicated beasts, there are really only five basic constructs that you can use within them. These five constructs are known as explicit rules, implicit rules, directives, variable definitions, and comments. In the last issue, we did use the explicit rule construct in our makefile, but we haven't looked at the others yet. We will examine each of the available constructs in detail as we progress through the articles. For the remainder of this issue, we will examine variable definitions and also begin to look deeper into rules, specifically explicit rules.

Variable Definitions

Before we dive into the intricacies of rules, we should examine how variables (also known as macros) are used in makefiles. Variables allow you to assign a specific value to a identifier; this value is not fixed, and thus can change throughout your makefile. Subsequent to assigning a value to an identifier, wherever the identifier is placed inside your makefile, the value assigned to it is put in the identifier's place during processing by make. Variables are assigned using the = symbol, with the identifier on the left hand side and the value on the right. To get the value out of a variable enclose the variable's name in either parentheses or braces and put a dollar sign before it. For example, to get the value out of a variable called hack you can write it as either $(hack) or ${hack}. Due to the usage of the dollar sign in variable reference, if you want to put an actual dollar sign in your makefile, you must write two dollar signs to cancel the special meaning of the dollar sign.

Here is an example of using a variable within a makefile:

  CC = g++
  target = main
  depends = hack.o crack.o
  $(target) : $(depends)
    $(CC) -o $(target) $(depends)

In this example, we first declare a variable called CC and assign it the value of g++. This variable holds the command that is used to execute the compiler. This makes it very easy to adapt the makefile to different compilers, especially when the makefile involves hundreds of references to your compiler. To change the command used to execute your compiler, simply change the value assigned to the variable CC. Next, we declare a variable called target and assign it the value of main. We then declare a third and final variable called depends and assign it the value of hack.o crack.o. Finally we have our rule which makes use of these variables, which when processed by make will actually look something like this:

  main : hack.o crack.o
    g++ -o main hack.o crack.o

As you can see, the values for the variables are put in place of the variables that were referenced in the $(variable_name) context.

Rules

There are two sets of rules. In our examples in the last issue, we used what are known as explicit rules, which describe exactly how a certain target is to be made, along with any dependencies that may be present. The other set of rules, implicit rules, do not describe how to make a specific target -- rather, they provide generic instructions for specific classes of files. For example, there may be an implicit rule that describes the process of compiling C++ programs which is just generic instructions for creating a file.o from a file.cc. To use this implicit rule you may use a command such as:

  $(CXX) -c $(CPPFLAGS) $(CXXFLAGS)

We will show examples of implicit rules in much greater detail in a future issue. For now, let's concentrate on explicit rules.

Explicit Rules

As explained previously, an explicit rule inside a makefile tells make exactly how to make a certain target, listing any dependencies for the target and giving the specific commands to use in building the target. Every makefile also has a special target, known as the default goal; this is almost always the first target contained within the first rule. Let's look at an example to put these terms into perspective:

  hack.o : hack.cc
    g++ -c hack.cc

In the above example, hack.o is the target, hack.cc is the dependency, and the command is g++ -c hack.cc. This gives a rule a general form of:

  targets : dependencies
    command
    ...

Don't forget to indent your command lines with a Tab character. Also, as mentioned earlier, if you wish to use a dollar sign inside your rules, you must put two dollar signs in place of the single dollar sign. When you only put one dollar sign, make thinks you are referring to a variable, but by putting two dollar signs you are telling make that you really want just a plain old dollar sign.

Using Wildcards within Rules

When writing your rules, it is perfectly acceptable to use wildcards. Make knows the standard wildcard characters *, ?, and [...]. Wildcards are very useful for specifying patterns and any files that match the wildcard pattern are expanded wherever the wildcard is. For example, *.tar.gz matches any files that end with the characters .tar.gz. To find out more about wildcards, consult any good Unix shell documentation.

A good place to use wildcards in your makefiles is when creating a clean rule. Such a rule might look like:

  clean :
    rm -f *.o main core

Here we are telling make that when someone types in make clean, make will delete all files that end in the .o extension along with a file called core (if it exists) and a file called main. Note that when you are using variables, wildcard expansion does not take place within the values you assign to variables. Therefore when you write depends = *.o, the variable depends is set to the string *.o itself.

Well, that's it for this issue. Thank you for reading. Next issue we will delve deeper into rules and also touch upon topics such as directives, specifically the include directive.

Derek Barber (dbarber@apiva.com) is a project manager for Apiva.com, a company developing search engine technology and Linux-based software.





   Page 1 of 1