规范设计

开源规范

开源协议概述

MIT是相对自由开发的协议

opensource_proctol

文档规范

最重要的三类文档:README文档、项目文档和API文档

README规范

主要用来介绍项目的功能、安装、部署和使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 项目名称

<!-- 写一段简短的话描述项目 -->

## 功能特性

<!-- 描述该项目的核心功能点 -->

## 软件架构(可选)

<!-- 可以描述下项目的架构 -->

## 快速开始

### 依赖检查

<!-- 描述该项目的依赖,比如依赖的包、工具或者其他任何依赖项 -->

### 构建

<!-- 描述如何构建该项目 -->

### 运行

<!-- 描述如何运行该项目 -->

## 使用指南

<!-- 描述如何使用该项目 -->

## 如何贡献

<!-- 告诉其他开发者如果给该项目贡献源码 -->

## 社区(可选)

<!-- 如果有需要可以介绍一些社区相关的内容 -->

## 关于作者

<!-- 这里写上项目作者 -->

## 谁在用(可选)

<!-- 可以列出使用本项目的其他有影响力的项目,算是给项目打个广告吧 -->

## 许可证

<!-- 这里链接上该项目的开源许可证 -->

项目文档规范

通常放在/docs目录下,包含开发文档和用户文档

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
docs
├── devel                            # 开发文档,可以提前规划好,英文版文档和中文版文档
   ├── en-US/                       # 英文版文档,可以根据需要组织文件结构
   └── zh-CN                        # 中文版文档,可以根据需要组织文件结构
       └── development.md           # 开发手册,可以说明如何编译、构建、运行项目
├── guide                            # 用户文档
   ├── en-US/                       # 英文版文档,可以根据需要组织文件结构
   └── zh-CN                        # 中文版文档,可以根据需要组织文件结构
       ├── api/                     # API文档
       ├── best-practice            # 最佳实践,存放一些比较重要的实践文章
          └── authorization.md
       ├── faq                      # 常见问题
          ├── iam-apiserver
          └── installation
       ├── installation             # 安装文档
          └── installation.md
       ├── introduction/            # 产品介绍文档
       ├── operation-guide          # 操作指南,里面可以根据RESTful资源再划分为更细的子目录,用来存放系统核心/全部功能的操作手册
          ├── policy.md
          ├── secret.md
          └── user.md
       ├── quickstart               # 快速入门
          └── quickstart.md
       ├── README.md                # 用户文档入口文件
       └── sdk                      # SDK文档
           └── golang.md
└── images                           # 图片存放目录
    └── 部署架构v1.png

API接口规范

API文档生成方式包括通过注释生成、编写Markdown格式文档, 通常拆分为多个文件

  • README: API介绍整体文档
  • CHANGELOG: PAI变更历史
  • generic: 通用的请求参数、返回参数、认证方法、响应状态码等说明
  • struct: 接口使用的数据结构
  • error_code: 业务错误码说明
  • 按资源划分API文档: API详细说明

版本规范

版本格式为:主版本号.次版本号.修订号(X.Y.Z)

版本号递增规则

  • 主版本(major): 不兼容API修改
  • 次版本(minor): 新增功能(一般偶数为稳定版,奇数为开发版本)
  • 修订版本:问题修正

版本号建议

  • 开始开发时,以0.1.0作为第一个开发版本号,后续发行时递增次版本号
  • 发布第一个稳定版时定为1.0.0
  • 后续迭代
    • fix commit将修订版本+1
    • feat commit将次版本号+1
    • BREAKING CHANGE commit将主版本号+1

commit_types

Commit规范

采用Angular风格的Commit Message, 其包含三个部分:Header、Body和Footer,具体格式如下:

1
2
3
4
5
<type>[optional scope]: <description>
// blank line
[optional body]
// blink line
[optional footer]

