Using the make utility to build software (Primer)

  • Post author:
  • Post last modified:December 29, 2023
  • Reading time:8 mins read

make is a utility for building executable programs and libraries from source files in Unix-like systems. make builds the software based on rules given in a makefile and optimizes on the build process by compiling only the files that have changed since the last build. make was first authored by Stuart Feldman in 1977 for the UNIX operating system. The present day GNU make available under Linux is authored by Richard Stallman, Roland McGrath and Paul D. Smith.

Makefile

make works on the basis of contents of a specially formatted text file, named Makefile or makefile in the current working directory where make has been invoked. It is preferable to name the makefile with an uppercase M as this makes the Makefile stand out in a directory listing. Once you have Makefile in your current directory, you can run make by simply typing,

make [target]

at the shell prompt. As will be explained later, a Makefile contains information about targets. So make target builds the specified target. If no target is given in the command, make builds the first target given in Makefile.

Makefile is a text file, with the format,

<target>: <pre-requisites> 
                <command> 
                <command> 
                ..... 

A typical Makefile entry is for building a <target>. A <target> is normally an executable file or an object file. The <pre-requisites> are the files the <target> depends on. If any of the <pre-requisites> files has changed since the last build of <target>, the <target> has to be built again. make optimizes its work and builds the <target> only if at least one of the <pre-requisites> files has changed since the last time <target> was built.

After <pre-requisites>, the subsequent lines give the command(s) to build the target. Here, we have the most important requirement. All command lines in a Makefile must start with a tab character. Failure to put the tab character at beginning of command lines has been a source of hard-to-solve problems in using make for quite sometime.

Lines starting with # in Makefile are comments.

A simple Makefile

Using the concepts described above, let's look at a simple Makefile,

# 
# Makefile 
# 

scheduler_app: scheduler_app.o scheduler.o linklist.o 
        gcc scheduler_app.o scheduler.o linklist.o -o scheduler_app 

scheduler_app.o: scheduler.h scheduler_app.c 
        gcc -c scheduler_app.c 

scheduler.o: scheduler.h scheduler.c 
        gcc -c scheduler.c 

linklist.o: linklist.h linklist.c 
        gcc -c linklist.c 

The above Makefile is for building an executable file scheduler_app. scheduler_app is a target that depends on scheduler_app.o, scheduler.o and linklist.o, which in turn are targets themselves dependent on their respective .c files. Although not visible, there is a tab character at the beginning of all command lines containing the gcc command.

PREREQUISITES

There are two types of prerequisites, normal prerequisites and order-only prerequisites. Normal prerequisites, impose two constraints on the target. It gives the order of execution of other targets for the given target. That is, the prerequisite targets should be built in the order listed for the given target. Second, if any of the prerequisite target is out of date, that is, its own prerequisites are newer than itself, then it should be built before building the original target. Order-only prerequisites only impose the first constraint. That is, prerequisites targets should be built in a particular order. It is not necessary to check whether a prerequisite target is out of date and should be re-built. Order-only prerequisites are listed after normal prerequisites and the two are separated by the pipe symbol, |,

targets: normal-prerequisites | order-only prerequisites

For example, if we wish to create scheduler_app in scheduler-app directory, we can make a target scheduler-app-dir as shown below and make it an order-only prerequisite for scheduler-app/scheduler_app.

scheduler-app-dir: 
      mkdir -p scheduler-app 

scheduler-app/scheduler_app: $(OBJECTS) | scheduler-app-dir 
      gcc $(OBJECTS) -o scheduler-app/scheduler_app 

PHONY TARGETS

Normally, a target is a file built by a program. However, sometimes, you need to do some housekeeping work. For this, phony targets are used (Phony targets have other uses also). A phony target is a name given to some command(s). For example, if you wish to remove all the .o files, you can have a phony target remove_objs, as shown below. If you wish to remove all .o files as well as the executable, scheduler_app so as to have a clean slate, you can add a clean phony target to the Makefile,

.PHONY: remove_objs clean 

remove_objs: 
        rm *.o 
clean: remove_objs 
        rm scheduler_app 

Here, remove_objs is a prerequisite for clean. For phony targets, a prerequisite works like a subroutine. So if you give the command make clean, first all the .o files are removed, and then scheduler_app is removed.

Another use of phony targets is the case where there is a single Makefile for multiple executables, say app1, app2 and app3. You can have a phony target all,

.PHONY: all 
all: app1 app2 app3 

app1: <pre-requisites> 
                <command> 

app2: <pre-requisites> 
                <command> 

app3: <pre-requisites> 
                <command> 

make invoked without any arguments builds the first target. In this case, when make is invoked without any arguments, it amounts to make all, which in turn, builds all the three programs, app1, app2 and app3.

VARIABLES

You can define variables in a Makefile. Variables are just like shell variables. Each variable contains a text string. The value of a variable VAR is $(VAR). For example, in the first Makefile, we could have defined,

OBJECTS = scheduler_app.o scheduler.o linklist.o 

The Makefile becomes much neater once we introduce OBJECTS variable and remove_objs and clean phony targets.

# 
# Makefile 
# 
OBJECTS = scheduler_app.o scheduler.o linklist.o  
scheduler_app: $(OBJECTS) 
        gcc $(OBJECTS) -o scheduler_app  

scheduler_app.o: scheduler.h scheduler_app.c 
        gcc -c scheduler_app.c  

scheduler.o: scheduler.h scheduler.c 
        gcc -c scheduler.c  

linklist.o: linklist.h linklist.c 
        gcc -c linklist.c  

.PHONY: remove_objs clean 
remove_objs: 
        rm *.o 
clean: remove_objs 
        rm scheduler_app  

MISCELLANEOUS

You can terminate the pre-requisites with a ; and continue writing the command on the same line. Also, if you write a @ character at the beginning of a command, it is not echoed on the output terminal as it is executed by make. For example.

OBJECTS = scheduler_app.o scheduler.o linklist.o 
linklist.o: linklist.c ; gcc -c linklist.c 
scheduler_app: $(OBJECTS) ; @echo "Making scheduler-app" 
      gcc $(OBJECTS) -o scheduler_app 
scheduler_app.o: scheduler_app.c ; gcc -c scheduler_app.c 
scheduler.o: scheduler.c ; gcc -c scheduler.c 

Turning the command echo off with @ is meaningful for echo command. Although you can write the command on the same line by putting a semicolon after prerequisites; it is more readable and hence preferable to write the command on a new line starting with the tab character.

REFERENCE

SEE ALSO

Using the make utility to build software (Advanced)

Share

Karunesh Johri

Software developer, working with C and Linux.
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments