Azure Pipelines で論文をビルドする(Pandoc&LaTeX)

Azure Pipelines とは

Azure Pipelines | Microsoft Azure

Microsoftの提供しているCI/CD環境です。類似サービスとしてはCircle CIやTravisCI、AppVeyorなんかが有名です。

Azure Pipelinesの特徴としては、WindowsとMacのサーバーが使えたり、AzureやGitHubのサービスと連携しやすいです。そして、何よりも大きいのはOSSに 10個の無料並列ジョブ を提供している点でしょう。他のCIサービスでもOSSは無料枠があるのですが、並列数が1つだったり、月間の制限時間があり使い方によっては問題が生じます。私は現在サクラエディタにコントリビューションしているのですが、サクラエディタではAppVeyorでビルドが通ることを確認してからマージすることになっています。しかし、(x86,x64,MinGW)x(Debug,Release)の6つの組み合わせでビルドを行っているためどうしてもビルドに時間がかかっており、Azure Pipelinesに乗り換えたいなーという話が上がっています。

今回はその調査も兼ねて自分のプロジェクトでCIの設定をしてみました。

論文をMarkdownで書く

さて、学生だとレポートや論文を書く機会があるかと思いますが、何で書いているでしょうか? Word、、、は簡単に使えていいのですが、綺麗に作ったり文献参照をきっちりやるのにそれなりに知識が必要です。また、版管理が面倒だったり、スタイルファイルがLaTeXでしか提供されていない場合があります。 LaTeXは綺麗に文章を作ってくれますし、版管理もGitで行えます。しかし、表やリストの記法が冗長で、記号類のエスケープなどに気をつける必要があり、執筆に集中できません。

そこで登場するのがMarkdown。gitでバージョン管理がたやすく、シンプルでGitHubなどでプレビューも可能。全てを解決してくれるツールです。(全てを解決するとはいってない)

PandocはMarkdownを様々な形式に変換してくれるHaskell製ツールです。LaTeXを経由してPDF化するので、LaTeXのスタイルファイルも使用可能です。また、途中でツールの使い方などで問題が起きてもLaTeXやワードファイルに変換して妥協もできるという安全ルートが存在するのが嬉しいところ。

Pandoc - About pandoc

今回は、MarkdownをGitHubで管理し、Azure Pipelinesで自動ビルドまでやります。

PandocとLaTeXの準備

Pandocを使うには、以下のものが必要です。興味のない人はdockerのところまで飛ばして大丈夫。

panodc自体はpdflatex、lualatex、xelatexに対応しています。短い宿題のレポートではLuaLaTexを指定して、Pandocから直接PDFを出力するのが手軽です。

メモ

今回は深く触れませんが、学会がスタイルファイルを提供していたり、自分LaTeXの命令を埋め込む時にPandocの出力と噛み合わない場合があります。その場合はMarkdown→LaTeXに変換し、テンプレートに\includeで取り込むようにすればいいです。

この方法ならpLaTeXなども問題なく使えますし、途中のファイルをawkでいじるといった闇技術も使えます。

インストール

aptなどのパッケージ管理から入れると、古いバージョンが入ることが多く、相性問題や不具合も多いので、最新版を使うのが無難です。

pandoc関連は上記のGitHubのReleasesにビルド済みバイナリが存在します。CabalやStackというHaskellのパッケージ管理からビルドする方法を書いているサイトがありますが、かなり時間がかかるのでオススメしません。

TeXはTeX Wikiに情報が充実してるので、それを見ながら。Fullで入れるとデカすぎるのですが、正直何のパッケージがいるかはよくわかっていません。LuaTeX系と、日本語系を入れれば動く・・・はずです。

Docker

PandocとLaTeXのインストールをCI上でやると無意味に時間がかかるので(特にTeX Live)、Dockerコンテナにします。自分のPCで使う場合でもこれを使えば面倒なインストールは不要になります。

README.mdにある通り、先頭にdocker run -it --rm -v pwd:/workspace kageshiron/pandoc をつければほぼローカルのpandocやlatexと同じです。

ローカルビルド環境を整える

論文を書いて入ればページ数確認や表示のチェックをするためにPDFを当然見たくなります。gitにpushしてCIが終わるのを待つわけにはいきませんから、ローカルに環境を整えます。

まず、前提としてDocker環境が必要です。MacでもWindows(WSL or Cygwin)でも大丈夫。WSLの場合やり方によってはパスの指定がうまくいきません。/mnt/c/cにシンボリックリンクを貼って、/cで始まるパス上でやると動くかも。

続いて、サンプルを用意したのでこちらをdownloadなりcloneして来てください。

https://github.com/KageShiron/pandoc-azurepipeline-sample

そして、以下のようにコマンドを叩きます。

$ docker pull kageshiron/pandoc
$ ./local.sh

しばらく待つと・・・。dist/output.pdfができるはずです。docker上で動かすので、簡単だ・・・!

Azure Pipelines

続いて、Azure DevOpsを開きます。Create Projectボタンから、適当な名前をつけてプロジェクトを作成します。今回はpandoc-demoとしました。続いて、New build pipelineを選択。

今回はGitHubと連携します。

対象のレポジトリを選んで・・・

