For more blog posts, seeTotal catalog of romantic carriages for audio and video systems learning

Introduction of Makefile

An analyses on the nature of C/C + + compiler has more detail this paper introduces the process of C/C + + compiler and preliminary discussed the compilation process at the bottom of some details, may be you have found that even if the Demo in the article is very small, but every time after modify the source code to invoke the g + + command link once again, changed a few times is a bit difficult, If you run into a large project and call g++ (GCC) one by one, it’s a bit of a rush.

We programmers tend to be lazy. We want to tell the machine a compilation process, and the machine will compile it for us with one click each time. Thus, makefiles are created.

It was Laplace, a French mathematician, who said that the invention of logarithms extended the lives of scientists, so makefiles also extended the lives of programmers.

What is a Makefile? First, understand make. Make is a command utility that interprets instructions in makefiles. In general, most ides have this command, such as: Visual C++ nmake, QtCreator qmake, but IDE help us package, so generally do not need us to write.

Overview of make: Overview of make

The make utility automatically determines which pieces of a large program need to be recompiled, And issues commands to recompile them. Our examples show C programs, since they are most common, but you can use make with any programming language whose compiler can be run with a shell command. Indeed, make is not limited to programs. You can use it to describe any task where some files must be updated automatically from others whenever the others change.

To prepare to use make, you must write a file called the makefile that describes the relationships among files in your program and provides commands for updating each file. In a program, typically, the executable file is updated from object files, which are in turn made by compiling source files.

Once a suitable makefile exists, each time you change some source files, this simple shell command:

make suffices to perform all necessary recompilations. The make program uses the makefile data base and the last-modification times of the files to decide which of the files need to be updated. For each of those files, it issues the recipes recorded in the data base.

You can provide command line arguments to make to control which files should be recompiled, or how.

Simply put, make is a build tool that uses the shell command of make to run Makefile scripts that specify which files in a project need to be compiled, their dependencies, and the final build target. Therefore, the dream of one-click compilation can be realized through make command and Makefile script. Being familiar with Makefile also lays a solid foundation for compiling FFmpeg and other C/C++ open source libraries in the future.

The Makefile rules

According to the rules

The most basic rules for makefiles can be expressed with the following statements:

Syntax for each rule:target1,target2... : depend1, depend2, ... command ...... .Copy the code

Target: the rules of the current generation targets, such as the need to generate a C language of the target file depend: to generate the target file depends on the file, such as to generate a C language of the target file, need an assembly file (. S file suffix) command: according to generate the target file from dependence on file. For example, to generate an object file from an assembler, use the GCC -c command.

The target of the first rule in the Makefile is the ultimate target of the current Makefile. If the dependency in the first rule does not exist, the rule that generated the dependency is searched down and executed recursively until all the dependencies that generated the ultimate (first rule) target exist and are regenerated into the ultimate target.

A Makefile is a large project built with a combination of such regular statements.

What about chestnuts?

C/C++ compilation nature of the animal example:

For convenience, SRC and output are more formally divided into source files and compiled files, respectively:

To get to main, we need object files for Cat, Dog, and main. To get to these object files, we need to preprocess, compile, and assemble them from their source files. First, we need to write the Makefile statement that generates cat. o.

# The ultimate goal is cat.o, the dependency is cat.s
Cat.o:Cat.s
		To get the specific command for cat.o
        g++ -c Cat.s -o Cat.o
# target is cat.s, dependency is cat.i
Cat.s:Cat.i
		To get the specific command of cat.s
        g++ -S Cat.i -o Cat.s
# the target is cat. I and the dependency is cat.cpp
Cat.i:Cat.cpp
		To get the specific command for cat. I
        g++ -E Cat.cpp -o Cat.i
Copy the code

In order to get cat. o, you need to get cat. s. In order to get cat. s, you need to look down to get the rule statement of cat. s, and so on until you find the last rule statement, find the dependency cat. CPP already exists, and then perform the reverse operation to generate the dependency of each step. Until the final cat.o is generated.

Execute make in the SRC directory:As you can see, the make command is very thoughtful, printing out the entire process, so you can see the entire dependency chain generation process clearly. The cat. I, cat. s, and cat. o files have been generated:Now the preliminary realization of a key to generate the object file.

The pseudo target

