通过 Drone CI 构建和测试基于 CMake + VCPKG 的程序

/ 1评 / 0

哥啊,为啥你手册上不写呢!

Docker 是新世界的痛

在我们那时候,自动化构建都是通过 Jenkins 完成的。你只需要在 Jenkins 上配置好构建方式,在节点上配好工具链,然后它就能用了。现在做什么都得要容器要镜像,以前写一个 Shell 脚本或者麻烦点 Jenkinsfile 就能完成的工作,现在非得再配上一堆 Dockerfile 和 docker-compose.yml. 我不知道这到底是为了谁好。

Docker 是新世界的痛。随着硬盘容量越来越不要钱,以前需要花费精力去维护一致性的内容被一个个容器所取代——毕竟,有什么比复制一份更省力、更能保证一致的方法呢?

但是这种做法也带来了一个问题:每次构建的时候,CI 工具都必须再次下载这个项目的依赖。现在一个个工程的依赖都和海一样,Node 这种一拖就是几十上百个包的更是重量级。而且最重要的一点是——

网络还是要钱的

如果是 Node, 那么配置一下国内镜像或许能解决这个问题(虽然每次下载几百个包也是着实蛋疼),但是 Vcpkg 这种压根没法镜像的就必须得挂着梯子才能正常拉取依赖。如果你的工程又不幸地需要用 boost 等重量级库,那么光拉取库的时间就够喝一壶了。更不要提梯子是按流量计费的!

或许你觉得可以配置一下 fastgithub 之类的神奇玩意到镜像里——不,继续往镜像里塞各种怪玩意不是好文明,而且你还是得等包下载。我们需要配置缓存机制把网络 IO 尽可能降到最小。毕竟,就算公司网络不要钱,摸鱼的带宽还是要给自己留的。

配置缓存机制

我们使用 drillster/drone-volume-cache 镜像来实现缓存机制。这个插件的实现方式是将工程中要被缓存的目录增量复制到 /cache 下,或者将已经缓存的目录从 /cache 中复制到工程中。注意到这个插件的主要限制:它只能操作工作空间内的文件!如果你的插件会在工作目录之外(比如用户家目录)写入缓存的话,这玩意就不能用了。

Vcpkg 会将所有的东西扔到 ${VCPKG_ROOT} 里。如果你将 Vcpkg 作为 Git 子模块加入到工程中,那么它的下载和构建都会在工程内进行。这样我们就可以使用缓存镜像来执行对应的缓存操作了!

设置该工程使用容器

首先,我们先声明该构建会使用一个容器,且这个容器是持久化到主机上的容器:

volumes:
  - name: vcpkg-cache # 这里的名字后面要用到
    host:
      path: /mnt/ephemeral/cache/vcpkg # 这里换成你的主机上的路径

这样声明了之后,必须在 Drone CI 后台里将该项目设置为「受信任的 (Trusted)」,否则构建会直接失败。什么?你说你进到项目设置里面找不到这个开关?那是因为你的账号不是管理员账号。你需要编辑 Drone CI 的 docker-compose.yml 给主机加一个环境:

services:
  server:
    image: drone/drone:2
    ...
    environment:
      - DRONE_USER_CREATE=username:<这里换成你登录 Drone 的用户名,去掉两边尖括号>,admin:true

然后重启一下 Drone CI.

设置恢复缓存步骤

在开始构建的步骤之前新增一个步骤来恢复缓存。这个步骤即使之前没有缓存也不会出错,所以不需要任何构建条件。

steps:
  - name: restore-cache
    image: drillster/drone-volume-cache
    pull: if-not-exists
    volumes: # 这里列出要用到的容器列表,但是这个插件只用一个
      - name: vcpkg-cache # 这里填之前创建的容器的名字
        path: /cache # 这个值是固定的,不能改
    settings:
      restore: true # 这里是 true, 表示将缓存内容复制到工程内
      mount: # 这里是相对于工程内的路径列表,填你要恢复的文件夹,以工程根目录为当前目录
        - ./vcpkg/buildtrees # Vcpkg 会产生的三个需要缓存的目录如下
        - ./vcpkg/downloads
        - ./vcpkg/packages
        - ./vcpkg/vcpkg # 也可以缓存编译好的 vcpkg 运行文件
  ...

