GitHub ActionsでLaTeXをビルドしてGoogle Driveに上げる


これは、「LOCAL students Advent Calendar 2019」25日目の記事です

前提

  • Git の基本的な知識
    • ビルドするLaTeXのファイルがリポジトリにすでにコミットされていること
  • Docker の基礎的な知識
    • Docker イメージを使ってビルドができること
  • YAML の記法

GitHub Actions とは

ソフトウェアのビルド・テスト・デプロイの自動化を行うCI/CDサービスの一つです。
こう聞くと難しく聞こえるかもしれませんが、リポジトリに対して行なっていたコマンドラインの手作業(今回では LaTeX のビルドと Google Drive へのアップロード)を自動化できるツールと考えると良いと思います。

GitHub Actions の概念

GitHub Actions は、ワークフロー・ジョブ・ステップを定義して使います。図に表すとこんな感じです。

  • ワークフロー
    いつこのワークフローを実行するのか指定します。リポジトリにプッシュされた時、毎週午前2時、などなど。
    それぞれのワークフローは並列に実行されます。
    1つ以上のジョブから構成され、1つのワークフローは1つのファイルに記述します。

  • ジョブ
    実行環境を Linux(Ubuntu)・Windows・macOS から選択します。
    それぞれのデフォルトでは並列に実行されますが、特定のジョブが終わった後に行う設定もできます。
    実行するステップを順番に定義します。ジョブの中のそれぞれのステップは同じ環境で実行されるので、ステップ中で作成したファイルを用いて次のステップを実行できます。

  • ステップ
    何を実行するのか(タスク)を書きます。
    実行するコマンドを書くこともできますが、すでに定義された処理を行うことができます。定義されたものは「アクション」と呼ばれ、GitHub の Marketplace から様々な人が開発したアクションを検索できます。
    例えば、リポジトリの clone (チェックアウト)を行うには GitHub 公式の actions/checkout を使います。

    この記事は筆者の作った2つのアクションの使い方です。

実行の流れ

実行の流れはこんな感じになります。

設定方法

トークンの作成・GitHub への登録

Google Drive へのアップロードに、CLIツールの skicka を使いました。
skicka 用の認証トークンを事前に作る必要があります。

  1. Dockerを使える環境で、以下のコマンドを実行します。

    docker run --rm -it --entrypoint "" satackey/skicka sh -c "skicka --no-browser-auth ls && cat /root/.skicka.tokencache.json"
    
  2. ブラウザで表示されたURLにアクセスします。

  3. アクセスを許可し、コード表示されたら、ターミナルに戻り貼り付けます。

    2020年1月2日現在、初めてskickaにログインするアカウントでこのアプリでは「Google でログイン」機能が一時的に無効と表示される問題が発生することがあります。
    自分で Google Drive API の Client ID と Client Secret をセットアップし、skicka に設定することで回避できます。セットアップの具体的な方法はググってください。

    2020年1月3日追記
    Client ID と Client Secret の取得方法をこちらに投稿しました。
    トークンの作成方法をこちらskicka を使ったことがない方・手順1のログイン時の問題の回避 に記載しました
    また、satackey/action-google-driveを使う際にID, Secretの指定が必要になります。Google Driveへアップロード の部分のステップの設定の説明を確認してください。

  4. GitHub の Secrets に最後の行のJSONを登録します。

  • GitHub のリポジトリを開きます。

  • Settings を開きます。

  • Secretsを開きます。

  • Add a new secret を押し、 Name に SKICKA_TOKENCACHE_JSON, Value にターミナルの最後の行に表示されたJSONをコピペします。

  • Add secret を押します。

これで準備完了です

ワークフローファイルの記述

  • GitHub のリポジトリの Actions タブを開きます。

  • Set up this workflow を押します。


    エディタが表示されましたね。このファイルを編集していきます。

  • 最後にスニペットをまとめたものがあります!!

  • 12行目以降は例なので消します。
    初期状態で、実行の流れステップ1・チェックアウトは書かれていますね。

  • latexmkの実行 の部分のステップはこのようになります。

      # 名前をつけます。ワークフローが実行された時に表示されます。
    - name: Build LaTeX files
    
      # 使うアクションを公開されているリポジトリを
      # ユーザ名 / リポジトリ名 @ gitのタグ・ブランチ で指定します
      # 今回は筆者の作ったアクションです
      uses: satackey/action-latex@v2
    
      # アクションの実行に必要なデータを渡します。
      # 必要なものがアクションの作者によって定義されています。
      with:
        # TeXLive・latexmk が入っている、ビルドに使用する Docker イメージを指定します。
        docker-image: paperist/alpine-texlive-ja
    
        # コンパイルのコマンドです
        build-entrypoint: latexmk
    
        # コンパイル時の引数を指定します
        build-args: # ここに、いつも latexmk を実行する時に使っているオプションを指定してください
    
        # ビルドするファイルを1行ごとに指定します。
        # ファイルが置かれたディレクトリに移動してビルドが行われます。
        build-files: |
          hogehoge/main.tex
          fugafuga/main.tex
    
  • Google Drive にアップロードするファイルが入ったディレクトリを作ります。

    - run: |
        mkdir upload
        find . -name "*.pdf" | xargs -I{} sh -c 'mkdir -p $(dirname {}); cp "{}" $(dirname "upload/{}")'
    

    拡張子が*.pdfのファイルがuploads/のディレクトリへ、階層を保ってコピーされます。

  • ビルドできたので、Google Driveへアップロードと進みたいところですが、Google Drive のフォルダを固定してしまうと、ブランチの中で最後にプッシュされたものしか残りません。固定の名前+ブランチ名でアップロードさせるため、ブランチ名を表示するコマンドを実行し、その結果を固定の名前と合わせてアクションに渡します。
    実行結果を以降のステップ使えるようにするには、::set-output name={名前}::{内容}の構文で出力します。
    このステップはこのようになります1

    - name: Extract branch name
      id: extract_branch
      run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
    

    id を指定したので、以降のアクションで ${{ steps.extract_branch.outputs.branch }} を記述すると、実行時にこの出力に展開されます。

  • Google Driveへアップロードのステップはこのようになります。

    - name: Upload to Google Drive
      uses: satackey/action-google-drive@v1
      with:
        # ${{ secrets.登録した名前 }} で secret の内容を参照できます。
        # トークンの作成・GitHub への登録 で登録した文字列を参照しています。
        skicka-tokencache-json: ${{ secrets.SKICKA_TOKENCACHE_JSON }}
    
        # アップロードするディレクトリを指定します。
        upload-from: ./upload
    
        # アップロード先のフォルダを指定します。
        # 1つ前のステップの実行結果を参照しています。
        upload-to: /path/to/upload/dir/${{ steps.extract_branch.outputs.branch }}
    
        # # skickaにログインできなかった人向け
        # # Google の Client ID, Secret を GitHub の Secrets に、
        # # SKICKA_TOKENCACHE_JSON と同様の手順で、以下のように名前を変更して登録してください。
        # google-client-id: ${{ secrets.GOOGLE_CLIENT_ID }}
        # google-client-secret: ${{ secrets.GOOGLE_CLIENT_SECRET }}
    

