writing practical makefiles linux examples illustrations


Advertisements

 | 

Article background

    In this article I will explain how to write practical makefiles for your projects. You can copy the makefiles used in this article right away to your project and modify as per your requirements.

Article contents


What is a Makefile?
    Make is Unix utility that is designed to start execution of a makefile. A makefile is a special file, containing shell commands, that you create and name makefile (or Makefile depending upon the system). While in the directory containing this makefile, you will type make and the commands in the makefile will be executed. If you create more than one makefile, be certain you are in the correct directory before typing make.
    Make keeps track of the last time files (normally object files) were updated and only updates those files which are required (ones containing changes) to keep the sourcefile up-to-date. If you have a large program with many source and/or header files, when you change a file on which others depend, you must recompile all the dependent files. Without a makefile, this is an extremely time-consuming task.
    As a makefile is a list of shell commands, it must be written for the shell which will process the makefile. A makefile that works well in one shell may not execute properly in another shell.
    The makefile contains a list of rules. These rules tell the system what commands you want to be executed.
    After the makefile has been created, a program can be (re)compiled by typing make in the correct directory. Make then reads the makefile and creates a dependency tree and takes whatever action is necessary. It will not necessarily do all the rules in the makefile as all dependencies may not need updated. It will rebuild target files if they are missing or older than the dependency files.
    Unless directed otherwise, make will stop when it encounters an error during the construction process.

Writing a simple Makefile
The makefile for executing basic.c program

#########################################################################################################
# This is an example Makefile for illustration of handling multiple .c .h programs from a single directory. This
# program uses the basic.c,basic.h,basic1.c programs.
# Typing 'make' or 'make basic' will create the executable file.
# Then execute the executable using ./basic
# define some Makefile variables for the compiler and compiler flags
# to use Makefile variables later in the Makefile: $()
# the compiler: gcc for C program, define as g++ for C++
#########################################################################################################
CC = gcc

# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS  = -g -Wall

# typing 'make' will invoke the first target entry in the file 
# (in this case the default target entry)
# you can name this target entry anything, but "default" or "all"
# are the most commonly used names by convention
#
# the build target executable:
TARGET = basic

all: $(TARGET)

# To create the executable file basic we need the object files
# basic.o basic1.o :
#
$(TARGET): basic.o basic1.o 
  $(CC) $(CFLAGS) -o $(TARGET) basic.o basic1.o 

basic.o: basic.c basic.h 
  $(CC) $(CFLAGS) -c basic.c  

basic1.o: basic1.c basic.h
  $(CC) $(CFLAGS) -c basic1.c

clean:
  $(RM) basic *.o *~
The basic.c file to be used in Makefile

#include <stdio.h>
#include "basic.h"
int total,tot = 0;
int main ()
{
  printf("This is a simple C Program\n");
  total = sum(1,2);
  printf("Sum of two numbers is %d\n", total );
  return 0;
}
The basic1.c file to be used along with basic.c program

#include "basic.h" 
int sum (int a, int b)
{
  return a + b;
}
The basic.h file to be used along with basic.c program

int sum (int, int);
    Let's execute the basic.c program now. The output of basic.c program is shown below. First we wrote basic.c, basic1.c, basic.h files in one directory. Then we executed make command to create target binary basic. Then we executed the basic binary using ./basic. Finally we could clean up the project using make clean command.
$ ls
basic1.c  basic.c  basic.h  Makefile
$ make
gcc -g -Wall -c basic.c
gcc -g -Wall -o basic basic.o basic1.o
$ ls
basic  basic1.c  basic1.o  basic.c  basic.h  basic.o  Makefile
$ ./basic
This is a simple C Program
Sum of two numbers is 3
$
$ make clean
rm -f basic *.o *~
$ ls
basic1.c  basic.c  basic.h  Makefile
$

Writing Makefile for most of your projects.
    In the last makefile we had to manually include all the object files(files with .o extensions) in the makefile which is something we can improve upon. We will use the same source code files basic.c, basic1.c and header file basic.h for this example. Only change the makefile as below.
    In this Makefile example we have used wildcard to include all the source code files from the current directory.
Improved makefile for executing basic.c program

CC = gcc
TARGET = basic

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)

