1 概述
前面写了两篇关于Makefile的文章Linux Makefile编写之静态库和Linux Makefile编写之可执行程序.虽然编译没有问题,但还有优化的空间。
2 优化
优化列表:
- 目标文件放入单独目录。
- 隐藏编译命令。
- 增加头文件依赖。
- 增量编译,只编译修改部分。
- 将生成lib和exe部分代码提取到单独文件,Makefile直接引用。
3 Makefile实例
这里以CppCmd(C++写的命令行系统)为例,代码结构:
cppcmd1.0.0$ tree
.
├── Makefile
├── inc
│ └── cppcmd.h
├── mkfiles
│ ├── exe.mk
│ └── lib.mk
├── src
│ ├── Makefile
│ ├── cmdhelper.h
│ ├── cmdio.cpp
│ ├── cmdio.h
│ └── cppcmd.cpp
└── test├── Makefile├── cmdtest.cpp├── cmdtest.h├── inc│ └── cpptest│ ├── cpptest-assert.h│ ├── cpptest-collectoroutput.h│ ├── cpptest-compileroutput.h│ ├── cpptest-htmloutput.h│ ├── cpptest-output.h│ ├── cpptest-source.h│ ├── cpptest-suite.h│ ├── cpptest-textoutput.h│ ├── cpptest-time.h│ └── cpptest.h├── lib│ └── libcpptest.a└── test.cpp7 directories, 24 files
3.1 Makefile
all:@make -C src@make -C testclean:@make -C src clean@make -C test cleanrun:@./bin/test.PHNOY: all clean test
说明:
- 根目录Makefile调用src和test目录下Makefile进行编译。
3.2 src/Makefile
这个Makefile编译src目录下文件并生成lib库。
PROJECT_NAME ?= cppcmdPWD := $(shell pwd)
TOP := $(PWD)/..
LIBMKFILE := $(TOP)/mkfiles/lib.mk
INCS := -I$(TOP)/inc
SRCDIR := $(TOP)/src
LIBDIR := $(TOP)/lib
OBJDIR := $(PWD)/.obj
INCFILES := $(SRCDIR)/cmdhelper.h
LIBNAME := $(LIBDIR)/lib$(PROJECT_NAME).aCFLAGS :=
C++FLAGS := -std=c++11include $(LIBMKFILE)
说明:
- 定义lib.mk文件路径 LIBMKFILE
- 定义include路径 INCS
- 定义src路径 SRCDIR
- 定义lib库存放路径 LIBDIR
- 定义.o文件存放路径 OBJDIR
- 定义依赖头文件cmdhelper.h,增加依赖头文件后,头文件修改后,make时会自动编译引用该头文件的源文件。
- 定义生成库名称 LIBNAME
- 定义C编译选项 CFLAGS
- 定义C++编译选项 C++FLAGS
- 引入生成lib库Makefile片段文件lib.mk
3.3 mkfiles/lib.mk
生成lib库文件的Makefile片段.
CC ?= gcc
CXX ?= g++
AR ?= ar
ECHO ?= echo
MAKE ?= makeLIBFLAGS := -rcDCSRC := $(wildcard $(SRCDIR)/*.c)
OBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(notdir $(CSRC)))CPPS := $(wildcard $(SRCDIR)/*.cpp)
CPPOBJS := $(patsubst %.cpp, $(OBJDIR)/%.o, $(notdir $(CPPS)))all: $(LIBNAME)@make -ts --no-print-directory$(LIBNAME): $(OBJS) $(CPPOBJS) $(LIBDIR)@$(ECHO) Ar $(LIBNAME)@$(AR) $(LIBFLAGS) $(LIBNAME) $(OBJS) $(CPPOBJS) $(OBJS): $(OBJDIR)/%.o:$(SRCDIR)/%.c $(INCFILES) $(OBJDIR)@$(ECHO) Cc $@@$(CC) -c $(CFLAGS) $(INCS) $< -o $@$(CPPOBJS): $(OBJDIR)/%.o:$(SRCDIR)/%.cpp $(INCFILES) $(OBJDIR) @$(ECHO) C++ $@@$(CXX) -c $(C++FLAGS) $(INCS) $< -o $@$(LIBDIR):@mkdir -p $(LIBDIR)$(OBJDIR):@mkdir -p $(OBJDIR).PHNOY: clean
clean:
ifeq ($(wildcard $(OBJDIR)), $(OBJDIR))@rm -rf $(OBJDIR)
endif@rm -f $(LIBNAME)
3.3.1 代码分析
3.3.1.1 变量定义
CC ?= gcc
CXX ?= g++
AR ?= ar
ECHO ?= echo
MAKE ?= makeLIBFLAGS := -rcD
说明:
- 定义编译器等程序名称。
- 定义生成库选项。
3.3.1.2 自动选择译源文件
CSRC := $(wildcard $(SRCDIR)/*.c)
OBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(notdir $(CSRC)))CPPS := $(wildcard $(SRCDIR)/*.cpp)
CPPOBJS := $(patsubst %.cpp, $(OBJDIR)/%.o, $(notdir $(CPPS)))
说明:
- 调用函数wildcard扫描src下所有.c/.cpp文件
- 调用函数patsubst通过源文件生成.o目标文件,注意目标文件放在OBJDIR目录下
3.3.1.3 增量编译
all: $(LIBNAME)@make -ts --no-print-directory
说明:
- 通过-t选项touch目标文件,更新目标文件日期,防止重新编译。
3.3.1.4 编译依赖项
$(LIBNAME): $(OBJS) $(CPPOBJS) $(LIBDIR)@$(ECHO) Ar $(LIBNAME)@$(AR) $(LIBFLAGS) $(LIBNAME) $(OBJS) $(CPPOBJS) $(OBJS): $(OBJDIR)/%.o:$(SRCDIR)/%.c $(INCFILES) $(OBJDIR)@$(ECHO) Cc $@@$(CC) -c $(CFLAGS) $(INCS) $< -o $@$(CPPOBJS): $(OBJDIR)/%.o:$(SRCDIR)/%.cpp $(INCFILES) $(OBJDIR) @$(ECHO) C++ $@@$(CXX) -c $(C++FLAGS) $(INCS) $< -o $@$(LIBDIR):@mkdir -p $(LIBDIR)$(OBJDIR):@mkdir -p $(OBJDIR).PHNOY: clean
clean:
ifeq ($(wildcard $(OBJDIR)), $(OBJDIR))@rm -rf $(OBJDIR)
endif@rm -f $(LIBNAME)
说明:
- $(OBJS)依赖项编译.c文件为.o文件,同时依赖 $(INCFILES)和 $(OBJDIR)
- $(CPPOBJS)依赖项编译.cpp文件为.o文件,同时依赖 $(INCFILES)和 $(OBJDIR)
- $(LIBDIR)依赖项创建目录lib
- $(OBJDIR)依赖项创建目录.obj
- $(LIBNAME) 依赖项将.o文件生成lib文件。
- clean依赖项删除编译生成.o和.a文件。
3.4 test/Makefile
这个Makefile编译test目录下文件并生成exe。
PROJECT_NAME := testPWD := $(shell pwd)
TOP := $(PWD)/..
EXEMKFILE := $(TOP)/mkfiles/exe.mk
INCS := -I$(TOP)/inc -I$(PWD)/inc
SRCDIR := $(PWD)
OBJDIR := $(PWD)/.obj
BINDIR := $(TOP)/bin
INCFILES := $(SRCDIR)/cmdtest.h
LIBS := $(TOP)/lib/libcppcmd.a $(PWD)/lib/libcpptest.a
APPNAME := $(BINDIR)/$(PROJECT_NAME)CFLAGS :=
C++FLAGS := -std=c++11
LINKFLAGS :=include $(EXEMKFILE)
- 定义exe.mk文件路径 EXEMKFILE
- 定义include路径 INCS
- 定义src路径 SRCDIR
- 定义.o文件存放路径 OBJDIR
- 定义exe文件存放路径 BINDIR
- 定义依赖头文件cmdtest.h, 增加依赖头文件后,头文件修改后,make时会自动编译引该用头文件的源文件。
- 定义生成exe名称 APPNAME
- 定义C编译选项 CFLAGS
- 定义C++编译选项 C++FLAGS
- 定义链接选项 LINKFLAGS
- 引入生成exe库Makefile片段文件exe.mk
3.5 mkfiles/exe.mk
生成exe文件的Makefile片段。
CC ?= gcc
CXX ?= g++
AR ?= ar
ECHO ?= echoCSRC := $(wildcard $(SRCDIR)/*.c)
OBJS := $(patsubst %.c, $(OBJDIR)/%.o, $(notdir $(CSRC)))CPPS := $(wildcard $(SRCDIR)/*.cpp)
CPPOBJS := $(patsubst %.cpp, $(OBJDIR)/%.o, $(notdir $(CPPS)))all: $(APPNAME)@make -ts --no-print-directory$(APPNAME): $(OBJS) $(CPPOBJS) $(LIBS) $(BINDIR)@$(ECHO) Link $(APPNAME)@$(CXX) $(OBJS) $(CPPOBJS) $(LIBS) $(LINKFLAGS) -o $(APPNAME)$(OBJS): $(OBJDIR)/%.o:$(SRCDIR)/%.c $(INCFILES) $(OBJDIR)@$(ECHO) Cc $@@$(CC) -c $(CFLAGS) $(INCS) $< -o $@$(CPPOBJS): $(OBJDIR)/%.o:$(SRCDIR)/%.cpp $(INCFILES) $(OBJDIR)@$(ECHO) C++ $@@$(CXX) -c $(C++FLAGS) $(INCS) $< -o $@$(OBJDIR):@mkdir -p $(OBJDIR)$(BINDIR):@mkdir -p $(BINDIR).PHNOY: clean
clean:
ifeq ($(wildcard $(OBJDIR)), $(OBJDIR))@rm -rf $(OBJDIR)
endif@rm -f $(APPNAME)
说明:
- 参考上面lib.mk说明。
4 运行
4.1 编译
4.1.1 编译代码隐藏编命令
cppcmd1.0.0$ make
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/src'
C++ /home/james/git/cppcmd1.0.0/src/.obj/cppcmd.o
C++ /home/james/git/cppcmd1.0.0/src/.obj/cmdio.o
Ar /home/james/git/cppcmd1.0.0/src/../lib/libcppcmd.a
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/src'
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/test'
C++ /home/james/git/cppcmd1.0.0/test/.obj/cmdtest.o
C++ /home/james/git/cppcmd1.0.0/test/.obj/test.o
Link /home/james/git/cppcmd1.0.0/test/../bin/test
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/test'
4.1.2 编译后重新make,由于代码没有修改多以没有修改。
cppcmd1.0.0$ make
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/src'
make[1]: 'all' is up to date.
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/src'
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/test'
make[1]: 'all' is up to date.
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/test'
4.1.3 修改头文件cmdhelper.h,相关联代码都编译了
cppcmd1.0.0$ make
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/src'
C++ /home/james/git/cppcmd1.0.0/src/.obj/cppcmd.o
C++ /home/james/git/cppcmd1.0.0/src/.obj/cmdio.o
Ar /home/james/git/cppcmd1.0.0/src/../lib/libcppcmd.a
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/src'
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/test'
Link /home/james/git/cppcmd1.0.0/test/../bin/test
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/test'
4.2 清理
cppcmd1.0.0$ make clean
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/src'
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/src'
make[1]: Entering directory '/home/james/git/cppcmd1.0.0/test'
make[1]: Leaving directory '/home/james/git/cppcmd1.0.0/test'