XCaddy源码

XCaddy是什么?

xcaddy的全称是Custom Caddy Builder。简单来说,XCaddy是一个用于定制化编译Caddy Web Server的命令行构建工具。主要是用于caddy的插件打包进caddy。

用过caddy的应该都知道在下载caddy的时候会让选择caddy的操作系统及需要的插件。然后caddy官方服务器会根据你的选择即是编译,将选择的插件和caddy本体编译到选定的操作系统格式,最终获得一个单一的二进制程序。xcaddy所能作的工作就是作为这个过程的Builder工具。

源码分析

输入信息的结构体定义

首先定义一个Builder结构体:

type Builder struct {
    Compile
    CaddyVersion string        `json:"caddy_version,omitempty"`
    Plugins      []Dependency  `json:"plugins,omitempty"`
    Replacements []Replace     `json:"replacements,omitempty"`
    TimeoutGet   time.Duration `json:"timeout_get,omitempty"`
    TimeoutBuild time.Duration `json:"timeout_build,omitempty"`
    RaceDetector bool          `json:"race_detector,omitempty"`
    SkipCleanup  bool          `json:"skip_cleanup,omitempty"`
}

其中Compile包含编译参数相关的信息:

type Compile struct {
    Platform
    Cgo bool `json:"cgo,omitempty"`
}

由此可见主要是平台信息:

type Platform struct {
    OS   string // 操作系统名称
    Arch string // 计算机架构
    ARM  string // arm版本
}

CaddyVersion用于指定caddy的版本信息

Plugins用于指定需要的插件列表:

type Dependency struct {
    PackagePath string // 插件路径
    Version string // 插件版本
}

`Replacements`用于替换插件路径的path(使用golang的应该知道是什么东西,详细的可以`go rename --help`):

type ReplacementPath string
type Replace struct {
    Old ReplacementPath // import中被替换掉的路径
    New ReplacementPath  // import中用于替换的新路径
}

TimeoutGet用于指定拉取插件的超时时间 TimeoutBuild用于指定构建的超时时间

RaceDetector:用于指定是否开启detector

SkipCleanUp:用于指定是否cleanup

将上面所有的字段都展开一下,Builder包含下面的配置字段信息:

  • OS :操作系统
  • Arch :计算机架构
  • ARM : arm版本
  • Cgo : 是否开启cgo
  • CaddyVersion :caddy版本
  • Plugins :插件信息列表[(插件1路径,插件1版本),(插件2路径,插件2版本)]
  • Replacements :路径替换列表[(old1->new1),(old2->new2)]
  • TimeoutGet :插件代码拉取超时时间
  • TimeoutBuild: 构建超时时间
  • RaceDetector:是否开启Detector
  • SkipCleanup:是否跳过CleanUp

这也是构建工作所需要的最多输入配置信息。

可用方法

作为构建器的结构体有以下方法:

  • Build(ctx context.Context, outputFile string) error
  • 按照config配置构建生成caddy二进制文件
  • ``

完整的逻辑流程

xcaddy rake整个流程的代码在xcaddy/cmd/main.go(b Builder) Build(ctx context.Context, outputFile string) error

首先:预先设置Signal的捕获

func trapSignals(ctx context.Context, cancel context.CancelFunc) {
    sig := make(chan os.Signal, 1)
    signal.Notify(sig, os.Interrupt)

    select {
    case <-sig:
        log.Printf("[INFO] SIGINT: Shutting down")
        cancel()
    case <-ctx.Done():
        return
    }
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go trapSignals(ctx, cancel)

在Build开始之前预先设置context,go程运行捕获signal。一个是接收到os.Interrupt 信号,终止程序运行;一种是xcaddy完成build停止程序的运行。

接着:命令行参数解析

当满足len(os.Args) > 1 && os.Args[1] == "build"的时候进行真正的build,若不然进行dev的测试

Build流程

  1. 解析命令行,接收Build参数(主要就是Builder struct的配置字段)
  2. 根据接收的参数实例化Builder struct
  3. 执行Build过程
    1. 通过环境变量填充缺省的Build参数,设置build构建所需环境变量
    2. 准备Build环境(主要是准备临时文件夹、go mod init ,go mod edit, path路径的设置)
    3. 执行go build
    4. clean up
  4. 通过打印版本信息验证程序Build成功(只有编译目标是当前平台的时候才验证,跨平台编译的不作此动作)

Dev流程

学到了什么

xcaddy本身并没用到什么特殊的技术,就是一个简单的构建流程脚本。但如果日后我们需要编写自己的Builder工具,xcaddy仍有可借鉴的技巧:

对于一个常见的Build工具

  1. 首先定义Builder Struct,清晰看出所需的数据P
  2. Builder Struct实现绑定的方法
  3. Builder Struct各部分分解为独立的struct,各部分struct实现各自需要的方法
  4. 在cli-main中rake整个build流程