Linux友人帐之自动化构建工具make/Makefile

一、背景

1.1概述

代码编程可执行文件,叫做编译(compile);在一个项目中,先编译这个,还是先编译那个(即编译的安排),叫做构建(build)。

make是最常用的构建工具,诞生于1977年,主要用于C语言想项目。

实际上,任何只要某个文件发生变化,就要重现构建的项目都可以用make构建。

1.2介绍 

  • 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
  • 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
  • makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
  • make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
  • make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。

 1.3 make的概念

make: 制作。比如,要做出文件a.txt,就可以执行:

$ make a.txt

make本身不知道如何做出a.txt,需要告知make如何调用其它命令完成这个目标。

比如,假设文件a.txt依赖于b.txt和c.txt,是后面两个文件连接(cat命令)的产物。那么,make需要知道下面的规则:

a.txt: b.txt c.txt

    cat b.txt c.txt > a.txt

也就是,make a.txt这条命令的背后实际分为两步:

确认b.txt和c.txt必须已经存在;

使用cat命令将两个文件进行合并,输出为新的文件。  

像这样的规则,需要写在一个名为Makefile的文件中,make命令依赖这个文件进行构建。

Makefile也可以写成makefile,或者用命令行参数指定为其它文件名:

$ make -f rules.txt

$ make --file=rules.txt

make只是一个根据指定的Shell命令进行构建的工具。

1.4 Makefile的文件格式

Makefile文件由一系列规则构成。每条规则的形式如下:

<target>: <prerequisites>

[tab] <command>

目标: 前置条件

[必须有个table键] <命令>

“目标”是必须的;“前置条件”和“命令”是可选的,但两者必存其一。

明确:构建目标的前置条件是什么,以及如何构建。

第一行是依赖关系,第二行必须以Tab键开头!!!,第二行写的是依赖方法
依赖关系可以为空

 目标(target)

一个目标(target)构成一个规则。目标通常是文件名,指明命令所要构建的对象,比如a.txt。

目标可以是一个文件名,也可以是多个文件名,之间用空格分隔。

除了文件名,目标还可以是某个操作的名字,这称之为“伪目标”(phony target)。

clean:

    rm *.o

上面代码的目标是clean,不是一个文件名,是一个操作的名字,属于“伪目标”,作用是删除文件。

$ make clean

为了避免这种情况,可以声明clean是“伪目标”:

.PHONY: clean

clean:

    rm *.o

声明clean是“伪目标”之后,make不会去检查是否存在一个叫做clean的文件,而是每次运行都执行对应的命令。

如果make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。

前置条件(prerequisites)

前置条件通常是一组文件名,之间用空格分隔。它指定了“目标”是否重新构建的判断标准: 只要一个前置文件不存在,或者更新过,“目标”就需要重新构建。

result.txt: source.txt

    cp source.txt result.txt

构建result.txt的前置条件时source.txt。如果当前路径下,source.txt已经存在,那么make result.txt可以正常运行;否则必须再写一条规则,用以生成source.txt。 

source.txt:

    echo “this is the source” > source.txt

source.txt没有前置条件,意味着跟其它文件无关,只要source.txt不存在,每次调用make source.txt,都会生成source.txt。 

连续执行两次make result.txt。

第一次会先创建source.txt,再创建result.txt。

第二次执行,make发现source.txt没有更新,就会不执行任何操作。

 命令(command)

命令(command)表示如何更新目标文件,由一行或多行Shell命令组成;

是构建“目标”的具体指令;

结果通常是生成目标文件。

每行命令之前必须有一个tab键。如果想用其它键,可以用内置变量.RECIPEPREFIX声明。

.RECIPEPREFIX=>

all:

>echo 123

用.RECIPEPREFIX指定大于号(>)替代tab键。

每行命令都是在一个单独运行的Shell中执行的,这些Shell间没有继承关系。

var:

    export foo=bar

    echo “foo=[$$foo]” 

解决方法:

  1. ;

  2. \

  3. .ONESHELL

补充:gcc如何得知,源文件不需要再编译了呢?

Modify代表文件内容被修改的时间,Change代表文件属性被修改的时间,Access代表最后一次访问文件的时间,值得注意的是,文件大小也算文件的属性

有时候访问文件的时间Access被更新的不是很灵敏,以前老的操作系统内核,对于这件事的原则就是,只要你访问了,就立马更新时间,但现在的操作系统内核,过一段时间之后才会更新我们的访问文件时间。
原因:文件操作的时候,改文件就一定会访问文件,但访问文件不一定该文件,所以访问文件的次数太多了,要进行更多次的IO,并且文件访问的时间基本没有人关心。

