Think Proof, Count Two

and look for a red shoe

GitLab を利用して LaTeX 文書を作成する話

Posted on — Dec 12, 2019

本記事は TeX & LaTeX advent calendar 2019 の 12 日目の記事です. 11 日目の記事は doraTeX さんの “TikZ でベン図をたくさん描いてみよう” でした. 13 日目の記事は mattskala さんの予定です.

Introduction / Motivation

最近,ようやく研究がまとまってきたので論文を書き始めました. 人生初の論文執筆ですので指導教員に多大な迷惑をかけつつ,手探りで書きすすめています. 本記事の目的の一つは,添削をしていただくための .tex ファイルの共有・編集履歴の管理・バックアップの作成などの,個人的なメモを残すことです.

また最近読了した『読みやすい技術書を書く技術』に触発され,GitLab を利用して LaTeX 文書を作成する試みについても述べたいと思います (あまりうまくいっていませんが). 『読みやすい技術書を書く技術』については以下を参照してください:

本記事では以下の事柄について扱いたいと思います:

このような技術に関しては全く詳しくありませんので,おかしい記述などありましたらお知らせいただけると喜びます.

Convention

筆者は,(いわゆる) 純粋数学を研究している学生です. したがって所々で数学の文書を念頭にいれた記述があると思います. 他の多くの分野では役に立ちにくい点に言及していたり,逆に役に立つであろう点に言及がない可能性があります.

また,普段は macOS または Linux1 を利用しています. Windows 利用者には向かない記述が多々ある可能性があります.

以上の点,どうかご寛恕ください.

Sample repository

本記事で扱った事柄についてサンプルリポジトリを用意しました. 適宜ご参照ください.

git 入門

.tex ファイルの編集履歴の管理には,git を利用するのをおすすめします. 筆者は以下のサイトで基本的な使い方を学びました:

バージョン管理ツールを利用することの利点は上記サイトを読んでいただければわかると思います.

筆者が git を使い始めた当初,コンフリクトが非常に怖かった覚えがあります. 筆者は試していませんが,コンフリクト解消の練習ができるサンドボックスが存在します:

昔は master 一本道の運用でしたが,最近は GitHub Flow を参考に運用するのが良いと考えています. 初心者のうちはうっかり git reset --hard してしまったり,履歴を吹き飛ばすことがあります (少なくとも筆者はありました). master ブランチへの push を禁止してトピックブランチから merge を行う,という運用にすればそのような悲劇はかなり減らせるでしょう. GitHub Flow の詳細は適当に検索していただくとして,最近の筆者は

という運用をしています. また,適切なタイミングでタグ (git tag) などを利用すれば latexdiff-vc などのツールも使いやすくなります. これらのツールについてはすでにいくつか解説記事がありますし,texdoc でドキュメントが読めます. 他にも,指導教員が git を使えるならば直接 Merge Request (Pull Request) を送ってもらえる,ということもあります.

GitLab.com

git ホスティングサービスには色々ありますが,ここでは GitLab.com を利用したいと思います. 理由として,プライベートリポジトリでの CI 利用が簡単そうである点が挙げられます2. GitLab.com の無料プランでは,2017 年 5 月 1 日以降,プライベートリポジトリでの CI は 1 グループあたり 2000 分/月に制限されているようです3. パブリックリポジトリならば制限はないようですが,執筆中の論文をパブリックリポジトリに上げることはないでしょう.

SSH 接続のための鍵の作成・登録はこちらを参照してください.

Protected branch

master ブランチを保護するには,プロジェクトページの Settings > Repository > Protected Branches を開いて,Allowed to pushNo one に設定すれば良いです. 共同編集者がいる場合は Allowed to merge も適切に設定しておくのが良いでしょう.

GitLab CI

GitLab.com で GitLab CI を利用するためにはプロジェクトルートに .gitlab-ci.yml というファイルを置くだけで良いはずです. .gitlab-ci.yml の詳しい書き方はドキュメントを読むなどしてください.

校正ツール

本節ではいくつかの校正ツールを紹介します. 扱わないツールとしては

などがあります. textlint のプラグインは最近更新があったため,もしかしたら最新版ではうまく動くかもしれません. 『読みやすい技術書を書く技術』でこれらに関する解説を読むことができます.

reviewdog

