Makefile入门笔记

Makefile

定义

  • Linux下用于编译大型C或C++工程时要用到的脚本语言

引子

  • 目前在Windows下编译大型工程,一般是在集成开发环境(IDE)下进行,如VC6.0,VS2008~2017,Dev,cfree等等,只需要点击编译按钮,即可生成最终的exe或dll文件,中间过程都隐藏了。
  • linux下用gcc编译C、C++工程, 可展示从源文件到最终的执行文件的整个微观过程。

linux下编程常用的工具及方式

  1. 虚拟机————安装Ubuntu操作系统
  2. SourceCRT————在windows系统下安装,配制虚拟机网关。控制linux的终端
  3. FileZila————远程上传工具,在windows和Linux之间传输文件

这种方式可以在windows下编写源代码文件,然后上传到linux下,进行编译并执行

源文件编译过程

预编译

  • linux命令:gcc -E XXX.C -o XXX.i
  • 将引用的头文件复制到源文件
  • 展开宏定义

汇编

  • Linux命令:gcc -S XXX.i -o XXX.S
  • 生成汇编代码

编译

  • Linux命令:gcc -c XXX.S -o XXX.o
  • 生成二进制文件

链接

  • LInux命令: gcc XXX.O -O XXX
  • 链接相关联的.o文件,生成最终的可执行文件.elf

问题:当只有一个源文件的时候,以这种命令行的形式去编译,是可以接受的,但对于大型工程,有几十,几百个源文件时,这种方法明显不现实。于是就有了Makefile,它是一门类似于python,shell的脚本语言,为了改善linux下编译大型C、C++工程编译问题而产生的

Makefile语法

创建一个文本文件,重命名为Makefile,且不需要后缀。

显示规则

  • 语法格式
目标文件:依赖文件
 
TAB+linux指令
  • 第一个目标文件是终极目标
  • 伪目标 .PHONY:
# 编译单个源文件
Hello : Hello.o
    gcc Hello.o -o Hello
Hello.o : Hello.S
    gcc -c Hello.S -o Hello.o
Hello.S : Hello.i
    gcc -S Hello.i -o Hello.S
Hello.i : Hello.c
    gcc -E Hello.c -o Hello.i

.PHONY:
#删除所有文件
clearAll:
    rm -rf Hello.i Hello.S Hello.o Hello
#删除中间文件
clear:
    rm -rf Hello.i Hello.S Hello.o
#编译多个源文件时
Test : cube.o circle.o main.o  
    gcc cube.o circle.o main.o -o Test
cube.o : cube.c
    #简化预编译和汇编过程
    gcc -c cube.c -o cube.o
circle.o : circle.c
    #简化预编译和汇编过程 
    gcc -c cirle..c -o circle.o
main.o : main.c
    gcc -c mian.c -o main.o

.PHONY:
#删除所有文件
clearAll:
    rm -rf cube.o circle.o main.o Test
#删除中间文件
clear:
    rm -rf cube.o circle.o main.o
  • linux下执行的时候,切换到Makefile文件所在文件夹,在命令行输入make,就可以执行Makefile中除伪目标之外的其他目标
  • 执行伪目标时,在命令行输入make 伪目标名,如make clearAll

变量

  • = 替换
  • += 追加
  • := 常量
#编译多个源文件时
#将目标或指令中的TAR全部替换成Test,使用变量:$(变量名)
TAR = Test
Obj = cube.o circle.o main.o
#将常量CC等价于gcc指令,不可更改,使用常量:$(常量名)
CC := gcc
$(TAR) : $(Obj)  
    $(CC) $(Obj) -o $(tar)
cube.o : cube.c
    #简化预编译和汇编过程
    $(CC) -c cube.c -o cube.o
circle.o : circle.c
    #简化预编译和汇编过程 
    $(CC) -c cirle..c -o circle.o
main.o : main.c
    $(CC) -c mian.c -o main.o

.PHONY:
#删除所有文件
clearAll:
    rm -rf $(Obj) $(TAR)
#删除中间文件
clear:
    rm -rf $(Obj)

隐含规则

  • %.c 表示任意的.c文件
  • %。o 表示任意的.o文件
  • *.c 表示所有的.c文件
  • *.o 表示所有的.o文件