type说明commit类型,主要分为Development和Production两大类

  • Development类别修改的是项目管理类的变更,如CI流程、构建方式等,不会影响到最终用户,具体类型
    • style:代码格式类的变更,如格式化代码、删除空行等
    • test: 增加或更新测试用例
    • ci:持续集成和部署相关的改动
    • docs:更新文档
    • chore:其他类型,如构建流程、依赖管理或辅助工具的变更等
  • Production类别会影响到最终用户,提交前需要做好充分的测试
    • feat: 新增功能
    • fix: bug修复
    • perf: 提高代码性能的变更
    • refactor: 不属于上面三类的其他类型,如简化代码、重命名变量等

scope用来说明影响范围,应根据项目情况设计大类如api、pkg、docs等

descrption是对commit的简单描述,必须以动词开头,使用现在时态,结尾不加句号

Body

body是对commit的详细描述,同样以动词开头,使用现在时态,内容包含改动的原因和改动点

通产用来说明不兼容的改动和关闭的issue,如下示例

1
2
BREAKING CHANGE: XXXXX
Cloes: #123, #234

Revert Commit

当还原commit的时,在还原的Header前面加revert: , Body里面说明还原的commit hash,如:

1
2
3
revert: feat(api): add 'Host' option

This reverts commit fjsdf34353534vdf

其他

合并提交: 对于过多的commit使用git rebase进行合并

修改message: 使用git rebase(注:修改message会将导致当前及置换的hash变更)

自动化工具:

目录结构设计

project-layer

目录介绍

1. /web

web目录主要存放web静态资源

2. /cmd

一个项目可能有多个组件,每个组件的main函数所在文件夹放在该目录

3. /internal

存放私有应用的代码,不能被其他项目导入

项目内应用之间共享代码存放于/internal/pkg

开发建议:最开始将共享代码都放/internal/pkg,做好对外发布的准备时再转到/pkg目录

IAM项目internal目录结构如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
├── apiserver
│   ├── api
│   │   └── v1
│   │       └── user
│   ├── options
│   ├── config
│   ├── service
│   │   └── user.go
│   ├── store
│   │   ├── mysql
│   │   │   └── user.go
│   │   ├── fake
│   └── testing
├── authzserver
│   ├── api
│   │   └── v1
│   ├── options
│   ├── store
│   └── testing
├── iamctl
│   ├── cmd
│   │   ├── cmd.go
│   │   ├── info
└── pkg
    ├── code
    ├── middleware
    ├── options
    └── validation

主要分为三大类

  • /internal/pkg: 内部共享包存放目录
  • /internal/iamctl: 对于大型项目,可能会存在客户端工具
  • /internal/apiserver: 应用目录

针对具体的应用目录,也会根据功能来划分:

  • /internal/apiserver/api/v1: HTTP API接口具体实现
  • /internal/apiserver/options: command flag
  • /internal/apiserver/service: 业务逻辑代码
  • /internal/apiserver/store/mysql: 数据库交互

/internal/pkg通常也会划分:

  • /internal/pkg/code: 业务Code码
  • /internal/pkg/validation: 通用验证函数
  • /internal/pkg/code: HTTP处理链
4. /pkg

pkg目录存放外部应用可以使用的代码库,应谨慎考虑

5. /vendor

项目依赖,通过go mod vendor创建

6. /third_party

外部帮助工具,比如fork了一个第三方go包,并做了小改动,可以放置该目录

7. /test

存放其他外部测试应用和测试数据

8. /configs

存放配置文件模板或默认配置

9. /deployments

存放系统和容器编排部署模板和配置

10. /init

存放初始化系统和进程管理配置文件,如systemd、supervisord等

11. /Makefile

项目管理文件

12. /scripts

存放脚本文件,通常可能分为三个目录

  • /scripts/make-rules: 存放maker文件,实现Makerfile文件中的各个功能
  • /scripts/lib: 存放shell脚本
  • /scripts/intall: 如果项目支持自动化部署,可以将部署脚本放在该目录
13. /build

存放安装包和持续集成相关的文件,通常可能包含三个目录

  • /build/package: 存放容器(Docker)、系统(deb,rpm)的包配置和脚本
  • /build/ci: 存放CI(travis,circle)的配置文件
  • /build/docker: 存放子项目各个组件的Dockerfile文件
14. /tools

