本文旨在介绍,如何从零开始写Makfile,实现多文件、多目标、多级目录的代码编译。
目录
5、编译lib & 编译bin & 执行可执行文件 & 清除编译产物
编写一个简单的main.c就行
- #include
- #include
-
- int main(void)
- {
- printf("Hello world!\n");
-
- return 0;
- }
同级目录下创建一个文件,名称为Makefile(建议文件名称就固定为Makefile)
Makefile的基础编写规则,我都写在下面的注释里面了^_^
- # 在Makfile中,以'#'作为注释开头,不支持多行注释
-
- #Makfile最朴素的用法就是,指定依赖文件,指定文件依赖关系,指定生成规则(如何将依赖文件转换成目标文件)
-
- # 指定编译器
- CC = gcc
-
- # Makefile中,通常需要指定一个目标文件名词,即下面的变量 TARGET,后面可以借用 $(TARGET) 来获取 TARGET 的值
- TARGET = main
-
- #指定用于生成目标文件的依赖文件如下,目标文件是可执行文件,所以依赖文件应该是一个对象文件(.o文件)
- #我们可以借助一个变量OBJS来指代它,后面可以使用 $(OBJS) 来获取变量 OBJS 的值
- OBJS = main.o
-
- #同样,生成对象文件(.o文件),也需要相应的源文件(.c或.cpp文件)
- SRC = main.c
-
- ##指定编译选项,用于gcc编译
- # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
- CFLAGS = -Wall -g -c
-
- # 需要指定一定的规则来生成目标文件,使用冒号指定依赖关系
- # 下面一行gcc命令是生成规则(若要在makefile中执行命令,行首必须以tab键开头)
- # 变量 $@ 指代冒号前面的参数,变量 $^ 指代冒号后面的所有参数
- $(TARGET) : $(OBJS)
- $(CC) -o $@ $^
-
- $(OBJS) : $(SRC)
- $(CC) -o $@ $(CFLAGS) $^
-
- # 通常会在 Makfile 结尾处编写一个clean规则用于清除我们的中间文件和目标文件
- # .PHONY 表示冒号后面的标签名 clean 是一个伪目标(只执行clean的命令,但不会生成一个名叫“clean”的目标文件)
- .PHONY:clean
-
- # 在命令前面加一个'@'符号,则只会执行命令,而不在终端打印出命令本身
- clean:
- $(RM) $(TARGET)
- @echo "Clean target files done."
-
- $(RM) $(OBJS)
- @echo "Clean object files done."
终端下执行命令make可以执行编译工作,执行make clean可以清除掉编译产物。
如果你的Makefile文件叫其他名称,可以使用 -f 参数指定文件名
现在就简单完成了一个Makefile的编译工程,可以通过make命令实现c代码的编译工作
通常来讲,我们编写的大型工程文件,代码都不可能放在同一个文件中,所以搭建如下图所示的一个代码结构
- #ifndef PUBLIC_H__
- #define PUBLIC_H__
-
- void tool1(void);
-
-
- #endif
代码内容如下
- #include
- #include
- #include "public.h"
-
- void tool1(void)
- {
- printf("This is tool1.\n");
-
- return;
- }
修改main函数,使其能够调用main_lib.c中的函数
- #include
- #include
- #include "public.h"
-
- int main(void)
- {
- printf("Hello world!\n");
-
- tool1();
-
- return 0;
- }
- ##指定源文件
- # 使用wildcard通配符,匹配所有 .c 文件
- SRC = $(wildcard *.c)
-
- ##指定生成目标文件的依赖文件
- # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
- # %.c 即表示所有以 .c 结尾的字符串
- OBJS = $(patsubst %.c, %.o, $(SRC))
-
- ##指定编译选项,用于gcc编译
- # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
- CFLAGS = -Wall -g -c
-
- ##指定编译器
- CC = gcc
-
- ##指定目标文件名称
- TARGET = main
-
- ##指定依赖关系
- $(TARGET) : $(OBJS)
- $(CC) -o $@ $^
-
- ##指定由 .c 文件生成 .o 的生成规则
- %.o : %.c
- $(CC) -o $@ $(CFLAGS) $^
-
- ##指定清除编译产物时的执行命令
- .PHONY:clean
- clean:
- $(RM) $(TARGET)
- @echo "Clean target files done."
-
- $(RM) $(OBJS)
- @echo "Clean object files done."
以上就是多个依赖文件(源文件)生成同一个可执行文件的make规则
- #include
- #include
-
- int main(void)
- {
- printf("This is in main1.\n");
-
- return 0;
- }
- ##指定源文件
- # 使用wildcard通配符,匹配所有 .c 文件
- SRC = $(wildcard *.c)
-
- ##指定生成目标文件的依赖文件
- # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
- # %.c 即表示所有以 .c 结尾的字符串
- OBJS = $(patsubst %.c, %.o, $(SRC))
-
- ##指定编译选项,用于gcc编译
- # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
- CFLAGS = -Wall -g -c
-
- ##指定编译器
- CC = gcc
-
- ##指定目标文件名称
- TARGET = main main1
-
- ##执行 make 命令时,缺省参数事实上就是 all
- all: $(TARGET)
-
- ##指定依赖关系
- ##指定依赖关系
- main : main.o main_lib.o
- $(CC) -o $@ $^
-
- main1 : main1.o
- $(CC) -o $@ $^
-
- ##指定由 .c 文件生成 .o 的生成规则
- %.o : %.c
- $(CC) -o $@ $(CFLAGS) $^
-
- ##指定清除编译产物时的执行命令
- .PHONY:clean
- clean:
- $(RM) $(TARGET)
- @echo "Clean target files done."
-
- $(RM) $(OBJS)
- @echo "Clean object files done."
这样就可以实现一个makefile编译生成多个可执行文件
前述代码组织方式都不够结构化,可执行文件和库文件杂糅在一起,下面整理一下代码文件组织方式,将 main.lib.c 打包成 .a 静态库文件供生成可执行文件时使用
将所有代码文件划分为可执行文件、库文件、头文件,将可执行文件放到 bin 目录下,将库文件放到 lib 目录下,将 头文件统一放到 include 目录下。
除 include 目录下,其他目录下各自创建一个 Makefile 文件,另外在lib目录下新建一个targets目录用于统一存放生成的静态库文件
- #include
- #include
- #include "../include/public.h"
-
- void tool1(void)
- {
- printf("This is tool1.\n");
-
- return;
- }
lib 目录下的 .c 文件要生成 静态库文件 .a,所以目标文件不再是可执行文件main,而是libmain.a 文件
- ##指定源文件
- # 使用wildcard通配符,匹配所有 .c 文件
- SRC = $(wildcard *.c)
-
- ##指定生成目标文件的依赖文件
- # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
- # %.c 即表示所有以 .c 结尾的字符串
- OBJS = $(patsubst %.c, %.o, $(SRC))
-
- ##指定编译选项,用于gcc编译
- # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
- CFLAGS = -Wall -g -c
-
- ##指定编译器
- CC = gcc
-
- ##指定目标文件名称
- TARGET = targets/libmain.a
-
- ##执行 make 命令时,缺省参数事实上就是 all
- all: $(TARGET)
-
- ##指定依赖关系
- ##指定依赖关系
- $(TARGET) : $(OBJS)
- ar rcs $@ $^
- ranlib $@
-
- ##指定由 .c 文件生成 .o 的生成规则
- %.o : %.c
- $(CC) -o $@ $(CFLAGS) $^ -I:$(INC_DIR)
-
- ##指定清除编译产物时的执行命令
- .PHONY:clean
- clean:
- $(RM) $(TARGET)
- @echo "Clean target files done."
-
- $(RM) $(OBJS)
- @echo "Clean object files done."
- ##指定头文件目录
- INC_DIR = ../public/
-
- ##指定源文件
- # 使用wildcard通配符,匹配所有 .c 文件
- SRC = $(wildcard *.c)
-
- ##指定生成目标文件的依赖文件
- # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
- # %.c 即表示所有以 .c 结尾的字符串
- OBJS = $(patsubst %.c, %.o, $(SRC))
-
- ##指定静态库文件所在路径
- LIB_DIR = ../lib/targets
-
- ##指定静态库文件名称
- # notdir 方法用于去除路径名
- LIB_FILES = $(notdir $(wildcard $(LIB_DIR)/*.a))
-
- ##指定编译选项,用于gcc编译
- # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
- CFLAGS = -Wall -g -c
-
- ##指定编译器
- CC = gcc
-
- ##指定目标文件名称
- TARGET = main main1
-
- ##执行 make 命令时,缺省参数事实上就是 all
- all: $(TARGET)
-
- ##指定依赖关系
-
- #生成可执行文件 main 需要链接静态库文件
- main : main.o
- $(CC) -o $@ $^ -I$(INC_DIR) -L$(LIB_DIR) -l:$(LIB_FILES)
-
- main1 : main1.o
- $(CC) -o $@ $^
-
- ##指定由 .c 文件生成 .o 的生成规则
- %.o : %.c
- $(CC) -o $@ $(CFLAGS) $^
-
- ##指定清除编译产物时的执行命令
- .PHONY:clean
- clean:
- $(RM) $(TARGET)
- @echo "Clean target files done."
-
- $(RM) $(OBJS)
- @echo "Clean object files done."
上面虽然已经实现了将可执行文件、库文件、头文件分别存放,但是编译和清理都需要分别切换目录,使用起来太不方便了,所以我们需要实现让makefile自动切换目录去编译和清理。
在根目录下创建一个新的 Makefile 文件,最终的代码目录结构如下图所示。
- ##指定当前目录下存在的子目录
- SUBDIRS = lib bin
-
- ##将执行当前 Makefile 文件时传入的参数都作为伪目标向下一级传递
- .PHONY: all clean $(SUBDIRS)
-
- # 注意 往下传递的参数 尽量不要与子目录中的相同(除非是提供给子目录 Makefile 使用时)
- # 我的子目录的指代目标文件使用的变量都是 TARGET ,所以这里这个变量名称 BIN 无论如何不能改成 TARGET
- all clean:
- $(MAKE) $(SUBDIRS) BIN=$@
-
- ##使用make -C 命令实现自动切换编译目录,注意参数 -C 必须是大写 C
- $(SUBDIRS):
- $(MAKE) -C $@ $(BIN)
以上就基本完成了一个比较完整的由Makefile组织的代码工程。
学习过程中自然离不开各位大佬、前辈们写的资料,学习期间网上查阅了不少的博客、文章,但有许多没有收藏or点赞,链接已经找不到了,就放两个对我帮助最大的链接吧
学识尚浅,如有纰漏,望不吝赐教!
以上,End.