社内で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-crossrefPandoc ASTを変換するフィルタを指定。別のファイルフォーマットに変換するためのフィルタ。-H listing.tex引数に指定されているファイルをヘッダの末尾に挿入。指定されているlisting.texはリポジトリ直下にコミットされている。--listingsコードブロックにLaTeXのlistingsパッケージを適用。行の折り返しやシンタックスハイライトが可能になる。--highlight-style=tangoソースコードのシンタックスハイライトスタイルを指定。-Vテンプレート変数を設定。--pdf-engine=lualatexPDFの変換で日本語が使えるように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の日本語ドキュメントに記載がありますので、こちらを参考にしてみてください。
