Makefile学习

libvpu 的Makefile

CC ?=$(CROSS_COMPILE)gcc
AR ?=$(CROSS_COMPILE)ar
#CC = gcc

# list of platforms which want this test case
#INCLUDE_LIST:= IMX27ADS IMX51 IMX53 IMX6Q

OBJ = vpu_io.o vpu_util.o vpu_lib.o vpu_gdi.o vpu_debug.o

LIBNAME = libvpu
SONAMEVERSION=4

ifeq ($(PLATFORM), $(findstring $(PLATFORM), $(INCLUDE_LIST)))

ifeq ($(findstring IMX5, $(PLATFORM)), IMX5)
VERSION = imx5
else
VERSION = $(shell echo $(PLATFORM_STR) | awk '{print substr($$0, 1, 5)}' \
	| awk '{print  tolower($$0) }' )
endif

all: $(LIBNAME).so $(LIBNAME).a

install: install_headers
	@mkdir -p $(DEST_DIR)/usr/lib
	cp -P $(LIBNAME).* $(DEST_DIR)/usr/lib

install_headers:
	@mkdir -p $(DEST_DIR)/usr/include
	cp vpu_lib.h $(DEST_DIR)/usr/include
	cp vpu_io.h $(DEST_DIR)/usr/include
else
all install :
endif

%.o: %.c
	$(CC) -Wall -g -O0 -I/home/cbb/unified_3d/GPU_Src_drv/build/sdk/include -fPIC -c $^ -o $@

$(LIBNAME).so.$(SONAMEVERSION): $(OBJ)
	$(CC) -shared -nostartfiles -Wl,-soname,$@ $^ -o $@ -lpthread -lGAL

$(LIBNAME).so: $(LIBNAME).so.$(SONAMEVERSION)
	ln -sf $< $@

$(LIBNAME).a: $(OBJ)
	$(AR) -rc $@  $^

.PHONY: clean
clean:
	rm -f $(LIBNAME).* $(OBJ)

CC ?=$(CROSS_COMPILE)gcc
AR ?=$(CROSS_COMPILE)ar

定义要使用的编译器,非交叉编译的场合,CROSS_COMPILE为空,所以使用的就是gcc,交叉编译时(如在x86 PC上编译在ARM上运行的软件),CROSS_COMPILE会定义为类似于arm_linux_gnu_的值,这时会使用交叉编译器(如arm_linux_gnu_gcc)。
使用gcc编译阶段,最常用的编译选项CFLAGS = -Wall -Werror -g -O0的解释:
-Wall:编译阶段显示所有警告。
-Werror:将所有的警告当成错误进行处理,使出现警告时就停止编译。
常见编译报错:cc1: warnings being treated as errors 。解决方法是:把-Werror去掉,不把warnning当作error处理。
-g:编译器在编译时,产生调试信息,最终产生供gdb调试使用的可执行文件。用了-g选项生成的a.out会比没用-g选项生成的a.out明显大点。
-O0:编译器的优化选项的4个级别
-O0:表示编译时没有优化。
-O1:表示编译时使用默认优化。
-O2:表示编译时使用二级优化。
-O3:表示编译时使用最高级优化。
-Os:相当于-O2.5优化,但又不所见代码尺寸。

ifeq ($(PLATFORM), $(findstring $(PLATFORM), $(INCLUDE_LIST)))

#$(findstring , )
#功能:在字串中查找字串。
#返回:如果找到,那么返回,否则返回空字符串。
echo “123456789 3333344” | awk ‘{print substr($1,5,2)}’
$1指的是第一列,也就是123456789
5指的是从第5个字符开始
2指的是截取2个字符

awk '{print  tolower($$0) }'

toupper() 小写转大写
tolower() 大写转小写
分别是 @ , @, @^,$<代表的意义分别是:
@ − − 目标文件, @--目标文件, @目标文件,^–所有的依赖文件, < − − 第一个依赖文件。其中 <--第一个依赖文件。 其中 <第一个依赖文件。其中@的文件名与$^的文件名一致,只是文件后缀一个.o,另一个.c。
.so 文件的生成:

%.o: %.c
	$(CC) -Wall -g -O0 -I/home/cbb/unified_3d/GPU_Src_drv/build/sdk/include -fPIC -c $^ -o $@

-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c
这里有一个-fPIC参数
PIC就是position independent code
PIC使.so文件的代码段变为真正意义上的共享 。
.so 生成的核心代码:

$(LIBNAME).so.$(SONAMEVERSION): $(OBJ)
	$(CC) -shared -nostartfiles -Wl,-soname,$@ $^ -o $@ -lpthread -lGAL

从目标文件生成动态链接库:

$ gcc -fPIC -c func.c -o func.o
$ gcc -shared func.o -o libfunc.so

-fPIC 选项作用于编译阶段,在生成目标文件时就得使用该选项,以生成位置无关的代码。
如果希望将一个动态链接库链接到可执行文件,那么需要在命令行中列出动态链接库的名称,具体方式和普通的源文件、目标文件一样。请看下面的例子:

$ gcc main.c libfunc.so -o app.out