reviewdog は校正ツールの結果を Merge Request にコメントしたりできるツールです. reviewdog を利用するために REVIEWDOG_GITLAB_API_TOKEN 変数を設定しておく必要があります. まず https://gitlab.com/profile/personal_access_tokens から GitLab Personal Access Token を取得します. scope の範囲は api を選択します. プロジェクトページの Settings > CI/CD > Variables を開き,取得したトークンを保存します. TypeVariable, KeyREVIEWDOG_GITLAB_API_TOKEN とします. また Masked にチェックを入れておきましょう.

『読みやすい技術書を書く技術』ではコマンドラインオプションを用いて reviewdog を利用していましたが,.reviewdog.yml という設定ファイルを用いることもできます.

筆者がしばらく使ってみたところ,merge-base commit を取得するのに失敗することがあります. 少なくともマージ先のコミットが,マージ元のコミットから辿れる場合は成功します (いまいちよくわかりません). GitLab CI の結果は GitLab から簡単に確認できるので,無理に reviewdog を利用する必要はないかもしれません.

GNU Aspell

スペルチェッカです.TeX Wiki に詳しい解説があるのでご存知の方も多いと思います. Emacs などのエディタから使うこともできます. 英語の文章を作成する際に大活躍します. ただしフランス語については公開されている辞書ファイルが古く,使い物になるかわかりません.

英語能力が貧弱なのはある程度仕方がないとしても,せめて簡単なスペルミスはスペルチェッカを利用して防ぎたいところです. CI で Aspell を利用するのはいくつかの理由から諦めました:

したがって以下の方法が考えられます:

例えば Emacs を利用しているならば before-save-hookispell を引っ掛ける,という方法が思い浮かびます4. 試していませんが,AUCTeX で .tex 文書を執筆しているならば

(add-hook 'LaTeX-mode-hook
          '(lambda ()
             (add-hook 'before-save-hook #'ispell-buffer nil 'local)))

とすればバッファ保存時に自動的に ispell (あるいは aspellhunspell) が走ると思います.

また git-hooks を利用する方法もあるでしょう. 次の内容を <project-root>/.git/hooks/pre-commit という名前で保存します. chmod a+x <project-root>/.git/hooks/pre-commit などで実行権限を与えるのを忘れないように注意します.

#!/bin/bash
echo "Have you spell-checked? [y/N]"

exec < /dev/tty
read ANSWER

case $ANSWER in
    "yes" | "Yes" | "YES" | "y" | "Y" ) echo "Ok, commit changes";;
    "no" | "No" | "NO" | "n" | "N" ) echo "Stop commit"; exit 1;;
esac
exit 0

git commit するとスペルチェック済か聞かれるので,そこで no と答えるとコミットを中断します. Bash 以外を使っている人は適当なスクリプトに書き直してください.

ChkTeX

有名な LaTeX のシンタックス・チェッカです. Emacs や VS Code などのエディタから使うこともできます.

reviewdog から ChkTeX を利用するには,それぞれ .reviewdog.yml

runner:
  chktex:
    cmd: chktex -v0 -n8 ./main.tex
    errorformat:
      - "%f:%l:%c:%n:%m"

.gitlab-ci.yml

chktex:
  stage: lint
  only:
    - merge_requests
  image:
    name: mahito1594/chktex:latest
    entrypoint: [""]
  before_script:
    - apt-get update && apt-get install -y git wget
    - wget -O - -q ${INSTALL_URL} | sh -s
  script:
    - bin/reviewdog -runners=chktex -reporter=gitlab-mr-discussion

のように記述します. ここで -v0chktex のエラーフォーマットを指定し,-n8 で一部のエラーを抑制しています. また mathito1594/chktex は筆者の作成した Docker イメージです.

マージリクエストのサンプルはこちらです.

TeXtidote

TeXtidote は LaTeX 文書のための文法チェッカです. 日本語には対応していないようですが,内部で使われている LanguageTool は日本語にも対応しているようです. プログラミングは全くできないのでよくわかりません.

Java (version 8 or later) が必要です. 詳しい使い方は README を参照してください. あまり使いこなせていないのか,特に役に立った印象が薄いです.

reviewdog だと errorformat の指定が良くないのか,うまく動いてくれません. TeXtidote のオプション --output singleline を指定して,reviewdog の方を -efm='%f(L%lC%c%.%#): %m' とすれば良いと思うのですが…….

仕方がないので端末から使うようにします. 次のシェルスクリプトは,TeXtidote によるチェック結果を一時ファイルに書き込み,それをブラウザで開きます:

