Juliaパッケージの標準的なディレクトリ構成
これはJulia Advent Calendar 2023の1日目の記事です。
はじめに
Julia言語には標準でPkg.jlというパッケージマネージャが備わっており、パッケージ管理しやすい言語として知られています。
他の言語、例えばPythonではrequirements.txt
のようなレガシーや、Poetry / Pipenv / Rye のようなパッケージマネージャーの乱立があったりしますが、Juliaでは標準的な方法が広く浸透しているため、このようなレガシーや乱立を見る機会はありません。[1]
この一貫性があるため、Juliaパッケージのディレクトリ構成に関する議論もスムーズに行うことができます。本記事では、最初に最小限のパッケージ構成を示し、その後で更により詳細な全部盛りのパッケージディレクトリ構成について紹介していきます。
最小限の構成
JuliaのREPLのパッケージモードでgenerate
コマンドを実行すれば最小限の構成でパッケージが生成できます。
(@v1.9) pkg> generate MyPkg
これらのファイル構成は以下のようになっています。
MyPkg
├── Project.toml
└── src
└── MyPkg.jl
name = "MyPkg"
uuid = "eb62ad0d-c07c-4140-a83b-b6ff560c525f"
authors = ["hyrodium <hyrodium@gmail.com>"]
version = "0.1.0"
module MyPkg
greet() = print("Hello World!")
end # module MyPkg
このパッケージを実行するには、以下のようにパッケージモードでdev
コマンドを実行してインストールします。
MyPkg.greet()
で挨拶します。
(@v1.9) pkg> dev /home/hyrodium/Git/zenn-content/MyPkg
Resolving package versions...
Updating `~/.julia/environments/v1.9/Project.toml`
[24b0717a] + MyPkg v0.1.0 `~/Git/zenn-content/MyPkg`
Updating `~/.julia/environments/v1.9/Manifest.toml`
[24b0717a] + MyPkg v0.1.0 `~/Git/zenn-content/MyPkg`
julia> using MyPkg
[ Info: Precompiling MyPkg [24b0717a-998c-4ceb-8e3e-6314e0e60432]
julia> MyPkg.greet()
Hello World!
全部盛りの構成
諸々全部盛りで構成すると以下のようなディレクトリ構成になります。[2]
以下ではこのパッケージをMyGreatPkg.jl
と呼ぶこととしましょう。
MyGreatPkg
├── .git
├── .gitignore
├── LICENSE.md
├── Project.toml
├── (Manifest.toml)
├── src
│ ├── MyGreatPkg.jl
│ └── (他のソースコード)
├── ext
│ ├── MyGreatPkgOtherPkgExt.jl
│ └── (他の拡張メソッドの定義)
├── test
│ ├── runtests.jl
│ └── (テスト用に分割したファイル)
├── README.md
├── docs
│ ├── make.jl
│ ├── Manifest.toml
│ ├── Project.toml
│ ├── src
│ │ ├── assets
│ │ │ ├── custom.css
│ │ │ ├── favicon.ico
│ │ │ ├── logo-dark.svg
│ │ │ └── logo.svg
│ │ ├── img
│ │ │ └── (画像ファイルはここにまとめて置く)
│ │ ├── index.md
│ │ └── (他のMarkdownドキュメント)
│ └── examples
│ └── (DemoCards.jl用のドキュメント)
├── .JuliaFormatter.toml
├── .pre-commit-config.yaml
└── .github
├── dependabot.yml
└── workflows
├── CI.yml
├── Docs.yml
├── CompatHelper.yml
├── TagBot.yml
├── Format.yml
├── SpellCheck.yml
├── LabelCheck.yml
└── Invalidations.yml
これら全部のファイルを自分で毎回用意するは大変なので、PkgTemplates.jlで自動生成する仕組みもあります。[3]
しかし
- PkgTemplates.jlでまだ対応していない設定がある
- 古いバージョンのPkgTemplates.jlで生成したリポジトリに新しい設定を加えたい
- PkgTemplates.jlで生成したファイルの役割が知りたい
などの要望もあるかと思います。
そういう訳で,これらのファイルの意味やカスタマイズを解説するのが本記事の趣旨になります。
Git関連
.git
JuliaのパッケージはGitでバージョン管理されることが求められます。
generate
コマンドで作ったMyPkg
ではGit管理していませんでしたが、Generalに登録されているパッケージではGit管理が求められます。[4]
.gitignore
リポジトリ直下のManifest.toml
やビルド済みドキュメントはignoreしましょう。
/Manifest.toml
/docs/build/
ライセンス
LICENSE.md
JuliaコミュニティではMITライセンスが好まれます。
これは以下のような理由からです。
また、「BSDライセンスをベースとして商用利用禁止を追加したもの」のようなライセンスもJuliaのパッケージライセンスとして不的確とされることがあります。[7]
ファイル名はLICENSE
よりもLICENSE.md
の方が自由度が高くて良いと思います。例えば、Julia本体でのLICENSE.md
のようにURLを使用できるのは便利ですね。
Copyright (c) 2009-2023: Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors: https://github.com/JuliaLang/julia/contributors
プロジェクトファイル
Project.toml
Project.toml
はパッケージのメタデータを管理するような設定ファイルです。[8]
- パッケージの名前
name
- パッケージのUUID
uuid
- パッケージのバージョン
version
- パッケージの依存しているパッケージ
[deps]
- パッケージの互換性
[compat]
- テスト環境で依存するパッケージ
[extras]
[targets]
Manifest.toml
Manifest.toml
も生成されますが、これは通常は.gitignore
で無視します。
ソースコード
src/MyGreatPkg.jl
通常のソースコードをsrc
以下に記載します。
ファイルを分けたい場合はsrc/func1.jl
のようにファイルを作ってinclude("func1.jl")
のようにsrc/MyGreatPkg.jl
内で読み込みます。
module MyGreatPkg
function myadd(a,b)
return a+b
end
include("func1.jl")
end
myprod(a,b) = a*b
ext/MyGreatPkgOtherPkgExt.jl
ext
ディレクトリにはPackage extension用のコードを置きます。
Package extentionsについては以下の記事などをご覧ください。
テスト
リポジトリ直下のtest
ディレクトリがテストとして使われます。[9]
test/runtests.jl
以下のように@testset
や@test
など使ってテストコードを書きます。
ここでも適当にinclude
を使ってファイル分割できます。
using MyGreatPkg
using Test
@testset "MyGreatPkg" begin
@test MyGreatPkg.myadd(1,2) == 3
include("func1.jl")
end
@test MyGreatPkg.myprod(3,4) == 12
doctest
Juliaには上記のようなTest.jl
によるテスト以外にも、doctestによるテストもあります。
doctestはdocstring内に書くテストで、REPLの実行結果がそのままテストになるものです。
"""
myadd(a, b)
Add two numbers.
# Examples
```jldoctest
julia> myadd(1,2)
3
julia> myadd(8,-4)
4
```
"""
function myadd(a,b)
return a+b
end
Documenter.jlでdoctestが実行できますが、以下の理由からtest/runtests.jl
にdoctest実行を含めないことが一般的です。
- doctestは実行例の明示が目的であり、coverageでカウントしたくない場合がある。
- REPLへの出力の変更は通常は破壊的変更とは見なされず、Juliaのバージョンに依存して変わるかも知れない。その場合に特定のバージョンでしかdoctestが通らないことになる。
ではいつdoctestを実行するのか。後述のDocs.yml
での実行が有力です。
ドキュメント (README)
README.md
リポジトリを参照した人が最初に見るファイルがREADME.mdです。
以下の3つは揃える方が好ましいです。
- ドキュメントへのリンク
- インストール方法
- 最小限の実行例
私がメンテナンスしているIntervalSets.jlのREADME.mdはシンプルで気に入っています。
READMEに色々なバッジが並んでいると格好いいですよね!付けましょう。
ドキュメント (最新リリース)
最新のリリースに対応したバージョンのドキュメントへのリンク
<!-- BasicBSpline.jlでの例 -->
[![Stable](https://img.shields.io/badge/docs-stable-blue.svg)](https://hyrodium.github.io/BasicBSpline.jl/stable)
ドキュメント (開発版)
最新のmain
ブランチに対応したバージョンのドキュメントへのリンク
<!-- BasicBSpline.jlでの例 -->
[![Dev](https://img.shields.io/badge/docs-dev-blue.svg)](https://hyrodium.github.io/BasicBSpline.jl/dev)
Build Status
GitHub Actionsのテストが成功しているかを表すバッジ
<!-- BasicBSpline.jlでの例 -->
[![Build Status](https://github.com/hyrodium/BasicBSpline.jl/workflows/CI/badge.svg)](https://github.com/hyrodium/BasicBSpline.jl/actions)
コードのカバレッジ
Codecovへのリンクとcoverageを表すバッジ
<!-- BasicBSpline.jlでの例 -->
[![Coverage](https://codecov.io/gh/hyrodium/BasicBSpline.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/hyrodium/BasicBSpline.jl)
Aqua.jl
Aqua.jlでテストしていることを表すバッジ
[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)
arXiv
リポジトリに対応するarXivの論文があることを表すバッジ
<!-- ElasticSurfaceEmbedding.jlでの例 -->
[![arXiv](https://img.shields.io/badge/math.DG-arXiv%3A2211.06372-B31B1B.svg)](https://arxiv.org/abs/2211.06372)
DOI
リポジトリに対応するDOIがあることを表すバッジ
<!-- BasicBSpline.jlでの例 -->
[![DOI](https://zenodo.org/badge/258791290.svg)](https://zenodo.org/badge/latestdoi/258791290)
Package downloads
パッケージのダウンロード数を表すバッジ
<!-- BasicBSpline.jlでの例 -->
[![BasicBSpline Downloads](https://shields.io/endpoint?url=https://pkgs.genieframework.com/api/v1/badge/BasicBSpline)](https://pkgs.genieframework.com?packages=BasicBSpline).
code style
採用しているコードスタイルを表すバッジ
<!-- BlueStyleでの例 -->
[![Code Style: Blue](https://img.shields.io/badge/code%20style-blue-4495d1.svg)](https://github.com/invenia/BlueStyle)
ColPrac
ColPracをリポジトリの規約として採用していることを明示するためのバッジ
[![ColPrac: Contributor's Guide on Collaborative Practices for Community Packages](https://img.shields.io/badge/ColPrac-Contributor's%20Guide-blueviolet)](https://github.com/SciML/ColPrac)
ドキュメント (Documenter.jl)
この節ではDocumenter.jlを使ったドキュメント生成について解説します。
docs/Project.toml
ドキュメント生成環境での仮想環境を管理するファイルです。
[deps]
にDocumenter.jlを含めましょう。
docs/make.jl
ドキュメントを生成するためのJuliaスクリプトです。
基本的にはmakedocs
でドキュメント生成してdeploydocs
でドキュメントをデプロイします。
using Documenter
using MyGreatPkg
makedocs(;
# オプション色々指定する
)
deploydocs(
# オプション色々指定する
)
docs/Manifest.toml
このファイルもgitignoreして良いですが、手元の環境とドキュメント生成環境を一致させたい場合にはgit管理に含めることもあります。
docs/src/assets/logo.svg
ドキュメントのロゴ画像です。SVGだけでなく、PNGなども可能です。
明々後日の記事ではロゴについて書く予定です。お楽しみに。
docs/src/assets/logo-dark.svg
ドキュメントのロゴ画像です。ダークモードで使われる画像を別途配置可能です。
docs/src/assets/custom.css
基本的に不要ですが、ドキュメントの見た目を変更したい場合に使用します。
私はドキュメントのロゴの大きさを変更するために以下のように記述することが多いです。(例)
#documenter .docs-sidebar .docs-logo > img,
html.theme--documenter-dark #documenter .docs-sidebar .docs-logo > img {
max-height: 8rem;
}
docs/src/assets/favicon.ico
ブラウザで表示した時に表示されるアイコンです。
docs/src/index.md
ドキュメントのトップページに関するmarkdownファイルです。
docs/src/hoge.md
他のページのmarkdownファイルはdocs/src
以下に置きます。
ページの順番や構造はmakedocs
内のキーワード引数で指定します。
ドキュメント (DemoCards.jl)
DemoCards.jlはデモを作りやすくするためのJuliaパッケージで、Documenter.jlのドキュメントに組み込むことができます。
詳細は記載しませんが、docs/src
以下にデモページを置かないことに注意してください。
docs/src
の代わりに、docs/examples
のようなディレクトリをdocs
以下に作成します。
examples
├── part1
│ ├── assets
│ ├── demo_1.md
│ ├── demo_2.md
│ └── demo_3.md
└── part2
├── demo_4.jl
└── demo_5.jl
のようにファイルを配置すれば
# Examples
## Part1
demo_1.md
demo_2.md
demo_3.md
## Part2
demo_4.jl
demo_5.jl
のようなmarkdownファイルが対応するようなデモページを作成することができます。
フォーマッタ
.JuliaFormatter.toml
JuliaFormatter.jlで使う設定を記述するファイルです。
style = "blue"
などが最小限の構成です。
.pre-commit-config.yaml
pre-commitの設定もできます。
GitHub Actions
.github/dependabot.yml
後述の.github/workfows
以下のactionsに記載されたバージョンを自動的にアップデートするためのbot。[10]
.github/workflows/CI.yml
GitHub Actionsでtest/runtests.jl
を実行するための設定。
julia-runtestを参考にしてください。
Quaternions.jlでの例も参考になると思います:
.github/workflows/Docs.yml
GitHub Actionsでdocs/makedocs.jl
を実行するための設定。
julia-docdeployを参考にしてください。
PkgTemplates.jlではCI.yml
にドキュメント生成を含めていますが、分割した方が便利な場合もあります。[11]
こちらもQuaternions.jlでの例が参考になると思います:
.github/workflows/CompatHelper.yml
GitHub ActionsでCompatHelper.jlを実行するための設定。
をコピペすればOK。
.github/workflows/TagBot.yml
パッケージをGeneralに登録したときに自動的にリリースを打つための設定。
GitHub ActionsでTagBotを実行するための設定。
https://github.com/JuliaRegistries/TagBot#setup に書かれてあるコードをコピペすればOK。
.github/workflows/Format.yml
GitHub ActionsでJuliaFormatter.jlを実行するための設定。
実行方法に関しては以下の2つの流儀があります。
- PRごとに適切にフォーマッティングされているかチェックする
- フォーマッティングのPRをbotが定期的に作成する
後者だと不要なcommitが増えてconflictを起こしがちなので、基本的には前者が良いと思います。
julia-formatのREADME.mdに記載されている設定を参照してください。[12]
.github/workflows/SpellCheck.yml
スペルチェックしてくれます。
Documenter.jlなどで導入されています。
.github/workflows/LabelCheck.yml
特定の種類のラベルが付いている時に間違ってmergeしないようにブロックしてくれます。
.github/workflows/Invalidations.yml
https://github.com/JuliaArrays/StaticArrays.jl/pull/1087 参照。
おわりに
本記事で列挙したもの以外にも色々なパッケージ構成があると思います。
たとえば、以下の項目には意図的に触れませんでした。
- 一つのリポジトリで複数のパッケージを管理する方法
- Documenter.jl以外でのドキュメント生成
- GitLabを使った場合のディレクトリ構成
「このファイルも便利だから用意した方が良いよ!」などあればコメントください!
-
よっぽどメンテナンスされていないJuliaパッケージでは古い形式の
REQUIRE
が存在してたりしますが、ほぼ見ないでしょう。参考: https://discourse.julialang.org/t/convert-require-to-project-toml-and-manifest-toml/17775/2 ↩︎ -
アルファベット順ではなく、説明順にsortしてます。 ↩︎
-
他にもPkgSkeleton.jlもあったりしますが、PkgTemplates.jlが最有力だと思います。 ↩︎
-
バージョンごとにgitのcommitがGeneralに登録されているためです。例えばBasicBSpline.jlでは https://github.com/JuliaRegistries/General/blob/master/B/BasicBSpline/Versions.toml のように登録されています。 ↩︎
-
"Why We Created Julia"では"liberal license"としか言及されていなくて、MITライセンスとは書かれていませんでした。 ↩︎
-
例えば、
PkgA.jl
がPkgB.jl
(GPLライセンス)に依存していた場合に、PkgA.jl
がPkgB.jl
の派生物とみなされるか(コピーレフトが適用されるか)が問題になります。Juliaのパッケージ依存はリンクではないので派生物でないと判断されることが通常ですが、一方で派生物に関する議論は紛らわしく、GPLではなくMITの方が好ましいと考える人も多いです。例えばDiscourseの https://discourse.julialang.org/t/gpl-and-virality/7715/11 を参照して下さい。 ↩︎ -
https://github.com/JuliaRegistries/General/pull/31549#issuecomment-804196208 を参照してください。 ↩︎
-
公式ドキュメントとJuliaCon2019の動画も参照してください。 ↩︎
-
python、少なくともpytestでは複数形の
tests
ディレクトリが推奨されているようですね。https://docs.pytest.org/en/7.1.x/explanation/goodpractices.html#tests-outside-application-code 後述のdocs
フォルダは複数形だし、英語は謎です。 ↩︎ -
StaticArrays.jlにdependabotを追加するPRやdependabotが送ったPRも参照して下さい。 ↩︎
-
CI.yml
側で定期実行させる場合など。 ↩︎ -
最近私がメンテナンスして使えるようになりました。 https://github.com/julia-actions/julia-format/pull/24 をご覧ください。 ↩︎
Discussion
IonでPkgTemplates.jlの代わりができるらしいです