azure-pipelines.ymlがあると勝手に表示してくれるので、Runボタンを押せば完了です。しばらくするとビルドが走ります。

ビルドが完了すると、右上のArtifactsからアーティファクト(ビルド結果のファイル)を取り出せます。ちゃんとoutput.pdfが手に入りました!!

簡単!・・・いや、今回はazure-pipelines.ymlがレポジトリに入ってたからですね。

azure-pipelines.ymlの書き方

azure-pipelines.ymlはPipelinesの設定ファイルです。GUI編集モードも存在し、オプションの内容によっては保管してくれるのでタスクによっては楽ですが、今回は直書きします。

トリガー

trigger:
  - master

triggerではビルドが走る条件を指定します。masterと指定すると、masterが更新されたタイミングでビルドが走ります。GitHubと連携してmasterへのPull Requestみたいな条件でもビルドできます。

リソース

resources:
  containers:
    - container: mypandoc
      image: kageshiron/pandoc

ビルドに使うリソース。今回はmypandocコンテナはkageshiron/pandoc (Docker Hubのもの)というDocker Imageを使うという指定です。

プールとコンテナ

Azure PipeliensにはContainer Jobsという仕組みがあり、ジョブをコンテナ上で実行できます。

Container Jobs in Azure Pipelines and TFS - Azure Pipelines | Microsoft Docs

pool:
  vmImage: 'ubuntu-16.04'
container: mypandoc

poolは要するにOS選択だと思えば良いはずです。Linuxのコンテナを使う場合はUbuntu-16.04を指定します。 containerには使用するコンテナリソースを指定します。resourcesで指定したmypandocを指定。これで、mypandocコンテナ on Ubuntu-16.04 でビルドが行われます。

(詳細はAgents pools - Azure Pipelines | Microsoft Docs を参照)

注意1 Dockerコンテナの要件

注意点として、Azure Pipelinesではパイプラインに組み込む都合上、現状ではDockerコンテナに要件があります。(KageShiron/pandocはこの条件を満たしています。)

alpineはglibcがなく、glibcを入れてもlibstdc++関連のエラーが出たりと若干面倒です。うまくいかない場合はUbuntuやdebianベースに切り替えてしまうのが一番簡単な解決方法です。また、ENTRYPOINTを指定してはならないことにも注意です。

注意2「Dockerfileのビルド」と混同しない

Dockerで検索すると、DockerイメージをCIビルドする 系の資料も多くヒットします。つまり、Dockerfileをビルドして、Docker Hubにpushしよう!みたいなやつは今回は違います。

別言語のコンパイルにDockerイメージを活用するのは Container Jobs という仕組みです。混同しないように十分注意してください。

stepsとtask

ここからはmypandoc上で動くジョブを指定します。今回は並列実行などもなく、非常にシンプルです。

steps:
- task: ShellScript@2
  inputs:
    scriptPath: build.sh
- task: CopyFiles@2
  inputs:
    contents: |
      dist/**
    targetFolder: $(Build.ArtifactStagingDirectory)
- task: PublishBuildArtifacts@1
  inputs:
    pathtoPublish: $(Build.ArtifactStagingDirectory)
    artifactName: MyBuildOutputs

タスクという小さい単位の作業を組み合わせて実行します。 標準で提供されるタスク一覧→ Catalog of the built-in tasks for build-release and Azure Pipelines & TFS - Azure Pipelines | Microsoft Docs そのほか、3rdパーティーが色々なタスクを公開してます。

シェルスクリプトやバッチファイルを実行するタスクもあるので、複雑なことをするならそれでゴリ押しするのが手っ取り早いでしょう。今回はローカル共通の処理はbuild.shでやっているので、CIだけでやるタスクを追加で指定しています。別のスクリプトファイルを書かなくて良いのでスッキリかけました。

タスク名はShellScript@2のようにタスク名@バージョンという指定になります。このバージョンは利用者ではなく、タスクの提供元(多くはMicrosoft)が修正や機能追加すると@2,@3と増えていくわけです。

今回使っているタスクを紹介。

ShellScript シェルスクリプトを実行する CopyFiles 指定箇所にファイルをコピー PublishBuildArtifacts アーティファクトを取り出せるようにする

アーティファクトはCIの結果物のことです。今回はdistディレクトリに作られるPDFがCIの結果物として取り出したいです。どうやら、distを直接指定せず、$(Build.ArtifactStagingDirectory)にコピーしてからPublishBuildArtifactsするのがルールのようです。

Predefined build variables - Azure Pipelines | Microsoft Docs

リリースする

アーティファクトのPDFを、例えばGitHub Releaseに登録するFTPでアップロードしたいといったことも可能です。Release Pipelineに分ければmasterにマージされたものだけリリースする、といったことも可能です。

ここからより複雑なことをするなら、Azure FunctionsだとかLogic Appsに処理を飛ばしてもいいかもしれません。

まとめ

ソースはGitで管理しても、実際にはPDFをストレージにアップしたりメールするように言われることは多々あるかと思います。すると、駆逐したはずの日付フォルダや(最新)新しい論文.oldみたいなファイル名との戦いが再び始まってしまいかねません。

CIでうまいこと管理して快適な執筆環境を整えましょう。

また、サクラエディタのAzure Pipelines化にも着手しようと思ってます。(先に論文ですって・・・?)