A Universal Makefile Template for Various Scenarios

1. Introduction

For development on Windows, many IDEs come with integrated compilers, such as Visual Studio, providing a “one-click compile” feature. After coding, you only need one operation to compile, link, and generate the target file. Linux development is different from Windows; typically, the gcc/g++ compiler is used on Linux. If you are developing Linux programs for ARM, you also need to use the arm-linux-gcc/arm-linux-g++ cross-compiler. The “one-click compile” feature can also be achieved on Linux, which requires a compilation script called a “Makefile”. A Makefile can be written manually or generated using automated build tools (like scons or CMake). Writing a Makefile manually is one of the distinctions between Linux and Windows programmers. Generally, a universal Makefile can suit most Linux project programs.

2. Universal Makefile Template

2.1 Makefile for Compiling Executable Files

VERSION  =1.00
CC   =gcc
DEBUG   =-DUSE_DEBUG
CFLAGS  =-Wall
SOURCES   =$(wildcard ./source/*.c)
INCLUDES   =-I./include
LIB_NAMES  =-lfun_a -lfun_so
LIB_PATH  =-L./lib
OBJ   =$(patsubst %.c, %.o, $(SOURCES))
TARGET  =app

#links
$(TARGET):$(OBJ)
 @mkdir -p output
 $(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o output/$(TARGET)$(VERSION)
 @rm -rf $(OBJ)
 
#compile
%.o: %.c
 $(CC) $(INCLUDES) $(DEBUG) -c $(CFLAGS) $< -o $@

.PHONY:clean
clean:
 @echo "Remove linked and compiled files......"
 rm -rf $(OBJ) $(TARGET) output 

Key Points:

[1] Program Version
During the development and debugging process, multiple program versions may be generated. You can append a version number identifier after (or before) the target file.

VERSION = 1.00
$(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o output/$(TARGET)$(VERSION)

[2] Compiler Selection
On Linux, use gcc/g++; for ARM, use arm-linux-gcc; the custom cross-compiler names provided by different CPU vendors may vary, such as Hisilicon’s “arm-hisiv300-linux-gcc”.

CC = gcc

[3] Macro Definitions
During development, special code generally adds macro conditions to choose whether to compile, such as debug print output code. -D is the identifier, followed by the “macro”.

DEBUG =-DUSE_DEBUG

[4] Compilation Options
You can specify compilation conditions, such as showing warnings (-Wall) and optimization levels (-O).

CFLAGS =-Wall -O

[5] Source Files
Specify the source file destination path, using “wildcard” to get all dependent source files in the path.

SOURCES =$(wildcard ./source/*.c)

[6] Header Files
Include the dependent header files, including the source file and library file headers.

INCLUDES =-I./include

[7] Library File Names
Specify the library file names, which have a fixed format: static libraries are libxxx.a; dynamic libraries are libxxx.so. You only need to write the “xxx” part to specify the library file name.

LIB_NAMES =-lfun_a -lfun_so

[8] Library File Paths
Specify the storage path for the dependent library files. Note that if you reference dynamic libraries, they may be copied to the “/lib” or “/usr/lib” directories. When executing the application, the system defaults to indexing dynamic libraries from these paths.

LIB_PATH =-L./lib

[9] Target Files
Use “patsubst” to compile source files (.c) into target files (.o).

OBJ =$(patsubst %.c, %.o, $(SOURCES))

[10] Executable File
Executable file name.

TARGET =app

[11] Compilation

%.o: %.c
 $(CC) $(INCLUDES) $(DEBUG) $(CFLAGS) $< -o $@

[12] Linking
You can create an “output” folder to store the target executable files. After linking and outputting the target executable files, you can delete the temporary files generated during compilation (.o).

$(TARGET):$(OBJ)
 @mkdir -p output
 $(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o output/$(TARGET).$(VERSION)
 @rm -rf $(OBJ)

[13] Clean Up Compilation Information
Execute “make clean” to remove temporary files generated during compilation.

.PHONY:clean
clean:
 @echo "Remove linked and compiled files......"
 rm -rf $(OBJ) $(TARGET) output 

2.2 Makefile for Compiling Static Libraries

VERSION     =
CC          =gcc
DEBUG   =
CFLAGS  =-Wall
AR   =ar
ARFLAGS     =rv
SOURCES   =$(wildcard *.c)
INCLUDES    =-I.
LIB_NAMES   =
LIB_PATH  =
OBJ         =$(patsubst %.c, %.o, $(SOURCES))
TARGET      =libfun_a

#link
$(TARGET):$(OBJ)
 @mkdir -p output
 $(AR) $(ARFLAGS) output/$(TARGET)$(VERSION).a $(OBJ)
 @rm -rf $(OBJ)

#compile
%.o: %.c
 $(CC) $(INCLUDES) $(DEBUG) -c $(CFLAGS) $< -o $@
  
.PHONY:clean
clean:
 @echo "Remove linked and compiled files......"
 rm -rf $(OBJ) $(TARGET) output 

Key Points:
The basic format is consistent with the “Makefile for Compiling Executable Files”. The differences include the following.

[1] The “ar” command is used to link the target files (.o) into a static library file (.a). The naming format for static library files is fixed: libxxx.a.

2.3 Makefile for Compiling Dynamic Libraries

VERSION   =
CC        =gcc
DEBUG     =
CFLAGS    =-fPIC -shared 
LFLAGS   =-fPIC -shared 
SOURCES   =$(wildcard *.c)
INCLUDES  =-I.
LIB_NAMES =
LIB_PATH  =
OBJ       =$(patsubst %.c, %.o, $(SOURCES))
TARGET    =libfun_so

#link
$(TARGET):$(OBJ)
 @mkdir -p output
 $(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) $(LFLAGS) -o output/$(TARGET)$(VERSION).so
 @rm -rf $(OBJ)
 
#compile
%.o: %.c
 $(CC) $(INCLUDES) $(DEBUG) -c $(CFLAGS) $< -o $@

.PHONY:clean
clean:
 @echo "Remove linked and compiled files......"
 rm -rf $(OBJ) $(TARGET) output 

Key Points:
The basic format is consistent with the “Makefile for Compiling Executable Files”. The differences include the following.

[1] The compilation options and linking options include the “-fPIC -shared” options. The naming format for dynamic library files is fixed: libxxx.so.

3. Demo

3.1 Application Program

Write a test routine, with the file storage directory structure as follows: header files stored in the “include” directory, library files stored in the “lib” directory, source files stored in the “source” directory, and the Makefile in the current directory.

Source Code 1

/*Header File*/
#ifndef _FUN0_H_
#define _FUN0_H_
#endif

extern void fun0_printf(void);
extern void fun1_printf(void);

/*Source File*/
#include <stdio.h>
#include "fun0.h"

void fun0_printf(void)
{
    printf("Call 'fun0'. \r\n");
}

Source Code 2

/*Header File*/
#ifndef _FUN1_H_
#define _FUN1_H_
#endif

extern void fun1_printf(void);

/*Source File*/
#include <stdio.h>
#include "fun1.h"

void fun1_printf(void)
{
    printf("Call 'fun1'.\r\n");
}

Main Function Source Code

/*Source File*/
#include <stdio.h>
#include "fun0.h"
#include "fun1.h"
#include "fun_lib_a.h"
#include "fun_lib_so.h"

int main(void)
{
    #ifdef USE_DEBUG
        printf("Debug Application startup.\r\n");
    #endif
    
        fun0_printf();
        fun1_printf();
        fun_lib_a_printf();
        fun_lib_so_printf();
        return 0;
}

Library files: two library files are stored in the “./lib” directory, one static library libfun_a.a and one dynamic library libfun_so.so.

The Makefile file is the “Makefile template” from section “2.1”.

Test Run:

If the executable file prompts that “libfun_so.so” is missing, you need to copy “libfun_so.so” to the “/lib” or “/usr/lib” directories under the root directory, as the system executes the program, defaulting to linking dynamic libraries from that path.

3.2 Generating Static Libraries

Write a test routine, the generated library file is the library file called by section “3.1” (libfun_a.a). The file storage directory structure is as follows:

Source Code

/*Header File*/
#ifndef _FUN_LIB_A_H_
#define _FUN_LIB_A_H_
#endif

extern void fun_lib_a_printf(void);

/*Source File*/
#include <stdio.h>
#include "fun_lib_a.h"

void fun_lib_a_printf(void)
{
    printf("Call 'fun_lib_a'.\r\n");
}

The Makefile file is the “Makefile template” from section “2.2”.

Compile to generate static library.

3.3 Generating Dynamic Libraries

Write a test routine, the generated library file is the library file called by section “3.1” (libfun_so.so). The file storage directory structure is as follows:

Source Code

/*Header File*/
#ifndef _FUN_LIB_SO_H_
#define _FUN_LIB_SO_H_
#endif

extern void fun_lib_so_printf(void);

/*Header File*/

#include <stdio.h>
#include "fun_lib_so.h"

void fun_lib_so_printf(void)
{
    printf("Call 'fun_lib_so'.\r\n");
}

Compile to generate dynamic library.

A Universal Makefile Template for Various Scenarios

Leave a Comment