设置增加缓存步骤

在完成构建的步骤之后新增一个步骤来保存缓存。大部分的配置和之前一样,只是把 rebuild 换成了 true.

注意 mount 部分需要和你之前定义的顺序保持一致。如果前后不一样的话,可能会导致一些难以预料的结果。最好的情况是一些文件没有被缓存,最差的情况是会损坏缓存。

steps:
  ...
  - name: rebuild-cache
    image: drillster/drone-volume-cache
    pull: if-not-exists
    volumes:
      - name: vcpkg-cache
        path: /cache
    settings:
      rebuild: true # 这里
      mount:
        - ./vcpkg/buildtrees
        - ./vcpkg/downloads
        - ./vcpkg/packages
        - ./vcpkg/vcpkg

假如要清除或跳过缓存

遇到需要清除缓存重新编译时,在提交信息里添加 [CLEAR CACHE] 即可清除缓存。如果本次构建需要跳过缓存,则在提交信息里添加 [NO CACHE].

更高级的配置

这个镜像还支持缓存过期机制、更改缓存结构等等操作。但是在我的用例里并没有这些需求,所以还是需要查阅原文档进行配置。

完整的 .drone.yml

下述是完整的 .drone.yml 文件。注意到其中的 dousha99/vcpkg-image:5-alpine 可能并不包含你的项目所需要的全部依赖,所以你可能会需要制作自己的构建镜像,或者使用其他开发镜像。具体的构建命令也需要跟随你的项目而修改。

kind: 'pipeline'
type: docker
name: default

clone:
  depth: 3

steps:
  - name: submodule
    image: alpine/git
    pull: if-not-exists
    commands:
      - git submodule update --init --recursive

  - name: restore-cache
    image: drillster/drone-volume-cache
    pull: if-not-exists
    volumes:
      - name: vcpkg-cache
        path: /cache
    settings:
      restore: true
      mount:
        - ./vcpkg/buildtrees
        - ./vcpkg/downloads
        - ./vcpkg/packages
        - ./vcpkg/vcpkg

  - name: bootstrap
    image: dousha99/vcpkg-image:5-alpine
    pull: if-not-exists
    commands:
      - if [ -f vcpkg/vcpkg ]; then echo "vcpkg exists"; else sh vcpkg/bootstrap-vcpkg.sh; fi

  - name: configure
    image: dousha99/vcpkg-image:5-alpine
    pull: if-not-exists
    commands:
      - cmake --preset ci

  - name: build
    image: dousha99/vcpkg-image:5-alpine
    pull: if-not-exists
    commands:
      - cmake --build --preset ci-debug-all

  - name: test
    image: dousha99/vcpkg-image:5-alpine
    pull: if-not-exists
    commands:
      - cd cmake-build-ci/test
      - ctest

  - name: rebuild-cache
    image: drillster/drone-volume-cache
    pull: if-not-exists
    volumes:
      - name: vcpkg-cache
        path: /cache
    settings:
      rebuild: true
      mount:
        - ./vcpkg/buildtrees
        - ./vcpkg/downloads
        - ./vcpkg/packages
        - ./vcpkg/vcpkg

volumes:
  - name: vcpkg-cache
    host:
      path: /mnt/ephemeral/cache/vcpkg

  1. […] 但是自从上一次和 Drone CI 的缓存机制搏斗之后,我就觉得这个东西多少有点问题了。当然这可以认为是因为我对于容器管理理解薄弱导致的。但是现在我又遇到了一个绝世难题: […]

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

Your comments will be submitted to a human moderator and will only be shown publicly after approval. The moderator reserves the full right to not approve any comment without reason. Please be civil.