Summary of getting started with Linux Makefile

Makefile, a skill that Linux programmers must know; of course, it does not mean that you need to "know the basics", just know some common usages!


Table of contents

1. Introduction to Makefile

2. Makfile usage at high difficulty points

1. Prepare the following project files

2. Display rules

3. Variables

4. Implicit rules

5. Wildcards

6. More advanced usage

7. Commands can be abbreviated

3. Makefile compiles the Makefile of the subdirectory (multi-path compilation)

1. Makefile in circle

2. Makefile in cube

3. Makefile in src (that is, the main Makefile)

4. Makefile link third-party library compilation

Five, Makefile enterprise project usage compilation

6. Summary


1. Introduction to Makefile

Notes for Makefile:

# this is a comment

Just use "#";

Simple usage of Makefile:

目标文件 : 依赖文件
[TAB]命令
  • [TAB]: It is the tab key on the left side of your keyboard;
  • Target file: the file to be generated;
  • Dependent files: Generate the files that the target files depend on;
  • Command: The command to execute to generate the target file.

Prepare a code, for example:

main.c 

#include <stdio.h>

int main(int argc, char **argv) {
	printf("hello world\n");
	return 0;
}

We compile normally in Linux, and generally compile like this:

 gcc main.c -o main

You can also create a new file, name it Makefile, and write the following command:

main: main.o
	gcc main.o -o main

main.o: main.c
	gcc -c main.c -o main.o 
# -o mian.o 可以省略

-c is a necessary parameter to generate .o files! 

After writing, directly make can be compiled, and the main executable file will be generated!

Makfile execution sequence:

The first is the mian target file. To generate the target file, you must rely on the main.o file; at this time, there is no main.o file, and Makefile will look down and find the main.o target file, which depends on the main.c file; main.c The file just exists, and then the following command ( gcc -c main.c -o main.o ) will be called to generate the main.o file; with the main.o file, the main target file can also be generated; Makefile is tight Then the command ( gcc main.o -o main ) will be executed to generate the main target file!

That is to say, first execute gcc -c main.c -o main.o, and then execute gcc main.o -o main


2. Makfile usage at high difficulty points

1. Prepare the following project files

circle.h

#include <stdio.h>

void test2();

circle.cpp

#include "circle.h"

void test2() {
	printf("test2()\n");
}

cube.h

#include <iostream>

void test22();

cube.cpp

#include "cube.h"

void test22() {
	std::cout << "test22()" << std::endl;
}

main.cpp

#include <iostream>

#include "cube.h"
#include "circle.h"

int main(int argc, char **argv) {
	std::cout << "hello world" << std::endl;
	test2();
	test22();
	
	return 0;
}

They are all under the same path!

2. Display rules

It is similar to the above example, except that there are a few more files in this example, so a few more commands are required.

What is the display rule, that is, the commands must be written in the Makefile correctly and clearly.

test2: main.o circle.o cube.o 
	g++ main.o circle.o cube.o -o test2
	
main.o: circle.h cube.h main.cpp
	g++ -c main.cpp -o main.o
	
circle.o: circle.h circle.cpp
	g++ -c circle.cpp -o circle.o
	
cube.o: cube.h cube.cpp
	g++ -c cube.cpp -o cube.o
	

	
.PHONY: clean
clean: 
	rm -fr main.o circle.o cube.o
cleanall:
	rm -fr main.o circle.o cube.o test2

test2 is the target executable file to be generated, it depends on main.o, circle.o, cube.o;

Therefore, main.o, circle.o, and cube.o have to be generated by writing commands for them. They each have their own dependent files, which are generated according to the dependent files; for example, cube.o depends on cube.h and cube.cpp;

So what are those below?

false target

Don't look at it first.PHONY: clean

Look at clean first:

He can also be a target, but it is a pseudo-target; when clean is triggered, the command specified under it will be executed: rm -fr main.o circle.o cube.o

Generally used to delete the .o files and executable files generated above! The same goes for cleanall.

usage:

make clean 

make cleanall

 .PHONY: What is clean?

It can be said that it is used to shield the clean folder or file with the same name; if there is a clean folder or file in the same path, then Makefile will fail to execute make clean!

3.  Variables

= : variable (modifiable)

+= : add

:= : constant (unmodifiable)

For example:

TARGET = test

CXX := g++

use:

$(TARGET) #equivalent  to the macro definition of C language

Use the English dollar $ symbol as the beginning, and then enclose it in parentheses!

