转自:http://blog.csdn.net/jundic/article/details/17886637

前面讲了Makefile 的简单语法和简单的应用模板,但在实际项目应用中比这个肯定复杂很多,但是我想说他的Makefile应用模式都是大同小异,只是代码量和工程复杂度大写而已。

我觉得一个完整的工程目录应该这样:

1、工程文件结构目录应该要非常清晰,并且模块化,各个模块下的Makefile 独立

2、可以分别单独编译各个单独模块成.a 或.so

3、在顶层目录下可以一次性生成可执行文件。

现在我搭建了如下工程目录

build为编译产生的结果文件,out 为可执行bin文件,libs为生成的库

project 为工程文件夹,下面有module1 、module2 、module3三个模块,main.c 为主函数它将生成可执行文件,虚线框为编译时生成的文件夹或文件

module2 module3和module1的文件结构相同图中没有画出来。

现进行如下分工,module1 module2 将产生静态库到 build/libs/static 中,module3将产生动态库到 build/libs/dynamic 中,最后main.c 将调用这些库编译生成可执行文件到

build/out 中。

为了提高模块的复用性,我们会在顶层目录下,写一个rules.mk文件,事实上很多项目是这么做的比如uboot 。

rules.mk 代码

 # rules .mk  

 # Generic Makefile for C/C++ Program
