社内でMarkdownをPDF化したい要望があり、かつ、GitHub上でファイルの管理からビルドまで完結できるといいよねっということで作ってみました。
しくみ
GitHubにChapter
プレフィックスのMarkdownファイルをコミットすることでGitHub Actionsが発火し、MarkdownからPDFの変換が行われます。MarkdownからPDFへの変換にはPandocを使用しました。
GitHub Actionsのワークフローは以下の2つ
md2pdf-demo/.github/workflows/pandoc.yaml at main · VirtualTech-DevOps/md2pdf-demo · GitHub
md2pdf-demo/.github/workflows/build-container.yaml at main · VirtualTech-DevOps/md2pdf-demo · GitHub
.github/workflows/pandoc.yaml
このワークフローはMarkdownからPDFを生成します。
on: push: paths: - Chapter*.md - .github/workflows/pandoc.yaml
Chapter
プレフィックスのMarkdownと、このワークフローのyaml自体に変更があった場合にワークフローが実行されます。
jobs: markdown_files: runs-on: ubuntu-latest outputs: matrix: ${{ steps.matrix.outputs.value }} steps: - uses: actions/checkout@v3 - id: matrix run: echo -E "value=[$(ls -Qm Chapter*.md | sed 's/\.md//g' | tr -d ' \n')]" >> "$GITHUB_OUTPUT"
markdown_files
ジョブはChapter
プレフィックスのファイルの一覧から.md
拡張子を削除した文字列をカンマ区切りの配列としてアウトプットするようにしています。["Chapter0","Cpater1",...]
こんな感じにアウトプットされます。
build-pdf: needs: markdown_files runs-on: ubuntu-latest strategy: matrix: value: ${{ fromJson(needs.markdown_files.outputs.matrix) }} steps: - uses: actions/checkout@v3 - uses: docker://ghcr.io/virtualtech-devops/md2pdf-demo:latest with: args: >- -o ${{ matrix.value }}.pdf -N --toc --toc-depth=3 -F pandoc-crossref -H listing.tex --listings --highlight-style=tango -V documentClass=ltjarticle -V mainfont=IPAPGothic --pdf-engine=lualatex ${{ matrix.value }}.md - uses: actions/upload-artifact@v3 with: name: ${{ matrix.value }}.pdf path: ${{ matrix.value }}.pdf
build-pdfジョブでMarkdownからPDFを生成しています。markdown_filesジョブで生成した配列をstrategy.matrix
に渡しています。受け取った配列の要素数分ジョブが増殖して並列実行されます。GitHub ActionsのMatrixについては以下の公式ドキュメントをご覧ください。
MarkdownからPDFを生成するためにdocker://ghcr.io/virtualtech-devops/md2pdf-demo:latest
というコンテナをuses
しています。このコンテナは次に説明するワークフローでビルドされています。中身は公式のpandocコンテナにフォントを追加したカスタムコンテナです。
args: >- -o ${{ matrix.value }}.pdf -N --toc --toc-depth=3 -F pandoc-crossref -H listing.tex --listings --highlight-style=tango -V documentClass=ltjarticle -V mainfont=IPAPGothic --pdf-engine=lualatex
この部分でpandocのオプションを指定しています。
-o
アプトプットするファイル。-N
目次に番号をつける。セクション(#から始まる行)の末尾に{.unnumbered}
を記載すると番号なしの見出しをつけられる。--toc
目次を自動生成。toc-depth=3
目次に含めるセクションレベル。3を指定しているので###
から始まるセクションが目次に表示される。-F pandoc-crossref
Pandoc ASTを変換するフィルタを指定。別のファイルフォーマットに変換するためのフィルタ。-H listing.tex
引数に指定されているファイルをヘッダの末尾に挿入。指定されているlisting.texはリポジトリ直下にコミットされている。--listings
コードブロックにLaTeXのlistingsパッケージを適用。行の折り返しやシンタックスハイライトが可能になる。--highlight-style=tango
ソースコードのシンタックスハイライトスタイルを指定。-V
テンプレート変数を設定。--pdf-engine=lualatex
PDFの変換で日本語が使えるようにLuaLaTeXを指定。LuaTex-jaはコンテナにインストール済み。
.github/workflows/build-container.yaml
このワークフローはMarkdownをPDFにビルドするためのコンテナイメージをビルドします。
on: workflow_dispatch: push: branches: - main paths: - Dockerfile - .github/workflows/build-container.yaml
Dockerfileと、このワークフローのyaml自体に変更があった場合にワークフローが実行されます。また、workflow_dispatch
も指定しているので手動でワークフローを実行することもできます。
jobs: build-container: runs-on: ubuntu-latest permissions: contents: read packages: write steps: - name: Checkout uses: actions/checkout@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to GitHub Container Registry uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - id: metadata run: | echo "image=$(echo ${{ github.repository }} | tr '[A-Z]' '[a-z]')" >> "$GITHUB_OUTPUT" echo "tag=$(git rev-parse --short $GITHUB_SHA)" >> "$GITHUB_OUTPUT" - name: Build and push uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64 push: true cache-from: type=gha cache-to: type=gha,mode=max tags: | ghcr.io/${{ steps.metadata.outputs.image }}:latest ghcr.io/${{ steps.metadata.outputs.image }}:${{ steps.metadata.outputs.tag }}
build-containerジョブでコンテナイメージのビルドからghcrへのPushまでを実行しています。
permissions: contents: read packages: write
ghcrにPushするためにpackages: write
を指定しています。このpermissions、デフォルトはwrite-allなんですが、1つでも値を指定すると他のスコープ(contentsとかpackages)がnoneになってしまいます。これではリポジトリのファイルをPullできないのでcontents: read
もつけています。詳細については公式ドキュメントを参考にしてください。
全体の流れは以下です。
PDFをビルドするためのコンテナイメージ
pandoc/extraというコンテナイメージにfonts-ipafont
パッケージとHaranoAjiFonts
フォントを追加でインストールしています。
ビルドしたコンテナイメージはGitHub Container Registryで公開しています。
いざ
MarkdownからPDFを生成するためのカスタムされたPandocコンテナが必要になるので、まずはコンテナイメージを作ります。コンテナイメージを作るには以下の画像の通り、Actionsタブを選択し左メニュのbuild pandoc container
を選択するとRun workflow
が現れるので、ここからワークフローを実行します。
今回はちょうど新しいバージョンのフォントがリリースされていたのでDockerfileを変更してPushしてしまいました。
コンテナイメージがビルドできたらPDFのジェネレート準備は完了です。
Chapter
プレフィックスのMarkdownファイルをコミットします。するとActionsが動きだし、ジョブが成功するとArtifactsにPDFがアップロードされます。
Chapter
プレフィックスのmarkdownファイルを増やすとジョブが増え、並列で実行されるようになります。
まとめ
ChapterX.mdをコミットすることでPDFをジェネレートできるようになりました。PDFをビルドするためのツールやリソースをローカルに用意する必要はありません。手元ではテキストのみ書ければいいのです。PDFの構文についてはPandocの日本語ドキュメントに記載がありますので、こちらを参考にしてみてください。