存放这个项目的支持工具,这些工具可导入来自/pkg和/internal目录的代码

15. /githooks

git钩子

16. /assets

项目使用的其他资源(图片、CSS、Javascript等)

17. /website

放置项目网站相关的数据

18. /README.md

一般包含项目介绍、功能介绍、快速按照、使用指引、详细文档连接和开发指引等 文件较长时可以用tocenize加目录

19. /docs

存放设计文档、开放文档和用户文档等,可能包含下面几个目录

/docs/devel/{en-US, zh-CN}: 存放开发文档 /docs/guide/{en-US, zh-CN}: 存放用户手册 /docs/images: 存放图片文件

20. /CONTRIBUTING.md

用来说明如何贡献代码,规范协同流程

21. /api

存放项目提供的各种不同类型的API接口定义文件,可能有openapi、swagger等目录

1
2
3
4
5
6
├── openapi/
│   └── README.md
└── swagger/
    ├── docs/
    ├── README.md
    └── swagger.yaml
22. /LICENSE

版权文件

如果需要给源码文件加license头时,可以使用addlicense

项目依赖第三方包使用的license检查使用glice

23. /CHANGELOG

项目更新日志,可结合Angular规范和git-chglog自动生成内容

24. /examples

存放代码示例

其他建议

  • 不使用/model目录,按功能拆分到使用的模块中
  • 目录和包尽量使用单数
  • 小项目可以先包含cmd、pkg、internal三个目录

工作流设计

功能分支工作流

开发新功能时,基于master分支新建一个功能分支,在功能分支上进行开发,开发完之后合并到master

该模式适合小规模、人员固定的项目

Git Flow工作流

Git Flow定义了5种分支: master、develop、release、 feature、hotfix,详细介绍如下

git-flow

假设当前在一个future分支开发,突然发现了线上bug,需要hotfix,则流程如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ git stash # 1. 开发工作只完成了一半,还不想提交,可以临时保存修改至堆栈区
$ git checkout -b hotfix/print-error master # 2. 从 master 建立 hotfix 分支
$ vi main.go # 3. 修复 bug,callmainfunction -> call main function
$ git commit -a -m 'fix print message error bug' # 4. 提交修复
$ git checkout develop # 5. 切换到 develop 分支
$ git merge --no-ff hotfix/print-error # 6. 把 hotfix 分支合并到 develop 分支
$ git checkout master # 7. 切换到 master 分支
$ git merge --no-ff hotfix/print-error # 8. 把 hotfix 分支合并到 master
$ git tag -a v0.9.1 -m "fix log bug" # 9. master 分支打 tag
$ go build -v . # 10. 编译代码,并将编译好的二进制更新到生产环境
$ git branch -d hotfix/print-error # 11. 修复好后,删除 hotfix/xxx 分支
$ git checkout feature/print-hello-world # 12. 切换到开发分支下
$ git merge --no-ff develop # 13. 因为 develop 有更新,这里最好同步更新下
$ git stash pop # 14. 恢复到修复前的工作状态

该模式适合人员固定、规模较大的项目

Forking工作流

开源项目常用模式

git-forking

研发流程设计

研发流程

通常研发流程包括6个阶段

  1. 需求阶段
  2. 设计阶段
  3. 开发阶段
  4. 测试阶段
  5. 发布阶段
  6. 运营阶段

git-forking

每个阶段结束时,需要一个最终产物,可以是文档、代码或者部署组件,这个产物是下一个阶段的输入

研发模式

研发模式有三种: 瀑布模式、迭代模式和敏捷模式

瀑布模式

瀑布墨迹按照预先规划好的阶段来推进研发进度,流程清晰,但研发周期长,交付后变更困难 git-forking

迭代模式

研发任务被切分为一系列轮次,先把主要功能搭建起来,在通过客户反馈不断完善

敏捷模式

敏捷模式把大需求分成多个、可分阶段完成的小迭代,每个迭代交付都是一个可用的软件,开发过程中,软件一直处于可用状态