#编译多个源文件时
#将目标或指令中的TAR全部替换成Test,使用变量:$(变量名)
TAR = Test
Obj = cube.o circle.o main.o
#将常量CC等价于gcc指令,不可更改,使用常量:$(常量名)
CC := gcc
$(TAR) : $(Obj)  
    $(CC) $(Obj) -o $(tar)
#这里*.o表示$(Obj)依赖的所有.o文件
#%.c和%.o表示其中的一个.c和.o文件
*.o : *.c
    #简化预编译和汇编过程
    $(CC) -c %.c -o %.o

.PHONY:
#删除所有文件
clearAll:
    rm -rf $(Obj) $(TAR)
#删除中间文件
clear:
    rm -rf $(Obj)

通配符

  • $^ ———— 表示所有的目标文件
  • $@ ———— 表示所有的依赖文件
  • $< ———— 表示所有依赖文件中的第一个文件
  • $> ———— 表示所有依赖文件中的最后一个文件

最后

Makefile的理解需要在不断的使用的过程中去加深,以上只是领进门。继续加油

补充内容

注意点

makefile 语法
目标 : 依赖(条件)
命令

all :
gcc add.c -o app

stat app.c 查看文件的访问,修改时间
touch app.c 会更新时间

clean:
-rm -f app.o # -f 强烈删除 -rm前面的-表示即使出错也会继续执行

.PHONY : clean #伪目标,使得clean时间戳会更新
rm -f app.o

test:
@echo “hello” #加@表示不打印命令

  1. 依赖可以不加
  2. 命令前必须加tab
  3. 目标名与生成的文件名可以不一致
  4. make执行编译时会检查文件更新,只更新修改过的文件
  5. 有依赖的话,先执行依赖对应的命令,没有依赖直接执行目标下的命令
  6. make 默认执行第一个出现的目标,可通过make 目标名来执行其他目标
  7. make clean执行目标clean
  8. make -C src #执行src目录下的makefile

通用模板

$@表示目标
$^表示所有依赖
$<表示依赖中的一个
src= ( w i l d c a r d ∗ . c ) / / 查 找 符 合 条 件 的 文 件 o b j = (wildcard *.c) //查找符合条件的文件 obj= (wildcard.c)//obj=(patsubst %.c %.o $(src)) //把查找到的.c文件替换成.o文件
target=app
CPPFLAGS=-Iinclude #预处理器参数
CFLAGS=-g -Wall #编译时的参数
LDFLAGS=-L…/lib -lmylib #连接时的参数
CC=gcc #编译器

扫描二维码关注公众号,回复: 12552795 查看本文章

( t a r g e t ) : (target) : (target):(obj)
$(CC) $^ $(LDFLAGS) -O $@
%.o:%.c
$(CC) -c $< $(CFLAGS) $(CPPFLAGS) -o $@

彻底清除过程文件

.PHONY : clean
clean:
-rm -f $(obj)

#彻底清除生成的过程文件及生成的配置文件
distclean:
rm /usr/bin/app
install:
cp app /usr/bin

编译动态库通用模板(.so)

#DIR指的是.o文件和.c文件所在的目录
DIR=.

#BIN指的是最终生成的目标对象名(包括路径),它可以是可执行程序、动态链接库或静态链接库
BIN=$(DIR)/libUIH.Web.Component.DicomImageWrapper.so

#SHARE特指链接生成动态链接库对象时的编译选项
SHARE=--share

#CFLAG即compile flag,表示在编译时所加入的选项参数
#参数包括
#-Wall  : 编译后显示所有警告信息
#-g     : 编译时加入调试信息,以便之后可以用gdb调试
#-fPIC  : 编译动态链接库时加入的选项
CFLAG=-fPIC -I/usr/local/include -I../ -g -Wall -std=c++11 -DHAVE_CONFIG_H

#LFLAG即library flag,表示链接生成可执行程序时所要链接的所有库的选项参数
#-L./lib : -L指示动态/静态链接库所在的目录,这里./lib即所在的目录
#-l      : -l指示动态/静态链接库的名字,注意: 这里的库名字并不包括前缀(lib)和后缀(.a或.so)
#$(SHARE) : 取SHARE变量对应的动态链接库链接选项--share
LFLAG=-L/usr/local/lib64 -ldcmdata -lofstd -loflog --share