# Author:
# Description: # ------------
# This is an easily customizable makefile template. The purpose is to
# provide an instant building environment for C/C++ programs.
#
# It searches all the C/C++ source files in the specified directories,
# makes dependencies, compiles and links to form an executable.
#
# Besides its default ability to build C/C++ programs which use only
# standard C/C++ libraries, you can customize the Makefile to build
# those using other libraries. Once done, without any changes you can
# then build programs using the same or less libraries, even if source
# files are renamed, added or removed. Therefore, it is particularly
# convenient to use it to build codes for experimental or study use.
#
# GNU make is expected to use the Makefile. Other versions of makes
# .PHONY : all clean # Top directory Makefile # The C program compiler
CC = gcc
MACRO = DEBUGALL
CFLAGS += -g -werror -D$(MACRO)
AR = ar
ARFLAGES = crv # default execute output directory
ifeq ($(DIR_EXES),)
DIR_EXES = $(TOPDIR)/build/out
endif # defaulet libaray creat directory
ifeq ($(DIR_LIBS),)
DIR_LIBS = $(TOPDIR)/build/libs
endif # directory
DIRS = $(DIR_OBJS) $(DIR_DEPS) $(DIR_EXES) $(DIR_LIBS) # include directory
ifneq ($(DIR_INCS),"")
DIR_INCS := $(strip $(DIR_INCS))
DIR_INCS := $(addprefix -I,$(DIR_INCS))
endif # when build execute file
ifneq ($(EXES),)
EXES := $(addprefix $(DIR_EXES)/,$(EXES))
RMS += $(EXES)
DIR_LIBS := $(strip $(DIR_LIBS))
DIR_LIBS := $(addprefix -L,$(DIR_LIBS))
endif # when build static libaray file
ifneq ($(LIBS),"")
LIBS := $(addprefix $(DIR_LIBS)/,$(LIBS))
RMS += $(LIBS)
endif # default source code file directory
ifeq ($(DIR_SRCS),)
DIR_SRCS = .
endif
SRCS = $(wildcard $(DIR_SRCS)/*.c)
OBJS = $(patsubst %.c, %.o,$(notdir $(SRCS)))
OBJS := $(addprefix $(DIR_OBJS)/,$(OBJS))
RMS += $(OBJS) $(DIR_OBJS) DEPS = $(patsubst %.c, %.dep,$(notdir $(SRCS)))
DEPS := $(addprefix $(DIR_DEPS)/,$(DEPS))
RMS += $(DEPS) $(DIR_DEPS) ifneq ($(EXES),"")
all : $(EXES)
endif ifneq ($(LIBS),"")
all : $(LIBS)
endif ifneq ($(LINK_LIBS),"")
LINK_LIBS := $(strip $(LINK_LIBS))
LINK_LIBS := $(addprefix -l,$(LINK_LIBS))
endif # include dependent files
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif $(DIRS):
mkdir -p $@ # creat execute file
$(EXES) : $(DIR_OBJS) $(OBJS) $(DIR_EXES)
$(CC) $(DIR_INCS) $(CFLAGES) -o $@ $(OBJS) $(DIR_LIBS) $(LINK_LIBS) # creat libaray file
$(LIBS) : $(DIR_LIBS) $(DIR_OBJS) $(OBJS)
# library type is static
ifeq ($(LIB_TYPE),static)
$(AR) $(ARFLAGS) $@ $(OBJS)
endif # library type is dynamic
ifeq ($(LIB_TYPE),dynamic)
$(CC) -shared -o $@ $(OBJS)
endif # creat object file
$(DIR_OBJS)/%.o : $(DIR_SRCS)/%.c
@echo "source files:" $<
@echo "object files:" $@
ifeq ($(LIB_TYPE),static)
$(CC) $(DIR_INCS) $(CFLAGES) -o $@ -c $<
else
$(CC) $(DIR_INCS) $(CFLAGES) -fPIC -o $@ -c $<
endif # creat depandant file
$(DIR_DEPS)/%.dep : $(DIR_SRCS)/%.c $(DIR_DEPS)
@echo "creating depend file ..." $@
@set -e;\
$(CC) $(DIR_INCS) -MM $< > $@.tmp;\
sed 's,$∗\.o[ :]*,$(DIR_OBJS)/\1.o $@ : ,g' < $@.tmp > $@
# rm $@.tmp clean:
rm -rf $(RMS)

这个makefile 没有什么好分析的和第二篇文章单目录工程makefile一样的。

好了进入module1 下我们想把 module1编译成libmodule1.a 对应的Makefile 就很简单啦。

 # module1 Makefile
DIR_CUR = $(shell pwd)
DIR_SRCS = $(DIR_CUR)/src
DIR_INCS += $(DIR_CUR)/inc
DIR_OBJS = objs
DIR_DEPS = deps
DIR_LIBS = ../../build/libs/static
# library type an name
LIB_TYPE = static
LIBS = libmodule1.a
# LIB_TYPE = dynamic
# LIBS = libmodule1.so include ../../rules.mk

这个makefile就是要配置输出文件路径和名字然后将顶层rules.mk包含就ok啦。

同样的module2 目录下Makefile 也是一样的只要将LIBS 改为 libmodule2.a 即可。

module3 的我们想把他编译成动态库 则对应为如下

 # module1 Makefile
DIR_CUR = $(shell pwd)
DIR_SRCS = $(DIR_CUR)/src
DIR_INCS += $(DIR_CUR)/inc
DIR_OBJS = objs
DIR_DEPS = deps
DIR_LIBS = ../../build/libs/static
# library type an name
#LIB_TYPE = static
#LIBS = libmodule3.a
LIB_TYPE = dynamic
LIBS = libmodule3.so include ../../rules.mk

在project 中要编译main.c 又要链接前面生成的三个库 那对应的Makefile 如下:

 # Current directory  Makefile
CURDIR = $(shell pwd) # executable file name
EXES = justest # head file directories
DIR_INCS = module1/inc\
module2/inc\
module3/inc # objs and dependant file directories
DIR_OBJS = objs
DIR_DEPS = deps # static libraries
LINK_LIBS = module1 module2 module3 # library file directories
DIR_LIBS = $(TOPDIR)/build/libs/static\
$(TOPDIR)/build/libs/dynamic # include common rule makefile
include ../rules.mk

在这个makefile 只要配置生成的可执行文件的路径名,还有头文件目录,链接库的路径和名字就可以生成可执行文件了,这里也顶层的rules.mk。

当然在实际应用中可以到各个模块中去执行make 但当想一次性生成可执行文件,则还需要一个顶层Makefile 。

 .PHONY: all clean  

 # sofeware version information
VERSION =
PROJECT = mk_demo
SUBLEVEL =
YEAL =
RELEASE_VERSION = $(PROJECT).$(YEAL).$(VERSION).$(SUBLEVEL) BUILD_DIRS = $(TOPDIR)/source/module1\
$(TOPDIR)/source/module2\
$(TOPDIR)/source/module3\
$(TOPDIR)/source all :
# @shell(export TOPDIR=`pwd`)
@echo "version information:" $(RELEASE_VERSION)
@echo "building ..."
@set -e;\
for dir in $(BUILD_DIRS);\
do\
cd $$dir && $(MAKE);\
done
@echo ""
@echo "\33[35m ~@^_^@~ \33[m"
@echo ""
@echo "Build Completed" clean:
@echo "cleaning ...."
for dir in $(BUILD_DIRS);\
do\
cd $$dir && $(MAKE) clean;\
done
rm -rf $(RMS)
@echo ""
@echo "Clean Completed" version:
@echo $(TOPDIR)
@echo $(RELEASE_VERSION)
@echo $(BUILD_DIRS)
@echo $(RMS)

这里最关键的几句代码就是

for dir in $(BUILD_DIRS)

do \

cd $$dir && $(MAKE);\

done

在这里将进入各个模块目录下 执行make ,到这里一个稍微可以用工程makefile 工程实例,实现完了,其实只要模块划分好,还是很好理解的。我想说的时这种方法很常用,但不是唯一的,我有见过有些工程中也许并不需要每个模块都编译成库,或者说他要编译成库本身就是个很大的工程,一个模块的文件就特别多,目录结果就比较复杂这中该怎么写,先讲个思路,对应模块的Makefile 就搜索该目录下所以的Makefile 并包含进来。而子目录下的Makefile 将只将目录下需要编译的文件包含和路径包含经来就ok了,有时间也要总结下。

至此非常感谢我参考的《驾驭Makefile》,《Makefile编程》,《GCC 中文手册》

Makefile研究(三) —— 实际应用的更多相关文章

  1. Makefile有三个非常有用的变量。分别是$@,$^,$

    原文地址:https://blog.csdn.net/u013774102/article/details/79043559 假设我们有下面这样的一个程序,源代码如下: /* main.c */ #i ...

  2. Makefile研究 (一)—— 必备语法

    摘自:http://blog.csdn.net/jundic/article/details/17535445 参考文档:http://blog.csdn.net/wrx1721267632/arti ...

  3. 跟我一起写 Makefile(三)[转]

    原文链接 http://bbs.chinaunix.net/thread-408225-1-1.html(出处: http://bbs.chinaunix.net/) make 的运行—————— 一 ...

  4. makefile实验三 理解make工作的基本原则

    代码简单,但测试花样多,若能回答对本博客的每个步骤的预期结果,可以说对makefile的基础掌握是扎实的. 一,当前的makefile代码 root@ubuntu:~/Makefile_Test# r ...

  5. 跟我一起写 Makefile(三)

    Makefile 总述 ------- 一.Makefile里有什么? Makefile里主要包含了五个东西:显式规则.隐晦规则.变量定义.文件指示和注释. 1.显式规则.显式规则说明了,如何生成一个 ...

  6. Java-WebSocket 项目的研究(三) WebSocketClient 类 具体解释

    通过之前两篇文章 Java-WebSocket 项目的研究(一) Java-WebSocket类图描写叙述 Java-WebSocket 项目的研究(二) 小试身手:client连接server并发送 ...

  7. Makefile学习(三)[第二版]

    make常用内嵌函数 1.函数调用 $(function arguments) #$引用的结果就是函数生成的结果 2.Makefile下常用的函数 1)$(wildcard PATTERN) #匹配当 ...

  8. C++的开源跨平台日志库glog学习研究(三)--杂项

    在前面对glog分别做了两次学习,请看C++的开源跨平台日志库glog学习研究(一).C++的开源跨平台日志库glog学习研究(二)--宏的使用,这篇再做个扫尾工作,算是基本完成了. 编译期断言 动态 ...

  9. 跟我学Makefile(三)

    紧接着跟我学Makefile(二)继续学习:变量高级用法 (1)变量值的替换 :替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,把变量“var”中所有以“a”字串 ...

随机推荐

  1. Android异步载入AsyncTask具体解释

    曾看见有人说过.认为非常有道理.分享一下:   技术分为术和道两种:   (1)具体做事的方法是术.   (2)做事的原理和原则是道. 近期项目发现个重大问题.结果打log跟踪查是AsyncTask导 ...

  2. java 回文字符串

    package string.string1_5; public class Palindrome { /** * 从两头向中间移动 * @param str * @return */ public ...

  3. Linux中的du和df命令

    现在也将前阵子学习到du/df两个命令总结一下吧.前阵子测试工作中有遇到过由于磁盘空间满导致程序无法执行到情况,所以使用了df和du两个命令. du查看目录大小,df查看磁盘使用情况.我常使用的命令( ...

  4. Andorid——ubuntu下的 NDK / JNI

    之前一直有接触源代码里面的JNI体系,知道个大概,仅仅管调进了哪个C/C++的接口,如今记录学习下. 撰写不易,转载请注明出处:http://blog.csdn.net/jscese/article/ ...

  5. Win10升级.NET Framework 3.5或2.0遇到错误0x800f081f

    具体方法如下: 1.将WIN10安装光盘ISO文件加载到虚拟光驱中. 2.WIN键+R键一起按,输入CMD后回车. 3.在CMD的命令行窗口里输入: cd C:Windowssystem32 跳转到s ...

  6. SpringBoot-(5)-properties的使用

    项目中经常需要进行一些配置,一般会使用springboot默认的application.properties文件,也可以自己创建配置文件 一,application.properties配置 logg ...

  7. [2017-10-26]Abp系列——DTO入参验证使用方法及经验分享

    本系列目录:Abp介绍和经验分享-目录 声明式的入参验证逻辑 声明式入参验证主要使用了System.ComponentModel.DataAnnotations中提供的各种验证参数的Attribute ...

  8. Java+Jsoup实现网页内容抓取

    不知不觉毕业快一年了,工作逐渐趋于平淡,从一个对编程了解得很少甚至完全一窍不通的小小菜,终于成为了一枚小菜,总而言之,算是入了IT这一行.这大半年马马虎虎做了三个项目,有安卓项目,有Java Web项 ...

  9. Vue中的计算属性和监听器(computed 与 watch)

    react中数据是单向绑定的,而vue中数据是双向绑定的.为什么? 在react中,主要是通过setState 去改变state的值:而在vue中,会自动的触发set 与get 改变属性的值. 在vu ...

  10. Codeforces Round #363 (Div. 2) D. Fix a Tree —— 并查集

    题目链接:http://codeforces.com/contest/699/problem/D D. Fix a Tree time limit per test 2 seconds memory ...