迭代模式关注研发流程。而敏捷模式不仅会关注研发流程,还会关注之外的一些东西,例如:团队协作,需求拆分

CI/CD

CI/CD通过自动化的手段来快速执行代码检查、测试、构建和部署任务,从而提高研发效率

  • CI:Continuous Integration,持续集成
  • CD:Continuous Delivery,持续交付
  • CD:Continuous Deployment,持续部署

git-forking

持续集成的核心在代码,持续交付的核心在可交付的产物,持续部署的核心在自动部署

持续集成

在代码push到git仓库后,CI工具会进行扫描、测试和构建,并将结果反馈给开发者

CI流程可以将问题在开发阶段就暴露出来,这会让开发人员交付代码时更有信心

持续交付

在持续集成的基础上,就构建产物自动部署到目标环境(测试、预发布)

持续部署

在持续交付的基础上,将经过充分测试的代码自动部署到生产环境,整个流程不在需要审核,完全自动化

DevOps

DevOps是一组过程、方法和系统的统称,用于促进开发、运维、质量部门之间的协作整合 git-forking

目前常用的Ops手段: AIOps、ChatOps、GitOps

ChatOps

通过发送指令给聊天机器人,执行某个任务 ChatOps对操作者友好,信息透明可追溯

GitOps

基于Git和K8S实现云原生的持续交付

AIOps

利用AI技术来智能化运维IT系统

设计方法

go-application

代码结构

按功能拆分目录而非按MVC等模块拆封

代码规范

可参考Uber的规范:Go Style Guide

可使用惊呆检查工具:golangcli-lint

官方CodeReview实践:Go Code Review Comments

代码质量

测试相关工具:

覆盖率检查

1
2
$ go test -race -cover  -coverprofile=./coverage.out -timeout=10m -short -v ./...
$ go tool cover -func ./coverage.out

编程哲学

  • 面向接口编程
  • 面向对象编程

软件设计方法

  • 设计模式
  • SOLD原则

高效项目管理

  • 使用Makefile管理项目

  • 自动生成代码 automate-code

  • 对接CI、CD

  • 编写高质量的文档

设计模式

design-pattern

创建型模式

创建型模式(Creational Patterns)提供了在创建对象的同事隐藏创建逻辑的方式,而不是使用new运算符直接实例化对象。

比较常见的是单例模式和工厂模式

单例模式

单例模式(Singleton Pattern)指全局只有一个实例,有利于减少内存开销,防止冲突等优点,常用于数据库实例、全局配置等

  • 饿汉模式:初始化时创建
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package singleton

type singleton struct {
}

var ins *singleton = &singleton{}

func GetInsOr() *singleton {
    return ins
}
  • 懒汉模式:实际使用时创建,可能有并发问题,需要加锁
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package singleton

import (
  "sync"
)

type singleton struct {
}

var ins *singleton
var once sync.Once

func GetInsOr() *singleton {
  once.Do(func() {
    ins = &singleton{}
  })
  return ins
}
工厂模式
  • 简单工厂模式:接受参数,返回一个对象实例
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
type Person struct {
  Name string
  Age  int
}

func (p Person) Greet() {
  fmt.Printf("Hi, my name is %s\n", p.Name)
}

func NewPerson(name string, age int) *Person {
  return &Person{
  Name: name,
  Age:  age,
  }
}
  • 抽象工厂模式: 返回接口而非结构体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

type Doer interface {
    Do(req *http.Request) (*http.Response, error)
}

func NewHTTPClient() Doer {
    return &http.Client{}
}

type mockHTTPClient struct{}

func (*mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
    res := httptest.NewRecorder()
    return res.Result(), nil
}

func NewMockHTTPClient() Doer {
    return &mockHTTPClient{}
}  
  • 工厂方法模式: 通过实现工厂接口来创建多种工厂
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

type Person struct {
  name string
  age int
}

func NewPersonFactory(age int) func(name string) Person {
  return func(name string) Person {
    return Person{
      name: name,
      age: age,
    }
  }
}


newBaby := NewPersonFactory(1)
baby := newBaby("john")

newTeenager := NewPersonFactory(16)
teen := newTeenager("jill")