If you want to delete files from makefiles (cat. I, cat.s, cat.o), the traditional approach is:This is of course very unautomated, and if you want to delete hundreds or thousands of files, it’s basically gg pace. Since the Makefile generated these files for us, it is also responsible for deleting them.

Yes, a Makefile can do that. It can encapsulate commands to execute some commands with one click, with pseudo-goals such as deleting files produced by the build:

Cat.o:Cat.s
        g++ -c Cat.s -o Cat.o
Cat.s:Cat.i
        g++ -S Cat.i -o Cat.s
Cat.i:Cat.cpp
        g++ -E Cat.cpp -o Cat.i

PHONY indicates that this is a rule statement without a target, that is, a pseudo target. Clear indicates the name of the pseudo target command
.PHONY:
clear: // Specify the execution commandrm -rf Cat.i Cat.s Cat.o
Copy the code

PHONY indicates that this is a rule statement without a target, clear indicates the command with a pseudo target, and newlines are the specific pseudo target command. The reason to add use. PHONY is used to prevent a conflict if a file named clear happens to exist in the current directory and the destination is clear.

To do this, just make followed by the command name:Rm -rf cat. I cat. s cat. o has been executed and the file is deleted successfully.

With pseudo targets, we can start to do some actions freely, as long as we are familiar with the commands

variable

The project has not yet been built. Write a complete Makefile based on the above:

# The ultimate target is the main executable, so the dependency is 3 object files
main:Cat.o Dog.o main.o
        g++ Cat.o Dog.o main.o -o main
Cat.o:Cat.s
        g++ -c Cat.s -o Cat.o
Cat.s:Cat.i
        g++ -S Cat.i -o Cat.s
Cat.i:Cat.cpp
        g++ -E Cat.cpp -o Cat.i
Dog.o:Dog.s
        g++ -c Dog.s -o Dog.o
Dog.s:Dog.i
        g++ -S Dog.i -o Dog.s
Dog.i:Dog.cpp
        g++ -E Dog.cpp -o Dog.i
main.o:main.s
        g++ -c main.s -o main.o
main.s:main.i
        g++ -S main.i -o main.s
main.i:main.cpp
        g++ -E main.cpp -o main.i
Copy the code

You can see that after a bunch of commands are executed, the main executable appears.

Executable files are there, but everyone can see them. What kind of automatic build system is such a small project with so many things to write?

Next, it’s time to take the plunge and simplify your Makefiles

GCC generates object files from source files in one step.

main:Cat.o Dog.o main.o
        g++ Cat.o Dog.o main.o -o main
Cat.o:Cat.cpp
        g++ -c Cat.cpp -o Cat.o
Dog.o:Dog.cpp
        g++ -c Dog.cpp -o Dog.o
main.o:main.cpp
        g++ -c main.cpp -o main.o

.PHONY:
clear:
        rm -rf Cat.o Dog.o main.o
Copy the code

Perform under:

ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ make
g++ -c Cat.cpp -o Cat.o
g++ -c Dog.cpp -o Dog.o
g++ -c main.cpp -o main.o
g++ Cat.o Dog.o main.o -o main
ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Cat.o  Dog.cpp  Dog.h  Dog.o  main  main.cpp  main.o  Makefile
ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ ./main 
Cat::eat
externData:10
I am dog
Here is a Cat
Copy the code

Of course, this is just a simplification of the GCC command, but the point is now.

It is easy to find duplicate contents in a Makefile, such as “cat.o dog.o main.o”. As a programmer, you should be sensitive to this, so you must do something like extract variables.

That’s right, makefiles also have the concept of variables, defined similarly to other languages. Common symbols are:

  1. = : replace
  2. := : identical to
  3. + = : additional

It’s actually quite simple. Change the above example to:

# define variables
TAR := main
DEPEND := Cat.o Dog.o main.o
G = g++
Use variables with ${}
${TAR}:${DEPEND}
        ${G} ${DEPEND} -o ${TAR}
Cat.o:Cat.cpp
        ${G} -c Cat.cpp -o Cat.o
Dog.o:Dog.cpp
        ${G} -c Dog.cpp -o Dog.o
main.o:main.cpp
        ${G} -c main.cpp -o main.o

.PHONY:
# delete intermediates
clear:
        rm -rf ${DEPEND}
Copy the code

