2020年の docker-satysfi 振り返り

この記事は SATySFi Advent Calendar 2020 の13日目の記事です。

SATySFi ユーザのみなさま今年も amutake/satysfi (以降 docker-satysfi) をご利用いただきありがとうございました (docker-satysfi は SATySFi と Satyrographos (SATySFi のパッケージマネージャ) が入った Docker イメージです。詳しくは satysfi-docker を使って SATySFi をお手軽に試す - amutake's blog をご覧ください。なお最近リポジトリの名前を satysfi-docker から docker-satysfi に変えた (理由は後述) ので、これまでは satysfi-docker と呼んでいましたがこれからは docker-satysfi と呼ぶことにします)。

Docker Hub を見ると Pull された回数が 10K+ とのことで、(もしかしたらほとんどが CI からなのかもしれませんが、) たくさんの方に使っていただいているみたいです。 ありがとうございます。

さて、今年の docker-satysfi の振り返りをしていきます。

新しいバージョンがリリースされたときに粛々と更新できた

SATySFi は今年1月にバージョン 0.0.4 が出て、現在は 0.0.5 (satyrographos-repo は 0.0.5+dev2020.09.05) になっています。

今年も昨年同様、SATySFi の新しいバージョンが出たときは docker-satysfi の方も更新することができました。 0.0.5+dev2020.09.05 だけは気がつくのがひと月ほど遅れてしまいましたが、それ以外はわりと早く追従できたのではないかなと思います。

docker-satysfi みたいなものは粛々と更新していくのが重要だと思っていますので、とりあえず今年はそれを達成できました。

まあ、新しいバージョンが出たときにやることと言っても各 Dockerfile に書かれているバージョンを書き換えて GitHub でリリースを作るだけなので、5分かからないくらいで済みます。

nightly ビルドによって SATySFi のビルドエラーにいくつか気づけた & 直せた

今年は docker-satysfi のおかげで SATySFi と Satyrographos のビルドエラーにいち早く気がつくことができ、そのうちのいくつかはパッチを送ることができました。

docker-satysfi では nightly ビルドとして毎日 SATySFi と Satyrographos の最新版のビルドをしているのですが、 SATySFi もしくは Satyrographos がビルドできなくなると nightly ビルドも失敗して、毎日しつこくビルドエラーがメールで届くようになってしまうので、 できるだけ早く直さないといけないという圧を受けることができました。実際、ものによってはビルドできなくなっても当日には直せていた気がします (レビュー・マージも素早く行っていただいたおかげ)。

docker-satysfi の利用者ではなくても、nightly ビルドの定義を見れば SATySFi をビルドできる方法がわかるので、ビルドできなくなったときは見てみてください。

パッケージの CI で使ってくれる方が増えてきた

そのままですがパッケージの CI で docker-satysfi を使っていただいている方をちょくちょく見かけるようになってきました。嬉しいです。

昨年の Advent Calendar で書いた記事 GitHub Actions で SATySFi の文書やパッケージの CI - amutake's blog のおかげかもしれません。

この昨年の記事ですが、いまは opam (opam ファイル) と satyrographos を使って依存パッケージをインストールする流れができているのでそれでやるとよりシンプルにできそうです。更新したほうがいいですがまだできていないです。 また、後述する opam-slim タグを使うと CI も1分くらい速くなります。

opam が使えてサイズが小さいイメージを作ることができた (opam-slim タグ)

まだ experimental ですが、opam-slim というタグができました。 ので軽く紹介をします。

利点と欠点

docker-satysfi にはいくつかのタグがあります。全部入りのタグ (latest, 無印)、SATySFi と Satyrographos のバイナリだけのタグ (slim)、日々の最新版を含むタグ (nightly) です。今回 opam-slim というタグが増えたのですが、他のタグに比べて opam-slim の利点はいくつかあります。

  • opam を使って satyrographos-repo にある SATySFi のパッケージをインストールできる
    • opam-slim という名前の通り、opam が使えるようになっています。
    • なお slim というタグもありますが、そちらは opam なしになります。
  • サイズが小さい
    • 同じく opam が使える latest タグは圧縮後で 800 MB 以上ありますが、opam-slim は圧縮後で 100 MB 程度です。
  • opam update が速い
    • latest に比べて opam update がかなり速いです。
    • latest はデフォルトのリポジトリ (opam-repository) が入っていてその更新もあるので遅いのですが、opam-slim は opam-repository が入っていないので、その分速いです。opam-repository は基本的には OCaml 用のリポジトリで、opam-slim タグは OCaml を切り捨てて SATySFi 専用としているので、opam-repository も入れていません。

以上が利点になります。欠点は以下です。

  • experimental である
    • experimental な理由はいろいろあるのですが、詳細は satysfi-docker の opam-slim タグ - amutake's blog をご覧いただければと思います (自分用に書いたので雑です)。
    • experimental ではありますが、satyrographos-repo にある OCaml を直接の依存に持たないパッケージだけ使う分には普通に使えると思います。
  • opam-slim 自体のビルドが遅い
    • GitHub Actions でだいたい30分くらいかかります。
  • opam-slim 自体のデバッグがつらい
    • サイズを小さくするために Docker のレイヤーキャッシュを効かせられず、デバッグがものすごく面倒です。30分後に間違いを発見してまた直して30分…という感じになります。つらい。

使い分け

現在の docker-satysfi には、latest (無印), slim, nightly, opam-slim という4つのタグがあります。

タグたくさんあるけどじゃあどれを使えばいいんだという方は、以下のように使い分けてみてください。 なんでもいいですという方は latest (無印) を使えば OK な気がします。

  1. satysfi と satyrographos のバイナリだけあればよくて、まだリリースされていない最新版を使いたい → nightly
  2. satysfi と satyrographos のバイナリだけあればよくて、バージョンは固定したい → slim
  3. OCaml コンパイラはいらないが、opam を使って satyrographos-repo にある SATySFi のパッケージをインストールして使いたい。多少トラブルが起こる可能性があってもできるだけ小さいイメージがいい → opam-slim
  4. 上記いずれも合致しない → latest (無印)

毎日の最新版を使いたいけど opam も使いたいという方に対してはいまのところ方法がないです。すみません。 nightly で opam を使いたい人がいるのか不明なので後回しになっています。nightly で opam を使いたい人は issue かなにかに書いていただけると対応するかもしれません。

(WIP) arm64 サポート

M1 Mac が出て、arm64 で docker-satysfi を使いたいユーザが今後増えそうです。 まだ M1 Mac 用の Docker は正式リリースされていないようですが、来年には正式リリースされるようなので、docker-satysfi も arm64 をサポートしなければいけません。

ということでこちらの PR で作業をしていて、とりあえず arm64 のイメージはビルドできるようになったのですが、ビルドがとっっっても遅く、3,4時間ほどかかってしまうのでマージを躊躇っています。

CI でいちいち3,4時間も待たされるのは辛いのと、じゃあ CI は amd64 だけにしてリリースだけ arm64 イメージをビルドするようにするとしても、latest や slim は数ヶ月に1度なのでいいのですが、nightly は毎日やるので毎日4時間かーとなってしまいます。まあビルドは勝手に行われるのでいいっちゃいいのですが、計算リソースもったいない感が…。

GitHub Actions で arm64 の環境を使えるようになればたぶんビルドも速くできるのですが、少なくともいまはありません。 ロードマップに Actions: Multiple hosted runner sizes, custom networking, and custom images · Issue #95 · github/roadmap · GitHub というのは見つけたのですが GitHub AE / GitHub Cloud / GitHub Server というのがなんなのかわからなかったです (Enterprise 系のやつなんですかね)。でも all ラベル (Available to all users, including a free tier. という意味らしい) がついているのでいつかは普通の人間にも使えるようになりそうです。 自分で環境を用意する (Self-hosted runner を使う) といますぐ arm64 で走らせられるらしいですがわざわざお金を出して用意するのもなあという…。

なお arm64 対応自体は、docker が公式で作っている Action (setup-buildx-action, setup-qemu-acton, build-push-action) を使えば簡単にできました。

(2020-12-20 追記) 一旦 latest, slim, opam-slim だけ arm64 サポートを入れました。0.0.5-59-g32f2525 から使えます。CI と nightly で arm64 をサポートするのは GitHub Actions に arm64 が出てからにします。

リポジトリ名を変えた

最近まで (2020/12/08 まで) は satysfi-docker というリポジトリ名だったのですが、docker-satysfi に変えました。

理由は、いろいろなリポジトリを見た感じ docker- 始まりのほうが一般的っぽいのと、satysfi-docker だと docker 関連の何かをする SATySFi パッケージのように見えるためです。

まあこれだけっちゃこれだけなので別に変えなくてもいいのですが、うーんと思いつつ半年くらいそのままでずっともやもやしていて、で変えるなら早いほうがいいので思い切って変えました。

リポジトリ名を変えた影響ですが、イメージの名前は変更していないので普通に使っている人には影響ないと思います。自分でイメージをビルドする人はもしかしたら影響があるかもしれませんがしばらくはリダイレクトされるはず?

2021年も docker-satysfi をよろしくお願いします

以上、docker-satysfi の2020年の振り返りでした。来年もよろしくお願いします。

一応、来年やりたいことも少し書いておきます。

  • 継続してメンテを続ける
    • 一番大事な気がします
  • arm64 サポートを入れる
    • マージボタンポチでいいのですが勇気が…
    • (2020-12-20 追記) マージしました
  • satyrographos のバージョンが上がったときも更新したい
    • 別のタグにするかイメージの上書きで対応? (トレードオフがあって迷い)
  • タグの整理
    • タグ多すぎ感あるので整理したいです
  • nightly ビルドのエラーを他の人も気づけるようにしたい
    • slack に投げるとか…

最後に、これは余談ですが、SATySFi には安心して後方互換性を壊していってほしいと思っています。すでに計画されているようなので改めて言う必要もないかもですが…。

Satyrographos (特に lockdown) や satyrographos-repo (特に snapshot) もありますし docker-satysfi もあるので、文書がビルドできなくなるといったことはほとんどないと思います。 docker-satysfi の最初のモチベーションも SATySFi のバージョンを固定して使いたいというものだったので、後方互換性がなくなったときに古いバージョンの SATySFi が簡単に使えるようになっていないと docker-satysfi の存在価値がなくなってしまうというか。

互換性の破壊に各種パッケージがついていくのが大変になるという懸念はありますが、いま satyrographos-repo にあるパッケージにはきちんと satysfi < 0.0.6 がついているので既存分は問題ないのと、何かあってもほとんどのパッケージが GitHub で管理されていて気軽に Issue や Pull Request を作れますし、そんなに問題にはならない気がします。そもそも SATySFi はまだバージョン 0.0.x ということもありますし、パッケージ作成者のみなさんもそれをわかって作っているはず…。

あとは SATySFi に関する記事の内容が古くなってしまうかもしれないですが、それはしょうがないというか、CHANGELOG もありますし影響は最小限に抑えられる気がします。

まあそんな感じで、こちらの準備は整っているので安心して壊してくれ、という気持ちです。

shinchoku-tairiku.satyh をリファクタしている話

これは SATySFi Advent Calendar 23 日目の記事です (なぜか12月12日付の記事になっていますが23日目の記事です…)。 22日目は zr_tex8r さんの徹底検証! SATySFiはLaTeXの代わりになるかでした。 24日目は monaqa さんの予定です。

私は進捗大陸という技術同人サークルに所属していて、進捗大陸では1年半ほど前から SATySFi を使って同人誌を書いています。 クラスファイルも自作していて、shinchoku-tairiku.satyh という名前で公開しています。

github.com

今回、shinchoku-tairiku.satyh をリファクタしようとしている (途中) ので、その話を書きたいと思います。

現状の shinchoku-tairiku.satyh の問題点

まずは現状の問題点から整理していきます。

モジュール密結合問題

進捗大陸では、2019年春に頒布した進捗大陸05 (05はナンバリング) から SATySFi を使っています。

進捗大陸05では stdjabook (だったかな。記憶が曖昧) をベースにいろいろと改変したものを使っていました。 とても大きな1つのファイルの中にごちゃごちゃと詰め込んでいたので、どことどこが結合しているのかわからず見通しが悪いコードになってしまっていました。

進捗大陸05当時のコード

nomaddo さんの記事

no-maddojp.hatenablog.com

そこで、進捗大陸06 では一つの大きなファイルを分割して見通しをよくするために (あとは見た目をいじるために) フルスクラッチでクラスライブラリを作りました。

進捗大陸06当時のコード

(進捗大陸06に SATySFi のクラスを作ってみたよという拙著の記事がありますのでよければぜひ)

一応、ある程度は分割して機能毎のモジュールを作ることができましたが、うまく分割できずに結局大きくなってしまうモジュールがあることに気が付きました。

複数の機能で、「現在の章・節」の情報 (番号やタイトルやページ番号) を使っていました。

  • 見出し
  • 目次
  • ヘッダ
  • インデックスタブ
  • キャプション
  • 参考文献

これらの機能で、一見分けられそうなに見えるが同じモジュールになっていたり、謎の依存関係が発生していたりと結局密結合になってしまっていました。

なのでこれをどうにかしたいです。

Satyrographos に対応していない問題

進捗大陸06ではフォントのダウンロードスクリプトを走らせて .satysfi 以下に入れるようにしていて、フォントハッシュはリポジトリに直接埋め込んでいました。 ちょっと微妙感あります。

また、SATySFi の先駆者のみなさんが公開してくださっている便利なパッケージを導入するためには、git submodule から使う必要がありました。まあいいといえばいいのですが面倒ではあります。

これらは Satyrographos および Satyrographos Repo を使えばやってくれるもので、Satyrographos を使うようにしたいと長らく思っていつつ対応していなかったのでこの機会に対応したいです。

(というか satyrographos 入りの docker-satysfi を作っておきながら自分では使っていないのは何事かという感じですが、opam 入りの docker image が大きすぎる問題があって Satyrographos 対応を躊躇していたのです。が opam-slim タグができたのでやる気になりました)

解決方法を考える

以上のような問題がありました。解決方法を考えていきます。

最初のモチベーションにはなかったけどやってみると改善したほうがいいと気づいた点についてもいくつか書きます。

モジュール間の接続方法の見直し

前述の通り、現在の shinchoku-tairiku.satyh はモジュールが中途半端に結合してしまっています。

モジュールは小さく単一の責務にして、モジュール間は疎結合に保ちたいです。できるなら、いつでもモジュール単体で別パッケージに切り出して使えるようにしたいです。

じゃあどうするかということで、いくつか考えた方法があるので考えた順に説明します。 なお、ここでは背景にも書いたとおり「現在の章や節の情報 (番号、ページ番号、タイトル、など)」を使いたいモジュールがたくさんあるという状況を想定しています。 また、以降では SATySFi のコードが出てきますが適当に書いているのでシンタックスや型など間違っているかもしれません……。

ref を公開する

まずは一番ナイーブな (と思われる) 方法です。

現在の章番号や章タイトルを持つような ref の値を公開してしまい、その ref をみんなが見ます。

情報提供側の例 (見出しモジュールが現在の章番号や章タイトルなどを管理しているとします):

module Headings : sig
  val current-chapter-num-ref : string ref
  val current-chapter-title-ref : inline-text ref
  ...
end = struct
  let-mutable current-chapter-num-ref <- 0
  let current-chapter-title-ref <- {}
  ...
end  

情報利用側の例 (図などに、 {章番号}.{章毎の図などの番号} + キャプションをつけるモジュール):

@import: ./headings

module Captioned : sig
  val scheme : context -> inline-text -> block-text -> block-boxes
end = struct
  let-mutable num-ref <- 0
  let scheme ctx it-caption bt =
    let () = increment num-ref in
    let num = !Headings.current-chapter-num ^ `.` ^ arabic !num-ref in
    let it-num = embed-string num in
    ...
end

この方法は、外から ref の値を書き換えて壊すことができてしまうためあまりよくなさそうなのと、 別の見出しモジュールに切り替えたくなったときに、見出しモジュールに依存している各モジュールの中身をいじらないといけなくなってしまうので、できれば避けたいです。

イベント通知ライブラリ

章や節が変わったときにそのイベントを各モジュールに通知するような仕組みがあればいいのかなと思ったので、下のライブラリを作りました。

GitHub - amutake/satysfi-event-source: A simple synchronous event-source library for SATySFi

内容はこれだけです。

@require: list

module EventSource : sig

  % `t` is the type of event-source. `'a` means the event type of the event-source.
  type 'a t

  % `make` creates a new event-source.
  val make : unit -> 'a t

  % `listen` registers the given callback function as a listener.
  val listen : ('a -> unit) -> 'a t -> unit

  % `dispatch` calls all listeners' callback immediately.
  val dispatch : 'a -> 'a t -> unit

end = struct

  type 'a t = Listeners of (('a -> unit) list) ref

  let make () =
    let-mutable fs <- [] in
    Listeners fs

  let listen f (Listeners fs) =
    fs <- f :: !fs

  let dispatch e (Listeners fs) =
    List.iter (fun f -> f e) !fs