So now you can use variables to improve the Makefile above:

CXX := g++
TARGET = test2
OBJ = main.o cube.o circle.o

$(TARGET):  $(OBJ)
	$(CXX) $(OBJ) -o $(TARGET)
	
main.o: cube.h circle.h main.cpp
	$(CXX) -c main.cpp -o main.o
	
cube.o: cube.h cube.cpp
	$(CXX) -c cube.cpp -o cube.o
	
circle.o: circle.h circle.c
	$(CXX) -c circle.c -o circle.o
	
.PHONY: clean
clean: 
	rm -fr $(OBJ)

cleanall:
	rm -fr $(OBJ)  $(TARGET)

Three variables are defined at the beginning of the file, and the subsequent commands are all replaced by these three variables. When executing make, it will be automatically recognized and replaced, which is equivalent to the macro replacement of C language!

4. Implicit rules

%c, %cpp, %o: any .c or any .cpp or any .o

*.c, *.cpp, *.o: all .c or all .cpp or all .o

When using %c or %cpp or %o or %h, it is preferred to add dot '.', such as: %.c, %.cpp, %.o, %.h;

If an error is reported when using a certain compiler, just remove the dot '.'!

Knowing these implicit rules, you can use them to optimize the Makefile:

CXX := g++
TARGET = test2
OBJ = main.o cube.o circle.o

$(TARGET):  $(OBJ)
	$(CXX) $(OBJ) -o $(TARGET)
	
%.o: %.h %cpp
	$(CXX) -c %.cpp -o %.o
	
.PHONY: clean
clean:
	rm -fr *.o
cleanall:
	rm -fr *.o $(TARGET)

5. Wildcards

$^ all dependencies
$< The first file of all dependent files (should also be the best matching file)
$@ all object files

Dependency file : I want to do this operation, which things I depend on;

Target file : I want to do this operation, what to generate;

Use wildcards to make final improvements to the Makefile:

CXX := g++
TARGET = test2
OBJ = main.o cube.o circle.o
RMRF := rm -rf

$(TARGET):  $(OBJ)
	$(CXX) $^ -o $@
	
%.o: %.h %cpp
	$(CXX) -c $< -o $@
	
	
.PHONY: clean
clean:
	$(RMRF) *.o
cleanall:
	$(RMRF) *.o $(TARGET)

How to use it and where should it be placed?

If you still don’t understand, then think about it this way, a command, which position is to write the dependent file, put $^; which position is to write the generated target file, put $@; as for $<, you can put it after -c go!

6. More advanced usage

CXX := g++
TARGET = test2
#OBJ = main.o cube.o circle.o
RMRF := rm -rf
SRC = $(wildcard *.cpp)    # 获取当前路径下的所有.cpp文件            
OBJ = $(patsubst %.cpp, %.o, $(SRC))    # 把$(SRC)中符合.cpp的所有文件转换成.o文件

CXXFLAGS = -c -Wall

$(TARGET):  $(OBJ)
	$(CXX) $^ -o $@
	
%.o: %cpp
	$(CXX) $(CXXFLAGS) $< -o $@
	
	
.PHONY: clean
clean:
	$(RMRF) *.o
cleanall:
	$(RMRF) *.o $(TARGET)

SRC = $(wildcard *.cpp) : Get all .cpp source files under the project path;

OBJ = $(patsubst %.cpp, %.o, $(SRC)) : link to .o file according to the source file;

wildcard and patsubst are the usage of Makefile functions, I don’t know the details, anyway, just use it like this!

The comment was added by me, I think it should be explained like this!

7. Commands can be abbreviated

That is, you don't need to write commands, Makefile will automatically recognize and generate commands!

CXX := g++
TARGET = test2
OBJ = main.o cube.o circle.o
RMRF := rm -rf

$(TARGET):  $(OBJ)
	#$(CXX) $^ -o $@
	
%.o: %.h %cpp
	#$(CXX) -c $< -o $@
	
	
.PHONY: clean
clean:
	$(RMRF) *.o
cleanall:
	$(RMRF) *.o $(TARGET)

3. Makefile compiles the Makefile of the subdirectory (multi-path compilation)

On the basis of the original project, create new folders circle, cube and src; copy circle.h and circle.cpp to circle; copy cube.h and cube.cpp to cueb; copy main.cpp to src; and in each A Makefile is created in each folder.

 