Execution Result:

ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ make
g++ -c Cat.cpp -o Cat.o
g++ -c Dog.cpp -o Dog.o
g++ -c main.cpp -o main.o
g++ Cat.o Dog.o main.o -o main
ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Cat.o  Dog.cpp  Dog.h  Dog.o  main  main.cpp  main.o  Makefile
Copy the code

The main executable was also successfully generated

Try cleaning up the intermediate’s pseudo-target again:

ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ make clear
rm -rf Cat.o Dog.o main.o
ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Dog.cpp  Dog.h  main  main.cpp  Makefile
Copy the code

The command is also executed successfully.

There are already predefined variables built into the Makefile: These predefined variables are also commonly used in major open source projects, so keeping them in mind is a good way to avoid running into makefiles in major open source projects.

So the above example of g++ can also be changed to CXX directly:

TAR := main
DEPEND := Cat.o Dog.o main.o
#g++ uses built-in variables
#G = g++

${TAR}:${DEPEND}
        ${CXX} ${DEPEND} -o ${TAR}
Cat.o:Cat.cpp
        ${CXX} -c Cat.cpp -o Cat.o
Dog.o:Dog.cpp
        ${CXX} -c Dog.cpp -o Dog.o
main.o:main.cpp
        ${CXX} -c main.cpp -o main.o

.PHONY:
clear:
        rm -rf ${DEPEND}

Copy the code

Perform under:

ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ make clear
rm -rf Cat.o Dog.o main.o
ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ make
g++ -c Cat.cpp -o Cat.o
g++ -c Dog.cpp -o Dog.o
g++ -c main.cpp -o main.o
g++ Cat.o Dog.o main.o -o main
ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ ls
Cat.cpp  Cat.h  Cat.o  Dog.cpp  Dog.h  Dog.o  main  main.cpp  main.o  Makefile
Copy the code

The same is ok.

Hidden rules

If a project has hundreds or thousands of source files, then you need to write hundreds or more lines of GCC. Even if there is a variable, that definition of a source file listing is also fatal, how to do it?

Why list the name of the file? If you have shell experience, you probably already have wildcards in mind. Shell commands can be used in makefiles, so the shell supports wildcards in Makefiles as well.

: Matches 0 or any character, for example.c indicates all files ending in. C. % : matches any character. It is generally used to traverse the list of dependent files in the previous layer and match the corresponding dependent files of the target

In addition to wildcards, makefiles also have automation variables to simplify scripts. Automatic variables are also similar to wildcards. Common automation variables:With hidden rules such as wildcards and automatic variables, scripts can be greatly simplified. Or chestnuts on top, it’s easy to see:

TAR := main
DEPEND := Cat.o Dog.o main.o

${TAR}:${DEPEND}
        ${CXX} ${DEPEND} -o ${TAR}
Change the command to the following:
Run GCC once to generate the corresponding.o file
%.o:%.cpp
		#$^ will DEPEND, $@ will be ${TAR}
        ${CXX} -c $^ -o $@
Copy the code

Perform under:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make clear rm -rf Cat.o Dog.o main.o ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls Cat.cpp Cat.h Dog.cpp Dog.h main main.cpp Makefile ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ make g++ -c Cat.cpp -o Cat.o g++ -c Dog.cpp -o Dog.o g++ -c main.cpp  -o main.o g++ Cat.o Dog.o main.o -o mainCopy the code

%. O :%. CPP is the equivalent of going through each.o file name in the DEPEND directory and finding the.cpp file in the current directory

Does it feel like a fat person suddenly loses weight?

Build dynamic link libraries

Now try writing dynamically linked libraries using makefiles as outlined in the previous article on the nature of C/C++ compilation.

Catt. CPP and dog. CPP are dynamically linked libraries. Here we put the resources that generated the dynamic libraries in the specific directory libSrc:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ mkdir libSrc ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ mv Cat.cpp Dog.cpp libSrc/ ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls Cat.h Dog.h libSrc main main.cpp Makefile  ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls libSrc/ Cat.cpp Dog.cpp ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ mv Cat.h Dog.h libSrc/ ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls libSrc main main.cpp Makefile ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls libSrc/ Cat.cpp Cat.h Dog.cpp Dog.h Ubuntu @ - 20-7 - ubuntu VM: ~ / study/projects/CatTest / $/ SRC/mobile head files in the include directory ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ mkdir include ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls Cat.cpp Cat.h Dog.cpp Dog.h include ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ mv Cat.h Dog.h include/ ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls Cat.cpp Dog.cpp include ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls include/ Cat.h Dog.hCopy the code

