前言

本文展示了一个比较完整的企业项目级别的Makefile文件,包括了:文件调用,源文件、头文件、库文件指定,软件版本号、宏定义,编译时间,自动目录等内容。

1、目录架构

本文中所采用的目录架构,在企业项目开发中十分常见:源文件都放在src目录中,头文件都放在inc目录中,并且这两个目录都可以有对应的子目录。库文件放在lib目录中,makefile相关文件放在build目录中,编程生成的程序放在自动生成的output目录中。目录结构展示如下:

.
├── build
│   ├── Makefile
│   └── srcpathconfig.mk
├── code
│   ├── inc
│   │   ├── com
│   │   └── func
│   │   └── fun.h
│   └── src
│   ├── com
│   │   └── main.c
│   └── func
│   └── fun.c
└── lib
├── inc
│   └── mylib.h
└── libs
└── libmylib.so

2、源文件及Makefile内容

本文所用到的所有文件,也可以直接到我的公众号,后台回复“ mk ”获取。

源文件

/* fun.h */
#ifndef __FUN_H__
#define __FUN_H__ void fun(); #endif /* fun.c */
#include <stdio.h> void fun()
{ #ifdef MACRO_DEF
printf("macro definition enable!\n");
#endif #ifdef COMPILER_IS_ARM_LINUX_GCC
printf("The compilation target is arm!\n");
#endif #ifdef COMPILER_IS_LINUX_GCC
printf("The compilation target is linux!\n");
#endif printf("This is fun()!\n");
} /* mylib.h */
void mylib(); /* libmylib.so */
// mylib()函数,打印This is mylib()! /* main.c */
#include "fun.h"
#include "mylib.h" int main()
{
fun();
mylib();
return 0;
}

srcpathconfig.mk

这个文件的内容,其实也可以放在Makefile中,本案例单独用一个文件来配置路径,是为了后期好管理

#源文件目录
SRCCODEDIRS :=../code/src/func \
../code/src/com \ #头文件目录
SRCHEADDIRS :=../code/inc/func \
../code/inc/com \ #lib文件目录
LIBFILEDIRS := ../lib/libs #lib头文件目录
LIBHEADDIRS := ../lib/inc/ #lib文件
LIBFILE := -lmylib

Makefile