CFLAGS  = -g -Wall

all: $(TARGET)

$(TARGET): $(OBJS) 
  $(CC) $(CFLAGS) -o $@ $(OBJS)

clean:
  rm -f basic *.o *~

Common makefile for project multiple directories.
    In this section let's look at a makefile that can be used for any project with multiple subdirectories as well.
Common makefile for project with multiple directories.

TARGET_EXEC ?= calc

BUILD_DIR ?= ./build
SRC_DIRS ?= ./src

SRCS := $(shell find $(SRC_DIRS) -name *.cpp -or -name *.c -or -name *.s)
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)

INC_DIRS := $(shell find $(SRC_DIRS) -type d)
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

CPPFLAGS ?= $(INC_FLAGS) -MMD -MP

$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
  $(CC) $(OBJS) -o $@ $(LDFLAGS)

# assembly
$(BUILD_DIR)/%.s.o: %.s
  $(MKDIR_P) $(dir $@)
  $(AS) $(ASFLAGS) -c $< -o $@

# c source
$(BUILD_DIR)/%.c.o: %.c
  $(MKDIR_P) $(dir $@)
  $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# c++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
  $(MKDIR_P) $(dir $@)
  $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@


.PHONY: clean

clean:
  $(RM) -r $(BUILD_DIR)

-include $(DEPS)

MKDIR_P ?= mkdir -p
    We will use this makefile for calculator project. So create a calc directory first. Then create src directory inside it. Also create Makefile inside it and add the above makefile content to your Makefile. Create add, subtract, multiply, divide directories inside src directory. Create main.c and common.h file inside src directory. Create add.c inside add directory, subtract.c inside subtract directory, multiply.c inside multiply directory, division.c inside divide directory.
The content of all files are below.
Content of main.c file.

#include 
#include "common.h"
int result;
int main()
{
  int a1 = 30, a2 = 10;
  result = add(a1, a2);
  printf("Addition Result is %d\n", result);
  result = subtract(a1, a2);
  printf("Subtration Result is %d\n", result);
  result = multiply(a1, a2);
  printf("Multiplication Result is %d\n", result);
  result = divide(a1, a2);
  printf("Division Result is %d\n", result);
}
Content of common.h file.

int add(int , int );
int subtract(int , int );
int divide(int , int );
int multiply(int , int );
Content of add.c file.

int add(int a, int b)
{
  return a + b;
}
Content of subtract.c file.

int add(int a, int b)
{
  return a - b;
}
Content of multiply.c file.

int multiply(int a, int b)
{
  return a * b;
}
Content of division.c file.

int divide(int a, int b)
{
  return a / b;
}
    The directory structure of the project and execution output is shown below.
pi@raspberrypi:~/errors $ ls
calc  Makefile
pi@raspberrypi:~/errors $ cd calc
pi@raspberrypi:~/errors/calc $ ls
build  Makefile  src
pi@raspberrypi:~/errors/calc $ make clean
rm -f -r ./build
pi@raspberrypi:~/errors/calc $ ls
Makefile  src
pi@raspberrypi:~/errors/calc $ cd src
pi@raspberrypi:~/errors/calc/src $ ls
add  common.h  divide  main.c  multiply  subtract
pi@raspberrypi:~/errors/calc/src $ cd add
pi@raspberrypi:~/errors/calc/src/add $ ls
add.c
pi@raspberrypi:~/errors/calc/src/add $ cd ..
pi@raspberrypi:~/errors/calc/src $ cd subtract/
pi@raspberrypi:~/errors/calc/src/subtract $ ls
subtract.c
pi@raspberrypi:~/errors/calc/src/subtract $ cd ..
pi@raspberrypi:~/errors/calc/src $ cd multiply/
pi@raspberrypi:~/errors/calc/src/multiply $ ls
multiply.c
pi@raspberrypi:~/errors/calc/src/multiply $ cd ..
pi@raspberrypi:~/errors/calc/src $ cd divide/
pi@raspberrypi:~/errors/calc/src/divide $ ls
division.c
pi@raspberrypi:~/errors/calc/src/divide $

 | 
Share

Articles


C Programming

More Articles Categories

Technical articles

Prepare for software jobs


Test your skills