Our current requirement is that the Makefile in src, after executing the make command, will automatically compile the Makefile in circle and cube, and generate their respective .a static libraries; then compile and link, and finally generate an executable file!

1. Makefile in circle

libcircle.a: circle.o
	ar crv libcircle.a circle.o		# 编译链接成.a静态库
	
circle.o: circle.cpp
	gcc -c circle.cpp
	
.PHONY: clean
clean:
	rm libcircle.a circle.o

2. Makefile in cube

libcube.a: cube.o
	ar crv libcube.a cube.o		# 编译链接成.a静态库
	
cube.o: cube.cpp
	gcc -c cube.cpp
	
.PHONY: clean
clean:
	rm libcube.a cube.o

3. Makefile in src (that is, the main Makefile)

GXX = g++
TARGET = test2
 
ALL_C = $(wildcard ./*.cpp)		# 获取当前路径下的所有.cpp文件
C_OBJ = $(notdir $(ALL_C))		# 去掉文件的绝对路径,只保留文件名
O_OBJ = $(patsubst %.cpp,%.o,$(C_OBJ))	# 把$(C_OBJ)中符合.cpp的所有文件转换成.o文件

CIRCLE := ../circle/
CUBE := ../cube/


.PHONE: all  					# 将all 设置成伪目标,all会第一个执行 ,但不会生成目标,依赖的目标会依次执行
all: libcircle.a libcube.a $(TARGET)   		#依赖的目标 libcircle.a 、libcube.a  、 test2

libcircle.a:
	$(MAKE) -C $(CIRCLE) 		# 进入指定路径,执行路径中的Makefile,然后回到当前目录。  $(MAKE) -C (路径)

libcube.a:
	$(MAKE) -C $(CUBE) 		

$(TARGET):$(C_OBJ)        		#编译当前目录下的.cpp 生成目标程序
	$(GXX) $^ -L $(CIRCLE) -L $(CUBE) -I $(CIRCLE) -I $(CUBE) -lcircle -lcube -o $@

.PHONY: clean cleanall
clean:
	rm -fr $(TARGET) $(CIRCLE)*.o $(CIRCLE)*.a $(CUBE)*.o $(CUBE)*.a   #清理子目录下的编译后产生的文件 ,当前目录下的目标文件
	
cleanall:
	rm -fr $(TARGET) $(CIRCLE)*.o $(CIRCLE)*.a $(CUBE)*.o $(CUBE)*.a $(TARGET)

MAKE is a variable defined inside the Makefile to obtain the path of the current Makfile; you can use the command make -p to view it;

I won’t explain the details, just copy it and modify it according to your own project! The notes are also very clear!

Eventually a command will be generated to execute:

g++ main.cpp -L ../circle/ -L ../cube/ -I ../circle/ -I ../cube/ -lcircle -lcube -o test2


4. Makefile link third-party library compilation

On the basis of the third step above, create a new include folder and lib folder; copy the header file of the third-party library that you need to link to include; copy the third-party library file that needs to be linked to the lib folder;

For example, I learned the usage of the log4cpp library before, and I will take this library as an example here: (use it according to the library you need)

 Then modify the main.cpp code, add the header file of the log, etc.: (use it according to your own library)

#include <iostream>

#include "cube.h"
#include "circle.h"

#include "log4cpp/Category.hh"
#include "log4cpp/Appender.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/Layout.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/Priority.hh"
#include "log4cpp/PatternLayout.hh"
 
 
int main(int argc, char **argv) {
	
	
	std::cout << "hello world" << std::endl;
	test2();
	test22();
	
 
	/* 1.日志输出到控制台 */
	{
		log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
		appender1->setLayout(new log4cpp::BasicLayout());	// 默认配置
 
		log4cpp::Category& root = log4cpp::Category::getRoot();
		root.setPriority(log4cpp::Priority::DEBUG);
		root.addAppender(appender1);
 	
        /* 执行后,会在终端输出这句话*/
		root.debug("This is log4cpp output log message!\n");
	}
	
	// 关闭日志
	log4cpp::Category::shutdown();
 
	return 0;
}

Modify the main Makefile:

GXX 	= g++
TARGET 	= test2
 
