Cache Brew 使用 travis ci 构建

Cache Brew builds with travis ci

我有一个 Travis CI osx 构建,它具有必须从源构建的 brew 依赖项。

我知道 Travis 具有 cache 功能,但它没有任何关于如何缓存 brew 构建或输出的文档。

知道如何在 travis 中缓存 brew 包吗?

Homebrew 允许您从源代码构建:

brew install --build-from-source [package-name]

如果你想为 Travis 缓存你的自制软件,我看到的唯一方法是创建你想要的自制软件依赖项的压缩版本,类似于 this example, travis.yml

您可以将 brew 缓存目录添加到 travis 缓存中:

cache:
  directories:
    - $HOME/Library/Caches/Homebrew

据我所知,travis 不支持开箱即用的自制程序缓存。

以下应缓存编译器结果:

cache:
  ccache: true
  directories:
    - $HOME/Library/Caches/Homebrew

在 OSX 上,Travis 目前似乎默认不提供 ccache => 在使用 ccache 之前,还必须完成以下工作:

before_install: 
  - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update        ; fi
   - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install ccache; fi

诚然,完成的构建仍然没有被缓存。但是导致那里的每个单独计时器 运行 的构建结果至少构建过程的大部分可以算作 "cached" 之后。

要缓存实际编译的依赖项而不是缓存源压缩包或缓存目标文件,将相应包的 Cellaropt 目录添加到缓存并使用适当的 before_install 检查似乎工作正常。

您还可以添加所有 /usr/local/Cellar//usr/local/opt/,但这会添加所有已安装的自制程序包,而不仅仅是您需要的包。

来自依赖于 openssl、libevent 和检查的项目的示例:

cache:
  directories:
    - /usr/local/Cellar/openssl
    - /usr/local/opt/openssl
    - /usr/local/Cellar/libevent
    - /usr/local/opt/libevent
    - /usr/local/Cellar/check
    - /usr/local/opt/check
before_install:
  - test -d /usr/local/opt/openssl/lib  || { rmdir /usr/local/opt/openssl; brew install openssl; }
  - test -d /usr/local/opt/libevent/lib || { rmdir /usr/local/opt/libevent; brew install libevent; }
  - test -d /usr/local/opt/check/lib    || { rmdir /usr/local/opt/check; brew install check; }

rmdir 是必需的,因为如果缓存目录不存在,TravisCI 会创建它们,如果 /usr/local/opt/$package 是目录(而不是符号链接到Cellar 中的特定安装版本)。出于同样的原因,test 测试子目录,而不是主包目录。

请注意,此方法需要您自己的项目能够获取 /usr/local/opt 中安装的依赖项。

这里有 3 个独立的、松散相关的问题:

  • 缓存下载的瓶子
  • 缓存locally-built瓶
  • 缓存 Homebrew 元数据

您不一定需要这三个部分,因此请按照您需要的部分进行操作。


缓存下载的瓶子

  • 添加$HOME/Library/Caches/Homebrew到Travis的缓存(实际上,这个路径应该用brew --cache来检索,但你不能在这里调用它,你能)

    cache:
      directories:
        - $HOME/Library/Caches/Homebrew
    
  • 运行 brew cleanupbefore_cache 阶段 -- 否则,缓存将随着新包版本的发布而无限增长

    before_cache:
      - brew cleanup
    

缓存locally-built瓶

full code太长了,这里就不一一列举了,给出算法。

