📖

ハードウェア記述言語Verylのパッケージマネージャ設計

2023/12/02に公開

はじめに

現在 Veryl というハードウェア記述言語を作っています。

https://github.com/dalance/veryl

その中でパッケージマネージャの設計について考えていたところちょうどびったりの記事が投稿され、大変参考になりました。

https://gfngfn.github.io/ja/posts/2023-02-15-on-creating-package-managers/

せっかくなので、この記事の内容をベースに、Verylでどういったパッケージマネージャの設計を採用したかについてまとめてみます。

設計上の選択肢

元記事ではパッケージマネージャにおける主要な設計課題について

  • レジストリは1つか複数か
  • レジストリ中のパッケージの名前空間
  • 開発者によるパッケージのレジストリへの登録は自由か否か

の3点を挙げています。
既存の言語についてまとめると以下のようになります。
(元記事ではGoについての記載はありませんでしたが、Verylの設計は結果的にGoに近くなったので追加しました)

Ocaml Rust Elm Go
レジストリ数 公式+サードバーティ 公式+サードバーティ 公式のみ サードパーティのみ
名前空間 単一 単一 ユーザ名毎 なし
登録 要レビュー 自由 要レビュー 自由

Go については元記事で言及がなかったので追記しておきます。
(Go は触ったことがある程度なので間違っている部分があるかもしれません)

まず、レジストリですがいわゆる中央レジストリ的なものはありません。各人がモジュールをGitHubなどで公開し、使いたい人はそのURIを直接指定してインポートする形式です。
従って公式レジストリはなく、サードパーティだけ存在する状態と言えます。
(Go の公式サーバは各レジストリへのキャッシュを持っておりアクセスを高速化するようですが、仮にそれがなくても動作に問題はないと思われるので、パッケージマネージャのモデルとしてここでは考慮しません)

各URIがレジストリであり1つのモジュールを表すのでレジストリ中の名前空間という概念はありません。登録は各自が適当なURIに公開するだけなので当然自由です。

Veryl での設計

まずレジストリの数についてです。Verylは新規言語であり、ハードウェア記述言語という特性上、将来的にもユーザ数はそれほど増えないと考えられます。
そのため中央レジストリの作成と維持にコストをかけてもあまり見返りがないように思いました。

また半導体設計においては秘密保持契約の影響を受けるソースコードがしばしば発生し、それらを取り扱う必要があります。
そういったソースコードは通常社内に閉じたリポジトリに置かれるので、必然的に中央レジストリとは分離してしまいます。

これらの理由から Go と同様に中央レジストリは持たず、サードパーティのみとすることにしました。これにより、例えば「アクセス制限された社内サーバ」といったものでも問題なく依存関係として追加できます。

名前空間と登録については必然的に Go と同じになります。
3つの設計課題に対する方針を見たところで、続いてもう少し実装の詳細に触れていきます。

Verylの実装詳細

レジストリへのアクセスプロトコル

レジストリをサードパーティのみとしたため、アクセスプロトコルは一般的なものが望ましいです。
専用のWeb APIなどを介してアクセスする場合、「レジストリを立てる」ことは「専用サーバを立てる」ことと同義になってしまい、一般のユーザが行うのはあまり現実的ではありません。

Go ではアクセスプロトコルをVCSのプロトコルとすることでサーバ構築部分をGitHubやGitLabなどに任せ、パッケージを公開したい人は単に各自のGitHub/GitLabアカウントで公開するだけで済むようになっています。

Go ではVCSとして Git 以外にも Mercurial や Bazaar などをサポートしているようですが、
Veryl では Git のみサポートすることにしました。

バージョンの登録

VCSのプロトコルを通してアクセスする方式の課題の1つにバージョンの登録があります。専用のWeb APIでアクセスする方式であれば、バージョンの登録や問い合わせなどのAPIを自由に増やせますが、VCSのプロトコルを使う以上そういったことはできません。

GoではVCSのタグを使ってバージョンの登録を実現しています。例えば v1.0.2 というタグをつけるとそれはセマンティックバージョニングのバージョン1.0.2であることを意味します。
この方式でも特に問題はなさそうなのですが(言語が提供するパッケージ管理システムではなく)VCS単体でバージョン登録できてしまう点が気になりました。

例えばバージョン1.0.2を登録したいとしたときに 1.0.2 1_0_2 v1.0.2 V-1.0.2と様々なバリエーションが考えられてしまい、間違った規則でタグ付けしたことに気づけないかもしれません。
またタグ付けの前にコンパイルエラーにならないかどうかをチェックするようなことも(pre-commitフックなどを設定しなければ)強制はできません。

そこで Veryl ではバージョン情報をファイルに書く方式としました。具体的にはリポジトリルートに置かれた Veryl.pub というファイルにバージョンとそれに対応するリビジョンを書いていきます。

# This file is automatically @generated by Veryl.
# It is not intended for manual editing.
[[releases]]
version = "0.1.0"
revision = "9e9a30a16c155a5bc62dd1a19c97129a9ab96537"

このファイルは Veryl コンパイラのサブコマンド veryl publish によって自動的に生成されます。その際バージョン番号がSemVer準拠かどうかや、コンパイルエラーの有無、未コミットの変更がないかどうかなどをチェックしています。

依存関係の指定

依存関係の指定はURIをそのまま指定します。

[dependencies]
"https://github.com/dalance/veryl_sample" = "0.1.0"

Go ではホスト名以降のみ指定し、実際のアクセスプロトコルはサーバとやり取りして決められるような複雑な仕様になっていますが、Veryl ではGitしか対応しないことにしたのでGitリポジトリとしてアクセス可能なURIをそのまま指定する方式としました。

依存関係取得の流れ

Veryl における依存関係取得の流れは以下のようになっています。

  • [dependencies] で指定されたURIのデフォルトブランチをチェックアウトする
  • チェックアウトしたリポジトリルートの Veryl.pub と、[dependencies] で指定されたバージョンから使用すべきバージョンを決定する
  • そのバージョンに対応するリビジョンをチェックアウトし、ソースコードを展開する

おわりに

Verylのパッケージマネージャの設計についてまとめてみました。パッケージマネージャは(特に新興言語においては)軽視されがちですが、言語の使い勝手に大きく影響する部分なので、次世代の言語としてはデフォルトで採用されていてほしい要素です。ここで紹介した方式(あるいはGoのような方式)は、中央レジストリを必要としないという点で新興言語がスモールステップで進める際の設計として参考になるかもしれません。

Discussion