准备Expressive1.0版本的最后一项任务是设置文档站点。为此,我们决定使用GitHubPages,并且我们希望自动化构建,以便在我们推送到master分支时部署文档。
事实证明,这个过程既简单又困难重重。这篇文章旨在帮助处于相同情况的其他人。
要求
在审视这个问题时,我们意识到对于我们开发的任何解决方案,我们都需要考虑许多要求。
技术
首先,我们选择MkDocs作为我们的文档。MkDocs使用普通的旧Markdown,由于在GitHub、StackOverflow、Slack和许多其他使用它的服务上,我们已经非常熟悉它。使用MkDocs,您创建一个文件,mkdocs.yml
,在其中指定目录,将标题链接到文档本身。运行它后,它会生成静态HTML文件。
MkDocs允许您指定一个模板,并附带几个它自己的模板;最著名的是ReadTheDocs上使用的那个。我们选择MkDocs的一个原因是因为它有一个规模庞大的生态系统,这意味着有很多主题可供选择;这极大地推动了我们推出既好看又实用的产品。
然而,这意味着我们需要以下依赖项来构建我们的文档:
- MkDocs本身。
- 一个或多个python扩展;特别是,我们选择了一个扩展来修复默认Markdown渲染器如何渲染作为项目符号点或块引号一部分的围栏代码块的问题。
- 我们正在开发的自定义主题。
- MkDocs本身。
- 一个或多个python扩展;特别是,我们选择了一个扩展来修复默认Markdown渲染器如何渲染作为项目符号点或块引号一部分的围栏代码块的问题。
- 我们正在开发的自定义主题。
因此,这意味着我们的构建自动化将需要获取这些项目,理想情况下在构建之间缓存它们。
只在必要时构建
另一个方面是没有理由为我们在CI服务器上进行的每个构建构建文档。我们只想构建:
- 在master分支上,
- 当它不是pullrequest时,
- 如果构建成功,
- 并且每个只有一次构建。
在任何给定的构建中,我们实际上至少运行了四个作业,一个用于PHP5.5、5.6、7和HHVM。我们不想为每个构建和部署文档!
可重用性
虽然我们最初是为Expressive做这件事,但我们也想为每个ZF组件做同样的事情。因此,我们构建的任何解决方案都需要以最少的麻烦进行重用。如果我们对主题进行了更新,我们不想更新每个组件存储库!同样,如果部署脚本有任何更改,我们也不想将其发布到所有存储库。
推送到gh-pages
最后,我们所做的任何构建自动化都需要在成功构建时推送到存储库的gh-pagesbranch。这需要在CI服务器上拥有令牌或用户凭据。
创建自动化
有了适当的要求,我们就可以开始研究解决方案了。由于我们已经将Travis-CI用于我们的构建,因此我们决定将其重新用于构建文档。当然,当时的挑战是创建适当的配置以满足我们的要求。
GitHub凭证
为了从Travis推送,我们需要有足够的凭据。有几种方法可以做到这一点:
- 使用个人访问令牌。
- 提供您的SSH私钥。
在这两种情况下,您都需要将信息添加到您的Travis环境中。但是,问题是如果任何人都可以访问这些值,他们基本上可以使用您的凭据进行提交——您肯定会这样做不想发生!因此,您需要加密该值,以便只有Travis知道它。
我在关于安全PHAR自动化的博客文章中介绍了加密SSH密钥,在那个特定情况下,我有几个文件需要加密,这导致了相当复杂的设置。如果您没有其他要加密的秘密,请使用个人访问令牌。一方面,它简化了安全性;如果您发现令牌已被泄露,您可以简单地将其从GitHub中删除,而无需创建新的SSH密钥并传播它的额外工作。它还简化了设置,因为您可以加密单个值,然后简单地对其进行配置。
要加密令牌,请使用TravisCLI工具,然后将值粘贴到您的.travis.yml
中。在下文中,我将其分配给环境变量GH_TOKEN
,这是一个常见的约定:
$ travis encrypt -r <org>/<repo> GH_TOKEN=<token value>
显然,替换您的组织和存储库名称,以及您的令牌。这将输出如下内容:
Please add the following to your .travis.yml file: secure: "......=" Pro Tip: You can add it automatically by running with --add.
注意:我从不使用--add
开关,因为travis
实用程序会更改文件中的所有空白。
将值复制并粘贴到.travis.yml
的env.global
部分(如果您还没有创建它):
env: global: - secure: "..."
Travis将自动解密该值并将其导出到您的环境。在其日志中,您将看到GH_TOKEN=secure
。
什么时候建?
我们知道Travis-CI在典型的构建工作流程中会触发许多事件:
before_install
安装
脚本
after_script
deploy
(以及before_deploy
和after_deploy
)
deploy
事件可能看起来是正确的,但它需要一个非常具体的工作流程,我们不会使用它。但结果是您可以使用另一个事件:after_success
!它在script
之后、deploy
或after_script
被触发之前运行。
也就是说,我发现了一些问题:Travis的缓存发生在script
之后,以及after_success
、deploy
或after_script之前。这意味着我们作为文档部署的一部分安装的任何资产——MkDocs、主题等——都不会被缓存。
所以我联系了Travis的支持团队,他们告诉我另一个很酷的技巧:$TRAVIS_TEST_RESULT
变量指示script
部分的当前退出状态;我们可以在script
部分的最后一行进行测试,以有条件地安装资产!
因此,我们最终在script
下有一行来安装资产,在after_success
下有另一行来执行实际构建。它们可能会合并,但我选择不合并:我不希望构建文档的结果导致构建失败。我希望有一天缓存会在构建结束时发生,这样我们就可以将它们都放在after_success
下。
环境变量
这会导致环境变量。为了确定是否需要构建文档,我们可以使用仅为我们要构建的环境设置的环境变量。因为我做的大多数项目都是PHP,所以我们必须选择要使用的矩阵中的哪个版本。我们的项目在PHP5.5、5.6、7.0和HHVM上进行测试。由于我们的大多数用户都使用PHP5版本,因此我们决定在最新的稳定版本5上编写文档:5.6。
我们也只想在主分支上构建,不作为拉取请求的一部分;如果针对该分支发出拉取请求,则该分支报告为master,这就是标准如此具体的原因。
最后,我知道我最终会安装MkDocs。由于我们使用的是基于Travis的Docker构建,我也知道这意味着我将使用pipinstall--user
而不是通过apt-get安装MkDocs,因为我们没有root访问权限.这意味着MkDocs将在$HOME/.local/bin
中,所以我需要为我构建的环境更新我的$PATH
。
幸运的是,您可以在.travis.yml
中声明作为计算结果的env变量声明。这意味着我最终得到了以下构建矩阵:
matrix: fast_finish: true include: - php: 5.5 env: - EXECUTE_CS_CHECK=true - php: 5.6 env: - EXECUTE_TEST_COVERALLS=true - DEPLOY_DOCS="$(if [[ $TRAVIS_BRANCH == 'master' && $TRAVIS_PULL_REQUEST == 'false' ]]; then echo -n 'true' ; else echo -n 'false' ; fi)" - PATH="$HOME/.local/bin:$PATH" - php: 7 - php: hhvm allow_failures: - php: hhvm
这将创建一个新的$DEPLOY_DOCS
环境变量,其值为“true”或“false”,稍后我可以对其进行测试。我的$PATH
也更新了。
以上是构建特定的变量。但是,我还需要一些可以被scipts访问的变量:
- 我希望能够为我的mkdocs配置提供基本站点URL。默认情况下,我们不将其包含在
mkdocs.yml
中,因此您可以在本地构建。但是,对于我们的生产页面,我们需要它来确保搜索功能正常工作,因为我们将在子路径中。 - 为了通过git提交,git需要用户名和电子邮件。我的经验还表明,这些需要与生成个人访问令牌的用户相匹配。
- 因为我们希望功能可重复使用,所以我还需要提供git存储库的位置。
因此,我在env.global
部分添加了以下内容:
env: global: - SITE_URL: https://organization.github.io/repository - GH_USER_NAME: "My Full Name" - GH_USER_EMAIL: me@domain.tld - GH_REF: github.com/organization/repository.git - secure: "..."
GH_REF
是对正在使用的github存储库的引用。您会注意到URL缺少方案;这是因为用于将提交推送到gh-pages分支的脚本将使用GH_TOKEN
创建完整的URL:
git remote add upstream https://${GH_TOKEN}@${GH_REF}
现在环境已经全部设置好,我们可以开始安装主题并构建文档了。
如何安装?
为了使用我们的自定义主题构建文档,我们需要在本地自定义主题。此外,我们可能希望仅在有更改时才下载主题;我们应该在请求之间缓存它。此外,如果资产未缓存,我们应该不下载它们,除非构建成功。
坦率地说,这是最难弄清楚的部分之一,我最终需要Travis支持团队的一些指导才能弄清楚。(感谢伟大的指点,你们这些Travis的好人!)
如前所述,缓存会在script
部分执行后立即发生。这排除了此任务的after_success
脚本,因为随后下载的任何资产都不会被缓存。但是我们如何知道构建何时成功?
如前所述,环境变量TRAVIS_TEST_RESULT
保存构建的退出值。如果脚本的任何部分返回非零值,则该值将从该点向前变为非零。因此,如果我们在测试此值的script
部分的末尾放置一个脚本,我们就可以有条件地触发一个动作!
我选择在我们的主题存储库中创建一个脚本,其中包含我们文档工具链安装的所有逻辑。这允许我们根据需要修改安装脚本,而无需更新将添加自动化的各种组件。该脚本目前看起来像这样:
#!/usr/bin/env bash pip install --user mkdocs pip install --user pymdown-extensions if [[ ! -d zf-mkdoc-theme/theme ]];then mkdir -p zf-mkdoc-theme ; curl -s -L https://github.com/zendframework/zf-mkdoc-theme/releases/latest | egrep -o '/zendframework/zf-mkdoc-theme/archive/[0-9]*\.[0-9]*\.[0-9]*\.tar\.gz' | head -n1 | wget -O zf-mkdoc-theme.tgz --base=https://github.com/ -i - ; ( cd zf-mkdoc-theme ; tar xzf ../zf-mkdoc-theme.tgz --strip-components=1 ; ); fi exit 0
上面运行我们的pipinstall
命令来安装MkDocs和我们使用的扩展,如果主题目录丢失,识别并下载主题的最新tarball并将其解压缩。
我遇到的一个陷阱:当您启用目录缓存时,Travis会创建该目录,即使没有找到它的缓存条目;因此,我们需要测试目录下的路径。
我遇到的一个陷阱:当您启用目录缓存时,Travis会创建该目录,即使没有找到它的缓存条目;因此,我们需要测试目录下的路径。
现在,我们如何获取安装脚本?在我们的script
部分中使用以下行:
script: - <build tasks> - if [[ $DEPLOY_DOCS == "true" && "$TRAVIS_TEST_RESULT" == "0" ]]; then wget -O theme-installer.sh "https://raw.githubusercontent.com/zendframework/zf-mkdoc-theme/master/theme-installer.sh" ; chmod 755 theme-installer.sh ; ./theme-installer.sh ; fi
上面获取脚本并执行它,但前提是我们处于指定用于文档部署的环境中,并且构建到此为止已经成功。这应该始终是脚本
部分的最后一行。
如何构建?
既然我们知道我们拥有构建工具,那么如何构建文档本身呢?
为此,我编写了一个部署脚本,我们将其包含在我们的主题存储库中。我们将其包含在主题中以实现可重用性,这是我们的要求之一。这确保了随着构建和部署的变化,我们不需要更新所有正在构建文档的存储库;我们可以在主题存储库中进行更改,标记新版本,然后在下一个构建中,每个版本都会采用更改。
部署脚本执行几项任务:
- 它创建构建目录,并使用
GH_TOKEN
和GH_HREF
将其初始化为git存储库,并将上游设置为存储库的gh-pages分支。 - 它将git配置设置为使用配置的GitHub用户名和电子邮件。
- 它运行构建(它本身是另一个脚本)。
- 它添加了更改文件,提交它们,并将它们推送到远程。
最后,部署脚本如下所示:
#!/usr/bin/env bash set -o errexit -o nounset SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)" # Get curent commit revision rev=$(git rev-parse --short HEAD) # Initialize gh-pages checkout mkdir -p doc/html ( cd doc/html git init git config user.name "${GH_USER_NAME}" git config user.email "${GH_USER_EMAIL}" git remote add upstream "https://${GH_TOKEN}@${GH_REF}" git fetch upstream git reset upstream/gh-pages ) # Build the documentation ${SCRIPT_PATH}/build.sh # Commit and push the documentation to gh-pages ( cd doc/html touch . git add -A . git commit -m "Rebuild pages at ${rev}" git push -q upstream HEAD:gh-pages )
脚本注释
- 我们在
doc/html/
中构建我们的文档,通过.gitignore,允许我们安全地克隆到该位置。
gitadd-A.
将删除以前跟踪但现在已删除的任何文件,并添加找到的任何新路径。这使得自动化变得更加简单,因为我们不需要担心添加、删除或重命名。此外,您会注意到
gitpush
命令包括-q
开关。这非常重要:如果您不包含它,命令输出将包含推送URL,其中包含GitHub令牌!同样,您不希望该值泄露,因此请采取措施确保它不会泄露!
脚本注释
- 我们在
doc/html/
中构建我们的文档,通过.gitignore,允许我们安全地克隆到该位置。
gitadd-A.
将删除以前跟踪但现在已删除的任何文件,并添加找到的任何新路径。这使得自动化变得更加简单,因为我们不需要担心添加、删除或重命名。此外,您会注意到
gitpush
命令包括-q
开关。这非常重要:如果您不包含它,命令输出将包含推送URL,其中包含GitHub令牌!同样,您不希望该值泄露,因此请采取措施确保它不会泄露!
构建脚本执行一些任务,这些任务可能因您自己的需要而异:
- 它向
mkdocs.yml
添加了一些配置,包括:- 根据我们的环境变量设置
site_url
值。 - 为多个扩展添加配置。特别是,我们不使用Pygments(相反,我们选择使用prism.js),我们使用pymdownx.superfences,它纠正了嵌套在列表或块引号中的围栏代码块的问题。
- 指定
theme_dir
.
- 根据我们的环境变量设置
- 它运行
mkdocsbuild--clean
- 它运行一些实用程序我们编写的目的是为了做一些事情,比如换掉着陆页,以及添加标记以使图像响应。
- 它向
mkdocs.yml
添加了一些配置,包括:- 根据我们的环境变量设置
site_url
值。 - 为多个扩展添加配置。特别是,我们不使用Pygments(相反,我们选择使用prism.js),我们使用pymdownx.superfences,它纠正了嵌套在列表或块引号中的围栏代码块的问题。
- 指定
theme_dir
.
- 根据我们的环境变量设置
- 它运行
mkdocsbuild--clean
- 它运行一些实用程序我们编写的目的是为了做一些事情,比如换掉着陆页,以及添加标记以使图像响应。
关于mkdocs.yml
更改,我们默认不包含这些更改的原因有两个:
- 它允许开发人员在本地运行
mkdocs
,而无需存在主题或扩展。 - 它允许我们在ReadTheDocs上预览文档;虽然我们正在设置的自动化在很大程度上消除了对该服务的需求,但它对于预览针对
develop
分支的文档仍然很有用。
构建脚本如下所示:
#!/usr/bin/env bash SCRIPT_PATH="$(cd "$(dirname "$0")" && pwd -P)" # Update the mkdocs.yml cp mkdocs.yml mkdocs.yml.orig echo "site_url: ${SITE_URL}" echo "markdown_extensions:" >> mkdocs.yml echo " - markdown.extensions.codehilite:" >> mkdocs.yml echo " use_pygments: False" >> mkdocs.yml echo " - pymdownx.superfences" >> mkdocs.yml echo "theme_dir: zf-mkdoc-theme/theme" >> mkdocs.yml mkdocs build --clean mv mkdocs.yml.orig mkdocs.yml # Make images responsive echo "Making images responsive" php ${SCRIPT_PATH}/img_responsive.php # Replace landing page content echo "Replacing landing page content" php ${SCRIPT_PATH}/swap_index.php
如果需要,您可以组合构建和部署脚本。我没有,因为它允许我将主题目录克隆到我的组件结帐中并构建将出现的文档:
$ echo "zf-mkdoc-theme/" >> .git/info/exclude $ git clone zendframework/zf-mkdoc-theme $ ./zf-mkdoc-theme/build.sh $ php -S 0:8000 -t doc/html/
现在我们的主题中已经有了脚本,我们需要告诉Travis执行它们。我们在after_success
脚本中执行此操作:
after_success: - if [[ $DEPLOY_DOCS == "true" ]]; then echo "Preparing to build and deploy documentation" ; ./zf-mkdoc-theme/deploy.sh ; echo "Completed deploying documentation" ; fi
以上只会在构建成功时执行,这意味着我们只需要检查我们是否在目标环境中。我们假设存在文档构建工具和主题,并简单地执行部署脚本。
缓存
其中一个要求是缓存,在上面,我们已经根据我们将缓存的假设做出了一些关于何时执行某些任务的决定。但是,我们实际上如何做到这一点?
Travis允许通过配置缓存资产。您可以指定目录或文件,条目是相对于检出的,除非它们是完全限定的路径。我们要缓存:
- 安装MkDocs的结果。
- 主题目录。
我们将添加以下配置:
cache: directories: - $HOME/.local - zf-mkdoc-theme
在ZF组件中,我们还缓存了供应商目录和globalComposer缓存,这有助于极大地加快构建速度。
在ZF组件中,我们还缓存了供应商目录和globalComposer缓存,这有助于极大地加快构建速度。
有了它,我们现在已经满足了所有要求!
综合考虑
最后,结果是我们新的zf-mkdoc-theme存储库。它包含:
- 在我们的
script
部分theme-installer.sh
中调用的主题安装程序脚本。 - 各种构建脚本和实用程序(
deploy.sh
、build.sh
等)。 - MkDocs主题(在
theme/
子目录下).
我们现在可以从我们的任何组件中使用它,方法是确保我们的.travis.yml
中包含以下内容:
sudo: false language: php cache: directories: - $HOME/.local - zf-mkdoc-theme env: global: - SITE_URL: https://organization.github.io/repository - GH_USER_NAME: "Name of Committer" - GH_USER_EMAIL: me@domain.tld - GH_REF: github.com/zendframework/zend-expressive.git - secure: "..." matrix: fast_finish: true include: - php: 5.6 env: - EXECUTE_TEST_COVERALLS=true - DEPLOY_DOCS="$(if [[ $TRAVIS_BRANCH == 'master' && $TRAVIS_PULL_REQUEST == 'false' ]]; then echo -n 'true' ; else echo -n 'false' ; fi)" - PATH="$HOME/.local/bin:$PATH" script: - build something - if [[ $DEPLOY_DOCS == "true" && "$TRAVIS_TEST_RESULT" == "0" ]]; then wget -O theme-installer.sh "https://raw.githubusercontent.com/zendframework/zf-mkdoc-theme/master/theme-installer.sh" ; chmod 755 theme-installer.sh ; ./theme-installer.sh ; fi after_success: - if [[ $DEPLOY_DOCS == "true" ]]; then echo "Preparing to build and deploy documentation" ; ./zf-mkdoc-theme/deploy.sh ; echo "Completed deploying documentation" ; fi
有了以上内容,在PHP5.6作业上成功推送到master分支将导致更新和部署我们的文档!
最后的笔记
这是一个有趣的实验,我对结果非常满意。我也期待将其部署到我维护或协助的其他组件和库中,因为我喜欢拥有最新的想法-具有项目独有风格的最新文档。
本文中引用的zf-mkdoc-theme位于github上,您可以将其用作您的ow项目的指南:
- https://github.com/zendframework/zf-mkdoc-theme
我希望其他人受到启发也这样做,并发现这篇帖子中的技巧很有用!