#CC即编译链接命令gcc -o 用于编译或者链接动态库以及可执行程序
CC=g++ -o

#AR即ar -cr ,ar -cr是专门链接生成静态库的命令
#-c : 即create,创建静态库
#-r : 即replace,当静态库改变时,替代原有静态库
AR=ar -cr

#最终用的是哪个链接命令
#链接生成动态库和可执行程序用CC
#链接生成静态库用AR
CO=$(CC)


#-------------------------以下为通用不变区域-----------------------

#SRC指的是指定目录下的所有.cpp文件名,OBJ指的是指定目录下的所有.o文件名
SRC=$(wildcard $(DIR)/*.cpp)
OBJ=$(patsubst %.cpp, %.o, $(SRC))

#链接命令
all:$(BIN)
$(BIN):$(OBJ)
        $(CO) $@ $^ $(LFLAG)

#编译命令
$(DIR)/%.o:$(DIR)/%.cpp
        $(CC) $@ -c $< $(CFLAG)

install:
        cp $(BIN) ../lib

#清除无用文件
.PHONY:clean
clean:
        rm $(OBJ) $(BIN)

参考手册

链接:https://pan.baidu.com/s/1tZG7Egsd6zeIAGZWuOQC2A
提取码:zygy

多文件目录下makefile文件递归执行编译所有c文件

在一个Makefile中控制所有工程下makefile的编译顺序

#将对应的cpp文件名转为.o文件后放在CUR_OBJS变量中
#CUR_OBJS=${patsubst %.c, %.o, $(CUR_SOURCE)}

#将以下变量导出到子shell中,相当于导出到子目录下的makefile中
export CC ROOT_DIR

#注意顺序,先执行SUBDIRS,最后才执行DEBUG
#all:$(SUBDIRS)
#递归执行子目录下的makefile文件
#$(SUBDIRS):ECHO
#       make -C $@
#DEBUG:ECHO
#       make -C debug
#ECHO:
#       @echo $(SUBDIRS)

all:Algo
Algo : BaseProcess Connector
        make -C $(ROOT_DIR)/$@
BaseProcess : DicomImageWrapper
        make -C $(ROOT_DIR)/$@
Connector : Library CBinding
        make -C $(ROOT_DIR)/$@

#all : .Library
#       make -C $(ROOT_DIR)/Library

#将cpp文件编译为o文件,并放在指定放置目标文件的目录中
#$(CUR_OBJS):%.o:%.c
#       $(CC) -c $^ -o $(ROOT_DIR)/$(OBJS_DIR)/$@

test:
        @echo $(SUBDIRS)
        @echo $(ROOT_DIR)

#clean:
#       @rm $(OBJS_DIR)/*.o
#       @rm -rf $(BIN_DIR)/*

在一个shell脚本中控制各工程下makefile的编译顺序

//make.sh
cd ./Wrapper
make
cd ../CBinding
make
cd ../Library
make
cd ../BaseProcess
make
cd ../Connector
make
cd ../Algo
make
~     

参考:https://www.cnblogs.com/Shirlies/p/4282182.html

makefile与cmake的区别

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。
因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。

因此,建议使用cmake来生成针对指定平台的makefile

补充

利用cmake编译多个工程的脚本

主目录下控制脚本

#cmake最低版本需求,不加入此行会受到警告信息
CMAKE_MINIMUM_REQUIRED(VERSION 3.7)
#设置环境变量,根据自己的环境调整路径
#${CMAKE_SOURCE_DIR}是CMAKE内置的宏,表示CMakeList.txt所在的目录
#设置boost库的头文件路径
SET(BOOST_INCPATH ${CMAKE_SOURCE_DIR}/../3rd/boost/include)
SET(BOOST_LIBPATH ${CMAKE_SOURCE_DIR}/../3rd/boost/lib)
SET(GRPC_LIBPATH  ${CMAKE_SOURCE_DIR}/../3rd/grpc/opt)
SET(PROTO_LIBPATH  ${CMAKE_SOURCE_DIR}/../3rd/grpc/opt/protobuf)
SET(GRPC_INCPATH  ${CMAKE_SOURCE_DIR}/../3rd/grpc/include)
SET(HIREDIS_INCPATH  ${CMAKE_SOURCE_DIR}/../3rd/hiredis-master)
SET(HIREDIS_LIBPATH  ${CMAKE_SOURCE_DIR}/../3rd/hiredis-master)
SET(ALL_INCPATH  ${CMAKE_SOURCE_DIR}/../3rd/include)
SET(ALL_LIBPATH  ${CMAKE_SOURCE_DIR}/../3rd/lib)

#设置执行文件输出目录
SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/../bin)
#设置动态库或静态库输出目录
SET(LIBRARY_OUTPUT_PATH ${CMAKE_SOURCE_DIR}/../lib)
#打印信息
message("BOOST_INC_PATH: ${BOOST_INCPATH}")
message("BOOST_LIB_PATH: ${BOOST_LIBPATH}")
message("GRPC_LIBPATH: ${GRPC_LIBPATH}")
message("PROJECT_SOURCE_DIR: ${CMAKE_SOURCE_DIR}")
message("HIREDIS _PATH: ${HIREDIS_INCPATH}")

SET(PROJECT_NAME IntelligenceApp)
PROJECT(${PROJECT_NAME}) #项目名称

INCLUDE_DIRECTORIES(${BOOST_INCPATH})
INCLUDE_DIRECTORIES(${HIREDIS_INCPATH})
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR})
INCLUDE_DIRECTORIES(${GRPC_INCPATH})
INCLUDE_DIRECTORIES(${ALL_INCPATH})

LINK_DIRECTORIES(${HIREDIS_LIBPATH})
LINK_DIRECTORIES(${GRPC_LIBPATH})
LINK_DIRECTORIES(${BOOST_LIBPATH})
LINK_DIRECTORIES(${PROTO_LIBPATH})
LINK_DIRECTORIES(${ALL_LIBPATH})
LINK_DIRECTORIES(${LIBRARY_OUTPUT_PATH})


#find_package( Boost REQUIRED )
#include_directories( ${Boost_INCLUDE_DIRS} )
#message("Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}")
#message("Boost_LIBS: ${Boost_LIBS}")


#添加项目子目录
ADD_SUBDIRECTORY(IA_TaskQueue)
ADD_SUBDIRECTORY(IA_DBVisitor)
ADD_SUBDIRECTORY(IA_CBinding)
ADD_SUBDIRECTORY(IA_Library)
ADD_SUBDIRECTORY(IA_DicomImageWrapper)
ADD_SUBDIRECTORY(IA_Connector)
ADD_SUBDIRECTORY(IA_BaseProcess)
ADD_SUBDIRECTORY(IA_LungNoudleAlgo)
ADD_SUBDIRECTORY(IA_RibAlgo)

#把当前目录(.)下所有源代码文件和头文件加入变量SRC_LIST
AUX_SOURCE_DIRECTORY(. MAIN_SRC)

#5.set environment variable,设置环境变量,编译用到的源文件全部都要放到这里,否则编译能够通过,但是执行的时候会出现各种问题,比如"symbol lookup error xxxxx , undefined symbol"
#SET(TEST ${SRC_LIST})

#生成应用程序 hello (在windows下会自动生成hello.exe)
ADD_EXECUTABLE(${PROJECT_NAME} ${MAIN_SRC})
#如果要生成动态链接库
#ADD_LIBRARY(TaskQueue SHARED ${TQ_SRC})

#添加链接库
TARGET_LINK_LIBRARIES(${PROJECT_NAME} IA_TaskQueue IA_DBVisitor)

TARGET_LINK_LIBRARIES(${PROJECT_NAME} protobuf grpc++ grpc grpc++_reflection boost_thread hiredis)

子目录下的脚本

ADD_DEFINITIONS(-O3 -g -W -Wall -std=c++11)
#将当前目录的源文件名称赋给变量SUB_SRC
AUX_SOURCE_DIRECTORY(. SUB_SRC)
#指示将SUB_SRC中的源文件编译成名称为DBVisitor的动态库
ADD_LIBRARY (IA_DicomImageWrapper SHARED ${SUB_SRC})

TARGET_LINK_LIBRARIES(IA_DicomImageWrapper dcmdata ofstd oflog)

参考:https://blog.csdn.net/qq_28038207/article/details/80791694
https://github.com/seisman/how-to-write-makefile
https://zhuanlan.zhihu.com/p/47390641

猜你喜欢

转载自blog.csdn.net/tianzhiyi1989sq/article/details/92431294