素敵なライブラリをSatyrographosで配布しよう！

この記事は [SATySFi Advent Calendar 2019](https://adventar.org/calendars/3929) 12日目の記事です。前日は @amaoto017 さんの『[SATySFi上で構文定義可能なシンタックスハイライト（の宣伝）](https://amaoto017.hatenablog.com/entry/2019/12/11/005715)』です。次日は[しょうだい](https://twitter.com/shoudai7856)さんです。


# 用語
本記事では、以下の用語を使用します。

|語|定義|
|---|---|
|SATySFi パッケージ|`packages/` 以下に置かれた `*.satyh` ファイルや `*.satyg` ファイル等。|
|ライブラリ・Satyrographos ライブラリ|SATySFi パッケージやフォントファイル等を纏めた配布物。|
|OPAM パッケージ|OPAM によって管理される配布物の単位。ライブラリに相当。|

OPAM パッケージと SATySFi パッケージは名前が紛らわしいので気を付けて下さい。

# 新しいライブラリを書いたら

SATySFi 用のライブラリは専ら以下の要素で構成されます[^fn-satysfi-libary]。

[^fn-satysfi-libary]: 他のパターンがあったら教えて下さい

- `packages/` ディレクトリ以下に配置された `*.satyh` ファイルや`*.satyg` ファイル
- `fonts/` ディレクトリ以下に配置されたフォントファイル
- `md/` ディレクトリ以下に配置された `*.satysfi-md`
- `hash/fonts.satysfi-hash` に追加されたエントリ
- ライブラリのドキュメント

# 名前を決めましょう！
ライブラリを書いたら（書く前でも無問題なのですが）、名前を決めましょう。

## 名前の付け方
Satyrographos ライブラリ名は、今のところ以下の命名を推奨しています[^name-schema]。 **satysfi- は付けないで下さい。**

[^name-schema]: 未定ですが、`*.satysfi-md` を配布するライブラリには `md-` を付けてもらった方が分かりやすいかもしれませんね？

|種類|名|備考|
|---|---|---|
|フォントライブラリ|`fons-*`|フォントファイルを含むライブラリ。例：`fonts-theano`|
|クラスライブラリ|`class-*`|`document`関数を含むライブラリ。例：`class-exdesign`|
|他|`*`|その他。例：`zrbase`, `base`|

ライブラリのインストール時に使われる OPAM パッケージ名は Satyrographos ライブラリ名に `satysfi-` を前置したものになります[^opam-package-name-prefix]。例えば、`base` という名のライブラリの OPAM パッケージ名は `satysfi-base` です。

[^opam-package-name-prefix]: OPAM パッケージ名に `satysfi-` を前置するやり方は、[Coq.io](http://coq.io/opam/) に<ruby>倣<rp>(</rp><rt>なら</rt><rp>)</rp></ruby>ったものです。

## 名前の使われ方
Satyrographos ライブラリを利用する際に`ライブラリ名/`を前置することになります[^fn-prefix-package-name]。

[^fn-prefix-package-name]: Satyrographos 0.0.2 から `ライブラリ名/` の前置が要求されます。これは、将来導入予定パッケージシステムを模擬するものです。（[na4zagin3/satyrographos#24](https://github.com/na4zagin3/satyrographos/issues/24)）

例えば、math-constant ライブラリとして `circular.satyh` を配布する時、使用する側では `math-constant/circular` を `@require` します。

```satysfi:circular.satyh
module Circular = struct
  let pi = 3.1415926535897932384626433832975
  let tau = 6.283185307179586476925286766559
end
```

```satysfi:abc.saty
@require: math-constant/circular
```

また、SATySFi フォント名には `fonts-ライブラリ名:` を前置して下さい。[SATySFi-fonts-theano](https://github.com/na4zagin3/SATySFi-fonts-theano/blob/0de464b69ea297940c8e45917733ee6d27e495b6/fonts.satysfi-hash) からの例を以下に示します。

```yojson
{
  "fonts-theano:TheanoDidot":
    <"Single":{"src-dist":"fonts-theano/TheanoDidot-Regular.otf"}>,
  "fonts-theano:TheanoModern":
    <"Single":{"src-dist":"fonts-theano/TheanoModern-Regular.otf"}>,
  "fonts-theano:TheanoOldStyle":
    <"Single":{"src-dist":"fonts-theano/TheanoOldStyle-Regular.otf"}>
}
```

# ビルドファイルを書きましょう！
現状、ライブラリ依存関係解決やネットワークからのデータ取得には OPAM を用いているために、Satyrographos 用のビルドファイルと OPAM パッケージファイルを両方書く必要があります……（解決する方法は模索中）。

## Satyrographos 用のビルドファイル（Satyristes）
Satyrographos 用ビルドファイル `Satyristes` は S 式で記述します [^satyristes-s-expr]。

[^satyristes-s-expr]: パーズを簡単にするため S 式にしてみたのですが、反応を見る感じ YAML の方がウケが良さそうなので切替えようかと思っています

概観するとこんな感じです。

```lisp:Satyristes
(version 0.0.2) ; Satyrographos 0.0.2 系列向け

(library ; ライブラリ宣言
  (name "great-library") ; ライブラリ名
  (version "1.0") ; ライブラリバージョン
  (sources ; ファイル
    (;; ./interesting-font.ttf を dist/fonts/interesting-font.ttf に配置
     (font "interesting-font.ttf" "./interesting-font.ttf")
     ;; ./fonts.satysfi-hash を dist/hash/fonts.satysfi-hash に追記
     (hash "fonts.satysfi-hash" "./fonts.satysfi-hash")
     ;; ./def.satyh を dist/packages/great-library/abc/def.satyh に配置
     (package "abc/def.satyh" "./def.satyh")
     ;; ./ghi.satyh を dist/packages/great-library/ghi.satyh に配置
     (package "ghi.satyh" "./ghi.satyh")
     ;; ./grc.satysfi-hyph を dist/hyph/grc.satysfi-hyph に配置
     (file "hyph/grc.satysfi-hyph" "./grc.satysfi-hyph")))
  (opam "satysfi-great-library.opam") ; OPAMパッケージファイル
  (dependencies
    (;; fonts-theano ライブラリに依存（バージョン指定無し）
     (fonts-theano ())))
  (compatibility ; 互換性警告 
    (;; パッケージ <old-name> が <new-name> に変更されたことを使用者に警告
     (renamePackage "<new-name>" "<old-name>")
     ;; フォント <old-name> が <new-name> に変更されたことを使用者に警告
     (renameFont "<new-name>" "<old-name>"))))
```

## OPAM パッケージファイル
OPAM パッケージファイルは `satyrographos-ライブラリ名.opam` という名前で作って下さい。

OPAMファイルは記法がややこしいので、**作成・更新後に `opam lint *.opam` を実行し、エラー・警告が出ないか確認することを奨めます。**

```opam:satysfi-great-library.opam
# satysfi-great-library.opam
opam-version: "2.0"
# OPAM パッケージ名
name: "satysfi-great-library" # satysfi- + ライブラリ名
version: "1.0"
synopsis: "A Great SATySFi Library"
description: """
Brilliant description comes here.
"""
maintainer: "Your name <email@example.com>"
authors: "Your name <email@example.com>"
license: "LGPL-3.0-or-later" # お好きなライセンスを
homepage: "<product home page>"
bug-reports: "<product issue tracker>"
dev-repo: "<repo url>"
depends: [
  "satysfi" {>= "0.0.5" & < "0.0.6"}
  "satyrographos" {>= "0.0.2.6" & < "0.0.3"}
  "satysfi-dist"

  # もし他のライブラリに依存している場合には、
  # OPAMパッケージ名とバージョン制約をここに記述して下さい
  "satysfi-fonts-theano" {>= "2.0+satysfi0.0.3+satyrograhos0.0.2"}
]
build: [ ]
install: [
  ["satyrographos" "opam" "install"
   "--name" "great-library" # ライブラリ名
   "--prefix" "%{prefix}%"
   "--script" "%{build}%/Satyristes"]
]
```

例：

- クラスライブラリ [ExDesign](https://github.com/puripuri2100/exdesign/blob/9ffd3a90cd4720dfa87fc55bbb71bc9b9465e853/satysfi-class-exdesign.opam)
- フォントライブラリ [SATySFi-fonts-theano](https://github.com/na4zagin3/SATySFi-fonts-theano/blob/0de464b69ea297940c8e45917733ee6d27e495b6/satysfi-fonts-theano.opam)


## ドキュメント
また、`libraryDoc` 節を Satyristes ファイルに追加（＋対応するOPAM ファイルも追加）することで、ライブラリのドキュメントをビルドすることもできます。

**実際の例を盛り込んだドキュメントを作ることを強く推奨します。**ドキュメントライブラリは SATySFi 本体や他ライブラリ更新時にビルドが壊れないか検証されるため、破壊的変更の影響を受けづらくなります。

`libraryDoc` 節は将来的に `library` 節に統合されるかもしれません。

```lisp:Satyristes（追加）
(libraryDoc ; ライブラリドキュメント宣言
  (name "great-library-doc") ; ライブラリ名 + "-doc"
  (version "1.0") ; ライブラリバージョン
  (workingDirectory "doc") ; ビルドコマンドを実行する為の作業ディレクトリ
  (build ; ビルドコマンド
    (;; make に引数 "-f" "build.make" を与えて起動
     (make "-f" "build.make")
     ;; SATySFiで処理
     (satysfi "great-library.saty" "-o" "great-library.pdf")))
  (sources ; ファイル
    ((doc "great-library.pdf" "doc/great-library.pdf")))
  (opam "satysfi-great-library-doc.opam") ; OPAMパッケージファイル
  (dependencies ; 依存関係
    ((great-library ())
     (exdesign ()))))
```

ビルドコマンドには `(make 引数…)` と `(satysfi 引数…)` が使えます。依存関係に書かれているライブラリを自動で準備してから satysfi コマンドを実行します。

対応する OPAM パッケージファイルは以下のようになります。

```opam:great-library-doc.opam
opam-version: "2.0"
# 対応するライブラリ名 + "-doc"
name: "satysfi-great-library-doc"
version: "1.0"
synopsis: "Document of A Great SATySFi Package"
description: """
Brilliant description comes here.
"""
maintainer: "Your name <email@example.com>"
authors: "Your name <email@example.com>"
license: "LGPL-3.0-or-later"
homepage: "<product home page>"
bug-reports: "<product issue tracker>"
dev-repo: "<repo url>"
depends: [
  "satysfi" {>= "0.0.5" & < "0.0.6"}
  "satyrographos" {>= "0.0.2.6" & < "0.0.3"}

  # satysfi- + ライブラリ名
  "satysfi-great-library" {= "%{version}%"}

  # もし他のライブラリに依存している場合には、
  # OPAMパッケージ名とバージョン制約をここに記述して下さい
  "satysfi-exdesign"
]
build: [
  ["satyrographos" "opam" "build"
   "--name" "great-library-doc" # ライブラリ名 + -doc
   "--prefix" "%{prefix}%"
   "--script" "%{build}%/Satyristes"]
]
install: [
  ["satyrographos" "opam" "install"
   "--name" "great-library-doc" # ライブラリ名 + -doc
   "--prefix" "%{prefix}%"
   "--script" "%{build}%/Satyristes"]
]
```

# ビルドしましょう！
Satyristes ファイルや OPAM ファイルを書き終えたらテストの時間です。
以下のコマンドで、現在のディレクトリをピン留め[^fn-opam-pin]してビルドしてみましょう。

[^fn-opam-pin]: ローカルの OPAM パッケージリストに仮登録

```shell-session:成功例
$ opam pin add -v "file://$PWD"
Package satysfi-great-library does not exist, create as a NEW package? [Y/n] y
The following actions will be performed:
  - install satysfi-great-library 1.0*
Do you want to continue? [Y/n] y

<><> Processing actions <><><><><><><><><><><><><><><><><><><><><><><><><><><><>
+ /home/<user>/.opam/opam-init/hooks/sandbox.sh "install" "satyrographos" "opam" "install" "-name" "great-library" "-prefix" "/home/<user>/.opam/4.09.0" "-script" "/home/<user>/.opam/4.09.0/.opam-switch/build/satysfi-great-library.1.0/Satyristes" (CWD=/home/<user>/.opam/4.09.0/.opam-switch/build/satysfi-great-library.1.0)
-> installed satysfi-great-library.1.0
Done.
```

以上のようなメッセージが出たら成功です！

失敗例としては以下のようなものがあります。

```shell-session:失敗例
$ opam pin add -v "file://$PWD"

...

#=== ERROR while installing satysfi-great-library.1.0 ================================#
# context     2.0.0 | linux/x86_64 | ocaml-base-compiler.4.09.0 | pinned(file:///home/<user>/Documents/Development/satysfi/packages/satysfi-lib)
# path        ~/.opam/4.09.0/.opam-switch/build/satysfi-great-library.0.0.1
# command     ~/.opam/opam-init/hooks/sandbox.sh install satyrographos opam install -name satysfi-great-library -prefix /home/<user>/.opam/4.09.0 -script /home/<user>/.opam/4.09.0/.opam-switch/build/satysfi-great-library.1.0/Satyristes
# exit-code   1
# env-file    ~/.opam/log/satysfi-base-472050-05296d.env
# output-file ~/.opam/log/satysfi-base-472050-05296d.out
### output ###
# Uncaught exception:
#   
#   (Failure "Build file does not contains library satysfi-great-library")
# 
# Raised at file "stdlib.ml", line 29, characters 17-33
# Called from file "src/command.ml", line 2817, characters 8-205
# Called from file "src/exn.ml", line 107, characters 6-10



<><> Error report <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
+- The following actions failed
| - install satysfi-great-library 1.0
+- 
+- The following changes have been performed
| - remove satysfi-great-library 1.0
+- 

The former state can be restored with:
    opam switch import "/home/<user>/.opam/4.09.0/.opam-switch/backup/state-20191212143911.export"
Or you can retry to install your package selection with:
    opam install --restore
'opam pin add file:///home/<user>/Documents/Development/satysfi/packages/satysfi-lib --verbose' failed.
zsh: exit 31    opam pin add "file://$PWD" --verbose
```

この場合は、`satysfi-great-library.opam` の `install:` 節が以下のようになっていることから、`satyrographos opam install -name satysfi-great-library -prefix …… -script ……` が失敗していることが分かります。


```opam:satysfi-great-library.opam
build: [
  ["satyrographos" "opam" "build"
   "--name" "satysfi-great-library" # <- satysfi- は要らない
   "--prefix" "%{prefix}%"
   "--script" "%{build}%/Satyristes"]
]
```

Satyrographos のエラーメッセージ等は改善していきたいので、何かありましたらご報告下さい。

# リリースしましょう！
GitHub で開発していることを仮定します。GitHub そのものの使い方の解説は他に譲ります。

GitHub にてリリースを行う際には、

1. `git tag -a <バージョン>` でタグ付けをして、
2. `git push origin <バージョン>` で push する

ことになるかと思います。

一旦 GitHub でリリースしてしまえば、以下のコマンドでライブラリを satyrographos-repo にプルリクエストを送ることができます。

```shell-session
$ cd OPAMファイルのある場所
$ opam publish --repo=na4zagin3/satyrographos-repo
```

送ったプルリクエストが受け入れられれば公開完了です。お疲れさまでした！

# Satyrographos 最後の実装予定機能
ところで

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">パッケージマネージャ，創らねばならないもの</p>&mdash; 画力・博士号・油田 (@bd_gfngfn) <a href="https://twitter.com/bd_gfngfn/status/1203993772528300032?ref_src=twsrc%5Etfw">December 9, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

というわけで（？）、Satyrographos 最後の機能はネイティブなパッケージマネージャ形式への移行支援であることは決定事項です。

それまではSATySFi用パッケージマネージャーのベースラインかつ実験場として開発を頑張っていこうと思っています。