1 回顾程序的编译和链接流程
1.1 一个C语言源文件变为可执行程序的过程
简单来讲,源代码文件一般由程序员使用高级计算机语言(C、Go、Java、C++等)进行编写,不同的语言有不同的文件后缀名,但是他们的共同点就是他们都是程序的源文件
,这些文件(一个或多个)需要进行编译和链接后生成可直接运行的程序,才能真正的被运行。
当然有些编程语言并没有这种机制,这种语言一般为解释执行型的语言,比如Java,以及解析型的脚本语言,比如Python。拥有这种机制的语言一般为编译性语言,比如经典的C语言。
1.2 程序源文件如何编译和链接
那么问题来了,程序的源文件是如何编译和链接的呢?
我们那最经典的C语言举例子,其实上图针对C语言的编译过程描述的并不是很全面,只是一个大概的过程,C语言较为完整的编译过程是C源文件->(预编译)->中间文件->(编译)->中间文件->(链接)->可执行文件
并且其中还依赖了众多的库文件。
1.3 源代码编译为程序的必要条件—编译器
以上并不是重点,我们都知道C语言是具有很好的移植性,并且能够在绝大多数操作系统下进行编译,可谓是一次编写,处处编译
,但是任何语言都是由编译器的,不同操作系统下C语言源文件编译成的可执行文件不同(比如在Windows下编译成xx.exe,而在Linux下编译成xx.sh或xx,所以我们可以逆向推导出,C语言的编译器在不同的操作系统中具有不同的种类
,但是所有的编译器种类都要符合一个特点的规范,如下:
- C语言规范:ANSI C
ANSI C是由美国国家标准协会(ANSI)及国际标准化组织(ISO)推出的关于C语言的标准。ANSI C 主要标准化了现存的实现, 同时增加了一些来自 C++ 的内容 (主要是函数原型) 并支持多国字符集 (包括备受争议的三字符序列)。 ANSI C 标准同时规定了 C 运行期库例程的标准。 - 流行的C语言编译器有以下几种:
- GNU Compiler Collection 或称 GCC
- Microsoft C 或称 MS C
- Borland Turbo C 或称 Turbo C
1.4 小结
通过以上内容我们知道了一个main.c程序是如何变为可执行程序的简单过程,这个过程看起来可以说并不简单,但是为什么我们测试程序的时候完全无感知呢?
答案就是——IDE(Integrated Development Environment ),中文名称:集成开发环境,正是因为有IDE我们才不需要每次启动程序时都进行重复流程的操作,而是他已经帮我们搞好了一套流程用来对源代码进行编译和运行。
所以本文的重点——Makefile也是如此,因为我们的项目代码最终都是要放到服务器上去运行的,但是服务器上并没有我们个人电脑上存在非常好用的IDE,最大的原因就是耗费服务器的资源,因此我们使用Makefile来进行对程序的自动化编译,下面步入正题。
2 Makefile快速入门
2.1 核心语法
target ... : prerequisites ... command ... ... 复制代码
解释:
- target:
可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。对于标签这种特性,在后续的“伪目标”章节中会有叙述。 - prerequisites:
生成该target所依赖的文件和/或target - command:
该target要执行的命令(任意的shell命令)
2.2 编译和运行单个源代码文件实战
2.2.1 Linux下gcc环境检测
gcc -v 复制代码
2.2.2 编写main.c源代码文件
#include<stdio.h> int main(){ printf("Hello Makefile ~\n"); return 0; } 复制代码
2.2.3 编写Makefile文件
main: main.o gcc -o main main.o main.o: main.c gcc -c main.c clean: rm main.o 复制代码
注意:
Makefile命令的开头一定要使用Tab键而不是空格键,否则就会报错。
2.2.4 运行Makefile
自定义运行:
2.2.5 Makefile中使用shell命令
我们也可以在Makefile中使用shell命令,举个例子:
hello: echo Hello Makefile @echo Hello Makefile Too @ls -al 复制代码
运行结果:
由此我们知道了,在shell命令前加@的不会输出命令的本身,反之则输出命令的本身。
3 Go项目中Makefile的简单使用
3.1 伪目标和变量
3.1.1 伪目标
最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,
clean: rm *.o temp 复制代码
更为稳健的做法是:
.PHONY : clean clean : - rm *.o temp 复制代码
前面说过, .PHONY
表示 clean
是一个“伪目标”。而在 rm
命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然, clean
的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。
.PHONY : all
声明了“all”这个目标为“伪目标”。(注:这里的显式“.PHONY : all” 不写的话一般情况也可以正确的执行,这样make可通过隐式规则推导出, “all” 是一个伪目标,执行make不会生成“all”文件,而执行后面的多个目标。建议:显式写出是一个好习惯。)
小结:因为伪目标总是会被执行,所以其依赖总是会被决议。通过这种方式,可以达到同时执行所有依赖项的目的。
3.1.2 变量的使用
(1)变量的声明:
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上 $
符号,但最好用小括号 ()
或是大括号 {}
把变量给包括起来。如果你要使用真实的 $
字符,那么你需要用 $$
来表示。
(2)演示
hello.txt:
Hello~ 复制代码
Makefile:
i=10 txt=./hello.txt run: @echo $(i) @cat $(txt) 复制代码
执行:
(3)扩展
变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 :
、 #
、 =
或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。
3.2 Go项目的常用命令
为什么要说一下Go中的常用命令呢,因为Go项目中Makefile的使用较为简单,大多都是对Go命令的一些执行操作,因此我们首先要熟知Go语言中的常用命令,才能更好的编写Makefile。
- Go一般命令:
- build: 编译包和依赖。
- clean: 移除当前源码包里面编译生成的文件。
- doc: 显示包或者符号的文档。
- env: 打印go的环境信息,查看GOPATH,GOROOT环境变量都可以使用该命令查看。
- bug: 启动错误报告。
- fix: 运行go tool fix。
- fmt: 运行gofmt进行格式化。
- generate: 从processing source生成go文件。
- get: 下载并安装包和依赖。
- install: 编译并安装包和依赖。
- list: 列出包。
- run: 编译并运行go程序。
- test: 自动读取源码目录下面名为*_test.go的文件,生成并运行测试用的可执行文件。
- tool: 运行go提供的工具。
- version: 显示当前环境安装go的版本。
- vet: 运行go tool vet。
- Go mod命令:
go mod init [项目名称]
:初始化工程项目。go mod graph
:输入工程当前所有的依赖。go mod download
: 使用此命令来下载指定的模块到本地。go mod tidy
:就是把不需要的依赖给删除掉。go mod verify
:验证mod里依赖是否正确,同时也会验证go源代码其它依赖的内容。go mod why
:可以展示及指定依赖关系。比如代码中有个依赖包,但是并不知道这个包的依赖关系,这时候就可以使用这个命令查看。
3.3 使用实践
项目目录:
Makefile文件内容:
.PHONY:tidy tidy: go mod tidy .PHONY:build build: go build ./cmd/main.go .PHONY:run run: @./main 复制代码
执行:
4 总结
Makefile文件是在项目构建这非常重要的一环,最初认识它是在GitHub上的开源项目中,发现每个项目都有一个Makefile,而且语法看不懂,于是就专门抽时间学习一下,当然由于本人初次学习,本文涵盖的知识点有限,更详细的学习请看 【这篇文章】,在本文这如有错误和不足,感谢读者指出!
参考链接:
seisman.github.io/how-to-writ…