#!/bin/bash
readonly SCRIPT_NAME=${0##*/}
temp_file="$(mktemp -d -q)/textidote.html"

if [ $? -ne 0 ]; then
    echo "${SCRIPT_NAME}: Can not create temp file, exiting..."
    exit 1
fi

echo "Grammer check...start"
java -jar $(which textidote.jar) --output html "$@" > ${temp_file}
echo "Grammer check...done, open in the browser"
open ${temp_file}
exit 0

これを,例えば texreport という名前で PATH の通ったディレクトリに保存します (忘れずに実行権限を与えてください). texreport --check en main.tex などのように使います. 他の TeXtidote のオプションも使えます.

ただし textidote.jar を PATH の通ったディレクトリに置いてあることを仮定しています. Linux を使っている方は openxdg-open などに適宜置き換えてください. また macOS を利用する場合は mktemp コマンドの挙動に注意する必要があるかもしれません (参考: https://rcmdnk.com/blog/2015/10/21/computer-mac-bsd-linux/).

RedPen

『読みやすい技術書を書く技術』にも記述があるため,詳細はそちらに譲ります. reviewdog と併用する方法が紹介されています.

現状,数学の文書を執筆するにあたってはあまり役に立たない印象があります. 例えば,一文中のコンマの数が指定された最大数より多いと警告してくれる機能があります.しかし数式中のコンマまで数えられてしまうためあまり役に立ちません. 他にも Weak Expression と呼ばれる表現が現れると警告してくれますが,bigsome にも反応するため数学の文章には向かない機能です.

機能一覧をよく見て,本当に必要な機能のみ利用するならば良いかもしれません.

PDF のコンパイル

さて,以前次のツイートを見かけました:

たしかに .tex ファイル等は git 管理で問題ないのですが,生成物である PDF をどのように管理するべきかは意見の分かれるところだと思います. PDF はバイナリなので git で差分がわかりません5. 筆者は PDF を git 管理するご利益があまりないため git 管理していません.

一方で次のような意見もあります:

そこで GitLab CI & Docker で .tex ファイルをコンパイルし,PDF を作成することにします. この件に関しても先駆者はたくさんいらっしゃるので,詳細は検索してください.

今年の重点テーマは「やっぱり Lua(La)TeX しよう」なので LuaLaTeX を用いて PDF を作成してみたいと思います. 最近は源ノフォントを利用することが多いので,源ノ角明朝・源ノ角ゴシックを含んだ Docker イメージを作ります6. 作成した Dockerfile はこちらです. またイメージはこちらから利用できます. Noto フォントは aptapk などからインストールできるため,Noto フォントを利用するほうが簡単かもしれません. フォントファイルの配置については @zr_tex8r さんの記事 を参考にしたため, uplatex + dvipdfmx でもちゃんと pxchfon パッケージを用いて源ノフォントが埋め込まれるはずです.

一通りのパッケージ・フォント類を使えるようにした結果,イメージサイズが大きくなってしまいました. 小さいイメージが好ましい方は他のイメージを利用するか,使わない collection 類を削ったりしてください.

以下を .gitlab-ci.yml に記述することで,master ブランチ上の main.texlatexmk を用いてコンパイルできます. プロジェクトルートに latexmkrc を置いておけば,ローカルでも CI 上でも同じようにコンパイルができます. サンプルリポジトリで作成された PDF には源ノフォントが埋め込まれているはずです.

compile:
  only:
    - master
  image:
    name: registry.gitlab.com/mahito1594/sample-latex-project:latest
    entrypoint: [""]
  script:
    - latexmk main
  artifacts:
    paths:
      - main.pdf
    expire_in: 1 day

また pipeline から作成した PDF をダウンロードできます. 次節で説明するように,作成した PDF はクラウドストレージサービスにアップロードするため,1 日で消去されるように設定します. artifacts:expire_in を設定しなかった場合,手動で削除されるまでダウンロードが可能です.

arXiv は TeX Live 2016 を利用しているようですので, arXiv への投稿を念頭にいれるならば TeX Live 2016 の Docker イメージを利用したほうがいいかもしれません.

クラウドストレージへの保存

前節で PDF を作成し,artifacts としてダウンロードができるようになりました. しかし以下の観点から作成した PDF を Dropbox などのクラウドストレージにも保存することは有効です:

例えば push 前にローカルで PDF を作成し,それを pre-push フックなどで同期フォルダへコピーする方法が挙げられます. 本記事では GitLab CI を利用してクラウドストレージにファイルをアップロードする方法について述べます.

Dropbox

皆さんご存知の Dropbox です. curl と API を利用して,生成した main.pdf をアップロードすることができます. 詳細は Dropbox API のドキュメントを参照してください.

具体的には次のコマンドで実行できます:

curl -X POST https://content.dropboxapi.com/2/files/upload \
    --header "Authorization: Bearer $DROPBOX_API_TOKEN " \
    --header "Dropbox-API-Arg: {\"path\": \"/main.pdf\",\"mode\": \"overwrite\"}" \
    --header "Content-Type: application/octet-stream" \
    --data-binary @main.pdf

DROPBOX_API_TOKEN は次のようにして取得できます:

  1. Dropbox 開発者ページの My Apps から新しいアプリを作成します8. ここでは次のようにした:
  1. Settings にある Generated access token の欄の Generate ボタンを押す.

取得したトークンをプロジェクトページの Settings > CI/CD > Variables から DROPBOX_API_TOKEN という名前で保存します. 繰り返しますが Masked にチェックを入れるのを忘れないようにしましょう.

.gitlab-ci.yml には次のように記述します:

upload_dropbox:
  stage: upload
  image:
    name: curlimages/curl:latest
    entrypoint: [""]
  script:
    - |-
      curl -X POST https://content.dropboxapi.com/2/files/upload \
          --header "Authorization: Bearer $DROPBOX_API_TOKEN " \
          --header "Dropbox-API-Arg: {\"path\": \"/main.pdf\",\"mode\": \"overwrite\"}" \
          --header "Content-Type: application/octet-stream" \
          --data-binary @main.pdf

また,生成した PDF だけでなく .tex.bbl などのファイルもアップロードしたいときもあると思います. このように複数ファイルをアップロードするには for 文を利用する方法があります. (参考: https://stackoverflow.com/a/46634228)

pCloud

Dropbox の無料プランでは連携できる端末に上限があるため,最近は pCloud を利用しています.

pCloud も API を利用してファイルをアップロードすることができます. ただし,ログインに2要素認証を利用していると uploadfile メソッドを用いたアップロードができないようです. ここでは uploadtolink メソッドを利用してみます. 本稿執筆時点 (2019 12 08) でドキュメントに不備があるようです. こちらのコメントを参照してください.

具体的には次のコマンドで main.pdfmain.tex をアップロードすることができます:

curl -F file=@main.pdf \
    -F file=@main.tex \
    "https://api.pcloud.com/uploadtolink?names=GitLabCI&code=${PCLOUD_UPLOAD_CODE}"

アップロードされたファイルはこちらから確認できます.

CI 変数 PCLOUD_UPLOAD_CODE には my.pcloud.com からアップロードしたいディレクトリ (フォルダ) を選択し, Share Upload Link をクリックして取得したものを保存します9. 三度目になりますが変数は Masked にしておきましょう.

.gitlab-ci.yml には次のように記述します:

upload_pcloud:
  stage: upload
  image:
    name: curlimages/curl:latest
    entrypoint: [""]
  script:
    - |-
      curl -F "file=@main.pdf" \
          -F "file=@main.tex" \
          "https://api.pcloud.com/uploadtolink?names=GitLabCI&code=${PCLOUD_UPLOAD_CODE}"

おわりに

あまり TeX & LaTeX advent calendar らしからぬ記事になってしまいました. git-tag を打つと 1 つ前の tag との latexdiff をとってコンパイル → pCloud へアップロード,ということもできると思ったのですが力尽きたので試していません. 後で追記するかもしれません.


  1. 最近は Debian を使っています

  2. プライベートリポジトリで他の CI サービスを利用したことがないので比較はできません

  3. https://about.gitlab.com/blog/2017/04/11/introducing-subscriptions-on-gitlab-dot-com/

  4. 特定のバッファだけで有効になるように工夫が必要です

  5. その代わり latexdiff や latexdiff-vc などのツールを用いて,TeX ファイルの差分から PDF の差分を見やすくすることができます

  6. ライセンスを読んだ限り問題はないと思うのですが,ライセンス難しいのでよくわかりません

  7. git を知らない人に「GitLab.com のアカウントを作ってこれこれして」と頼むのは難しいものがあります

  8. 既存のアプリを利用しても構わないと思います

  9. 表示される URL 末尾の code=xxxxxxxxxxxx となっている部分です