rust+npm+wasm-packなrepoをGithub Actionsで自動テスト＆デプロイできるようにしてみた

# はじめに

Rustでコードを書いて、WASMにしてnpmのパッケージにするとなると、wasm-packを使うのが一般的らしい。

https://github.com/rustwasm/wasm-pack

ところがいろいろなツールを使うために自動テスト&デプロイには一工夫がいる。

# 最初の試み(結局やめた): Docker + Circle CIでの自動化

最初に自動化を試みたころ、ちょうどDockerの勉強をしていたので、Dockerが使いたかった。あとCircle CIを使い始めた頃だったのでそれを選んだ。

## Docker Imageの作成

### 最初の試み(失敗): Alpine Linuxベースで作る

```dockerfile
FROM    alpine:3.10
RUN apk add --no-cache zlib-dev openssl-dev git bash rust cargo nodejs npm && \
        npm update -g npm && \
        cargo install wasm-pack && \
# https://rustwasm.github.io/wasm-pack/book/prerequisites/non-rustup-setups.html
        wget https://static.rust-lang.org/dist/rust-std-1.34.2-wasm32-unknown-unknown.tar.gz && \
        tar -xf rust-std-1.34.2-wasm32-unknown-unknown.tar.gz && \
        mv rust-std-1.34.2-wasm32-unknown-unknown/rust-std-wasm32-unknown-unknown/lib/rustlib/wasm32-unknown-unknown /usr/lib/rustlib/ && \
        rm -r rust-std-1.34.2-wasm32-unknown-unknown && rm rust-std-1.34.2-wasm32-unknown-unknown.tar.gz
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command: Running build command...
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command::build: Checking rustc version...
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command::build: rustc version is 34.
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command::build: Checking crate configuration...
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command::build: Crate is correctly configured.
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command::build: Checking for wasm-target...
# [INFO]: Checking for the Wasm target...
#  INFO 2019-09-09T16:55:21Z: wasm_pack::build::wasm_target: Looking for wasm32-unknown-unknown in "/usr/lib/rustlib"
#  INFO 2019-09-09T16:55:21Z: wasm_pack::build::wasm_target: Found wasm32-unknown-unknown in "/usr/lib/rustlib"
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command::build: Checking for wasm-target was successful.
#  INFO 2019-09-09T16:55:21Z: wasm_pack::command::build: Building wasm...
# [INFO]: Compiling to Wasm...
#  INFO 2019-09-09T16:55:21Z: wasm_pack::child: Running "cargo" "build" "--lib" "--target" "wasm32-unknown-unknown"
# [2019-09-09T16:55:21Z INFO  cargo::util::rustc] failed to read rustc info cache: failed to read `/markdown_img_url_editor/markdown_img_url_editor_rust/target/.rustc_info.json`
# [2019-09-09T16:55:21Z INFO  cargo::util::rustc] rustc info cache miss
# [2019-09-09T16:55:21Z INFO  cargo::util::rustc] rustc info cache miss
# [2019-09-09T16:55:21Z INFO  cargo::util::rustc] rustc info cache miss
# [2019-09-09T16:55:21Z INFO  cargo::util::rustc] rustc info cache miss
# [2019-09-09T16:55:21Z WARN  cargo::util::rustc] failed to update rustc info cache: failed to write `/markdown_img_url_editor/markdown_img_url_editor_rust/target/.rustc_info.json`
# error: failed to run `rustc` to learn about target-specific information

# Caused by:
#   process didn't exit successfully: `rustc - --crate-name ___ --print=file-names --target wasm32-unknown-unknown --crate-type bin --crate-type rlib --crate-type dylib --crate-type cdylib --crate-type staticlib --crate-type proc-macro` (exit code: 1)
# --- stderr
# error: Could not create LLVM TargetMachine for triple: wasm32-unknown-unknown: No available targets are compatible with triple "wasm32-unknown-unknown"
ENV PATH $PATH:/root/.cargo/bin
```

https://github.com/yumetodo/BuildContainers/blob/8011ab2426779f6e5fcefa08ffdf57482b0ce2ca/rust-1.34.2-wasm-node-10/Dockerfile

上のようなDocker Fileを作ったのだが、コメントで書いたようなエラーが出てうまくいかなかった

### 次の試み: OfficialのRustイメージベース

```dockerfile
FROM rust
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get install -y nodejs && rm -rf /var/lib/apt/lists/* && \
        curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
```

https://github.com/yumetodo/BuildContainers/blob/8011ab2426779f6e5fcefa08ffdf57482b0ce2ca/rust-latest-wasm-lts/Dockerfile

これは一応うまく行った。

## Cirlce CIの設定