Create a new Makefile. It is easy to write a Makefile script according to the syntax above:

LIBTARGET:=libAnimal.so
LIBDEPEND:=Cat.cpp Dog.cpp
# specify to build a dynamic library
LDFFLAGS:=-shared
The C++ compilation option specifies the PIC code to generate and the location of the header file
CXXFLAGS:=-fpic -Iinclude

${LIBTARGET}:${LIBDEPEND}
        ${CXX} ${CXXFLAGS} ${LDFFLAGS} $^ -o $@
Copy the code
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ make
g++ -fpic -Iinclude -shared Cat.cpp Dog.cpp -o libAnimal.so
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src/libSrc$ ls
Cat.cpp  Dog.cpp  include  libAnimal.so  Makefile
Copy the code

The dynamic library libAnimal. So has been generated

Nested make

Now that you’ve generated the dynamic link library, you just need to generate the executable along with the object file of the main project.

Adjust the directory structure:

ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls
app  libSrc  Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls app/
include  main.cpp Makefile
ubuntu@VM-20-7-ubuntu:~/study/projects/CatTest/src$ ls libSrc/
Cat.cpp  Dog.cpp  Makefile
Copy the code

The project directory contains app and libSrc. App stores the main project code, while libSrc stores the dynamic link library code. Both directories have their own makefiles, and the project root directory has the root Makefile.

Makfile under app:

TAR := main
CXXFLAGS := -Iinclude
DEPEND := main.o
#.o files and dynamically linked libraries generate executables
${TAR}:${DEPEND}
        ${CXX} $^ *.so -o $@
%.o:%.cpp
        ${CXX} ${CXXFLAGS} -c $^ -o $@

.PHONY:
clear:
        ${RM} main

Copy the code

Makefile libSrc directory:

# Dynamic library generates output to app directoryLIBTARGET=.. /app/libAnimal.so LIBDEPEND=Cat.cpp Dog.cpp LDFFLAGS=-shared
CXXFLAGS=-fpic -I../app/include
Build dynamic libraries
${LIBTARGET}:${LIBDEPEND}
        ${CXX} ${CXXFLAGS} ${LDFFLAGS} $^ -o $@

.PHONY:
clear:
        ${RM} ${LIBTARGET} *.o *.i *.s

clearTemp:
        ${RM} *.o *.i *.s
Copy the code

This is mainly to look at the Makefile in the root directory, which is mainly used to execute the Makefile in the 2 subdirectories

LIB := libSrc
APP := app

.PHONY : all $(APP) $(LIB)
# this is a traversal of the folder statement, each traversal of the following command, i.e. Make command
$(APP) $(LIB) :
# -c indicates make in the current directory
        $(MAKE) -C $@
LIB Makefile = LIB Makefile = LIB Makefile = LIB Makefile
$(APP):$(LIB)
Copy the code

This is done by traversing the subdirectory to execute the corresponding Makefile. Note that the subdirectory Makefile dependencies are specified on the last line.

Execute the root Makefile:

ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ make
make -C libSrc
make[1]: Entering directory '/home/ubuntu/study/projects/CatTest/src/libSrc'
g++ -fpic -I../app/include -shared Cat.cpp Dog.cpp -o ../app/libAnimal.so
make[1]: Leaving directory '/home/ubuntu/study/projects/CatTest/src/libSrc'
make -C app
make[1]: Entering directory '/home/ubuntu/study/projects/CatTest/src/app'
g++ main.o *.so -o main
make[1]: Leaving directory '/home/ubuntu/study/projects/CatTest/src/app'
ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ lsApp libSrc Makefile // Execute the generated main executable ubuntu@VM- 207 --ubuntu:~/study/projects/CatTest/src$ ./app/main 
Cat::eat
externData:10
I am dog
Here is a Cat
Copy the code

You can see that the Makefile is still very sweet, and the entire path that swims between the various directories is printed out. Everything is very smooth.

This article is about getting started with Makefiles. Based on this article, we will start to talk about Cmake and NDK.