push時に自動でcargo docしてGitHub Pagesに公開するGitHub Actions


この記事

GitHubで管理しているRustプロジェクトにドキュメントをつけたくなることがあるかと思います。
ドキュメントはcargo doc --no-depsなどのコマンド一つで生成できるので、pushの際に自動で生成して公開できるとうれしいです。
自動で生成するのはGitHub Actionsを使うことができるし、公開するのはGitHub Pagesを使うことができるので、この二つを使うことで目的が達成できそうです。
しかし、これを達成するためのまとまった日本語の文書が見つからなかったので、備忘録も兼ねて残しておきたいと思います。

対象読者

  • RustのプロジェクトをGitHubで管理している
  • GitHub Actionsの役割がわかる
  • GitHub Pagesの役割がわかる

結論

GitHub Actionsのymlファイルだけ知りたい人用 https://github.com/hayas1/ghtest-project/blob/master/.github/workflows/rust.yml

手順

準備

まずは、GitHubのリポジトリは適当に作っておいてください。ここではghtest-projectという名前で作成しています。

Rustのプロジェクトを作成し、GitHubへpushします。ここではghtest_projectというプロジェクト名で進めていきます。

cargo new --lib ghtest_project
cd $_
git add .
git commit -m "first commit"
git remote add origin https://github.com/[username]/ghtest-project.git
git push -u origin master

[username]などと書いているところは適宜置き換えてください。
すでにリポジトリを作成している場合はここまでは関係ないです。

ドキュメントを書いた関数を作成

まずは、src/lib.rsに簡単な引き算関数とドキュメントを書いていきます。
テストコードがcargoによって初めから作成されていますが、テストが通ったときのみドキュメントを生成するようにしたいので、今回は残しておきます。

src/lib.rs
//! ghtest_project
//! # Document
//! 自動でドキュメントを作成して公開するテストです。