#引用其他文件
include srcpathconfig.mk #时间信息
tmpbuildtm := `date |sed 's/ /_/g'`
TMPBUILDTM = $(tmpbuildtm) #软件版本
APPVERSION = 1.0.0.0 #不同的目标采用不同的宏定义
ifeq ($(MAKECMDGOALS),arm)
COMPILEMACRO += COMPILER_IS_ARM_LINUX_GCC
else
COMPILEMACRO += COMPILER_IS_LINUX_GCC MACRO_DEF
endif #循环获取源文件和中间件
SRCFILE := $(foreach d,$(SRCCODEDIRS),$(wildcard $(addprefix $(d)/*,.c)))
OBJFILE := $(patsubst %.c,%.o,$(SRCFILE)) #宏定义,源文件路径,头文件路径
CURCMPLMACRO := $(addprefix -D ,$(COMPILEMACRO))
CURSRCHEADDIRS := $(addprefix -I ,$(SRCHEADDIRS))
CURLIBHEADDIRS := $(addprefix -I ,$(LIBHEADDIRS)) #程序输出路径
OUTPUTDIR := ../output #编译器及选项
CC := gcc
CFLAGS := -Wall -c RM := rm
RMFLAGS := -rf #目标文件
TARGETNAME = app $(TARGETNAME):$(OBJFILE)
@mkdir -p $(OUTPUTDIR)
@echo ""
@echo "all files have been compiled , now begin to link every obj for excutable file"
@echo ""
@echo "linking............"
@echo $(OBJFILE)
@$(CC) -o $(OUTPUTDIR)/$(TARGETNAME).$(APPVERSION) $(OBJFILE) -L$(LIBFILEDIRS) $(LIBFILE)
@echo ""
@echo "linked ok," $(TARGETNAME) "has been created"
@echo ""
@echo $(TMPBUILDTM) %.o: %.c
@echo ""
@echo "start " $< "......compiling"
@$(CC) $(CURCMPLMACRO) $(CFLAGS) $(CURSRCHEADDIRS) $(CURLIBHEADDIRS) $< -o $@
@echo "created " $@
@echo "end " $< "......compiled ok"
@echo "" .PHONY: arm clean arm:$(TARGETNAME) clean:
@-$(RM) $(RMFLAGS) $(TARGETNAME) $(OBJFILE) $(OUTPUTDIR)

3、效果演示

输入make 或者 make arm ,打印如下

start  ../code/src/func/fun.c ......compiling
created ../code/src/func/fun.o
end ../code/src/func/fun.c ......compiled ok start ../code/src/com/main.c ......compiling
created ../code/src/com/main.o
end ../code/src/com/main.c ......compiled ok all files have been compiled , now begin to link every obj for excutable file linking............
../code/src/func/fun.o ../code/src/com/main.o linked ok, app has been created Fri_Mar__3_22:14:09_PST_2023

生成的文件架构如下

.
├── build
│   ├── Makefile
│   └── srcpathconfig.mk
├── code
│   ├── inc
│   │   ├── com
│   │   └── func
│   │   └── fun.h
│   └── src
│   ├── com
│   │   ├── main.c
│   │   └── main.o
│   └── func
│   ├── fun.c
│   └── fun.o
├── lib
│   ├── inc
│   │   └── mylib.h
│   └── libs
│   └── libmylib.so
└── output
└── app.1.0.0.0

运行output中生成的app.1.0.0.0程序

/* 由make命令编译生成的app.1.0.0.0 */

macro definition enable!
The compilation target is linux!
This is fun()!
This is mylib()
/* 由make arm命令编译生成的app.1.0.0.0 */

The compilation target is arm!
This is fun()!
This is mylib()

4、Makefile内容解析

4.1 文件调用

include srcpathconfig.mk

相当于把srcpathconfig.mk的内容都拿过来,srcpathconfig.mk中的变量,在Makefile文件中都可以直接使用。

4.2 编译时间

tmpbuildtm := `date |sed 's/ /_/g'`
TMPBUILDTM = $(tmpbuildtm) @echo $(TMPBUILDTM)

这个是把当前的时间,保存到TMPBUILDTM变量中,可以运用到源码中,本案例只是打印一下此变量。

4.3 软件版本

APPVERSION = 1.0.0.0
@$(CC) -o $(OUTPUTDIR)/$(TARGETNAME).$(APPVERSION) $(OBJFILE) -L$(LIBFILEDIRS) $(LIBFILE)

开发过程中,我们会有多个版本的程序,可以在程序加上版本号作为后缀。

4.4 宏定义

ifeq ($(MAKECMDGOALS),arm)
COMPILEMACRO += COMPILER_IS_ARM_LINUX_GCC
else
COMPILEMACRO += COMPILER_IS_LINUX_GCC MACRO_DEF
endif CURCMPLMACRO := $(addprefix -D ,$(COMPILEMACRO)) %.o: %.c
@$(CC) $(CURCMPLMACRO) $(CFLAGS) $(CURSRCHEADDIRS) $(CURLIBHEADDIRS) $< -o $@

makefile中也可以使用条件判断,具体用法这里不多做介绍。

MAKECMDGOALS,是make命令后面跟的目标,比如make arm,那么MAKECMDGOALS的值就为arm。

这里利用MAKECMDGOALS的值来选择使用哪些宏定义,假如make 后面跟的是arm,宏定义则是COMPILER_IS_ARM_LINUX_GCC,假如make后面跟的不是arm,宏定义则是COMPILER_IS_LINUX_GCC和MACRO_DEF。

这些宏定义在fun.c中有使用,对应的是打印不同的内容。在实际项目中,宏定义的作用很广,可以用来跨平台开发,也可以用来调试打印。

4.5 源文件及中间件

SRCFILE := $(foreach d,$(SRCCODEDIRS),$(wildcard $(addprefix $(d)/*,.c)))
OBJFILE := $(patsubst %.c,%.o,$(SRCFILE))

由于我们的源文件是放在src目录下的不同子目录中,所以使用了foreach函数来循环获取。简单说明一下,foreach后面跟着的d,是中间变量,这一行的作用就是将SRCCODEDIRS的路径下的.c文件,逐个逐个拿出来,加上对应的路径前缀。

关于foreach的函数的具体使用方法,不做过多介绍。

4.6 头文件

SRCHEADDIRS   :=../code/inc/func \
../code/inc/com \ LIBHEADDIRS := ../lib/inc/ CURSRCHEADDIRS := $(addprefix -I ,$(SRCHEADDIRS))
CURLIBHEADDIRS := $(addprefix -I ,$(LIBHEADDIRS)) %.o: %.c
@$(CC) $(CURCMPLMACRO) $(CFLAGS) $(CURSRCHEADDIRS) $(CURLIBHEADDIRS) $< -o $@

将普通头文件和库头文件的存放路径单独用变量表示

4.7 库文件

LIBFILEDIRS := ../lib/libs
LIBFILE := -lmylib $(TARGETNAME):$(OBJFILE)
@$(CC) -o $(OUTPUTDIR)/$(TARGETNAME).$(APPVERSION) $(OBJFILE) -L$(LIBFILEDIRS) $(LIBFILE)

将库文件的名字和存放路径单独用变量表示

4.8 编译选项

CC := gcc
CFLAGS := -Wall -c RM := rm
RMFLAGS := -rf

CC := gcc,指定编译器为gcc;CFLAGS 和RMFLAGS中的内容可以根据需求调整,所以单独拿出来,-Wall是表示编译的时候可以产生告警,便于分析。

4.9 自动目录

OUTPUTDIR := ../output

@mkdir -p $(OUTPUTDIR)

@-$(RM) $(RMFLAGS) $(TARGETNAME) $(OBJFILE) $(OUTPUTDIR)

make命令会自动创建output目录,用来存放生成的目标文件。

make clean会将此目录及目录中的所有内容都删除

4.10 打印信息

TARGETNAME = app

$(TARGETNAME):$(OBJFILE)
@mkdir -p $(OUTPUTDIR)
@echo ""
@echo "all files have been compiled , now begin to link every obj for excutable file"
@echo ""
@echo "linking............"
@echo $(OBJFILE)
@$(CC) -o $(OUTPUTDIR)/$(TARGETNAME).$(APPVERSION) $(OBJFILE) -L$(LIBFILEDIRS) $(LIBFILE)
@echo ""
@echo "linked ok," $(TARGETNAME) "has been created"
@echo ""
@echo $(TMPBUILDTM) %.o: %.c
@echo ""
@echo "start " $< "......compiling"
@$(CC) $(CURCMPLMACRO) $(CFLAGS) $(CURSRCHEADDIRS) $(CURLIBHEADDIRS) $< -o $@
@echo "created " $@
@echo "end " $< "......compiled ok"
@echo ""

所有@echo的内容,都是为了编译的时候,打印一些信息,方便查看才加上去的,实际上有真正有用的是下面这些

TARGETNAME = app

$(TARGETNAME):$(OBJFILE)
@mkdir -p $(OUTPUTDIR)
@$(CC) -o $(OUTPUTDIR)/$(TARGETNAME).$(APPVERSION) $(OBJFILE) -L$(LIBFILEDIRS) $(LIBFILE) %.o: %.c
@$(CC) $(CURCMPLMACRO) $(CFLAGS) $(CURSRCHEADDIRS) $(CURLIBHEADDIRS) $< -o $@

————————————————————————————————

码字不易,点个赞再走吧!

欢迎关注我的同名公众号,这里有更多好料等着你哦!

轻松玩转Makefile | 企业项目级Makefile实例的更多相关文章

  1. Linuxc - Makefile完成项目的管理。

    Makefile完成项目的管理. root@jiqing-virtual-machine:~/cspace/les2# ls main.c Makefile max.c max.h min.c min ...

  2. 为Go项目编写Makefile

    为Go项目编写Makefile 借助Makefile我们在编译过程中不再需要每次手动输入编译的命令和编译的参数,可以极大简化项目编译过程. make介绍 make是一个构建自动化工具,会在当前目录下寻 ...

  3. LibOpenCM3(二) 项目模板 Makefile分析

    目录 LibOpenCM3(一) Linux下命令行开发环境配置 LibOpenCM3(二) 项目模板 Makefile分析 LibOpenCM3 项目模板 项目模板地址: https://githu ...

  4. 转发:All in one:项目级 monorepo 策略最佳实践

    0. 前言 在最近的项目开发中,出现了一个令我困扰的状况.我正在开发的项目 A,依赖了已经线上发布的项目 B,但是随着项目 A 的不断开发,又需要不时修改项目 B 的代码(这些修改暂时不必发布线上), ...

  5. 第八章 企业项目开发--分布式缓存memcached

    注意:本节代码基于<第七章 企业项目开发--本地缓存guava cache> 1.本地缓存的问题 本地缓存速度一开始高于分布式缓存,但是随着其缓存数量的增加,所占内存越来越大,系统运行内存 ...

  6. 工程管理之makefile与自动创建makefile文件过程

    (风雪之隅 http://www.laruence.com/2009/11/18/1154.html) Linux Makefile自动编译和链接使用的环境 想知道到Linux Makefile系统的 ...

  7. 研发团队管理:IT研发中项目和产品原来区别那么大,项目级的项目是项目,产品级的项目是产品!!!

    前言   从事IT行业多年,一路从小杂兵成长为大团队Leader,对于研发整个体系比较清楚,其实大多人都经历过但是都忽略了的研发成本管控的一个关键的点就是研发过程中项目级和产品级的区别.   市场基本 ...

  8. 2021 .NET Conf China 主题分享之-轻松玩转.NET大规模版本升级

    去年.NET Conf China 技术大会上,我给大家分享了主题<轻松玩转.NET大规模版本升级>,今天把具体分享的内容整理成一篇博客,供大家研究参考学习. 一.先说一下技术挑战和业务背 ...

  9. 企业项目开发--分布式缓存Redis

    第九章 企业项目开发--分布式缓存Redis(1) 注意:本章代码将会建立在上一章的代码基础上,上一章链接<第八章 企业项目开发--分布式缓存memcached> 1.为什么用Redis ...

  10. 第九章 企业项目开发--分布式缓存Redis(1)

    注意:本章代码将会建立在上一章的代码基础上,上一章链接<第八章 企业项目开发--分布式缓存memcached> 1.为什么用Redis 1.1.为什么用分布式缓存(或者说本地缓存存在的问题 ...

随机推荐

  1. 11-verilog-有限状态机

    有限状态机 写RTL的时候,实现一个功能的时候有很多种方法 将系统划分为多个状态,状态之间有状态的转移,第一步,第二步......形成有限状态机 流水线技术设计,从输入到输出有多个步骤,多个步骤可以并 ...

  2. 【LINT】cpplint 分析笔记

    cpplint 分析笔记 · [前提得看下google规范] @2022-1-13 20:44:48 error message formate: [filename] [linenum] [mess ...

  3. [转帖]12.24.2 DECIMAL Data Type Characteristics

    https://dev.mysql.com/doc/refman/8.0/en/fixed-point-types.html This section discusses the characteri ...

  4. [转帖]TIDB-TIDB节点磁盘已满报警

    一.背景 今日突然收到tidb节点的磁盘报警,磁盘容量已经超过了80%,但是tidb是不放数据的,磁盘怎么会满,这里就需要排查了 二.问题排查 解决步骤 1.df -h查看哪里占用磁盘比较多,然后通过 ...

  5. [转帖]KingbaseES wal(xlog) 日志清理故障恢复案例

    https://www.cnblogs.com/kingbase/p/16266365.html 案例说明:在通过sys_archivecleanup工具手工清理wal日志时,在control文件中查 ...

  6. [转帖]关于虚拟化中cpu的指令集SSE 4.2的不支持

    背景: 局域网中有两台服务器proxmox进行了虚拟化,跑一些测试应用.今天正好想要安装一下clickhouse跑一下.安装前准备: 测试服务器是否支持sse 4.2指令集-如下 [root@slav ...

  7. [转帖]JMeter学习(二)搭建骨架--JMeter重要组件

    https://www.cnblogs.com/tian-yong/p/4460665.html JMeter的属性和变量 JMeter属性统一定义在jmeter.properties文件中.JMet ...

  8. js判断一个时间是否在某一个时间段内

    很多时候,我们需要对时间进行处理: 比如说:获取当前的时间 判断某一个时间是否在一段时间内:如果在显示出某一个按钮: 让用户可以操作:如果不在,按钮隐藏 这个时候,我们就需要对时间进行处理了 < ...

  9. Web 3.0 - 圈里的百科

    Web3.0只是由业内人员制造出来的概念词语,最常见的解释是,网站内的信息可以直接和其他网站相关信息进行交互,能通过第三方信息平台同时对多家网站的信息进行整合使用:用户在互联网上拥有自己的数据,并能在 ...

  10. Python Fire:自动生成命令行接口

    命令行程序是平时写一些小工具时最常用的方式. 为了让命令行程序更加灵活,我们常常会设置一些参数,根据参数让程序执行不同的功能.这样就不用频繁的修改代码来执行不同的功能. 随着命令行程序功能的丰富,也就 ...