结构型模式

结构型模式的特点是关注类和对象的组合

策略模式

定义策略接口,并实现不同的策略,策略执行者可以设置不同的策略

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
type IStrategy interface {
	do(a, b int) int
}

type add struct {
}

func (*add) do(a, b int) int {
	return a + b
}

type reduce struct {
}

func (*reduce) do(a, b int) int {
	return a - b
}

type Operator struct {
	strategy IStrategy
}

func (o *Operator) setStrategy(strategy IStrategy) {
	o.strategy = strategy
}

func (o *Operator) calculate(a, b int) int {
	return o.strategy.do(a, b)
}
模板模式

将一个类中能公共使用的方法放置在抽象类中实现,将不能公共使用的方法作为抽象方法强制子类去实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

package template

import "fmt"

type Cooker interface {
  fire()
  cooke()
  outfire()
}

// 类似于一个抽象类
type CookMenu struct {
}

func (CookMenu) fire() {
  fmt.Println("开火")
}

// 做菜,交给具体的子类实现
func (CookMenu) cooke() {
}

func (CookMenu) outfire() {
  fmt.Println("关火")
}

// 封装具体步骤
func doCook(cook Cooker) {
  cook.fire()
  cook.cooke()
  cook.outfire()
}

type XiHongShi struct {
  CookMenu
}

func (*XiHongShi) cooke() {
  fmt.Println("做西红柿")
}

type ChaoJiDan struct {
  CookMenu
}

func (ChaoJiDan) cooke() {
  fmt.Println("做炒鸡蛋")
}

行为型模式

行为模式的特点是关注对象之间的通信

代理模式

代理模式Proxy pattern 可以为另外一个对象提供一个替身或者占位符,以控制对这个对象的访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

package proxy

import "fmt"

type Seller interface {
  sell(name string)
}

// 火车站
type Station struct {
  stock int //库存
}

func (station *Station) sell(name string) {
  if station.stock > 0 {
    station.stock--
    fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, station.stock)
  } else {
    fmt.Println("票已售空")
  }

}

// 火车代理点
type StationProxy struct {
  station *Station // 持有一个火车站对象
}

func (proxy *StationProxy) sell(name string) {
  if proxy.station.stock > 0 {
    proxy.station.stock--
    fmt.Printf("代理点中:%s买了一张票,剩余:%d \n", name, proxy.station.stock)
  } else {
    fmt.Println("票已售空")
  }
}
选项模式

通过选项模式可以创建一个带有默认值得struct变量,并选择性的修改其中一些参数的值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package options

import (
  "time"
)

type Connection struct {
  addr    string
  cache   bool
  timeout time.Duration
}

const (
  defaultTimeout = 10
  defaultCaching = false
)

type options struct {
  timeout time.Duration
  caching bool
}

// Option overrides behavior of Connect.
type Option interface {
  apply(*options)
}

type optionFunc func(*options)

func (f optionFunc) apply(o *options) {
  f(o)
}

func WithTimeout(t time.Duration) Option {
  return optionFunc(func(o *options) {
    o.timeout = t
  })
}

func WithCaching(cache bool) Option {
  return optionFunc(func(o *options) {
    o.caching = cache
  })
}

// Connect creates a connection.
func NewConnect(addr string, opts ...Option) (*Connection, error) {
  options := options{
    timeout: defaultTimeout,
    caching: defaultCaching,
  }

  for _, o := range opts {
    o.apply(&options)
  }

  return &Connection{
    addr:    addr,
    cache:   options.caching,
    timeout: options.timeout,
  }, nil
}

基础功能设计和开发

API风格

Restful API设计原则

URI设计

通常情况下:

  • 资源使用名词复数表示
  • URI结尾不包含/
  • 推荐使用-
  • 使用小写
  • 避免层级过深,超过2层时将其他资源转为?参数,比如
1
2
/schools/tsinghua/classes/rooma/students/zhang # 不推荐
/students?school=qinghua&class=rooma # 推荐

