【版权声明:转载请保留出处:blog.csdn.net/gentleliu。Mail:shallnew at 163 dot com】

在大一些的项目里面,全部源码不会仅仅放在同一个文件夹,一般各个功能模块的源码都是分开的,各自放在各自文件夹下。而且头文件和.c源文件也会有各自的文件夹。这样便于项目代码的维护。这样我们能够在每一个功能模块文件夹下都写一个Makefile,各自Makefile处理各自功能的编译链接工作,这样我们就不必把全部功能的编译链接都放在同一个Makefile里面,这可使得我们的Makefile变得更加简洁,而且编译的时候可选择编译哪一个模块,这对分块编译有非常大的优点。

如今我所处于project文件夹树例如以下:

.

├── include
│ ├── common.h
│ ├── ipc
│ │ └── ipc.h
│ └── tools
│ ├── base64.h
│ ├── md5.h
│ └── tools.h
├── Makefile
├── src
│ ├── ipc
│ │ ├── inc
│ │ ├── Makefile
│ │ └── src
│ │ └── ipc.c
│ ├── main
│ │ ├── inc
│ │ ├── Makefile
│ │ └── src
│ │ ├── main.c
│ │ └── main.c~
│ └── tools
│ ├── inc
│ ├── Makefile
│ └── src
│ ├── base64.c
│ ├── md5.c
│ └── tools.c
└── tags 13 directories, 16 files

这样组织项目源代码要比之前合理一些。那这样怎么来写Makefile呢?我们能够在每一个文件夹下写一个Makefile,通过最顶层的Makefile一层一层的向下嵌套运行各层Makefile。那么我们最顶层的Makefile简单点的话能够这样写:

# top Makefile for xxx

all :
>---$(MAKE) -C src tags:
>---ctags -R clean :
>---$(MAKE) -C src clean .PHONY : all clean tags

命令:

>---$(MAKE) -C src

就是进入src文件夹继续运行该文件夹下的Makefile。然后src文件夹下的Makefile在使用相同的方法进入下一级文件夹tools、main、ipc。再运行该文件夹下的Makefile。事实上这样有些麻烦。我们能够直接从顶层文件夹进入最后的文件夹运行make。再增加一些伪目标完好下。我们的顶层Makefile就出来了:

# Top Makefile for C program

# Copyright (C) 2014 shallnew \at 163 \dot com

all :
>---$(MAKE) -C src/ipc
>---$(MAKE) -C src/tools
>---$(MAKE) -C src/main tags:
>---ctags -R help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, withoutlink"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vimeditor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.0 =======================" obj:
>---$(MAKE) -C src/ipc obj
>---$(MAKE) -C src/tools obj
>---$(MAKE) -C src/main obj clean :
>---$(MAKE) -C src/ipc clean
>---$(MAKE) -C src/tools clean
>---$(MAKE) -C src/main clean distclean:
>---$(MAKE) -C src/ipc distclean
>---$(MAKE) -C src/tools distclean
>---$(MAKE) -C src/main distclean .PHONY : all clean distclean tags help

当我们这样组织源码时。最以下层次的Makefile怎么写呢?肯定不能够将我们上一节的Makefile(version 1.1)直接复制到功能模块文件夹下,须要稍作改动。

不能全部的模块都终于生成各自的可运行文件吧,我们眼下是一个project,所以最后仅仅会生成一个可运行程序。我们这样做,让主模块文件夹生成可运行文件。其它模块文件夹生成静态库文件,主模块链接时要用其它模块编译产生的库文件来生成终于的程序。将上一节Makefile稍作改动得出编译库文件Makefile和编译可运行文件Makefile分别例如以下:

# A Makefile to generate archive file
# Copyright (C) 2014 shallnew \at 163 \dot com CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc -I../../include # SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_LIB = libtools.a all : $(SRC_LIB) $(SRC_LIB) : $(SRC_OBJ)
>---$(AR) rcs $@ $^
>---cp $@ ../../libs obj : $(SRC_OBJ) # clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) distclean:
>---$(RM) $(SRC_OBJ) $(SRC_LIB) tags *~ .PHONY : all obj clean disclean

==========================================================================

