工作过程中,平时不怎么关注Makefile的书写规则,对于遇到的编译错误一般能看懂Makefile的基本规则也能解决。但如果想要编写Makefile文件还是有相当的难度的,更不用说包含多个目录和文件的功程了。于是在调试了一下午的情况下,终于把一个包含多个目录的基本Makefile给实现了,特此记录下来。 其实编写这个Makefile的目的很简单:便于集中分类管理平时的小程序。 (文章不是用来描述Makefile规则)

1. Makefile多目录结构

在windows上可以使用tree命令查看目录的树状信息(下图是在Windows下通过tree /f目录查看的)

简单的说明:
该工程目前包含有四个源代码目录和一个编译目录target,编写该功能的目的是为了方便对平时编写的小程序的管理和测试。

目录名 说明
main 存放main函数的C文件,用来测试运行其他接口程序
process 进程和线程相关
scheduler 任务调度代码实现
sleep 延时函数实现:select, pselect等实现
target 存放编译过程中生成的.o文件,因此不包含makefile

2. 多目录Makefile编写思路

  • 首先编译各个子目录的文件,生成一系列目标文件(.o文件)
  • 然后将各个子目录编译生成的目标文件链接为最终的可执行文件

3. 工程根目录下的总控Makefile

#Makefile
#author : sunzd
#date : 2020-02-23
#location: Beijing CC = gcc
MAKE = make
CFLAGS = -g -W -O2
LFLAGS = #directory
TOP_DIR := $(PWD) OBJ_DIR := $(TOP_DIR)/target BIN_DIR := $(TOP_DIR) SRC_SCHEDULE_DIR := $(TOP_DIR)/scheduler
SRC_SLEEP_DIR := $(TOP_DIR)/sleep
SRC_PROCESS_DIR := $(TOP_DIR)/process
SRC_MAIN_DIR := $(TOP_DIR)/main
#这里指定要编译的子目录信息
SUB_DIR := $(SRC_SCHEDULE_DIR) $(SRC_SCHEDULE_DIR) $(SRC_SLEEP_DIR) $(SRC_MAIN_DIR) TARGET := demo.out #方法一:手动指定所有的目标文件
#OBJS ?= $(OBJ_DIR)/scheduler.o
#OBJS += $(OBJ_DIR)/scheduler_demo.o
#OBJS += $(OBJ_DIR)/mySleep.o
#OBJS += $(OBJ_DIR)/main.o #方法二:自动获取指定目录下的目标文件
#OBJS = $(shell ls $(OBJ_DIR)/*.o) works
#OBJS := $(shell ls $(OBJ_DIR)/*.o) can't work
#OBJS ?= $(OBJ_DIR)/$(wildcard *.o) can't work
OBJS ?= $(shell ls $(OBJ_DIR)/*.o) #将重要的变量export,供子目录中的Makefile使用
export CC CFLAGS TOP_DIR OBJ_DIR BIN_DIR SRC_PROCESS_DIR SRC_SCHEDULE_DIR SRC_SLEEP_DIR OBJS \
SRC_MAIN_DIR all : $(SUB_DIR) $(TARGET) #依次进入各个子目录进行编译
$(SUB_DIR):ECHO
$(MAKE) -C $@ ECHO:
@echo sub directorys: $(SUB_DIR)
@echo begin compile:::: #最终要生成的目标文件规则
$(TARGET):$(OBJS)
#@echo ======== OBJS: $(OBJS) =======
$(CC) $(LFLAGS) $(CFLAGS) $(OBJS) -o $@ .PHNOY: clean
clean:
rm -rf $(OBJ_DIR)/*.o $(TARGET)

说明

变量定义方式 说明
= 延时变量
?= 延时变量
:= 立即变量
延时变量 在定义时不计算变量的值,在用到时再计算变量的值
立即变量 在定义时便计算出变量的值

这里由于不想通过手动指定所有的.o文件,而这些文件只有在子目录完全编译成功后会生成,因此在定义时不能采用立即变量,必须采用延时变量,等到最后编译生成可执行文件时在计算获取target目录下的所有.o文件。主要是为了方便后续扩展。

4. 子目录下的Makefile

工程中四个子目录的Makefile文件都一样(确实很方便),如果后续需要提前新的功能创建新的目录,如果没有特殊需求,可以直接将Makefile拿来使用。

#获取当前目录下所有的.c文件
src := $(shell ls *.c) #获取要编译的目标文件(.o文件): 使用patsubst函数做后缀替换实现
objs := $(patsubst %.c,%.o,$(src)) #OBJS += $(OBJ_DIR)/$(objs) #目标: 1)编译.o文件 2)将.o文件移动到指定目录
all : $(objs) MOVE #1)编译.o文件
%.o:%.c
$(CC) -c $^ -o $@ #2)将.o文件移动到指定目录
MOVE:
mv *.o $(OBJ_DIR)

4. 疑问

在Makefile规则的最后,一般是链接各个目录的目标文件生成最终的可执行文件。可以想到的方式有三种:

  • 手动在中控Makefile中指定所有的.o文件(可能还有链接库,头文件等)。这种方式就是不方便扩展,每次增删文件需要修改总控的Makefile
  • 将所有的目标文件放到一个特定的目录,最后读取对应的目录文件完成最后的链接操作。缺点很明显:对于需要链接特殊库和搜索指定路径的程序没有办法实现,还需要手动指定。
  • 在总控Makefile中预先定义变量,每一个子目录的变量将各自的目标文件、链接文件、搜索路径添加到特定的全局变量中,最终由总控Makefile在生成最后的可执行文件时引用相应的全局变量即可。

在尝试使用第三种方式时,子目录虽然成功修改了全局变量OBJS,但是在总控的Makefile中,依然为空,尚不清楚哪个原因。留着后续处理

多文件Makefile编写的更多相关文章

  1. 单目录下多文件 makefile编写

    makefile很久就接触过了,但是一直没怎么深入的去学习和总结:在项目中我也只是看看makefile或者修改部分语句,全部自己动手写的话还真没有:知识在于沉淀,这句说的非常好,所以现在把自己理解的东 ...

  2. 多目录下多文件 makefile编写

    前面已经分享了单目录项下多文件的makefile的编写,现在来看看多目录下多文件makefile的编写: 在做项目时,一般文件都会分几个目录来存放:基本的是  include/  bin/ src/ ...

  3. linux 下C语言编程库文件处理与Makefile编写

    做开发快3年了,在linux下编译安装软件算是家常便饭了.就拿gcc来说,都有不下10次了,可基本每次都会碰到些奇奇怪怪的问题.看来还是像vs.codeblocks这样的ide把人弄蠢了.便下定决心一 ...

  4. 如何将多个C文件链接在一起----Makefile编写及make指令

    需使用GCC编译器,关于MinGW的安装指南:https://people.eng.unimelb.edu.au/ammoffat/teaching/20005/Install-MinGW.pdf 单 ...

  5. Makefile 文件的编写

    目录 目录 Makefile 编写规则 Makefile 编写规则 生成的目标文件:依赖文件 生成目标文件所需执行的动作(注:命令行前需加Tab推进) 例: VPATH=inc src main:ma ...

  6. Yocto开发笔记之《Makefile编写》(QQ交流群:519230208)

    开了一个交流群,欢迎爱好者和开发者一起交流,转载请注明出处. QQ群:519230208,为避免广告骚扰,申请时请注明 “开发者” 字样 =============================== ...

  7. Make和Makefile编写(详见GCC手册)

    Makefile和Make Rules 多模块软件.依赖树和Make 默认规则 Make使用程序对简单变量的支持 内建变量 虚目标 特殊目标 一般性语法错误及其纠正措施 命令行的使用和调试 Makef ...

  8. 转载linux c语言程序的Makefile编写

    对于程序设计员来说,makefile是我们绕不过去的一个坎.可能对于习惯Visual C++的用户来说,是否会编写makefile无所谓.毕竟工具本身已经帮我们做好了全部的编译流程.但是在Linux上 ...

  9. makefile编写--引用

    1. Makefile 简介 Makefile 是和 make 命令一起配合使用的. 很多大型项目的编译都是通过 Makefile 来组织的, 如果没有 Makefile, 那很多项目中各种库和代码之 ...

随机推荐

  1. LAMP和LNMP环境搭建的艰辛历程

    目录 1. LAMP环境的搭建 1. Apache 安装apache遇到的问题 2. mysql 登录mysql的方法 3. PHP 2. Lnmp环境的搭建 1. nginx 2. PHP 3. 配 ...

  2. Serilog 最佳实践

    Serilog 最佳实践 概述 Serilog是 Microsoft .NET 的结构化日志记录库,并已成为[Checkout.com .NET 的首选日志记录库..它支持各种日志记录目的地(称为接收 ...

  3. 8.7考试总结(NOIP模拟)[Smooth·Six·Walker]

    前言 踩了挺多以前没踩过的坑... T1 一开始是打了一个 60pts 的 DFS ,在与暴力拍了几组数据保证正确性之后, 突然想到 BFS 可能会更快一些,然后就又码了一个 BFS,又和 DFS 拍 ...

  4. ArrayList 源码底层实现解析 基于1.8

    ArrayList 介绍 ArrayList是一种线性数据结构,它的底层是用数组实现的,是动态数组.与Java中的数组相比,它的容量能动态增长.源代码里有解释.当创建一个数组的时候,就必须确定它的大小 ...

  5. Jetpack Compose 1.0 终于要投入使用了!

    前言 Jetpack Compose 是用于构建原生界面的「新款 Android 工具包」.2021 Google IO 大会上,Google宣布:「Jetpack Compose 1.0 即将面世」 ...

  6. POSIX多线程编程-条件变量pthread_cond_t

    条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用.使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化.一旦其它 ...

  7. 那些shellcode免杀总结

    首发先知: https://xz.aliyun.com/t/7170 自己还是想把一些shellcode免杀的技巧通过白话文.傻瓜式的文章把技巧讲清楚.希望更多和我一样web狗也能动手做到免杀的实现. ...

  8. 为什么网络损伤仪WANsim中没有流量通过

    在使用网络损伤仪 WANsim 的过程中,有时候发现网损仪中没有流量通过.有些小伙伴可能会想:自己所有配置都是正确的 ,为什么会没有流量通过呢? 有可能,是你忽略了一些东西. 下面,我总结了一些导致网 ...

  9. final、finally与finalize的区别?

    一.final.finally与finalize的区别 final:final是一个修饰符,可以修饰类,方法和变量.final修饰类表示类不能被其它类继承,并且该类中的所有方法都会隐式的被final修 ...

  10. shiro(二)

    public class AuthorizerTest { @Test public void testIsPermitted() { login("classpath:shiro-auth ...