/// a - b を計算します
/// # Panics
/// a < b の時、オーバーフローします
/// # Examples
/// ```
/// use crate::ghtest_project::sub;
/// let (a, b) = (3, 2);
/// assert_eq!(sub(a, b), 1);
/// ```
pub fn sub(a: u64, b: u64) -> u64 {
    a - b
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

さて、この状態で、まずはローカルにドキュメントを作成してみます。
cargo doc --no-deps --openとすることで、ドキュメントを生成し、ブラウザで開くことができます。(WSLを使っている場合は--openがうまく効かず、ブラウザで起動できないかもしれません。そのときは、explorer.exe .などして、target/doc/ghtest_project/index.htmlをダブルクリックするなど)

このようなよく見るRustのドキュメントが生成されたのではないかと思います。
いまいち関数などがいい感じに表示されない場合は、可視性の設定の問題だと思います。**pub**をmodfnstructtraitなどに適切につけておいてください。

sub関数のページはこんな感じです。書いたドキュメントがちゃんと反映されています。

では、この状態でcommitし、pushしておきます。(ここからはcommitやpushのコマンドは特に明記しません。)

GitHub Actions を設定

次に、GitHub Actionsを設定します。上のタブの、Actionsをクリックします。

すると、一番上にRust用のワークフローのテンプレートがリコメンドされると思います。
それを選択します。

このワークフローは、デフォルトで、「pushかプルリクされたときに、ビルドとテストを行ってくれる」コマンドが記述してあります。

rust.yml(default)
name: Rust

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Build
      run: cargo build --verbose
    - name: Run tests
      run: cargo test --verbose

今回の目的は、テストが通ったときにドキュメントを自動で生成し、GitHub Pagesに公開することですので、このファイルを少し書き換えます。

rust.yml
name: Rust

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

env:
  CARGO_TERM_COLOR: always

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Run tests
      run: cargo test --verbose

  doc:
    runs-on: ubuntu-latest
    needs: test
    if:  github.event_name == 'push' || github.event.pull_request.merged == true

    steps:
    - uses: actions/checkout@v2
    - name: Run doc
      run: cargo doc --no-deps

    - name: Deploy
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./target/doc/

先ほどのymlファイルに加えて、ドキュメント生成用のワークフローを追記しています。needsを用いて、テストが完了したときのみ、そして、pushされたときと、マージされたときにのみドキュメントを作成するようにifで条件を指定しています(プルリクが作られただけの時にドキュメントを作成してしまうと困るので)。 (ここはあまり洗練されていないかもしれません。ごめんなさい。)
また、GitHub Pagesは、gh-pagesというブランチを作成してデプロイするなどの手順がありますが、それらの設定はGitHub上で公開していただいているものを使うことができるようです(https://github.com/peaceiris/actions-gh-pages)。そのため、ドキュメントが出力されるディレクトリをpublish_dirに指定するだけでよいです。

さて、ymlファイルが編集出来たら、適当に保存してコミットしておきましょう。ファイル名はそのままrust.ymlでいいかと思います。このときmasterにそのままコミットするか、新たにブランチを作ってマージする形でコミットするかを聞かれるかと思いますが、お好みの方でいいと思います。

GitHub Pages を見る

ここまでくると、先ほどymlファイルをいじったときのコミットですでにGitHub Actionsが走り、gh-pagesブランチが作成されています(ただし、全てのテストが通っている場合に限る)。ブランチの一覧から存在を確認できるかと思います。

というわけで、さっそくindex.htmlへアクセスしてみましょう。URLは、 https://[username].github.io/ghtest-project/ghtest_project/ です。[username]ghtest_projectのところは適切なリポジトリ名へ変更してください。(GitHub Pagesは、ghtest-project(リポジトリ名)直下にindex.htmlがあると思っているので、自動で生成されるGitHub Pagesへのリンクでは404が発生すると思います。実際はさらに一つ下のghtest_project(クレート名)の場所にcargoによって生成されるので、一つ深くなることに注意が必要です。)

GitHub Pagesは少し反映が遅いので、いくらか待たないとアクセスできないかもしれません。あるいは、GitHubのSetting->Optionの下の方の「GitHub Pages」オプションのブランチがNoneになっているかもしれませんので、gh-pagesブランチへ変更して保存してください。そしてもう一度GitHubからGitHub ActionsのRerunなどをすれば反映されるのではないかと思います。

それでもまだ反映されない場合は、一度gh-pagesブランチの最も上の階層に、index.htmlを作成する必要があるかもしれません。GitHubのページでAdd fileボタンから新たにファイルを作成することができるので、gh-pagesブランチへindex.htmlを作成してください。ファイルの中身は、lib/index.htmlへリダイレクトさせる内容を書いておきます(参考: https://users.rust-lang.org/t/whats-the-best-documentation-solution-other-than-docs-rs/16211/3)。これについても、ghtest_projectの場所は適切なパスへ書き換えておいてください。(ここで作成したindex.htmlは次のpushの際のビルドで自動的に消去されるので、関係ないとは思いますが、試した手順として念のため残しておきます)

index.html
<html>
  <head>
	<noscript><meta http-equiv="refresh" content="0; url=ghtest_project/index.html"></noscript>
  </head>
  <body onload="window.location = 'ghtest_project/index.html'">
	<a href="ghtest_project/index.html">look here</a>
  </body>
</html>

ここまですると、ドキュメントが表示されるのではないかと思います(重ねて、URLは https://[username].github.io/ghtest_project(リポジトリ名)/ghtest_project(クレート名)/ です)。

pushしてみる

最後に、本当にpushすると自動でドキュメントが更新されるか試してみます。
先ほど作成した引き算関数の上に足し算関数を定義して、pushしてみます。ついでにテストも書いておきました。

lib.rs
//! ghtest_project
//! # Document
//! 自動でドキュメントを作成して公開するテストです。

/// a + b を計算します
/// # Examples
/// ```
/// use crate::ghtest_project::add;
/// let (a, b) = (3, 2);
/// assert_eq!(add(a, b), 5);
/// ```
pub fn add(a: u64, b: u64) -> u64 {
    a + b
}

/// a - b を計算します
/// # Panics
/// a < b の時、オーバーフローします
/// # Examples
/// ```
/// use crate::ghtest_project::sub;
/// let (a, b) = (3, 2);
/// assert_eq!(sub(a, b), 1);
/// ```
pub fn sub(a: u64, b: u64) -> u64 {
    a - b
}

#[cfg(test)]
mod tests {

    use super::*;

    #[test]
    fn add_test() {
        let (a, b) = (3, 2);
        assert_eq!(add(a, b), 5);
    }

    #[test]
    fn sub_test() {
        let (a, b) = (3, 2);
        assert_eq!(sub(a, b), 1);
    }
}

さて、またcommitし、pushしてみます。
GitHub Actionsの動作過程は、リポジトリのトップ画面のコードのところの黄色い○のアイコンから確認できます。テストが通った後、ドキュメントの生成が始まる過程を見るのも面白いと思います。(なお、全て終了するとこのアイコンは緑のチェックマークになります。)

先ほどのアイコンが緑のチェックマークになったのを確認したら、もう一度GitHub Pagesのページへ飛んでみます。

先ほど追加したadd関数が追加されています!

参考までに今回使用したリポジトリの、GitHub Pagesへのリンクを貼っておきます。https://hayas1.github.io/ghtest-project/ghtest_project/
(そのうち公開を停止するかもしれません、ご了承ください)

以上です。お疲れ様でした。

最後に

どこかおかしかったり、もっといいymlファイルの書き方があるなどがありましたら、コメントなどよろしくお願いします。