实际场景中某些操作不能很好地映射为资源,可以参考如下做法:

  • 将一个操作变成一个资源的属性,比如禁用用户可以设计URI: /users/zhangsan?active=false
  • 将操作当做是一个资源的嵌套资源,如github star:
1
2
PUT /gits/:id/star
DELETE /gits/:id/star
  • 有时也可以打破规范,如登录操作URI设计为:/login
REST资源操作和HTTP方法之间的映射

resource-operation

对资源的操作应蛮子安全性和幂等性:

  • 安全性:不会改变资源状态,可以理解为只读
  • 幂等性:执行1次和执行N次,对资源状态改变的效果是等价的

idempotence

POST一般用在新建和批量删除这两种场景,批量删除更建议使用:DELETE /users?id=1,2,3

统一分页/过滤/排序/搜索功能
  • 分页:在列出一个Collection所有Member时应该提供分页功能,如:/users?offset=0&limit=20
  • 过滤:当不用返回一个资源的全部属性时可以指定,如:/users?fields=email,username,address
  • 排序:根据指定字段排序,如:/users?sort=age,desc
  • 搜索:当一个资源的Member太多时,可能需要提供搜索功能,搜索建议按模糊搜索来匹配
域名

主要有两种方式:

RPC API

RPC(Remote Procedure Call)即远程过程调用,通俗地来讲就是服务端实现了一个函数, 客户端使用RPC框架提供的接口,想调用本地函数一样调用这个函数,并获取返回值。

protobuf3可以使用optional关键字来支持显示判断是否传入该字段

  • golang会将该字段转为指针类型,可以判断是否为nil
  • python可以通过HasField方法来判断

可以通过grpc-gateway来同时支持restful-api

rpc-vs-rest

Makefile

基本形式

1
2
<target> : <prerequisites> 
[tab]  <commands>
  • target: 目标,目标非文件时称之为伪目标PHONBY
  • prerequisities: 前置依赖目标
  • command: 具体的shell命令,以tab起手,每一行都是单独的session, 可用共用session,使用\作为换行符

基本语法

声明伪目标

避免有和target同名的文件

1
2
3
.PHONY: clean
clean:
    rm -rf *.c
关闭回声

命令前面加@不会先打印命令

1
2
test:
    @echo 'hello world'
忽略错误

命令前面加-可忽略错误,继续往下执行

1
2
3
test:
	-eco 'hello world'
	@echo 'hello world2'
通配符

支持%*~

变量和赋值

  • 使用=自动以变量
1
msg = 'Hello world' 
  • 四种赋值运算符
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
VARIABLE = value
# 在执行时扩展,允许递归扩展。

VARIABLE := value
# 在定义时扩展。

VARIABLE ?= value
# 只有在该变量为空时才设置值。

VARIABLE += value
# 将值追加到变量的尾端。
  • 使用$()引用变量
1
2
3
msg = 'hello world'
test:
    echo $(msg)
  • 调用系统变量是需在再加一个$
1
2
test:
    echo: $$HOME
自动变量
  • $@: 指当前目标
1
2
a.txt:
    echo 'hello' > $@
  • $<: 指第一个前置条件
1
2
3
4
5
a.txt: b.txt c.txt
    cp $< $@ 
# 等同于  
a.txt: b.txt c.txt
    cp b.txt a.txt 
  • $^: 指所有前置条件
  • $*: 指被%匹配的部分
  • $(@D):指向$@的目录名
  • $(@F):指向$@的文件名

条件语句

  • ifeq
1
2
3
4
5
ifeq (arg1, arg)
    echo 'eq'
else
    echo 'neq'
endif
  • ifdef
1
2
3
ifdef var1
    echo 'var1 exist'
endif

循环语句

1
2
3
4
LIST = one two three
for i in $(LIST); do
    echo $$i;
done;

内置函数

  • shell: 执行shell命令
1
files := $(shell echo *.c)
  • substr: 字符串替换
1
2
# 将ee换成大写
$(subst ee,EE,feet on the street)
  • patsubstr: 字符串模式匹配替换
1
$(patsubst %.c,%.o,x.c bar.c)

参考