1.5示例

改进 

cc 命令 

 在 Linux 的 makefile 中,cc 命令通常用于编译 C 语言程序。cc 命令可以使用以下选项:

- `-c`:编译源文件,生成目标文件。
- `-o`:指定编译生成的目标文件名。
- `-Wall`:显示所有警告信息。
- `-g`:生成调试信息,用于 gdb 调试。
- `-I`:指定头文件路径。
- `-L`:指定库文件路径。
- `-l`:指定要链接的库文件名。

例如,假设有一个名为 `main.c` 的源文件,需要编译生成可执行文件,可以使用以下 makefile:

CC = cc
CFLAGS = -Wall -g -I./include
LDFLAGS = -L./lib -lm
OBJS = main.o foo.o bar.o

program: $(OBJS)
    $(CC) -o $@ $(LDFLAGS) $(OBJS)

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -rf *.o program

在上面的 makefile 中,`CC` 定义了编译器为 `cc`,`CFLAGS` 定义了编译选项,`LDFLAGS` 定义了链接选项,`OBJS` 定义了需要编译的目标文件。`program` 是最终生成的可执行文件名,它依赖于 `$(OBJS)` 中的目标文件,通过 `$(CC)` 命令进行链接生成。`%.o: %.c` 表示根据 `.c` 文件生成 `.o` 文件。`clean` 是清理编译过程中生成的目标文件和可执行文件。

 二、Linux的第一个小程序-进度条

2.1缓冲区概念

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6     printf("you can see me ........\n");
  7     sleep(2);                                                                                                                                                         
  8     return 0;
  9 }
  
	// 可以利用fflush(stdout)语句来刷新缓冲区,这样数据就会立马显示出来
	

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 int main()
  5 {
  6     printf("you can see me ........");
  7     sleep(2);                                                                                                                                                         
  8     return 0;
  9 }

如果加上\n那就会先显示you can see me然后停下来2秒钟,如果没有\n的话,那就会先停下来2秒,然后才显示出you can see me。

第一种拥有\n可以立即输出的原因是因为,\n是行缓冲,只要遇到\n就会立马将这一行的内容输出到显示器上面


针对第二种没有\n的情况,首先printf语句肯定是要先执行的,唯一的可能性就是,这个you can see me没有被立即显示出来,等到走完sleep语句,它才显示出来,在未显示的这段时间里面,you can see me一直被存储在行缓冲区里面 

 2.2\r && \n

理论上,回车和换行不是一个概念,换行是回到当前行的最开始,换行是将光标挪到下一行相同的光标位置,\r是回车\n是换行,\r\n合起来我们称之为回车换行。

语言层面上,\n就是回车换行,因为编译器在内部对于\n做了特殊处理,回车换行其实分成两步,先换行,再回车。

我们看一下回车\r 

不显示的原因就是我们的回车\r将之前的内容给覆盖掉了,并且在缓冲区中回到了这一行的起始位置,因此程序结束也并没有打印。

2.3fflush(stdout)

因此为了解决上面的问题,可以用刷新缓冲区的办法实现

 2.4 倒计时实现

显示器能够显示各种符号,包括数字,汉字,字母等等,这些都被计算机看作符号,只不过人把它分为数字还是汉字,字母等,能够显示这么多符号是因为显示器面板上有各种像素点,点亮对应显示器上的像素点,就可以给人类显示出来各种各样的符号了,并且凡是能够显示到显示器上的,其实都是字符

通过上面的了解,大家已经知道了缓冲区的概念,因此为了下面的进度条实现,在这里我们先通过上面的知识实现一下倒计时:

上述实际上有一定的细节,我们知道/r只是回到起始位置,但如果不控制格式2d,就会出现打印10,90,80……的情况,因为我们每次只覆盖了第一个位置,因此在这里要控制格式,并且fflush(stdout)。 

2.5进度条

对于进度条来说,通过最上面的航缓冲的知识,我们已经知道应该如何去规避了,因此在这里直接展示进度条,我将程序分成三个部分,即经典的main.c/process.c/process.h,并且将makefile中的依赖对象也改变,对于依赖对象来说,只要-o后面最靠近的是要生成的即可。

接下来我们看看代码,并将其执行:(主程序)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟雨平生9527

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值