Skip to content

jtortoise/linuxC_makefile_template

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Linux环境C语言编程Makefile多级目录简单模板

C语言工程项目的Makefile模板,支持一级目录,二级目录,多级目录的Makefile模板。

测试平台:

  • Linux操作系统环境,elemantary OS(基于ubuntu16.04)

    eleOS

  • make版本,4.1

    make

目录结构:

linuxC_makefile_template
|—— image ******************* 放笔记用的图片
|—— material **************** 放笔记用的一些资料文档
|—— multilevel_dirs ********* 多级目录的Makefile模板示例
|—— onelevel_dirs *********** 一级目录的Makefile模板示例
|—— secondlevel_dirs ******** 二级目录的Makefile模板示例

Makefile模板功能:

  • 支持自动推导头文件依赖

    修改项目工程中的头文件,能够导致包含该文件的对象能够重新编译。在之前一些简单的Makefile模板中,当修改头文件时,重新执行make,项目不会进行对应模块的编译。

  • 支持多级目录结构划分

    多级目录的Makefile模板,使得在顶层执行make的时候,能够对多个目录下的所有源文件进行重新编译。这样子,我们就能够把代码按照模块功能的划分组织我们的代码,而编译的时候,仅仅通过一条make命令就可以帮助我们完成复杂的编译和链接,多级目录比较适合一些稍微复杂的项目工程。

  • 支持一级目录和二级目录结构划分

    Makefile模板支持多级目录的编译,也就意味着能够支持简单目录结构的编译,在这里给出相应的示例。

使用:

简单介绍make实现多级目录编译的Makefile编写思想以及如何在多级目录下添加新的子模块下的Makefile的添加。

顶层目录添加的Makefile

TOPDIR:=$(shell pwd)/
CC:=gcc
export TOPDIR CC

exclude_dirs:=inc lib

include $(TOPDIR)Makefile.env
  • 传递顶层目录的绝对路径(TOPDIR)

    顶层目录的Makefile指定顶层目录的绝对路径,并且将这个变量传递到下一层的Makefile中,这样子所有的Makefile就知道顶层目录在哪里。

  • 传递CC变量(CC)

    统一整个项目使用变量CC指定的编译器进行编译。

  • exclude_dirs

    排除指定的目录不用进入到里面执行make编译。

注意:传递TOPDIR目录时,理论上目录/xx/xx/xx/xx/指的都是相同的目录,但在这里统一使用第二种表示,这样子是为了方便执行make clean能够清除复制的文件的时候不会报错。

###src目录添加的Makefile

exclude_dirs:=

include $(TOPDIR)Makefile.env

直接扫描当前目录下的各个模块,进入到里面执行make编译,如果不想要某一个模块也参与编译,可以通过变量exclude_dirs指定。

一般模块目录添加的Makefile

对于一般模块,通过将其所有的源文件链接成一个动态库,然后放到顶层目录下的lib目录下面,让其它模块调用。

LIB:=libmylib.so
LIBPATH:=$(TOPDIR)lib/

CPPFLAGS:=-I $(TOPDIR)inc
CFLAGS:=
LDFLAGS:=
LDFLAGS+= -lm

include $(TOPDIR)Makefile.env
  • 指定生成的动态库名称(LIB)
  • 指定存放动态库的路径(LIBPATH)
  • 指定编译器参数,即生成的库文件所需要的信息
    • CPPFLAGS:指定头文件的位置
    • CFLAGS:编译器编译参数
    • LDFLAGS:指定要链接的库位置和库名称等

注意:LIBPATH指定的目录变量也是以/xx/xx/的格式。

主模块目录添加的Makefile

主模块里面包含主函数,在这里统一使用main文件夹来存放,若改成别的文件夹名,编译顺序会乱掉,从而报错。

vpath %.so $(TOPDIR)lib

TARGET:=make_test
EXEPATH:=$(TOPDIR)

TARGET_LIBS:=libmylib.so

CPPFLAGS:= -I $(TOPDIR)inc
CFLAGS:= -Wl,-rpath=$(TOPDIR)lib
LDFLAGS:= -L $(TOPDIR)lib 
LDFLAGS+= -lmylib

include $(TOPDIR)Makefile.env
  • vpath关键字指定依赖的库文件.so到指定目录查找

    主程序的编译依赖生成的库文件,库文件改变,主程序应当也需要重新编译。

  • 指定生成的程序名称(TARGET)

  • 指定程序存放的路径(EXEPATH)

    可以放到顶层目录去,一执行make完毕,可以直接在当前目录下执行文件,方便。

  • 指定主程序依赖的库文件列表(TARGET_LIBS)

    这是主程序依赖的库文件列表,如果不写,但又依赖某个生成的库文件,当这个库文件更新时候,主程序不会更新,这是不对的。所以,需要写好主函数依赖的库文件列表,使得主程序能够及时更新。

  • 指定编译器的参数

    • CPPFLAGS:指定头文件的位置

    • CFLAGS:

      编译器编译参数,指定程序运行时到指定的位置寻找动态库文件,不指定的话,程序执行的时候可能会出现找到库文件的,当然你也可以把库文件丢到类似/lib,/usr/lib等目录下,执行的时候是找的到的,项目在测试过程中是不建议这么做的。

    • LDFLAGS:指定要链接的库位置和库名称等

