📖

go-taskを使ったSphinxドキュメント運用

2024/12/01に公開

今年参画していた案件で見て以降、go-taskを使うモチベーションが上がってきています。
「モチベーションがあるうちに」ということで、最近ではライブラリのドキュメント(Sphinx製)のタスクランナーをmakeからgo-taskに置き換える試みをしてみました。

この記事では、go-taskを使ったMakefileからTaskfileへの置き換えを紹介します。
なお、置き換え時には次の点について配慮していました。

  • もともとできたことが全部できる。(自分が扱う範囲で)
  • go-taskの文法を使い、簡易に書けるところは簡易に書く。

「〇〇ってそもそも何?」な人向け

実物と使用法

go-taskのタスク定義として使うTaskfile.yamlはこんな感じになっています。

Taskfile.yaml
version: '3'

vars:
  # If you run bare environment or activated venv, set '' (blank string)
  # RUN_PYTHON: ''
  SPHINX_DEFAULT_BUILD: 'mini18n-dirhtml'
  SPHINX_OPTIONS: ''
  SPHINX_LANGUAGES:
    - 'ja'

env:
  SPHINXINTL_TRANSLATOR: "Kazuya Takei <myself@attakei.net>"

tasks:
  intl:
    desc: 'Sync i18n environment'
    dir: '{{.TASKFILE_DIR}}'
    cmds:
      - '{{.RUN_PYTHON}} sphinx-build -M gettext . _build {{.SPHINX_OPTIONS}}'
      - '{{.RUN_PYTHON}} sphinx-intl update --language={{.SPHINX_LANGUAGES | join ","}}'
  dev:
    desc: 'Run docs server'
    dir: '{{.TASKFILE_DIR}}'
    cmds:
      - '{{.RUN_PYTHON}} sphinx-autobuild -b dirhtml . _build/dirhtml'
  build-*:
    desc: 'Make docs'
    dir: '{{.TASKFILE_DIR}}'
    vars:
      TARGET: '{{index .MATCH 0}}'
    cmds:
      - '{{.RUN_PYTHON}} sphinx-build -M {{.TARGET}} . _build'
  build:
    desc: 'Make docs (default target)'
    deps:
      - 'build-{{.SPHINX_DEFAULT_BUILD}}'
  help:
    desc: 'Display help of docs'
    deps:
      - 'build-help'
  clean:
    desc: 'Clean build files of docs'
    deps:
      - 'build-clean'
  • task build-BUILDERmake BUILDERと同じ。
  • task cleanmake cleanと同じ。
  • task helpmake helpと同じ。
  • task buildSPHINX_DEFAULT_BUILDで指定したビルダーを実行するショートカット。
  • task intlSPHINX_LANGUAGESで指定したi18nリソースを生成or更新(sphinx-intlインストール時)
  • task dev (sphinx-autobuildインストール時)

Makefileが最初からあるのにgo-taskに置き換えた理由

Sphinxドキュメントの運用時において「Makefileから置き換えるメリット」は、総合的に見るとそこまで大きくありません。
というわけで、個人的な嗜好が強いのですが、go-taskを使っている理由を軽く説明してみます。

ファイルが減る

sphinx-quickstartで生成されるファイルは、Makefileの他にmake.batがあります。
後者のファイルは名前の通りWindows用なのですが、正直なところ自分の環境下で使うことがほぼありません。

とはいえ、パブリックなプロジェクトでは「Linux/macOSのmakeで使えるならmake.batでも使えるべき」となるでしょう。
つまり、「使う機会が少ないのに編集はしたほうが良い」という状況になります。
そこで、いっそのことタスクランナーを完全に統一することでファイルの削減をする方針を取りました。 [1]

インデント形式を統一できる

自分が普段扱っているコードは基本的にスペースでインデントをしています。
一方で、Makefileはタブインデントなファイルフォーマットとなっています。

エディターやIDEでなんとかなる話ではあるのですが、ターミナル等からコピペする時に事故ることがあります。 [2]
最終的に「コンテキストスイッチを減らす」目的とセットで、スペースインデントで書けるYAMLを使うことにしました。

複雑なことがしやすい

例えば、go-taskの文法では desc という要素を定義することで、 task -l でタスクの一覧を分かりやすくできます。
更に、varsでの変数定義周りの書式などを含めて、「ちょっと複雑なこと」をしやすくなっています。

やってること

今回使い始めたTaskfile.yamlを組み立てるにあたって、試したりした工夫点をまとめます。

実行環境の変数化

vars要素内に、Taskfile内で使用可能な変数としてRUN_PYTHONを定義しています。
(ただし、テンプレート内の初期構造では、コメントアウトしています)