ALL_C = $(wildcard ./*.cpp)		# 获取当前路径下的所有.cpp文件
C_OBJ = $(notdir $(ALL_C))		# 去掉文件的绝对路径,只保留文件名
O_OBJ = $(patsubst %.cpp,%.o,$(C_OBJ))	# 把$(C_OBJ)中符合.cpp的所有文件转换成.o文件

CIRCLE := ../circle/
CUBE   := ../cube/

LOG4CPP_INCLUDE := ../include/			# 第三方库的头文件路径
LOG4CPP_LIB		:= ../lib/log4cpp		# 第三方库的库路径

LIBS 	= -lpthread
LOG4CPP = -llog4cpp				# 链接第三方库

.PHONE: all  					# 将all 设置成伪目标,all会第一个执行 ,但不会生成目标,依赖的目标会依次执行
all: libcircle.a libcube.a $(TARGET)   		#依赖的目标 libcircle.a 、libcube.a  、 test2

libcircle.a:
	$(MAKE) -C $(CIRCLE) 		# 进入指定路径,执行路径中的Makefile,然后回到当前目录。  $(MAKE) -C (路径)

libcube.a:
	$(MAKE) -C $(CUBE) 		

$(TARGET):$(C_OBJ)        		#编译当前目录下的.cpp 生成目标程序
	$(GXX) $^ -L $(CIRCLE) -L $(CUBE) -L $(LOG4CPP_LIB) -I $(CIRCLE) -I $(CUBE) -I $(LOG4CPP_INCLUDE) -lcircle -lcube $(LOG4CPP) $(LIBS) -o $@  

.PHONY: clean cleanall
clean:
	rm -fr $(TARGET) $(CIRCLE)*.o $(CIRCLE)*.a $(CUBE)*.o $(CUBE)*.a   #清理子目录下的编译后产生的文件 ,当前目录下的目标文件
	
cleanall:
	rm -fr $(TARGET) $(CIRCLE)*.o $(CIRCLE)*.a $(CUBE)*.o $(CUBE)*.a $(TARGET)

Eventually a command will be generated to execute:

g++ main.cpp -L ../circle/ -L ../cube/ -L ../lib/log4cpp -I ../circle/ -I ../cube/ -I ../include/  -lcircle -lcube -llog4cpp  -lpthread -o test2


Five, Makefile enterprise project usage compilation

Personally, I think that in an enterprise project, the files will be arranged and the Makefile will be compiled as follows!

First prepare the code and compile it into a .so library

hello.h

#ifndef _HELLO_H_
#define _HELLO_H

void sayHello();

#endif 

hello.cpp

#include "hello.h"

#include <iostream>

void sayHello() {
    std::cout << "hello world!" << std::endl;
}

Compile into a dynamic library .so by the following command

g++ -c hello.cpp -o hello.o
ar -rsv libhello.so hello.o

Copy the hello.h header file to the include folder; copy libhello.so to the lib folder;

The file directory is as follows:

.
├── include
│   ├── hello.h
│   └── log4cpp
│       ├── AbortAppender.hh
│       ├── Appender.hh
|       .
|       .    # 文件太多,省略了。。。
|       .
│       ├── TriggeringEventEvaluator.hh
│       └── Win32DebugAppender.hh
├── lib
│   ├── libhello.so
│   └── log4cpp
│       ├── liblog4cpp1.so
│       ├── liblog4cpp.a
│       ├── liblog4cpp.la
│       ├── liblog4cpp.so.5
│       └── liblog4cpp.so.5.0.6
└── src
    ├── circle
    │   ├── circle.cpp
    │   ├── circle.h
    │   └── Makefile
    ├── cube
    │   ├── cube.cpp
    │   ├── cube.h
    │   └── Makefile
    ├── main.cpp
    └── Makefile

8 directories, 68 files

circle.h and circle.cpp call the libhello.so library, and then they compile it into a .so library by themselves!

 include/hello.h

#ifndef _HELLO_H_
#define _HELLO_H

void sayHello();

#endif 

src/circle/circle.h

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

void test2();

src/circle/circle.cpp (the sayHello() method in the libhello.so library is called here)

#include "circle.h"

void test2() {
	printf("test2()\n");

	// 调用libhello.so库中的方法
	sayHello();
}

src/circle/Makefile

GXX := g++

HELLO_INCLUDE 	:= ../../include/			# libhello.so库的头文件路径
HELLO_LIB		:= ../../lib/				# libhello.so库的路径

libcircle.so: circle.o
	ar crv libcircle.so circle.o		# 编译链接成.so动态库
	
circle.o: circle.cpp
	$(GXX) -I $(HELLO_INCLUDE) -L $(HELLO_LIB) -lhello -c circle.cpp
	
.PHONY: clean
clean:
	rm libcircle.so circle.o

crc/cube/cube.h

#include <iostream>

void test22();

crc/cube/cube.cpp

#include "cube.h"

void test22() {
	std::cout << "test22()" << std::endl;
}

crc/cube/Makefile

GXX := g++

libcube.a: cube.o
	ar crv libcube.a cube.o		# 链接成.a静态库
	
cube.o: cube.cpp
	$(GXX) -c cube.cpp
	
.PHONY: clean
clean:
	rm libcube.a cube.o

src/main.cpp (the sayHello() method in the libhello.so library is called here)

#include <iostream>

#include "cube.h"
#include "circle.h"

#include "log4cpp/Category.hh"
#include "log4cpp/Appender.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/Layout.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/Priority.hh"
#include "log4cpp/PatternLayout.hh"
 
 
int main(int argc, char **argv) {
	
	
	std::cout << "hello world" << std::endl;
	test2();
	test22();
	
	// 注意,这里调用了libhello.so库的sayHello方法
	sayHello();
 
	/* 1.日志输出到控制台 */
	{
		log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
		appender1->setLayout(new log4cpp::BasicLayout());	// 默认配置
 
		log4cpp::Category& root = log4cpp::Category::getRoot();
		root.setPriority(log4cpp::Priority::DEBUG);
		root.addAppender(appender1);
 	
		root.debug("This is log4cpp output log message!\n");
	}
	
	// 关闭日志
	log4cpp::Category::shutdown();
 
	return 0;
}