```yml
version: 2.1
executors:
    default:
        docker:
            - image: yumetodo/buildcontainers:rust-latest-wasm-lts
        working_directory: ~/repo
commands:
    tool_install:
        steps:
            - run: npm i -g can-npm-publish
    cargo_restore_cache:
        steps:
            - run:
                name: Calculate dependencies
                command: cargo generate-lockfile
                working_directory: markdown_img_url_editor_rust
            - restore_cache:
                keys:
                  - v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }}
    cargo_save_cache:
        steps:
            - save_cache:
                paths:
                  - /usr/local/cargo/registry
                  - target/debug/.fingerprint
                  - target/debug/build
                  - target/debug/deps
                  - target/release/.fingerprint
                  - target/release/build
                  - target/release/deps
                key: v4-cargo-cache-{{ arch }}-{{ checksum "Cargo.lock" }}
jobs:
    test:
        executor: default
        steps:
            - checkout
            - cargo_restore_cache
            - run: cargo test
            - run: wasm-pack build
            - cargo_save_cache
    publish:
        executor: default
        steps:
            - tool_install
            - checkout
            - run: wasm-pack build --release --target nodejs
            - run: echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > ~/.npmrc
            - run:
                command: |
                    if can-npm-publish --verbose pkg; then
                        wasm-pack publish
                    else
                        echo "publish skipped"
                    fi
workflows:
    version: 2
    publishflow:
        jobs:
            - test
            - publish:
                context: npm
                requires:
                    - test
                filters:
                    tags:
                        only: /^v.*/
```

https://github.com/yumetodo/markdown_img_url_editor_rust/blob/bbf656c6ed9874b77a23508dce6432da8a1bc4a7/.circleci/config.yml

さっき作ったDocker Imageを指定してこんな感じに設定したらうまく動いた。

# 次の試み: Github Actionsをつかう

しかしよく考えてほしい。node.jsは頻繁に新しいバージョンがでるし、rustもそれなりにバージョンアップが来る。wasm-packだって更新が来る。そのたびにImage作って更新していくと考えるとかなりだるい。

そもそもDockerを使うべきじゃなかった気がしてくる。

そこで目をつけたのがGithub Actionsだ。これはUbuntuの上で走る(いや他のも選べるし、更にその上でDocker動かすとかもできるけど)。

ちょっとまえにcpprefjp（C++のリファレンスサイト)にかんたんなチェックをするCIをGithub Actionsで組んだばかりだったので、勝手がわかってるのも大きい。
https://github.com/cpprefjp/site/pull/737

```yml
name: Test and Release

on:
  [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Cache cargo registry
      uses: actions/cache@v1
      with:
        path: ~/.cargo/registry
        key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
    - name: Cache cargo index
      uses: actions/cache@v1
      with:
        path: ~/.cargo/git
        key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }}
    - name: Cache cargo build
      uses: actions/cache@v1
      with:
        path: target
        key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}
    - name: Run tests
      run: cargo test --verbose
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: 12.x
        registry-url: 'https://registry.npmjs.org'
    - name: install wasm-pack
      run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
    - run: wasm-pack build --release --target nodejs
    - name: wasm-pack publish when possible
      run: |
        if npx can-npm-publish --verbose pkg; then
            wasm-pack publish
        else
            echo "publish skipped"
        fi
      env:
        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

https://github.com/yumetodo/markdown_img_url_editor_rust/blob/aeb43c0d46439c24c665848183cece9599c71695/.github/workflows/release.yml

Cargoのキャッシュ設定は
https://github.com/actions/cache/blob/master/examples.md#rust---cargo
のものを拝借した。

Github ActionsのRustの設定例
https://github.com/actions/starter-workflows/blob/e5a3a0e11268f84ad0ff6569a47e197cc8e1f758/ci/rust.yml
を見てみたところ、どうも何もしなくてもcargoは使えるらしい。どのバージョンとかは面倒くさいので調べてないが、まあ動いているのでいい。

`npm publish`する時

```yml
    - run: npm publish
      env:
        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
```

のようにするのだが、`wasm-pack publish`する先行事例が見つからなくて不安だった。結果動いたのでよい。

あと、PRからのビルドのときはpublish周りはいらないので
https://github.com/yumetodo/markdown_img_url_editor_rust/blob/aeb43c0d46439c24c665848183cece9599c71695/.github/workflows/test.yml#L1
省略版を作った。

ところで`secrets.NPM_TOKEN`ってPRから見えたりはしないですよね？確認してないけど。

# 成果

https://www.npmjs.com/package/markdown_img_url_editor_rust

に上がっている。

ちなみにこのパッケージは何かというと

- https://github.com/yumetodo/vscode-google-photos-uploader
    - https://github.com/yumetodo/markdown_img_url_editor
        - https://github.com/yumetodo/markdown_img_url_editor_rust

というような依存関係になっている。