Taskfile.yaml(抜粋)
vars:
  # If you run bare environment or activated venv, set '' (blank string)
  # RUN_PYTHON: ''

tasks:
  intl:
    desc: 'Sync i18n environment'
    dir: '{{.TASKFILE_DIR}}'
    cmds:
      - '{{.RUN_PYTHON}} sphinx-build -M gettext . _build {{.SPHINX_OPTIONS}}'

Taskfile内では、定義の各所にて{{.<変数名>}}を記述しておくと、あらかじめvars内で宣言した変数を埋め込むことができます。
上記の例ではコメントアウトしていますが、RUN_PYTHON: 'uv runとしておくと、uvを使用している環境下であることを前手にできます。

コメントアウトして変数自体の定義がない場合は、ただの空文字列として扱われます。
上記の例ではuv等を経由せずsphinx-buildを実行しようとすることになり、「venv環境下」や「Sphinxをグローバル環境下にインストールしている」ことを前提にできます。

タスクのワイルドカード化

下記の抜粋箇所では、タスク定義に*が使われています。
これはよくあるワイルドカードの記述として機能しており、build-から始まる全ての文字をタスクとして捕捉します。

Taskfile.yaml(抜粋)
tasks:
  build-*:
    desc: 'Make docs'
    dir: '{{.TASKFILE_DIR}}'
    vars:
      TARGET: '{{index .MATCH 0}}'
    cmds:
      - '{{.RUN_PYTHON}} sphinx-build -M {{.TARGET}} . _build'

このタスク自体はsphinx-buildコマンドで「何かしらのビルダーを指定してビルドを実行する」ことを目的としています。
cmds内の記述にもあるように、-Mというビルダー指定のオプションとして{{.TARGET}}を指定しています。
このTARGETはタスクをスコープとしたvars内で定義されており、{{index .MATCH 0}}を指定しています。

タスク定義時にワイルドカードを使用すると、MATCHという変数に文字列のリストが設定されます。 [3] [4]
さらに、リスト型の変数に対して指定したインデックスの箇所を指定する{{index}}を使用することで、
{{index .MATCH 0}}は「最初に指定したワイルドカードの箇所」をそのまま文字列として使用できます。

最終的にこの記述をすることによって、make XXXというビルダーを指定するmake処理をtask builder-XXXという形式に置換しています。

depsを使ったショートカット

Taskfileの最後にこのようなタスクを定義しています。

Taskfile.yaml(抜粋)
tasks:
  help:
    desc: 'Display help of docs'
    deps:
      - 'build-help'
  clean:
    desc: 'Clean build files of docs'
    deps:
      - 'build-clean'

depsはタスクの前提条件として、cmdsの実行前に他のタスクを実行したい時に記述します。

sphinx-buildで呼べるビルダーは通常のビルダーの他にも特殊な用途を持つものが存在します。

  • helpビルダー: 使用可能なビルダーの一覧を出力する。
  • cleanビルダー: 出力先となるフォルダを削除してクリーンアップする。

今回のTaskfileを定義するにあたって、これらの特殊用途のものに対しては「何をしたいか」を分かりやすくするべきでしょう。
よって、task help,task cleanという形式で実行できる方が望ましいものとなります。

実は、タスク自体はcmdsを宣言する必要はありません。
depsbuild-help,build-cleanだけを宣言しておくことで、実質的にtask helptask builder-helpのエイリアスとして機能するようになります。 [5]

ちなみにbuildも同様となっていて、SPHINX_DEFAULT_BUILD変数で指定したビルダーを使うショートカットになっています。

留意事項的なこと

個人利用がメインのライブラリに関しては、ドキュメント用のタスクはTaskfileへの置き換えが進んでいます。
少なくとも開発に使っている端末にはgo-taskがインストールされているため、困るシーンはありません。

とはいえ、OSS上でこのアプローチを採用するということは、コントリビューターにもgo-taskの使用を少なからず強制することになります。
README上で明記したり、そもそもgo-taskが無くても平気は範囲での使用に留めるなど、気をつけていくほうが良いでしょう。

脚注
  1. 当然ながらこの方法だとgo-taskのインストールが必要になりますが、嫌な場合はコマンドラインで頑張ってもらう形になります。 ↩︎

  2. 「タブ記号をコピーできない」という事象が起きがち。 ↩︎

  3. https://taskfile.dev/usage/#wildcard-arguments ↩︎

  4. build-*-* のように複数個のワイルドカードも使えるため、build-a-bと指定したらa,bのリストとなります。 ↩︎

  5. 見ての通り、build-helpは普通にワイルドカード通りに処理されていきます。 ↩︎

Discussion