# A Makefile to generate executive file
# Copyright (C) 2014 shallnew \at 163 \dot com CFLAGS += -g -Wall -Werror -O2
CPPFLAGS += -I. -I./inc -I../../include
LDFLAGS += -lpthread -L../../libs -ltools -lipc # SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c))
SRC_FILES = $(wildcard src/*.c)
SRC_OBJ = $(SRC_FILES:.c=.o)
SRC_BIN = target_bin all : $(SRC_BIN) $(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o $@ $^ $(LDFLAGS) obj : $(SRC_OBJ) # clean target
clean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe distclean:
>---$(RM) $(SRC_OBJ) $(SRC_BIN) $(SRC_BIN).exe tags*~ .PHONY : all obj clean disclean

最后在顶层运行:

# make clean

make -C src/ipc clean
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/ipc'
rm -f src/ipc.o libipc.a
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/ipc'
make -C src/tools clean
make[1]: Entering directory `/home/Myprojects/example_make/version-3.0/src/tools'
rm -f src/base64.o src/md5.o src/tools.o libtools.a
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/tools'
make -C src/main clean
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/main'
rm -f src/main.o target_bin target_bin.exe
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/main'
# make
make -C src/ipc
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/ipc'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/ipc.osrc/ipc.c
ar rcs libipc.a src/ipc.o
cp libipc.a ../../libs
make[1]: Leaving directory `/home/Myprojects/example_make/version-3.0/src/ipc'
make -C src/tools
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/tools'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/base64.osrc/base64.c
cc -g -Wall -Werror -O2 -I. -I./inc -I../../include -c -o src/md5.o src/md5.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/tools.osrc/tools.c
ar rcs libtools.a src/base64.o src/md5.o src/tools.o
cp libtools.a ../../libs
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/tools'
make -C src/main
make[1]: Entering directory`/home/Myprojects/example_make/version-3.0/src/main'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/main.osrc/main.c
cc -o target_bin src/main.o -lpthread -L../../libs -ltools-lipc
make[1]: Leaving directory`/home/Myprojects/example_make/version-3.0/src/main'
#

最后生成了可运行程序文件。这种话一个project的各个模块就变得独立出来了,不但源代码分开了,并且各自有各自的Makefile,并且各个功能模块是可独立编译的。

我们发现顶层Makefile还有能够改进的地方,就是在进入下一层文件夹是要反复写多次,例如以下:

>---$(MAKE) -C src/ipc
>---$(MAKE) -C src/tools
>---$(MAKE) -C src/main

每添加一个文件夹都要在多个伪目标里面添加一行。这样不够自己主动化啊,于是我们想到shell的循环语 句,我们能够在每条规则的命令处使用for循环。

例如以下:

DIR = src
SUBDIRS = $(shell ls $(DIR)) all :
>---@for subdir in $(SUBDIRS); \
>---do $(MAKE) -C $(DIR)/$$subdir; \
>---done

这样懒人有能够高兴非常久了。

只是还有问题:

上面for循环会依次进入系统命令ls列出的文件夹,但我们对每一个文件夹的make顺序可能有要求,在该项目其中。main文件夹下的Makefile必须最后运行,由于终于的链接须要其它文件夹编译生成的库文件,否则会运行失败。

而且在当前的Makefile中,当子文件夹运行make出现错误时。make不会退出。在终于运行失败的情况下,我们非常难依据错误的提示定位出详细是是那个文件夹下的Makefile出现错误。这给问题定位造成了非常大的困难。为了避免这种问题,在命令运行错误后make退出。

所以将刚才的Makefile改动为例如以下

DIR = src
SUBDIRS = $(shell ls $(DIR)) all :
>---@for subdir in $(SUBDIRS); \
>---do $(MAKE) -C $(DIR)/$$subdir || exit 1; \
>---done

这样在运行出错时立刻退出,但这样还是没有解决这个问题。编译错误还是会出现。

那怎么解决呢?

我们能够通过添加规则来限制make运行顺序,这样就要用到伪目标,对每个模块我们都为他写一条规则,每个模块名称是目标,最后须要运行的模块目标又是其它模块的目标,这样就限制了make顺序。在运行到最后须要运行的目标时,发现存在依赖,于是先更新依赖的目标,这样就不会出错了。而且这种话,我们还能够对指定模块进行编译,比方我仅仅改动了tools模块,我仅仅想看看我改动的这个模块代码能否够编译通过,我能够在编译时这样:

# make tools
make -C src/tools
make[1]: Entering directory`/home/Myprojects/example_make/version-2.1/src/tools'
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/base64.o src/base64.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/md5.osrc/md5.c
cc -g -Wall -Werror -O2 -I. -I./inc-I../../include -c -o src/tools.osrc/tools.c
ar rcs libtools.a src/base64.o src/md5.o src/tools.o
cp libtools.a ../../libs
make[1]: Leaving directory`/home/Myprojects/example_make/version-2.1/src/tools'
#

还有第二种方法也能够解决此问题,就是手动列出须要进入运行的模块名称(这里就是文件夹了)。把最后须要运行的模块放在最后,这样for循环运行时最后须要编译链接的模块就放在最后了,不会像我们之前那样make是依照使用系统命令ls列出模块文件夹的顺序来运行。

ls列出文件夹是依照每一个文件夹的名称来排序的,我们总不能要求写代码的时候最后运行的模块的名称必须是以z开头的吧。总之不现实。

我们的顶层Makefile又进化了,也是这一节终于Makefile:

# Top Makefile for C program
# Copyright (C) 2014 shallnew \at 163 \dot com DIR = src
MODULES = $(shell ls $(DIR))
# MODULES = ipc main tools all : $(MODULES) $(MODULES):
>---$(MAKE) -C $(DIR)/$@ main:tools ipc obj:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done clean :
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done distclean:
>---@for subdir in $(MODULES); \
>---do $(MAKE) -C $(DIR)/$$subdir $@; \
>---done tags:
>---ctags -R help:
>---@echo "===============A common Makefilefor c programs=============="
>---@echo "Copyright (C) 2014 liuy0711 \at 163\dot com"
>---@echo "The following targets aresupport:"
>---@echo
>---@echo " all - (==make) compile and link"
>---@echo " obj - just compile, withoutlink"
>---@echo " clean - clean target"
>---@echo " distclean - clean target and otherinformation"
>---@echo " tags - create ctags for vimeditor"
>---@echo " help - print help information"
>---@echo
>---@echo "To make a target, do 'make[target]'"
>---@echo "========================= Version2.0 =======================" .PHONY : all clean distclean tags help

从头開始写项目Makefile(五):嵌套运行的更多相关文章

  1. 从头開始写项目Makefile(十):make内嵌函数及make命令显示

    [版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com]     这一节我们讲一下make的函数,在之前的章节已经讲到了几 ...

  2. 从头開始写项目Makefile(七):统一目标输出文件夹

    [版权声明:转载请保留出处:blog.csdn.net/gentleliu. Mail:shallnew at 163 dot com]     上一节我们把规则单独提取出来,方便了Makefile的 ...

  3. 从0開始写MyScrollView

    从0開始写MyScrollView 上篇文章对ScrollView的详细实现进行了分析.本文依据上篇分析的结果.自己动手写一个ScrollView. step1 尾随手指滑动,非常easy.重写2个函 ...

  4. [php learn] php 从头開始学习1

    前言:大概在2006年的时候,学习过一段时间的php.而且当时做了一个下载的站点,后来因为读研究生阶段用的是java.j2ee相关,所以php就搁浅掉了,php这些年也发生了非常大的变化,最大一个变化 ...

  5. 从零開始写游戏引擎(一) - project创建以及文件夹设置还有版本号控制

    一句话提要 好的開始等于成功了一半. 创建文件夹结构 project文件夹下最好分为以下几个文件夹 Docs - 开发文档,设计文档 Assets - 角色,动作,模型和音效等 Source - 代码 ...

  6. SQL从头開始

    SQL 分为两个部分:数据操作语言 (DML) 和 数据定义语言 (DDL) 查询和更新指令构成了 SQL 的 DML 部分: SELECT - 从数据库表中获取数据 UPDATE - 更新数据库表中 ...

  7. [Golang] 从零開始写Socket Server(3): 对长、短连接的处理策略(模拟心跳)

    通过前两章,我们成功是写出了一套凑合能用的Server和Client,并在二者之间实现了通过协议交流.这么一来,一个简易的socket通讯框架已经初具雏形了,那么我们接下来做的.就是想办法让这个框架更 ...

  8. 从头開始学 RecyclerView(六) LayoutManager

    前言 在前面的文章中.每一个演示样例,都使用了LayoutManager,毕竟它是RecyclerView必不可少的一部分. LayoutManager,顾名思义,就是『布局管理器』. 使用例如以下代 ...

  9. 从头开始写项目Makefile(十):make内嵌函数及make命令显示【转】

    转自:http://blog.csdn.net/shallnet/article/details/38314473#comments 版权声明:本文为博主原创文章,未经博主允许不得转载.如果您觉得文章 ...

随机推荐

  1. linux系统——网络调试工具

    http://blog.csdn.net/chinalinuxzend/article/details/1799279 1.网络调试工具概说: 如 果我们把一台机器接入网络中,通过网络配置工具的配置这 ...

  2. 【tmux】常用命令

    https://www.cnblogs.com/lizhang4/p/7325086.html 复制 prefix [ + vim风格选择复制 新建session tmux new -s name 为 ...

  3. 糗事百科python爬虫

    # -*- coding: utf-8 -*- #coding=utf-8 import urllib import urllib2 import re import thread import ti ...

  4. tomcat镜像构建

    1.目录结构与配置文件如下 [root@centos05 java]# tree . ├── apache-tomcat-.tar.gz ├── Dockerfile ├── jdk-8u45-lin ...

  5. Visual Studio调试技巧 -- Attach to Process

    本文系作者原创,但可随意转载.另:图中使用的IDE为Visual Studio 2013 RC 英文版. 一般写完代码时,我们通常会启动调试运行一下看看是否正确,启动运行的方式无非是F5-- Star ...

  6. token防止重复提交

    Token,可以翻译成标记!最大的特点就是随机性,不可预测,一般黑客或软件无法猜测出来. Token一般用在两个地方: 1: 防止表单重复提交 2: anti csrf攻击(Cross-site re ...

  7. HTML 删除和添加字

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  8. mac 重置mysql root密码

    1. 关闭mysql服务 sudo /usr/local/mysql/support-files/mysql.server stop 如果出现Starting mysqld daemon with d ...

  9. 记录: 一次解决整型溢出攻击(使用scala,隐式转换)

    最近项目遇到一次整型溢出攻击 有一个功能,玩家购买num个物品. 每个物品花费14货币. 客户端限制玩家只能购买 1-9999个该物品. 但是某玩家通过技术手段,获得了客户端的运行权限. 于是发送协议 ...

  10. LeetCode OJ-- Substring with Concatenation of All Words ***

    https://oj.leetcode.com/problems/substring-with-concatenation-of-all-words/ 找S中子串,每个元素都在T中出现了,且所有T中元 ...