src/Makefile

GXX 	= g++
TARGET 	= test3
 
ALL_C = $(wildcard ./*.cpp)		# 获取当前路径下的所有.cpp文件
C_OBJ = $(notdir $(ALL_C))		# 去掉文件的绝对路径,只保留文件名
O_OBJ = $(patsubst %.cpp,%.o,$(C_OBJ))	# 把$(C_OBJ)中符合.cpp的所有文件转换成.o文件

CIRCLE := ./circle/
CUBE   := ./cube/

HELLO_LIB := ../lib/	# 自定义库 libhello.so库的路径

LOG4CPP_INCLUDE := ../include/			# 第三方库的头文件路径
LOG4CPP_LIB		:= ../lib/log4cpp		# 第三方库的库路径


LIBS 	= -lpthread
LOG4CPP = -llog4cpp				# 链接第三方库

.PHONE: all  					# 将all 设置成伪目标,all会第一个执行 ,但不会生成目标,依赖的目标会依次执行
all: libcircle.so libcube.a $(TARGET)   		#依赖的目标 libcircle.so 、libcube.a  、 test2

libcircle.so:
	$(MAKE) -C $(CIRCLE) 		# 进入指定路径,执行路径中的Makefile,然后回到当前目录。  $(MAKE) -C (路径)

libcube.a:
	$(MAKE) -C $(CUBE) 			# 进入指定路径,执行路径中的Makefile,然后回到当前目录。  $(MAKE) -C (路径)

$(TARGET):$(C_OBJ)        		#编译当前目录下的.cpp 生成目标程序
	$(GXX) $^ -L $(CIRCLE) -L $(CUBE) -L $(LOG4CPP_LIB) -L $(HELLO_LIB) -I $(CIRCLE) -I $(CUBE) -I $(LOG4CPP_INCLUDE) -lcircle -lcube -lhello $(LOG4CPP) $(LIBS) -o $@  

.PHONY: clean cleanall
clean:
	rm -fr $(TARGET) $(CIRCLE)*.o $(CIRCLE)*.so $(CUBE)*.o $(CUBE)*.a   #清理子目录下的编译后产生的文件 ,当前目录下的目标文件
	
cleanall:
	rm -fr $(TARGET) $(CIRCLE)*.o $(CIRCLE)*.so $(CUBE)*.o $(CUBE)*.a $(TARGET)

Eventually a command will be generated to execute:

g++ main.cpp -L ./circle/ -L ./cube/ -L ../lib/log4cpp -L ../lib/ -I ./circle/ -I ./cube/ -I ../include/ -lcircle -lcube -lhello -llog4cpp  -lpthread -o test3


6. Summary

The basic usage of Makefile has been introduced. What I think is that if you are a Linux programmer, you can understand the usage described above; if you are an architect or project manager, of course you have to learn more ! 

Guess you like

Origin blog.csdn.net/cpp_learner/article/details/128807093