以上的介绍是多级目录Makefile模板的使用方法,对于一级目录和二级目录的使用也是类似,直接生成可执行文件,不需要生成库文件,这些在自己写一些小项目的时候做为Makefile来管理工程是非常方便的,具体的写法直接看下示例就行了。

##实现思想:

在这里,先阅读一篇文章:一个适用于层级目录结构的makefile模版 ,这个项目的实现也基本都是参考这篇文章来写的,核心思想不变,在细节上做了一些改善。

Makefile.env

能够实现多级目录编译的Makefile,完全依赖一个公共Makefile模板实现,在这个模板里面,能够实现三个基本的功能:

  • 动态库的生成及复制到指定路径(可选);
  • 可执行目标文件生成及复制到指定路径(可选);
  • 递归执行每个目录下的Makefile(一定执行);

一个模块,是要生成库文件(普通模块)还是可执行文件(主模块),只需要给对应的变量(LIB,TARGET)赋值,比如在main模块里面(包含main.c文件)要生成可执行文件,也就是程序,直接在该模块下面的Makefile文件这么写上:TARGET:=main,就代表这个模块要生成一个叫main的可执行文件,相应的:LIB:=libmylib.so就是要生成一个动态库,目前也只支持动态库生成。

在这里,对公共的Makefile文件Makefile.env每个模块作个简单介绍:

####目录搜寻

dirs:=$(shell find . -maxdepth 1 -type d)
dirs:=$(basename $(patsubst ./%,%,$(dirs)))
maindir:=$(findstring main,$(dirs))
dirs:=$(filter-out $(exclude_dirs) main,$(dirs))
SUBDIRS:=$(dirs) $(maindir)

这一部分的代码,用来查找需要编译的目录,并将主模块main目录放到最后才执行,因为主模块的编译依赖其它模块,必须在其它模块之后执行编译,做了个简单目录调整,确保main模块在最后才执行。在这里,如果不想对一些目录编译的话,通过指定变量exclude_dirs来排除不需要编译的目录,如:

exclude_dirs:=module1 module2 ...

源文件搜寻

SRC:=$(wildcard *.c)
OBJS:=$(SRC:%.c=%.o)
DEPENDS:=$(SRC:%.c=%.d)

简单查找下当前目录下所有的.c文件,放到SRC变量中,简单的转换成.o(可重定位目标文件)和.d(头文件依赖关系文件)。

####all伪目标

all:$(TARGET) $(LIB) subdirs

在这里,是这个Makefile需要执行的规则,$(TARGET)$(LIB)是可选的,不需要执行这两个目标时候执行留空,这样子目标all就会变成:

all: subdirs

也就是只执行subdirs规则,这个规则在后面介绍。

subdirs规则

subdirs:$(SUBDIRS)
	@for dir in $(SUBDIRS); \
	do $(MAKE) -C $$dir all || exit 1; \
	done

就是进入到该目录下的每个子目录执行里面的Makefile文件,实现递归编译,这样子,当我们添加新的模块时候,就能够自动进行编译了。

####$(TARHET)规则

$(TARGET):$(OBJS) $(TARGET_LIBS)
	$(CC) $(CPPFLAGS) $^ -o $@ $(CFLAGS) $(LDFLAGS)
	$(if $(EXEPATH),@cp $@ $(EXEPATH))

这个规则目前是用来生成可执行文件的,可执行文件依赖当前模块下的一堆.o文件($(OBJS))以及库文件.so($(TARGET_LIBS))。然后就执行gcc进行编译,根据一堆编译器参数,生成可执行文件,最后判断下存放程序的路径($(EXEPATH))是不是空,不是空就复制下,要是空的就不用复制。

  • TARGET_LIBS:指定程序依赖的库文件列表
  • EXEPATH:指定程序存放的路径
  • TARGET:程序名

$(LIB)规则

$(LIB):CFLAGS+= -fPIC
$(LIB):$(OBJS)
	$(CC) -shared $(CPPFLAGS) $^ -o $@ $(CFLAGS) $(LDFLAGS)
	$(if $(LIBPATH),@cp $@ $(LIBPATH))

在这里,默认生成动态库,所以给这个模式的变量CFLAGS强制加上-fPIC选项,然后编译器根据一堆编译参数,由外部指定,生成一个库文件,再拷贝到对应的位置($(LIBPATH))。

  • LIB:库文件名
  • LIBPATH:存放库文件路径