ワークフローファイル全体

# GitHub リポジトリの Actions ページで表示されるワークフローの名前です。お好きなものに変えてください。
name: Build and upload reports

on: [push]

jobs:
  # ジョブの名前です。お好きな(ry
  build-and-upload:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2

      # 名前をつけます。ワークフローが実行された時に表示されます。
    - name: Build LaTeX files

      # 使うアクションを公開されているリポジトリを
      # ユーザ名 / リポジトリ名 @ gitのタグ・ブランチ で指定します
      # 今回は筆者の作ったアクションです
      uses: satackey/action-latex@v2

      # アクションの実行に必要なデータを渡します。
      # 必要なものがアクションの作者によって定義されています。
      with:
        # TeXLive・latexmk が入っている、ビルドに使用する Docker イメージを指定します。
        docker-image: paperist/alpine-texlive-ja

        # コンパイルのコマンドです
        build-entrypoint: latexmk

        # コンパイル時の引数を指定します
        build-args: # ここに、いつも latexmk を実行する時に使っているオプションを指定してください

        # ビルドするファイルを1行ごとに指定します。
        # ファイルが置かれたディレクトリに移動してビルドが行われます。
        build-files: |
          hogehoge/main.tex
          fugafuga/main.tex

    - run: |
        mkdir upload
        find . -name "*.pdf" | xargs -I{} sh -c 'mkdir -p $(dirname {}); cp "{}" $(dirname "upload/{}")'

    - name: Extract branch name
      id: extract_branch
      run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"

    - name: Upload to Google Drive
      uses: satackey/action-google-drive@v1
      with:
        # ${{ secrets.登録した名前 }} で secret の内容を参照できます。
        # トークンの作成・GitHub への登録 で登録した文字列を参照しています。
        skicka-tokencache-json: ${{ secrets.SKICKA_TOKENCACHE_JSON }}

        # アップロードするディレクトリを指定します。
        upload-from: ./upload

        # アップロード先のフォルダを指定します。
        # 1つ前のステップの実行結果を参照しています。
        upload-to: /path/to/upload/dir/${{ steps.extract_branch.outputs.branch }}

        # # skickaにログインできなかった人向け
        # # Google の Client ID, Secret を GitHub の Secrets に、
        # # SKICKA_TOKENCACHE_JSON と同様の手順で、以下のように名前を変更して登録してください。
        # google-client-id: ${{ secrets.GOOGLE_CLIENT_ID }}
        # google-client-secret: ${{ secrets.GOOGLE_CLIENT_SECRET }}

応用編

複数の LaTeX プロジェクトが存在する時、その TeX ファイルを find を用いて一覧にし、その結果を LaTeX ビルドのアクションに参照させることで、新しくプロジェクトを追加した時でも、ワークフローファイルを変更せずにコンパイルを行うことができます。以下はすべての main.tex をコンパイルさせる例です。2
GitHub Actions の Outputs の出力時には、改行等のエスケープを行う必要があります3

    - name: List TeX files
      id: list-tex-files
      run: |
        SOURCE_FILES=$(find . -name "main.tex")
        SOURCE_FILES="${SOURCE_FILES//'%'/'%25'}"
        SOURCE_FILES="${SOURCE_FILES//$'\n'/'%0A'}"
        SOURCE_FILES="${SOURCE_FILES//$'\r'/'%0D'}"
        echo "::set-output name=tex-files::$SOURCE_FILES"

    - name: Build LaTeX files
      uses: satackey/action-latex@v2
      with:
        docker-image: paperist/alpine-texlive-ja
        build-files: ${{ steps.list-tex-files.outputs.tex-files }}
        # ...その他設定

参考資料

GitHub Actionsの中核的概念
Docker コンテナのアクションを作成する


  1. ステップのスニペットを https://stackoverflow.com/questions/58033366/how-to-get-current-branch-within-github-actions より引用 

  2. find コマンドの -name 引数の内容を *.tex として、すべての .tex ファイルをビルドさせることなども可能です。 

  3. エスケープ部分のスニペットを https://github.community/t5/GitHub-Actions/set-output-Truncates-Multiline-Strings/td-p/37870 より引用