从头開始写项目Makefile(五):嵌套运行

【版权声明:转载请保留出处: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:
>[email protected] "===============A common Makefilefor c programs=============="
>[email protected] "Copyright (C) 2014 liuy0711 \at 163\dot com"
>[email protected] "The following targets aresupport:"
>[email protected]
>[email protected] " all              - (==make) compile and link"
>[email protected] " obj              - just compile, withoutlink"
>[email protected] " clean            - clean target"
>[email protected] " distclean        - clean target and otherinformation"
>[email protected] " tags             - create ctags for vimeditor"
>[email protected] " help             - print help information"
>[email protected]
>[email protected] "To make a target, do ‘make[target]‘"
>[email protected] "========================= 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 [email protected] $^
>---cp [email protected] ../../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 [email protected] $^ $(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 :
>[email protected] 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 :
>[email protected] 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)/[email protected]

main:tools ipc

obj:
>[email protected] subdir in $(MODULES); >---do $(MAKE) -C $(DIR)/$$subdir [email protected]; >---done

clean :
>[email protected] subdir in $(MODULES); >---do $(MAKE) -C $(DIR)/$$subdir [email protected]; >---done

distclean:
>[email protected] subdir in $(MODULES); >---do $(MAKE) -C $(DIR)/$$subdir [email protected]; >---done

tags:
>---ctags -R

help:
>[email protected] "===============A common Makefilefor c programs=============="
>[email protected] "Copyright (C) 2014 liuy0711 \at 163\dot com"
>[email protected] "The following targets aresupport:"
>[email protected]
>[email protected] " all              - (==make) compile and link"
>[email protected] " obj              - just compile, withoutlink"
>[email protected] " clean            - clean target"
>[email protected] " distclean        - clean target and otherinformation"
>[email protected] " tags             - create ctags for vimeditor"
>[email protected] " help             - print help information"
>[email protected]
>[email protected] "To make a target, do ‘make[target]‘"
>[email protected] "========================= Version2.0 ======================="

.PHONY : all clean distclean tags help
时间: 2024-07-29 12:40:00

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

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

[版权声明:转载请保留出处:blog.csdn.net/gentleliu. Mail:shallnew at 163 dot com] 上一节我们把规则单独提取出来,方便了Makefile的维护,每一个模块仅仅须要给出关于自己的一些变量,然后再使用统一的规则Makefile.这一节我们继续改进我们的Makefile,到眼下为止我们的Makefile编译链接输出的目标都在源文件同文件夹下或模块Makefile同一文件夹下.当一个项目大了之后,这样会显得非常乱,寻找编译输出的文件也比較困难. 既然

从头開始写项目Makefile(三):变量的使用

[版权声明:转载请保留出处:blog.csdn.net/gentleliu. Mail:shallnew at 163 dot com] 细致研究我们的之前Makefile发现.我们还有改进的地方.就是此处: target_bin : main.o debug.o ipc.o timer.o tools.o >---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o 假设添加一个源文件xx.c的话.须要在两处或多处添加xx.o文件. 我们

从头开始写项目Makefile(五):嵌套执行

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 在大一些的项目里面,所有源代码不会只放在同一个目录,一般各个功能模块的源代码都是分开的,各自放在各自目录下,并且头文件和.c源文件也会有各自的目录,这样便于项目代码的维护.这样我们可以在每个功能模块目录下都写一个Makefile,各自Makefile处理各自功能的编译链接工作,这样我们就不必把所有功能的编译链接都放在同一个Makefile里面,这可使得我们的Ma

从头开始写项目Makefile(六):参数传递、条件判断、include

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 在多个Makefile嵌套调用时,有时我们需要传递一些参数给下一层Makefile.比如我们在顶层Makefile里面定义的打开调试信息变量DEBUG_SYMBOLS,我们希望在进入子目录执行子Makefile时该变量仍然有效,这是需要将该变量传递给子Makefile,那怎么传递呢?这里有两种方法: 1.     在上层Makefile中使用"export&qu

从头开始写项目Makefile(四):伪目标

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 一般情况下,Makefile都会有一个clean目标,用于清除编译过程中产生的二进制文件.我们在第一节的Makefile就用到了这个 clean目标,该目标没有任何依赖文件,并且该目标对应的命令执行后不会生产clean文件. 像这种特点目标,它的规则所定义的命令不是去创建文件,而仅仅通过make指定目标来执行一些特定系统命令或其依赖为目标的规则(如all),称为

从头开始写项目Makefile(三):变量的使用

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 仔细研究我们的之前Makefile发现,我们还有改进的地方,就是此处: target_bin : main.o debug.o ipc.o timer.o tools.o >---gcc -o target_bin main.o debug.o ipc.o timer.o tools.o 如果增加一个源文件xx.c的话,需要在两处或多处增加xx.o文件.我们可以

从头开始写项目Makefile(零):前言

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 一不小心工作三年了,也就是写了三年代码了,码农生活过的真快.最近发现我们项目的Makefile不够好,于是着手改了一下,以前Makefile写好后就很少动它了,直接在项目之间拷来拷去直接拿来用,这次重新修改项目Makefile,发现自己又学到不少东西,于是乎决定总结一下记录下来与各位苦逼的程序猿一道分享一下. 其实各大论坛博客已经有很多关于Makefile的文章

从头开始写项目Makefile(一):基本规则

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 一般一个稍大的linux项目会有很多个源文件组成,最终的可执行程序也是由这许多个源文件编译链接而成的.编译是把一个.c或.cpp文件编译成中间代码.o文件,链接是就使用这些中间代码文件生成可执行文件.比如在当前项目目录下有如下源文件: # ls common.h debug.c debug.h ipc.c ipc.h main.c tags timer.c ti

从头开始写项目Makefile(二):自动推导

[版权声明:转载请保留出处:blog.csdn.net/gentleliu.Mail:shallnew at 163 dot com] 上一节的Makefile勉强可用,但还写的比较繁琐,不够简洁.对每一个.c源文件,都需要写一个生成其对应的.o目标文件的规则,如果有几百个或上千个源文件,都手动来写,还不是很麻烦,这也不够自动化啊. 这样,我们把生成.o目标文件的规则全部删除掉,就是这样一个Makefile文件: target_bin : main.o debug.o ipc.o timer.o