$(OBJS)规则

$(OBJS):CFLAGS+= -c
$(OBJS):%.o:%.c
	$(CC) $(CPPFLAGS) $< -o $@ $(CFLAGS)

把每个源文件.c编译成可重定位的目标文件。

$(DEPENDS)规则

-include $(DEPENDS)
$(DEPENDS):%.d:%.c
	@set -e; $(RM) $@; \
	$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[:]*,\1.o $@:,g' < $@.$$$$ > $@; \
    $(RM) $@.$$$$

这个规则就是用来推导源文件.c的头文件依赖关系,然后存到.d文件去。

clean伪目标

clean:
	@for dir in $(SUBDIRS); \
	do $(MAKE) -C $$dir clean || exit 1; \
	done
	$(RM) $(TARGET) $(LIB) $(OBJS) $(DEPENDS)
	$(if $(notdir $(LIBPATH)$(LIB)),$(RM) $(LIBPATH)$(LIB))
	$(if $(notdir $(EXEPATH)$(TARGET)),$(RM) $(EXEPATH)$(TARGET))

把生成的.o .d .so等编译产生的文件都清除掉,递归扫描每个目录清除。

调用Makefile.env

Makefile.env文件的实现中可以看出来,想要执行相应模块功能,直接给相应模块的变量进行赋值控制就行了,就像在使用章节中所展示的那样,定义变量,然后includeMakefile.env文件。

参考链接

  1. coderkian. 一个适用于层级目录结构的makefile模版[DB/OL]. http://www.cnblogs.com/coderkian/p/3479564.html

附录A:知识点

  • 从代码到生成可执行文件的四个阶段[^1]

  • Makefile中gcc编译程序各阶段的参数变量[^2][^3]

  • Makefile中的通配符[^4]

    • %

      适用用于Makefile中的函数中的模式匹配以及静态模式和多模式规则中模式匹配。

    • *, ?, [xxx]

      Shell中的通配符,适用于变量的定义以及规则中shell命令。

  • Makefile中的vpath关键字[^5]

    让Makefile到指定的目录查找匹配模式的文件。

    语法:vpath <pattern> <path> ,如:vpath %.c ../src

  • Makefile中的字符串处理函数[^6]

  • gcc动态库以及静态库生成[^7][^8][^9]

    • gcc的三种library简单介绍[^7]

      1. 静态库(Static Library)

        程序编译的时候链接上去。

      2. 动态库(Shared Library)

        在程序加载到内存的时候,也就是程序启动的时候,才加载库函数。

      3. dll(Dynamically loaded libraries)

      程序在运行的过程中才加载库函数,也就是需要的时候才加载。

    • 利用ldd查看程序链接库的地址情况[^8]

      执行命令:ldd 程序名

      简单示例:

      ldd

    • 注意事项:

      1. 生成单个可重定位的目标文件,gcc需加上-fPIC选项;
      2. 在制作自己的动态库中引用其他的库函数,在生成动态库时,需链接上相应的库;
  • Makefile中实现自动生成头文件的依赖[^10][^11]

  • shell相对路径转绝对路径[^12]

附录A:参考链接

  1. Randal E.Bryant,David O'Hallaron. 深入理解计算机系统(第三版)[M]. 龚奕利,贺莲. 机械工业出版社,2016:3-3.
  2. 陈皓. 跟我一起写Makefile[DB/OL]. 2005:65-66.
  3. 宋劲杉. Linux C编程一站式学习[DB/OL]. http://docs.linuxtone.org/ebooks/C&CPP/c/index.html . B. 编译开发工具小结:gcc常用选项.
  4. 陈皓. 跟我一起写Makefile[DB/OL]. 2005:18-19.
  5. 陈皓. 跟我一起写Makefile[DB/OL]. 2005:19-20.
  6. 陈皓. 跟我一起写Makefile[DB/OL]. 2005:44-51.
  7. kaineshu. [轉貼]用gcc 自製 Library[DB/OL]. https://kaineshu.wordpress.com/2007/05/02/%E8%BD%89%E8%B2%BC%E7%94%A8gcc-%E8%87%AA%E8%A3%BD-library/
  8. vfhky. Linux gcc编译生成静态库和共享动态库的过程[DB/OL]. https://typecodes.com/cseries/gccgensharedlib.html
  9. René Nyffenegger's. Creating a shared and static library with the gnu compiler(gcc)[DB/OL]. http://www.adp-gmbh.ch/cpp/gcc/create_lib.html
  10. 陈皓. 跟我一起写Makefile[DB/OL]. 2005:15-16.
  11. 陈皓. 跟我一起写Makefile[DB/OL]. 2005:24-25.
  12. maimake. shell 相对路径转绝对路径[DB/OL]. http://blog.sina.com.cn/s/blog_7a691e910101gnra.html

About

Linux环境C语言编程项目多级Makefile管理模板

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published