Linux Makefile之优化

ops/2024/9/25 9:34:10/

1 概述

  前面写了两篇关于Makefile的文章Linux Makefile编写之静态库和Linux Makefile编写之可执行程序.虽然编译没有问题,但还有优化的空间。

2 优化

优化列表:

  • 目标文件放入单独目录。
  • 隐藏编译命令
  • 增加头文件依赖。
  • 增量编译,只编译修改部分。
  • 将生成lib和exe部分代码提取到单独文件,Makefile直接引用。

Makefile_9">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

Makefile_48">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_65">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文件。

Makefile_209">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'

http://www.ppmy.cn/ops/29319.html

相关文章

【JavaEE】Thread的方法和属性

文章目录 1、Thread的常见构造方法2、Thread的几个常见属性2.1 ID2.2 名称2.3 状态2.4 优先级2.5 是否后台线程2.6 是否存活2.7 是否被中断 3.补充说明3.1 Thread.sleep()的作用3.2 Thread.sleep()的异常处理方式 1、Thread的常见构造方法 方法说明Thread()创建线程对象Thread…

OPPO A72/A55/K7X/A53真我Q3S等手机ROOT刷机后广电卡没信号不读卡解决办法

目前运营商除了移动联通电信以外&#xff0c;还存在1个中国广电&#xff0c;广电属于第四大运营商&#xff0c;由于广电起步较晚&#xff0c;对于手机频段要求也自然不一样&#xff0c;导致目前市面上部分手机出厂没有信号或者不读卡等问题&#xff0c;特别在手机被用户自行刷机…

解决IDEA下springboot项目打包没有主清单属性

1.问题出现在SpringBoot学习中 , 运行maven打包后无法运行 报错为spring_boot01_Demo-0.0.1-SNAPSHOT.jar中没有主清单属性 SpringBoot版本为 2.6.13 Java 版本用的8 解决方法 1.执行clean 删除之前的打包 2.进行打包规范设置 2.1 3.进行问题解决 (借鉴了阿里开发社区) 使用…

PostgresQL-丢失各种数据文件如何恢复

环境准备索引文件丢失fsm文件丢失mv文件丢失数据文件丢失pg_wal日志丢失pg_xact日志丢失pg_authid系统表数据丢失总结 环境准备 --创建测试表 postgres# create table test (n_bh int4 primary key,c_name varchar(300)); CREATE TABLE Time: 1162.555 ms --插入数据 postgre…

Python基础学习之try

在Python编程中&#xff0c;异常处理是一种非常重要的编程技巧&#xff0c;它允许程序在运行时遇到错误或异常情况时能够优雅地处理&#xff0c;而不是直接崩溃。Python提供了try-except-finally结构来实现异常处理&#xff0c;使得程序能够在遇到错误时采取适当的措施&#xf…

Vue入门篇:样式冲突scoped,data函数,组件通信,prop data单向数据流,打包发布

这里写目录标题 1.组件的样式冲突scoped2.data函数3.组件通信1.两种组件关系分类和对应的组件通信方案2.父子通信方案的核心流程 4.prop & data、单向数据流5.打包发布6.打包优化:路由懒加载 1.组件的样式冲突scoped 默认情况:写在组件中的样式会全局生效→因此很容易造成多…

模块化兼容性

模块化兼容性 由于webpack同时支持CommonJS和ES6 module&#xff0c;因此需要理解它们互操作时webpack是如何处理的 同模块化标准 如果导出和导入使用的是同一种模块化标准&#xff0c;打包后的效果和之前学习的模块化没有任何差异 不同模块化标准 不同的模块化标准&#x…

贪心算法 Greedy Algorithm

1) 贪心例子 称之为贪心算法或贪婪算法&#xff0c;核心思想是 将寻找最优解的问题分为若干个步骤 每一步骤都采用贪心原则&#xff0c;选取当前最优解 因为没有考虑所有可能&#xff0c;局部最优的堆叠不一定让最终解最优 v2已经不会更新v3因为v3更新过了 贪心算法是一种在…