这是对上一节的补充。如果在没有它的情况下使用,请在 安装时将本地瓶保存在 Homebrew 缓存之外的某个地方步骤,并在下面的启动时步骤中以适当的名称将它们添加到缓存中。

  • 安装时:

    • 使用 brew deps 递归地检查包的依赖关系
      • 如果您的环境没有可用的包瓶(brew info <pkg> 输出中没有 (bottled)),请包含带有 --include-build
      • 的构建依赖项
    • 对于每个包和依赖项,

      • 如果已经安装(brew list --versions <pkg>成功)和最新版本(brew outdated不存在),跳过它
      • 如果存在旧版本,在以下步骤中,you'll need to install the new version alongside the old one
        • brew unlink 旧版本,如果不是 keg-only(brew info 输出中没有 [keg-only]
        • --force
        • 调用所有 brew install
      • 如果有一瓶可用,就brew install
      • 如果没有瓶子,

        • 构建并安装它 with the following sequence:

          brew install --build-bottle <pkg>
          brew bottle --json <pkg>
          brew uninstall --ignore-dependencies <pkg>
          brew install <bottle>
          

          (似乎没有任何官方方法可以获取生成的瓶子和 JSON 文件的名称。我从 brew bottle 输出中获取瓶子名称并从中推断出 JSON 文件名。)

        • 将瓶子信息添加到包裹的公式中

          brew bottle --merge --write <json file>
          
        • 将 bottle 文件以 brew --cache <pkg>

          给出的适当名称保存到 Travis 缓存中
          • 仅在添加瓶子信息后执行此操作——否则,您将获得源包的路径。
          • (Homebrew 还为 $HOME/Library/Caches/Homebrew 中下载的文件创建符号链接。您不需要这样做。)
        • 保存JSON 文件供以后使用。确保将其位置添加到 Travis 缓存中。
  • 启动时:

    • brew update 如果你要
    • 浏览已保存的 .json 文件。对于每一个,检查本地 bottle 是否仍然合适(通过比较版本和重建编号;您可以解析 brew info --json=v1 <pkg>brew info --json=v1 <bottle> 的输出以获得此数据)。
      • 删除缓存的瓶子和 .json 如果没有
        • 由于此时您无法使用 brew --cache 获取瓶子的路径,因此您需要单独保存它。在撰写本文时,符号链接并未保存在 Travis 的缓存中,因此我最终使用了包含路径的常规文件。
      • Re-add 如果是,则将瓶子信息输入上述公式
        • 他们也不太可能更改公式中的下载 URL 而不影响版本——然后瓶子的预期缓存名称将更改,因为其中的哈希是哈希下载 URL。为此,请在添加信息后检查 brew --cache <pkg> 是否仍指向您的瓶子。
  • before_cache:

    • 如果您使用的是上一节中的 brew cleanup,请在 运行 之前将缓存中的 locally-built 瓶子文件保存在某处,因为 cleanup 可能会删除那些这次不需要。在cleanup之后,恢复那些被删除的。

缓存 Homebrew 元数据

(同样,full code 太长,所以给出算法。)
如果您 运行 brew update --verbose (并确保 .travis.yml 或您的 Travis 项目设置中没有秘密变量 - brew 仅在以下情况下打印许多状态消息stdout 是一个 tty)——你会看到 Homebrew 自我更新操作的确切组成——因此你应该缓存什么:

  • 拉取(实际上,默认情况下 rebaseing)到几个实际上是 git 存储库的路径:
    • /usr/local/Homebrew -- 自制程序本身
    • /usr/local/Homebrew/Library/Taps/*/* -- 安装的水龙头
  • 检查水龙头和缓存并迁移过时的位。自从特拉维斯·卡内容被添加到现有目录结构而不是替换它,第二次,可能会出现奇怪的操作和错误,这些文件是由作为更新的一部分删除的文件引起的,但在新的 VM 中又出现了。我亲眼所见的:
    • 将始终尝试将 Taps/caskroom/homebrew-cask 迁移到 Taps/homebrew/homebrew-cask,并在 Taps/homebrew/homebrew-cask/homebrew-cask 处创建一个副本。如果缓存,此副本将导致下一个 运行 上的 "error: file exists"。
    • 将始终尝试将大量 non-committed 文件导入 Taps/homebrew/homebrew-versions

因此,操作将是:

  • /usr/local/Homebrew 添加到 Travis 缓存

    • 添加/usr/local/Cellar/usr/local/opt被证明是个坏主意:首先,它们太大了,导致制作和上传缓存超时;其次,这是不安全的,因为 postinstall 脚本可能会影响系统的其他任意部分,因此每次都应该从(缓存的)bottles 安装新的包版本,而不是缓存结果。无论如何安装一个瓶子只需要几秒钟。
  • brew update: 清理 Homebrew 代码库之前

    • 如果 Taps/homebrew/homebrew-cask 存在,则删除 Taps/caskroom/homebrew-cask 目录
    • 找到/usr/local/Homebrew下的所有git回购(find -type d -name .git,得到dirname的结果)和运行 git clean -fxd在每个到摆脱特拉维斯的剩菜
    • 也可以使用 brew cleanup 清除剩余的 Homebrew 缓存(如果与上一节一起使用,请参阅其他操作)——否则,您会在 brew update"Migrating cache entries..." 舞台上。
  • brew update:

    • 改用brew update --merge——它会auto-resolve任何可能与你本地提交的瓶子信息发生冲突
  • 当re-adding本地瓶(如果与上一节结合使用):

    • 不要 re-add 将信息放入公式中(如果公式中已经存在)
    • 如果包版本已更改并且您的瓶子信息出现在公式中,请将其从公式中删除并 git commit 结果。没有常用的方法可以做到这一点,因此您必须使用脚本解析和编辑公式文件,并从 bottle do table 中删除相应的行。使用 brew formula <pkg>.
    • 检索公式文件的路径
  • 安装时:

    • 如果使用 3rd-party tap,请务必检查您是否已经安装了该 tap:

      brew tap | grep -qxF <tap> || brew tap <tap>
      
      • 由于符号链接没有保存在 Travis 缓存中,pin 可能不会被记住。但是检查它们也没什么坏处:

        brew tap --list-pinned | grep -qxF <tap> || brew tap-pin <tap>
        
  • before_cache:

    • 删除Taps/homebrew/homebrew-cask/homebrew-cask如果存在

要缓存 brew update 元数据,您 only need to cache .git 文件夹 /usr/local/Homebrew

供其他人阅读的 Travis 配置:

cache:
  directories:
    - $HOME/Library/Caches/Homebrew
    - /usr/local/Homebrew
before_cache:
  - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then brew cleanup; fi
# Credit https://discourse.brew.sh/t/best-practice-for-homebrew-on-travis-brew-update-is-5min-to-build-time/5215/9
# Cache only .git files under "/usr/local/Homebrew" so "brew update" does not take 5min every build
  - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then find /usr/local/Homebrew \! -regex ".+\.git.+" -delete; fi