end

使い方は以下のような感じです (リポジトリの example と同じです)。

情報提供側:

@import: ../event-source

type page-event = (|
  page-number : int;
  point : (length * length);
  title : inline-text;
|)

module Headings : sig
  val section-page-event-source : page-event EventSource.t
  val section-scheme : inline-text -> context -> block-boxes
end = struct
  let section-page-event-source = EventSource.make ()
  let section-scheme it ctx =
    let page-hook pbinfo point =
      let e = (| page-number = pbinfo#page-number; point = point; title = it |) in
      EventSource.dispatch e section-page-event-source
    in
    let ib = read-inline ctx it ++ inline-fil ++ hook-page-break page-hook in
    line-break true false ctx ib
end

情報利用側:

@import: ../event-source

module Header : sig
  val init : 'a EventSource.t -> unit constraint 'a :: (| title : inline-text |)
  val scheme : context -> block-boxes
end = struct
  let-mutable current-section-title <- {}
  let init title-events =
    let f e = current-section-title <- e#title in
    EventSource.listen f title-events
  let scheme ctx =
    let it = !current-section-title in
    let ib = inline-fil ++ read-inline ctx it ++ inline-fil in
    line-break false false ctx ib
end

(init は constraint 付きなのでいらないフィールドが入っていても大丈夫)

モジュールを取りまとめてつなげるモジュール:

@import: ./headings
@import: ./header

module Class : sig
  val document : block-text -> document
end = struct
  let document bt =
    % connects header and headings
    let () = Header.init Headings.section-page-event-source in
    % ...snip
end

この方法は、情報提供側と情報利用側に依存関係がないので、各モジュールが疎結合になっていますが、 これだけのためにわざわざよくわからない型を使ってライブラリの読者を混乱させるのもな、と思ったのでやめました。

あとモジュール単体をライブラリとして切り出したとき、ライブラリのインタフェースがこれだったらちょっと「えっ」と思うと思います。 特に init がよくわからないです。

callback listener を登録させる

章や節の情報提供側は callback listener を登録できるインタフェースを作っておいて、 章や節の情報利用側には現在の章や節の情報を登録できるインタフェースを作っておいて、 document 関数の中でそれらをつなげる方法です。event-source と似ていますが、EventSource.t はインタフェースには出てきません。

情報提供側:

type heading-changed = (|
  num : string;
  title : inline-text;
|)

module Headings : sig
  val chapter-scheme : context -> inline-text -> block-text -> block-boxes
  val add-chapter-changed-listener : (heading-changed -> unit) -> unit
end = struct
  let-mutable current-chapter-num <- 0
  let-mutable chapter-changed-listeners <- []
  let chapter-scheme ctx it-title bt-inner =
    let () = increment current-chapter-num in
    let e = (|
      num = arabic !current-chapter-num;
      title = it-title
    |) in
    let () = List.iter (fun l -> l e) !chapter-changed-listeners in
    ...
  let add-chapter-changed-listener l =
    chapter-changed-listeners <- l :: !chapter-changed-listeners
end

情報利用側:

module Captioned : sig
  val scheme : context -> inline-text -> block-text -> block-boxes
  val set-base-num : string -> unit
end = struct
  let-mutable base-num-ref <- ` `
  let-mutable num-ref <- 0
  let scheme ctx it-caption bt =
    let () = increment num-ref in
    let num = !base-num-ref ^ `.` ^ arabic !num-ref in
    let it-num = embed-string num in
    ...
  let set-base-num base-num =
    let () = base-num-ref <- base-num in
    num-ref <- 0
end

document 関数:

@import: ./headings
@import: ./captioned

module Class : sig
  val document : block-text -> document
end = struct
  let document bt =
    % モジュールを接続
    let () = Headings.add-chapter-changed-listener (fun e ->
      Captioned.set-base-num e#num
    ) in
    % ...snip
end

つまらないですがもうこれでいい気がしています。わかりやすいしモジュール間の謎の依存関係もなくて単体で切り出してもそんなに違和感ないと思います。

こういう形なら、モジュールを捨てたり他のモジュールに入れ替えたりするのもやりやすそうです。

というわけで shinchoku-tairiku.satyh ではこの方法でモジュールを疎結合に保っていきたいと思います。

Satyrographos 対応

これは特に難しいことはなく、適当なディレクトリで satyrographos new lib class-shinchoku-tairiku して生成されたファイルをリポジトリにぶちこんで適当に編集するだけです。

(以降、satyrographos new lib class-shinchoku-tairiku して生成される satysfi-class-shinchoku-tairiku 側をライブラリ、 satysfi-class-shinchoku-tairiku-doc 側をドキュメントと呼ぶことにします)

ただ、ライブラリの開発は opam を入れずに docker しか使わない開発方法と相性が悪いなと感じています。 ドキュメントであれば docker だけで開発できます。docker の中で依存ライブラリをインストールして $REPO/.satysfi にコピーというのを一度すればあとは依存ライブラリの再インストールは必要ありません (依存ライブラリを増やしたりしない限り)。

つまり、

docker -it --rm -v $(pwd):/satysfi amutake/satysfi sh -c "opam pin add . --no-action && opam install satysfi-class-shinchoku-tairiku-doc --deps-only && satyrographos install --output .satysfi/dist --copy"

しておけばいいです。

が、docker オンリーでライブラリを開発する場合には問題があって、ライブラリの開発ではライブラリを編集してそのドキュメントを編集するという流れがあると思うのですが、ライブラリの変更を .satysfi に反映させるためには .satysfi を消してもう一度依存ライブラリを全部インストールし直す必要があります (なおここではドキュメントからライブラリを使う際は @import ではなく @require を使う想定)。shinchoku-tairiku.satyh は satysfi-fonts-noto-{sans,serif}-cjk-jp に依存しているのですが、これをインストールするのに手元で実行すると数分かかってしまって大変です。

なので、ライブラリの開発ではおとなしく opam をホストにインストールして、時間がかかるけど switch もちゃんと作って使うのがよさそうです。

cross-reference のキーの扱い

shinchoku-tairiku.satyh では \ref コマンドで章や図、参考文献などの番号を取得できるようになっています。

この \ref コマンドは、典型的には各モジュールのなかで label ^ `:num` のような文字列を cross-reference のキーとして番号を保存しておき、 \ref コマンドの定義の中で label ^ `:num` の番号を取得する、という作り方になると思います。

なので、この「キーの作り方は label ^ `:num` である」というモジュール内部の知識が外側に漏れ出ていることになります。

これが思いの外つらくて、クラスライブラリが小さいうちはいいのですが大きくなってくるとどこで何がどうやって登録されていてどこから使われているのかよくわからなくなってきます。 \ref だけならいいのですが別のモジュールから使われていたり…。cross-reference を使うとモジュールの依存関係として明示的に @import として現れるわけではないので、それもつらい感じがします。

これに対する解決策ですが、モジュールの疎結合化のところにも書いたように callback listener を設定して document 側で cross reference の処理をする方法がひとつあります。

が、流石にそれはやりすぎなのかなという気もしていて、 そもそも val chapter-scheme : context -> string -> inline-text -> block-text -> block-boxes という関数があったら、label (string) を渡すということはその label で章番号を保存することを期待しているので、callback listener 側で cross-reference を設定するのはあまりよくない気がします。では chapter-scheme にはラベルを渡さないようにして、番号をタプルかなにかで返すようにして呼び出し側で設定するか?というと、それも微妙…。

ということで、(あまりいい解決方法ではない気もするのですが、) 以下のような RefNum モジュールを作って 外部仕様としてモジュールのインタフェースのコメントに「RefNum モジュールに番号が登録されます」と明記するようにしました。

module RefNum : sig
  val get : string -> string
  val set : string -> string -> unit
end = struct
  let make-key label = 
    `shinchoku-tairiku:` ^ label ^ `:num`
  let get label = 
    match make-key label |> get-cross-reference with
    | None -> `?`
    | Some n -> n
  let set label num =
    let key = make-key label in
    register-cross-reference key num
end

これでキーの生成については RefNum モジュールに隠蔽されるのでまあ、という感じです。

あるいは、章・図・参考文献などを参照するときは、専用のコマンド (\ref-chapter, \ref-figure, \ref-bib など) を用意することにして、各モジュールでラベルから番号を取得する口を開けるようにしたほうが自然かもしれません。そうするとラベルの生成についてもモジュール内に閉じます。

ですが今回はリファクタリングということでいままでのコマンドを壊さないように直すことが目的だったのでとりあえず全部 \ref のままにしています。

コマンドの定義を1ファイルに集める

いままでは各モジュールにインラインコマンドやブロックコマンドを定義していたのですが、クラスライブラリのインタフェースとなるファイル (class-shinchoku-tairiku/shinchoku-tairiku) に全て置くようにしました。

各モジュールでは context を受け取って block-boxes または inline-boxes を返すような関数だけ定義しておいて、インタフェースとなるファイルからそれらを呼び出すようにします。

利点としては、

  • どういったコマンドが使えるかについてはインタフェースとなるファイルだけを見ればよくなる
  • インタフェースとなるファイルだけインポートすればいい
    • 現状はあるファイルをインポートするとそのファイルがインポートしているファイルに定義されているコマンド・関数・モジュールもすべて使えるようになるので、各モジュールのファイルにコマンドを定義するのでもインタフェースとなるファイルがそれらをインポートしていればコマンドは使えるようになりますが、SATySFi slack で「この挙動は今後変わるかも」という話がありました

といった点があります。

なお、この「context を受け取って block-boxes または inline-boxes を返すような関数」はSATySFi の標準ライブラリにならってなんちゃら scheme という名前にしましたが、個人的には Flutter のように build-* のような名前が好みです。

設定値の問題

複数モジュールにまたがる設定値 (フォントやテキスト幅など) は config.satyh というファイルにハードコードされているのですが、すごく微妙感があって、これをモジュール毎に最小限だけ設定したいです。

なぜこうなっているかというと、各モジュールから設定にアクセスするのがこういう形でないととても面倒だからです (関数にいちいち渡したり ref で参照するとかになる)。

また、shinchoku-tairiku.satyh では標準ライブラリの itemize.satyh をコピーしてきてリストアイテムの間の幅だけ調整しているのですが (そのためLGPLライセンスになっている)、コードを再定義せずとも幅だけ外から渡せるようになるといいなと思います。

これらは残念ながらいい解決方法があまり思い浮かびません。いちいち関数にクソデカレコードを渡すわけにもいかないですし、ref で外から設定するのもなあという…。

ですが、モジュールタイプとモジュールファンクタが入ればだいぶきれいに書けるようになるのかなと思います。 例えば以下のようなものが書けるようになって再利用性が上がりそうです。

ライブラリ側:

module type ParagraphConfig = sig
  val indent : context -> length
end

module Paragraph (C : ParagraphConfig) : sig
  val scheme : context -> inline-text -> block-boxes
end = struct
  let scheme ctx it =
    let ib = inline-skip (C.indent ctx) ++ read-inline ctx it ++ inline-fil in
    line-break true true ctx ib
end

ライブラリの利用者側:

module MyParagraphConfig : ParagraphConfig = struct
  let indent = get-font-size
end

module MyParagraph = Paragraph MyParagraphConfig

module MyClass : sig
  direct +p : [inline-text] block-cmd
end = struct
  let-block ctx +p it = MyParagraph.scheme ctx it
end

標準ライブラリが全部この形になるとそれはそれで気軽に使えなくなったりしてしまうのかなとは思うのですが、 個人的にはこの形になってくれると嬉しいなーと思います。

F-ing modules が入るとできるようになるのでしょうか?楽しみです。

(…ここまで書いてから gfn さんの資料 SATySFiのこれからの課題たち を改めて見直すと普通に同じことが書いてありました)

テストの存在

このようなリファクタをする前には、まずテストを書くことをおすすめします (自戒)。

テスト書かずに進めると、変更前と変更後で挙動を変えていないことを確かめるのがとても面倒になってしまいます。

組版結果 (PDF) のテスト方法は、

などいろいろありそうです。

このへんしっかりやられているのが yabaitech.tokyo さんのクラス https://github.com/yabaitechtokyo/satysfi-class-yabaitech です。

他のクラスライブラリ

他にもクラスライブラリはたくさんありますが、あまり読めていません 🙇‍♂️

複数のファイルに分かれているクラスライブラリは、自分が見つけた範囲だと、

(他にもあったら教えて下さい)

特に yabaitech.tokyo さんのは状態を持つ部分と見た目の部分で分かれている実装になっていそうで参考になりそうです。

おわりに + 進捗大陸08の宣伝

以上、shinchoku-tairiku.satyh をリファクタしている話でした。

当初はリファクタ完了の状態でこの記事を出したかったので「〜しました」と言っている箇所が多いのですが、まだ shinchoku-tairiku.satyh に入っていないものもたくさんあります… 🙇‍♂️

やってみてこうすればできるだろうというところまではわかっているのですが、そこで時間が来てしまいました。 なにかあったらまた追記しようと思います。

そして、shinchoku-tairiku.satyh を使っているサークル進捗大陸の新刊が、12月26日から来年1月6日までに行われる技術書典10にて頒布される予定です。

techbookfest.org

techbookfest.org

ぜひよろしくお願いします🙏

なお今回も内容は GitHub で公開されています GitHub - shinchoku-tairiku/book08

SATySFi で書かれた本などのまとめを作りました

この記事は SATySFi Advent Calendar 2020 8日目の記事です。7日目は abenori さんによるSATySFiで可換図式でした。9日目は fiveseven さんの予定です。

表題の通り、SATySFi で書かれた本などのまとめを作ってみました。こちらのリンクにあります。

最新のリストは上に貼った GitHub のリンクからご覧いただきたいのですが、こちらにも2020/12/08時点でのリストも載せておきます。

title author published_at
yabaitech.tokyo vol.5 yabaitech.tokyo 2020-09-12
進捗大陸07 進捗大陸 2020-09-12
yabaitech.tokyo vol.4 yabaitech.tokyo 2020-03-07
OS Girls 2 hikalium 2019-09-22
yabaitech.tokyo vol.3 yabaitech.tokyo 2019-09-22
進捗大陸06 進捗大陸 2019-09-22
ニポリ 第16号 開成学園パズル研究部 2019-09-21
OS Girls hikalium 2019-04-14
yabaitech.tokyo vol.2 yabaitech.tokyo 2019-04-14
進捗大陸05 進捗大陸 2019-04-14
Ruby で好みの日本酒分類をお手伝い ~ 準備編 ~ katsuyoshi 2018-10-08
The SATySFibook Takashi SUWA 2018-10-08
yabaitech.tokyo vol.1 yabaitech.tokyo 2018-10-08

(公開日の降順。同じ日付の場合はタイトル順)

以上の13冊になります。多いのか少ないのか…。

いまのところ著者の方による「SATySFi を使って書きました」という宣伝ツイート・ブログ記事・発表があったものだけ (自分が見つけたものだけ) を載せているつもりです。 勝手に載せているので、載せてほしくないなどあれば教えていただければ削除します。

たぶん自分が把握できていないものもあると思うので、そのときは教えて下さい or この記事のリストへの追加方法のところに書いてある方法でプルリクエストください。

また、公開日は本文内や奥付に書いてあるものについてはそのとおりに (改訂されている場合は初版の日付に) しましたが、ないものついてはそれっぽい日付 (技術書典の開催日など) にしています。 URL も適当にそれっぽいものにしてしまっているので、「こちらのほうがいい」などあれば教えて下さい。

モチベーション

おそらく、SATySFi を使ってなにか書いてみようかなと考えている方の中には「SATySFi を使うとどんな見た目の文書が作れるんだろう」というのが気になる方もいるのではないかなと思います。少なくとも自分は最初そうでした。

自分 (技術同人誌のサークルである進捗大陸に所属していて SATySFi を使っています) が SATySFi に初めて触れた頃には SATySFi の事例はほとんどなくて、 なにができるかもよくわかっていないまま書き始めたのですが、いまは (少なくとも2年前よりは) いろいろあるので、 SATySFi でどういうことができるかは以前よりはわかるのではないかと思います。

ただ SATySFi 事例がまとまっているところはなさそうだったので (もし既にあったらすみません)、じゃあということでまとめてみました。 参考にしていただければ幸いです。

とはいってもいまのところはクラスファイル自作勢ばかりで、同等の見た目を実現するためにはそこそこ SATySFi に詳しくならないといけない気がするのですが、 そのうちクラスファイルも充実してきて例えば技術同人誌に適した標準的な (無難な) クラスファイルなども出てくるのではと思っていて、そのくらいまでいけばまた事例も増えてくるのかなと思います。 たぶん普通の人は自分でコマンドなんて定義したくなくて (学習コストを払いたくなくて)、ありもので執筆体験良く書ければいいと思うので。

(ここから宣伝)

また、邪なモチベーションとしては、リストを作ることで弊サークル進捗大陸の宣伝にもなりそうという気持ちもまあないといえば嘘になります。 なお次の技術書典10でも SATySFi で書いた新刊を出す予定なのでよろしくお願いします!(ただ今回は自分出ません…すみません)

他の SATySFi ユーザの方も、ぜひ著書をリストに追加して宣伝していってください。

(ここまで宣伝)

リストへの追加方法

おそらくですが、実は SATySFi で書かれているけれど自分が拾えていないものもあるのではないかと思っています。あればぜひ amutake までお知らせください。

もしくは、やり方がわかる方は GitHubリポジトリにプルリクエストを送っていただけると大変助かります。

data.csv というCSVファイルがあるのでそれだけ適当に編集してプルリクエストを送っていただければそれだけで大丈夫です。 data.csv が変更されると README には勝手に反映されるようになっているので、README.md をいじる必要はありません。data.csv 内の順序も気にしなくて大丈夫です。

なお、2020/12/08現在は同人誌と部誌しかないですが、SATySFi で作られた文書であればなんでも歓迎します。 なんでも受け入れているとリストが大量になって見づらくなるかもしれませんが、それはそれで喜ばしいことなので気にしないことにします。

という感じです。よろしくお願いします。

satysfi-docker の opam-slim タグ

(これは主に自分の備忘のための記事です)

最近出た opam-bin を使って、opam で satysfi パッケージをインストールできる小さい docker image を作りました。satysfi-docker の opam-slim タグです (experimental 扱い)。

satysfi-docker については

satysfi-docker を使って SATySFi をお手軽に試す - amutake's blog

を参照してください。

2020-12-20 追記

opam-bin はやめました。

依存を消して無理やりインストールする方法は opam-bin を使わなくてもできることに気がついたので、そうすることにしました。ビルド時間も普通になり、デバッグも簡単になりました。

github.com

opam ファイルのフィールド順に依存しているのがやばいですがビルドできなくなったらすぐ直せるでしょう…。

背景

satyrographos-repo というリポジトリがあります。これは satysfi のパッケージを集めた opam リポジトリです。 satyrographos-repo にある SATySFi のパッケージは、そのほとんどが satysfi と satyrographos を依存に持っているので、satysfi と satyrographos を opam 経由でインストールしていないとインストールできません。 amutake/satysfi:latest はそういうわけで opam で satysfi と satyrographos をインストールしたものをそのまま残していて、ocaml コンパイラと satysfi/satyrographos の依存ライブラリが全部入っているのでかなり大きいイメージになってしまっていました。

そこで amutake/satysfi:slim という小さいイメージを作ったのですが、これは satysfi と satyrographos のバイナリだけを入れたものになっていて opam 環境がありません。 satysfi 単体で使うならいいのですが、opam がないと satyrographos が入っている意味がほぼありません (なくはないのですが、よさの9割が失われてしまう linter などあるので9割は言い過ぎでしたすみません)。

現状では satysfi の外部パッケージを使いたい (opam を使いたい) なら latest、いらないなら slim、という使い分けをする感じになっています。

最近は satyrographos-repo に便利なパッケージが増えてきていて、opam を使いたい場面も多くなってきました。 ただ latest は大きすぎて CI で使うといちいちダウンロードだけで30秒くらい1分以上かかってしまうので、「うーんじゃあ git submodule にしちゃうか」みたいな気持ちになっていました。

satysfi の slack を見ていると、opam-bin というのが最近出て satysfi-docker のイメージも小さくできるのではという話があったので、やってみました。

opam-bin

opam-bin はビルド済みバイナリの opam リポジトリを簡単に作れる opam plugin です。一度ビルドしてしまえば再インストールは速く行えます。

詳しくは https://ocamlpro.github.io/opam-bin/ を見てください。でもところどころ動かないことがあります。ハマりポイントメモとしては、どうも依存ライブラリは全部 opam-bin がある状態でインストールされていないとローカルのバイナリ用 opam リポジトリ (local-bin という名前) に登録されないようで、つまりは opam-bin をインストールしたあとに opam switch して別の switch を新しく作り直さないといけないところでハマりました。追記: いまドキュメントを改めて見たら普通にこのこと書いてありました…

あとなんとなく static なバイナリを生成してくれるのかなと思っていましたがそういうやつではありませんでした。

やったこと

Experimental opam-slim tag by amutake · Pull Request #38 · amutake/satysfi-docker · GitHub

opam-bin はビルド済みバイナリの opam リポジトリを作ってくれますが、各パッケージの依存関係は (いいのか悪いのか) 保存されます。 なので、opam-bin を普通に使っているだけでは今回の目的にはあまり効果がありません。satysfi のビルド済みバイナリを再インストールしても結局依存ライブラリが全部インストールされてしまうからです。

ですが、他の switch に移ってもビルド済みバイナリのインストールができる、そして依存ライブラリの定義を消してもインストールが可能という特徴があります (普通は当たり前ですが依存ライブラリの定義を空にするとビルドできなくなる)。

そこで、opam-bin を使ってバイナリだけインストールしつつ、opam pin で local-bin からコピーをしてきて opam ファイルをいじくって依存ライブラリを無理やり空にしました。 これで依存ライブラリをインストールすることなしに satysfi と satyrographos をインストールすることができました。

ただ、opam-bin を使うとバージョンが 元のバージョン+bin+ハッシュ値っぽい文字列 になってしまい、これだと satysfi の特定のバージョンでしか動かないパッケージがインストールできない問題が出てきそうです。 これに対してもまた opam ファイルをいじくってバージョンを元のバージョンに戻してインストールするようにしました。

…というあまりよろしくなさそうなことをしました。あまりよろしくないのでいつトラブルが発生するかわからないということで experimental 扱いにしています。 でも適当に自分で使う分には便利だと思います。壊れたら教えて下さい。

これによってイメージのサイズを277MB (圧縮後で約100MB) にすることができました。latest は2.51GB (圧縮後で約800MB) なので CI でもだいぶ使いやすくなったと思います。 たぶん自分はこのタグだけ使う感じになりそうです。

たぶん、あるパッケージを opam install せずにインストール済み扱いにする方法があればもっと簡単にできそう & opam-bin を使わなくてもいいのですが、探しても見つかりませんでした。 どちらにしろいい方法ではないと思うのですが、opam-bin を使う方法はいちいちデバッグに時間がかかって辛すぎる (switch を分けないといけない関係で ocaml コンパイラのビルドが2回走る。イメージを小さくするためにキャッシュも効かせられないので、opam でビルド済みの ocaml コンパイラを使い回す方法があればいいのですが…。まあ system 使えということなのかもしれません) ので、それをなくせるだけでだいぶ嬉しいです。

他にもなにかいい方法を知っている方がいれば教えて下さい。

他メモ

  • conf-satysfi と conf-satyrographos を作って satyrographos-repo のパッケージ群をそれに依存するようにすると satysfi と satyrographos のバイナリだけ持っておけばよくなったりするのかな、と思いました。conf-* のことをあまりよく知らないのでそれが本当にいいかどうかはわかりません。opam にある各種 ocaml ライブラリも依存に ocaml が入っているのでだめかもしれません。
  • そろそろタグの名前の整理をしたい気がしています。opam-slim が問題なさそう (各種ハックを消せた) なら、今の opam-slim を latest に、今の latest を full かなにかに、今の slim と nightly は消して、satysfi と satysfi-dist だけインストールしてあるものを slim かなにかに、nightly は opam-slim の nightly 版に、という感じにしたい気がしています。
    • あといままでは satysfi の更新に合わせてイメージを更新していて satyrographos のバージョンの更新もそれに合わせてやっていたのですが、satysfi よりも satyrographos のほうが更新が早い (そして satyrographos のバージョンが古くなってしまう) ので、 amutake/satysfi:0.0.5-satyrographos-0.0.2.8 みたいなタグにするのもありかもと思っています。
    • あるいは イメージの名前を分けてしまって、satysfi と satyrographos にするのもいいかもしれません。
  • あと全然関係ないですがリポジトリの名前を satysfi-docker から docker-satysfi に変えたいです。docker は prefix についているもののほうが多そうなのと、 satysfi-docker だと docker 関連のコマンドが詰まった SATySFi パッケージみたいな名前に見えます。docker 関連のコマンドって何というのはありますが…。

GitHub Actions で SATySFi の文書やパッケージの CI

これは SATySFi Advent Calendar 2019 6日目の記事です。 5日目はzr_tex8rさんによるSATySFiコード中で整数を16進数で書きたいでした。

3日目の記事では satysfi-docker の紹介をしましたが、今回は satysfi-docker と GitHub Actions を使って SATySFi パッケージや文書の CI をする例を紹介したいと思います。

GitHub Actions は最近正式版になった GitHub の機能で、GitHub の様々なイベント (リポジトリへのプッシュ、Pull Request、リリースの作成、などなど) に反応してワークフローと呼ばれる一連のタスクを実行できるというものです。

GitHub のサービスなだけあって GitHub との親和性は抜群で、CI サービスとしては後発なのでいろいろやりやすかったり (個人の感想です)、Microsoft パワーなのか CI サービスとしては利用制限が緩かったりします。

サンプルリポジトリ

github.com

こちらがサンプルコードが入ったリポジトリです。 簡単なものから少しだけ複雑なものまで5つの例を用意したので、順に説明していきます。

  • 01-simple-example
  • 02-library-example
  • 03-submodule-example
  • 04-satyrographos-example
  • 05-custom-action-example

01-simple-example

一番簡単な例です。 doc.saty というファイルがあり、リポジトリへの push 毎にビルドできるか確認したいという状況です。 依存パッケージもなく、文書をあるバージョンの SATySFi でだけビルドできればいいならこの方法で十分だと思います。

name: build   # ワークフローの名前は build (バッヂをつける際はこの名前で参照することになります)
on: push      # push 毎にワークフローを実行

jobs: # job 定義
  build-simple-example:      # job 定義の名前
    runs-on: ubuntu-latest   # ubuntu 環境で実行
    steps:
      - uses: actions/checkout@v1   # ソースコードをチェックアウトしてくる
      # satysfi-docker を使ってビルド (この例くらいなら slim タグを使ったほうがいいですが簡単のため latest)
      - run: docker run --rm -v $(pwd):/satysfi amutake/satysfi satysfi doc.saty  
        # 今回は 01-simple-example というディレクトリの下にあるのでその中でコマンドを実行
        working-directory: ./01-simple-example   
      - uses: actions/upload-artifact@master
        with:
          name: 01-simple-example          # 01-simple-example という名前で、
          path: 01-simple-example/doc.pdf  # doc.pdf をアップロード

GitHub Actions ではジョブの中で普通に docker run できるので、satysfi-docker の使い方の通りに satysfi を実行するだけです。

生成した PDF はアーティファクトとしてアップロード・ダウンロードできます。 勝手に zip 形式に圧縮されます。PDF はブラウザから確認できたら嬉しいのですがダウンロードして展開してとやらなければいけないのでちょっと面倒。

実行されたワークフローは Actions タブ から確認することができます。

↓ここからダウンロードできるようになります。

f:id:amutake:20191202194831p:plain

02-library-example

次は SATySFi のライブラリの CI をしたい例です。以下のような状況だと思ってください。

  • commands.satyh をライブラリとして配布したい
  • commands.satyh の使い方の説明ドキュメント doc.saty (この中で commands.saty を使っている) をテストとしてビルド・目視確認したい
  • ライブラリなので SATySFi の複数のバージョンで使えるか確認したい

この場合は下のようにするといいと思います。

  build-library-example:
    runs-on: ubuntu-latest
    strategy:
      matrix:
         # 確かめたい SATySFi のバージョンを列挙
        version: [0.0.3-slim, 0.0.3-dev2019.11.16-slim, nightly] 
    steps:
      - uses: actions/checkout@v1
      # matrix.version によってタグを指定
      - run: docker run --rm -v $(pwd):/satysfi amutake/satysfi:${{ matrix.version }} satysfi doc.saty  
        working-directory: ./02-library-example
      - uses: actions/upload-artifact@master
        with:
          name: 02-library-example_${{ matrix.version }}
          path: 02-library-example/doc.pdf

01-simple-example との差分は matrix を使っている部分です。 GitHub Actions の matrix 機能を使って簡単に複数バージョンでのビルドができます。今回はバージョン0.0.3, 2019-11-16時点のスナップショット、nightly で確認しています。 そしてバージョンごとに成果物がアーティファクトとしてアップロードされます。

ライブラリのドキュメントや使用例のPDFはリポジトリに含めておきたい (つまり生成した PDF をリポジトリに push したい) ことが多いと思いますが、 その場合も、 github actions push to repo とかで検索すればいろいろ出てくるので簡単に実現できると思います (試してはないですが…)。

03-submodule-example

以降の3つは依存パッケージがある場合の例です。 依存パッケージがある場合にビルドする方法はいくつかありますがこの例では submodule を使う方法を説明します。

  build-submodule-example:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        version: [0.0.3-dev2019.11.16-slim, 0.0.3-dev2019.07.14-slim]
    steps:
      - uses: actions/checkout@v1
        with:
          submodules: recursive  # submodules: recursive を指定することで submodule init --recursive をしてくれるようになる
      - run: docker run --rm -v $(pwd):/satysfi amutake/satysfi:${{ matrix.version }} satysfi doc.saty
        working-directory: ./03-submodule-example
      - uses: actions/upload-artifact@master
        with:
          name: 03-submodule-example_${{ matrix.version }}
          path: 03-submodule-example/doc.pdf

02-library-example との差分は checkout に submodules: recursive を指定していることです。 あとは doc.saty から @import: ... で読み込むと submodule にしたライブラリが使えるようになります。

この方法の利点としては、slim が使えるので CI も速い (ダウンロード時間が小さい) のと、ジョブ定義もシンプルに保てて手軽なことです。 ただ、文書を作成したい場合はこれでいいかもしれませんが、@import を使っているため、ライブラリとして配布したい場合にこの方法を使うのは配布方法によっては微妙かもしれません (ライブラリの利用者にも submodule で recursive update してもらうことになるかも)。

04-satyrographos-example

こちらは依存パッケージを Satyrographos と opam を使ってインストールする例です。

  build-satyrographos-example:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        version: [0.0.3-dev2019.11.16]
    container:
      image: amutake/satysfi:${{ matrix.version }}   # run で使うイメージを指定
    steps:
      - uses: actions/checkout@v1
      - run: |
          export HOME=/root # workaround
          eval $(opam env)
          opam update
          opam install satysfi-grcnum # opam で SATySFi のライブラリをインストール
          satyrographos install -library grcnum # Satyrographos で SATySFi のライブラリをインストール
          satysfi doc.saty
        working-directory: ./04-satyrographos-example
      - uses: actions/upload-artifact@master
        with:
          name: 04-satyrographos-example_${{ matrix.version }}
          path: 04-satyrographos-example/doc.pdf

差分は run のところで、 opam と Satyrographos を使って SATySFi のライブラリ (ここでは satysfi-grcnum) をインストールしています。

また、 container でジョブの中でコマンドを実行するコンテナイメージを指定しています。 こうしないと、

run: docker run --rm -v $(pwd):/satysfi amutake/satysfi:${{ matrix.version }} bash -c "opam update && opam install satysfi-grcnum && satyrographos install -library grcnum && satysfi doc.saty"`

と長々と書かなければならずちょっとだるいです。ただ GitHub Actions は環境変数 HOME を上書きして走らせるため、 opam がインストールされてあるディレクトリに指定し直しています。

こういう感じで opam リポジトリに登録されている SATySFi のパッケージを使うことができます。

(なお、インストールするパッケージはワークフローの定義に直接書くのではなくパッケージ一覧を書いたファイルかなにかをリポジトリに入れておくのがいいと思います)

05-custom-action-example

SATySFi 用の Action を作って使う例です。 04-satyrographos-example は run にコマンドがベタ書きされていて見づらかったので、 Action としてモジュール化しています。

以下がジョブの定義になります。

  build-custom-action-example:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: ./.github/actions/satysfi
        with:
          main: ./05-custom-action-example/doc.saty # メインファイルを指定
          packages: 'satysfi-fonts-theano satysfi-grcnum' # パッケージ一覧を指定
      - uses: actions/upload-artifact@master
        with:
          name: 05-custom-action-example
          path: 05-custom-action-example/doc.pdf

.github/actions/satysfi に Action の定義を入れておいて、ジョブの中からはそれを参照しています。

以下が Action の定義 (.github/actions/satysfi/action.yml) です。いわゆる container action になっています。mainpackages を受け取って、docker run の引数として与えています。

name: 'SATySFi'
description: 'Build SATySFi file'
inputs:
  main:
    description: 'Target file which will be built via SATySFi.'
    required: true
  packages:
    description: 'Space separated SATySFi packages which will be installed via opam & Satyrographos'
    required: false
    default: ''
outputs:
runs:
  using: docker
  image: Dockerfile
  args:
    - ${{ inputs.main }}
    - ${{ inputs.packages }}

以下が docker のエントリポイントとなるファイル (.github/actions/satysfi/entrypoint.sh) です。 ここに、04-satyrographos-example に長々と書いていたコマンドの列を書いています。

#!/bin/sh -l

main=$1
shift
packages=$@

# workaround. GitHub Actions overwrites $HOME.
export HOME=/root
eval $(opam env)

opam update
opam install $packages

satyrographos install

satysfi $main

あとはsatysfi-docker をベースイメージにした Dockerfile を書いてやればそれで完了です。CI が走るたびにイメージがビルドされて Action が実行されます。

おわりに

この記事では、GitHub Actions を使って SATySFi のパッケージや文書の CI をする説明をしました。 GitHub Actions を使うと、依存ライブラリをインストールしつつ文書をコンパイルできたり、 matrix を使ってライブラリがサポートしたいバージョンで簡単に CI を走らせることができたりします。 ライブラリの信頼性を高めたり、複数人で文書を書く際には有効だと思うので、ぜひ使ってみてください。

satysfi-docker を使って SATySFi をお手軽に試す

この記事は SATySFi Advent Calendar 2019 3日目の記事です。 2日目はSATySFiの作者であるgfngfnさんによるSATySFiの2段組機能でした。

追記

2020/12/08: satysfi-docker から docker-satysfi に名前を変更しました。以降の文章は面倒なので satysfi-docker のままですが、今後書く文章には docker-satysfi を使います。

前置き

SATySFi をインストールするためには opam と OCaml コンパイラのインストールが必要ですが、OCaml を使わない人にとっては SATySFi のためだけに opam 環境を入れるのは面倒です。 opam をすでに使っている人にとっても、SATySFi のインストールには専用の opam repository を使うので環境を分けるために SATySFi 用の switch を作って OCaml コンパイラをビルドして〜といったことをすることになると思います。この作業もそれなりに時間がかかるので面倒です。 ようやく opam 環境を用意して SATySFi をインストールする環境が整った後も、SATySFi のインストールでまた少し時間がかかります。 いずれもやればよくはあるのですが、少しだけ試したいときでも時間がかかりますし、罠にはまる可能性もありますし、もっとお手軽に SATySFi を試せたらいいですよね。

ホストの環境を壊さずにソフトウェアを試すために、最近は Docker を使うことが多くなっていると思います。 SATySFi も有志の方が作ってくださった Docker イメージがあったのですが、バージョンが古いあるいは不明だったり、メンテされているか怪しかったので、自分で作ってみました。

github.com

satysfi-docker の使い方

まず docker をインストールしていない方はしてください (結局インストールするのかという感じですが、opam よりは Docker のほうがすでにインストールしている人が多そうなのと、いろいろ便利なので入れておいて損はないのではと思います。Docker のインストールに関する資料も少なくとも SATySFi のそれよりは多いはずです)。なお、以降 Docker に関する説明は省略します。

Docker Developer Tools | Docker

Docker をインストールできたら、あとは .saty ファイル (今回は demo.saty とします。 ) があるディレクトリに行き、

docker run --rm -v $(pwd):/satysfi amutake/satysfi satysfi demo.saty 

で文書 (ここでは demo.pdf) がビルドできます (イメージは勝手にダウンロードされてきます)。イメージのダウンロードと展開に多少時間がかかります(後述する slim タグを使うともっと速くなります)が、何も考えなくていいのでお手軽なのではないかと思います。

各タグの紹介

amutake/satysfi にはいくつかタグがあります。 無印、slim、nightly と、バージョン付きかそうでないかの組み合わせがあります。 すべてのイメージで SATySFi と Satyrographos (SATySFi のパッケージマネージャ。これについては7日目、12日目で作者の na4zagin3 さんによる解説があることと思います) が含まれていますが、それぞれ特徴があるので、適切に使い分けていただけたらと思います。

タグ 説明
latest SATySFi の新しいバージョンが出たら更新されます (2019-11-19 時点では 0.0.3-dev2019.11.16 と同じイメージ)。opam 環境と、ビルドに便利ないくつかのパッケージ付き。
slim SATySFi の新しいバージョンが出たら更新されます (2019-11-19 時点では 0.0.3-dev2019.11.16 と同じイメージ)。opam など諸々が入っていないのでサイズが小さいです。
nightly 毎日朝9時 (JST) に更新されます。SATySFi と Satyrographos の master ブランチが入っています。slim と同様に opam 環境は入っていません。
0.0.3-dev2019.11.16 2019年11月16日時点の SATySFi の master ブランチのスナップショットが含まれます。更新しません。
0.0.3-dev2019.11.16-slim 0.0.3-dev2019.11.16 の slim バージョン。
0.0.3 SATySFi の 0.0.3 が入っています。基本更新しません (といいつつ2019年11月15日くらいにインタフェースの破壊的変更を入れました…)
0.0.3-slim 0.0.3 の slim バージョン。

無印

latest, 0.0.3-dev2019.11.16 のようなタグをここでは無印と呼ぶことにします。無印は opam 環境がフルで入っている環境です。また、 make や fontconfig など、文書のビルドに便利そうないくつかのパッケージも含まれています。

opam と Satyrographos を使って SATySFi のいろいろなパッケージをインストールするのであればこちらを利用してください。

欠点はイメージのサイズが大きいことです。opam 環境がフルで入っているので、圧縮した状態で715MBほど、解凍すると2.2GBくらいあります。

slim

無印は2GB以上のイメージになっているのに対し、こちらは圧縮した状態で50MBほど、展開して150MB程です (これでも大きいと言えば大きいですが)。 SATySFi 単体で使う場合はこちらで SATySFi を試すのがいいんじゃないかと思います。

ただしこちらは opam 環境を削除して SATySFi と Satyrographos のバイナリだけを置いているので、 opam を使って SATySFi のパッケージをインストールすることはできなくなります。 システムフォントを Satyrographos でインストールして使うくらいであればこちらで十分です。

nightly

「SATySFi の master ブランチに面白い機能が入ったけど自分でビルドするの面倒」という方はこちらを利用してください。 毎日 09:00 (JST) 時点の SATySFi と Satyrographos が入っています。

中身は slim 版と同様に opam 環境が削除されたものになっています。おそらく nightly は SATySFi の新機能を軽く試すものになると思っていて、その場合は外部パッケージも使わなさそう、かつサイズは小さい方がよさそうなため、こうしています。

イメージの中には /satysfi-revision/satyrographos-revision/build-date という3つのファイルが含まれていて、それぞれどのリビジョンの SATySFi・Satyrographos が同包されているか、そしてビルドした日付が書かれています。

バージョン付き

GitHub - na4zagin3/satyrographos-repo: Custom OPAM repository for SATySFi libraries managed by Satyrographos不定期で SATySFi のスナップショットを取ってくださっているので、 そのバージョンと同一のタグがついています (0.0.3+dev2019.11.16 なら 0.0.3-dev2019.11.16 というタグ。docker のタグは + が使えないようなので - にしています)。 複数人で SATySFi の文書を書くときには生成物の違いをなくすために各自のローカル環境のバージョンと CI のバージョンを揃えておきたいので、バージョン付きが便利です。

他の手軽に試す方法

macOS を使っている方は GitHub - nyuichi/homebrew-satysfi で1コマンドでインストールできます。こちらの方法でインストールすると (おそらく) HEAD になります。

他には Web 上で SATySFi による組版を試すことができるページがいくつかあります (以下2019-11-19時点で動いているもの)。

  • SATySFi Playground
    • 中で satysfi-docker (amutake/satysfi:slim) を使っていただいています。ありがとうございます。
  • satysfi.ml

おわりに

satysfi-docker はちゃんとメンテしていくつもりなのでなにか不具合や要望があれば issue にお願いします。 6日目には satysfi-docker と GitHub Actions を使って文書やパッケージの CI をする話を書くのでそちらもよろしくお願いします。

2018年の思い出

尊敬している先輩が毎年の年末に一年の総括を書いているので、自分も真似してやってみる。

仕事

今年の半分は sluicegate という、メディアストリームを fan-out させるシステムを作っていた。これに関しては Erlang & Elixir Fest. 2018 で発表しているので、概要はそちらの発表資料を見てほしい。設計・実装は、レビューはしてもらいつつもほぼ一人で作ったのでいい経験になったと思う。パフォーマンスチューニングもしたが、なかなか速くならず難しかった。

今年の1/4は fialyzer という、Erlang の (後付けの) 型検査ツールである dialyzer の軽量高速版を作ったりしていた。一応OSSになっているけど、まだ全然できていない。

あとは sluicegate の一部を Coq で証明して PPL2018 でポスター発表をしたりした。型検査器の開発や Coq での証明を給料をもらいながらできるのは改めてすごい環境だと思う。

趣味プログラミング

友人からの依頼などでコードを書いた (しなければいけなかった) ことはちょこちょこあったが、趣味では特にしていない。

最近ではプログラミングが楽しいと思うことがほとんどなくなってしまった。昔はプログラミング自体が楽しくて仕方がないこともあったような気がするけど、ここ1,2年はその気持ちになれなくて悲しい。やらないと怒られたり締め切りに追われたりしないとプログラミングする気が起きなくなってしまった。面倒という気持ちが強い。かといって他のことに熱中しているというわけでもなく、昔好きだったこともいまではそれほど心を動かされなくなってしまった。何かに熱中できる人が羨ましい。熱意を持ち続けられることが一番貴重な性質だなと最近よく思う。

ちなみにこういう気持ちは昨年からあって、「今年はなにかするのを極力やめよう、そうすれば治るかも」と思っていたけど、特に変わらなかった。悲しいね。

本・論文

全然読んでいなくてもうだめかもしれない。 ぱっと思いつくのは、

  • Communication and Agreement Abstractions for Fault-Tolerant Distributed Systems
  • みんなのデータ構造
  • 純粋関数型データ構造 (途中)
  • Real World HTTP
  • プロフェッショナルSSL/TLS (途中)
  • The QUIC Transport Protocol: Design and Internet-Scale Deployment

とかそのくらい。 本・論文以外ではQUIC関連のRFC・Internet-Draftは結構読んだかもしれない。

技術同人誌

技術書典4,5でも昨年同様サークル進捗大陸で合同誌を出した。 おそらく他のメンバーの記事目的の方が多いのだとは思うけど、たくさんの人に買っていただいてとても嬉しい。ありがとうございます。 自分のやったことが直接お金になるというのはあまり得られない経験だし楽しいので、とても大変だけど、機会があればまたやりたい。というかこういうのをやらないと腐っていく気がする。

前から飼いたかったねこを飼いはじめた。とてもかわいい。最近は寒いので、寝ているとよく布団の中に入ってきてかわいい (寝返りがうてなくなるけど)。

レーニン

少し太ってきたこともあって、12月初めから家でNIKEのアプリを使ってトレーニングを始めた。このアプリがけっこうよくて、自分専用のプランを作ってくれてその通りにやっていればいいので続けるハードルがだいぶ低い。トレーニングは自分で適当にやると本当にこれでいいのかなと思うこともあるけどこのアプリなら正しいトレーニングをしている感も出ていい。ダイソーで600円で売っていたヨガマットの上でやっている。まだ半月ほどだが腹筋が軽くついたおかげかお腹がへこんだ気がする。

Webサービス

以前ははてブスマホアプリを一日中眺めていたが、コメントが精神を蝕んでいくような気がしたのでアプリを消した。全然困っていない。 Twitter も宣伝以外はもうほとんどツイートしてない (タイムラインはわりと見てはいるけど)。

メモは scrapbox に全部寄せた。メモとか適当な文章は見られたくない (見られたことを知りたくない) ことがけっこうあって、 scrapbox はあまり検索にも出てこないし、自分にとってよかった。リンクも便利でスケールしそう。scrapbox は未来の自分用、他人に見せる用の文章ははてなブログなりQiitaなりでという使い分けになった。ただ情報発信したいという気持ちが基本ない (したほうがいいだろうなとは思うけど) ので、scrapbox ばかり使うことになりそう。

ゲーム

undertale がよかった。スマホのゲームも何個かやったけど一週間くらいですぐ飽きてしまう。

漫画

いま単行本を買っているのはアオアシ約束のネバーランドBLUE GIANT SUPREME。 あとは毎日朝起きたらLINE漫画の毎日更新される漫画を読んでから活動を始めるのが習慣になっている。

なにが幸せなのか

上にも書いたが、最近なにかについて楽しいと思うことが少なくなったり、つらいと思うことが多くなったので、なにをしたら幸せなのか、どうなったら幸せなのかを考えることが多くなった。もやもやとしていて結論はでていない。

あとは最近自分の存在価値がわからない。いや、頭では自分はいないよりいいはずだとは思うけれども、心ではそう思えなくなったりする (波がある)。無条件で自分という存在を心から認めることができている状態が一番よいのだろうけど、難しい。

活躍している人を見ると、なんで自分はこうなんだもうだめだと思ったりする。比較しなければいいのだろうけど難しい。穏やかな気持ちになりたい。歩き遍路とかしたい。

2019年

2019年はもうちょっと元気になりたい。