将 main.c 和 libfunc.so 一起编译成 app.out,当 app.out 运行时,会动态地加载链接库 libfunc.so。
当然,必须要确保程序在运行时可以找到这个动态链接库。你可以将链接库放到标准目录下,例如 /usr/lib,或者设置一个合适的环境变量,例如 LIBRARY_PATH。不同系统,具有不同的加载链接库的方法。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
Linux系统下的动态库命名方 式是 lib*.so,而在链接时表示位 -l* , *是自己起的库名。
-nostartfiles
连接的时候不使用标准系统的启动文件。标准系统库通常被使用,除非选项“-nostdlib”和“-nodefaultlibs”被使用。不链接系统标准启动文件,而标准库文件仍然正常使用。
-Wl
选项告诉编译器将后面的参数传递给链接器。
-soname
则指定了动态库的soname(简单共享名,Short for shared object name)
soname的关键功能是它提供了兼容性的标准:
当要升级系统中的一个库时,并且新库的soname和老库的soname一样,用旧库链接生成的程序使用新库依然能正常运行。这个特性使得在Linux下,升级使得共享库的程序和定位错误变得十分容易。
在Linux中,应用程序通过使用soname,来指定所希望库的版本,库作者可以通过保留或改变soname来声明,哪些版本是兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。

可以通过readelf -d来查看每个动态库的SONAME

  1. 声明libto.so.1,并生成libto.so.1.2

[root@localhost c]# gcc -fPIC -shared -Wl,-soname,libto.so.1 -o libto.so.1.2 to.c
[root@localhost c]# ls -lh
-rwxr-xr-x 1 root root 4268 Jan 10 17:22 libto.so.1.2
[root@localhost c]# ldconfig -n ./
lrwxrwxrwx 1 root root 12 Jan 10 17:23 libto.so.1 -> libto.so.1.2
-rwxr-xr-x 1 root root 4.2K Jan 10 17:22 libto.so.1.2
[root@localhost c]# readelf -d libto.so.1.2
一个C/C++文件要经过预处理(preprocessing)、编译(compilation)、汇编(assembly)、和连接(linking)才能变成可执行文件。
这两条命令的功能就是,大目标是OBJS,这个OBJS就是各种.o文件,然后%.o就是具体的解释,而%.c就是对应同样名字的.c文件,而下面的命令,结合了2个自动化变量, < 表示依赖对象集中的第一个, < 表示依赖对象集中的第一个, <表示依赖对象集中的第一个,@ 则代表了目标集。所以这个功能就是要遍历所有的.c文件,对所有的.c文件进行编译,然后编译生成对应的.o文件。我们在实际编写程序时,targets是不需要的,可以简写如下:

%.o : %.c
gcc -c $< -o $@
————————————————
AR = ar rc —–生成静态库文件命令

在Windows下也就是.obj文件,UNIX下是.o文件,即ObjectFile,这个动作叫做编译(compile)。然后再把大量的ObjectFile合成执行文件,这个动作叫作链接(link)
编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。
链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(ObjectFile),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(LibraryFile),也就是.lib文件,在UNIX下,是ArchiveFile,也就是.a文件。
源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成ObjectFile。而在链接程序时,链接器会在所有的ObjectFile中找寻函数的实现,如果找不到,那到就会报链接错误码(LinkerError),在VC下,这种错误一般是:Link2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的ObjectFile.
target…:prerequisites…

command
说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

# Get all dirs with a Makefile
TMP_DIRS := $(foreach dir, $(wildcard *), $(wildcard $(dir)/Makefile))
DIRS := $(patsubst %/Makefile,%,$(TMP_DIRS))

.PHONY: all install clean
.PHONY: $(DIRS)
all: TARGET=all
all: $(DIRS)

install: TARGET=install
install: $(DIRS)

clean: TARGET=clean
clean : $(DIRS)

$(DIRS):
	$(MAKE) -C $@ $(TARGET)

1.获取匹配模式的文件名wildcard

这个函数的功能是查找当前目录下所有符合模式 PATTERN 的文件名,其返回值是以空格分割的、当前目录下的所有符合模式 PATTERN 的文件名列表。其原型如下:

$(wildcard PATTERN)

例如,如下模式返回当前目录下所有扩展名位 .c 的文件列表。

$(wildcard *.c)

2.模式替换函数patsubst

这个函数的功能是查找字符串 text 中按照空格分开的单词,将符合模式 pattern 的字符串替换成 replacement。 Pattern 中的模式可以使用通配符, % 代表 0 个到 n 个字符,当 pattern 和 replacement 中都有 % 时,符合条件的字符将被 replacement 中的替换。函数的返回值是替换后的新字符串。其原型如下:

$(patsubst pattern, replacement, text)

例如,需要将 C 文件替换为 .o 的目标文件可以使用如下模式:

$(patsubst %.c, %.o, add.c)

上面的模式将 add.c 字符串作为输入,当扩展名为 .c 时符合模式 %.c ,其中 % 在这里代表 add,替换为 add.o,并作为输出字符串。

$(patsubst %.c, %.o, $(wildcard *.c))

输出的字符串将当前扩展名为 .c 的文件替换成 .o 的文件列表。

3.循环函数foreach

这个函数的原型为:

$(foreach VAR, LIST, TEXT)

函数的功能为 foreach 将 LIST 字符串中一个空格分割的单词,先传给临时变量 VAR ,然后执行 TEXT 表达式, TEXT 表达式处理结束后输出。其返回值是空格分割表达式 TEXT 的计算结果。

例如,对于存在 add 和 sub 的两个目录,设置 DIRS 为 “add sub ./” 包含目录 add、sub 和当前目录。表达式 $(wildcard $(dir)/*.c) ,可以取出目录 add 和 sub 及当前目录中的所有扩展名为 .c 的C语言源文件:

DIRS = sub add ./
FILES = $(foreach dir, $(DIRS), $(wildcard $(dir)/*.c))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值