57
48

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Juliaのパッケージ開発に便利なPkgTemplates, Revise, developコマンド

Last updated at Posted at 2019-05-10

この記事では,パッケージのひな形を生成するところからGithubでの公開までのステップをひとつずつ見ていきながら,developdev)コマンドなど,REPLでのパッケージ開発に有用な機能を紹介します。まずは簡単なプロジェクトを作成し,それをパッケージとしてまとめてみます。

パッケージ開発の手順

前回の記事で説明したプロジェクトの作成方法は(pkgモードでgenerateコマンド実行),パッケージのひな形を作るのに有効です。しかし,パッケージの開発を助けるためには,もう少し多くの手順を,順を追って実行することになるでしょう。それらの手順を簡素化するために,外部パッケージやJuliaコマンドを利用します。

  1. まずパッケージのひな形を用意します。必要なファイルを自動生成するPkgTemplatesパッケージが便利です。
  2. 開発途中のパッケージを,Juliaに認識させます。これはpkgモードのdevelopコマンドで実現できます。
  3. 開発途中のパッケージをJuliaに認識させたあとで,そこに含まれるファイルを修正したとき,その変更をJuliaに反映するには,Juliaを再起動しなければなりません。この問題はReviseパッケージで解消できます。
  4. テストはtest/runtests.jlに記述します。pkgモードのtestコマンドで,このスクリプトを実行できます。
  5. 必要ならば,パッケージをGithubで公開します。公式レポジトリに登録するには,さらにもうひと手間かかります。この手順を丁寧に説明したWebサイトがあります。

上から順に見てゆきましょう。なお,この記事の執筆に当たり,以下のサイトを参考にしました。

2023年9月26日追記:PkgTemplatesは積極的に更新されており,その使い方も以前と比べてかなり変わっています。上記のリストも,随時入れ替えていますが,一部には古い内容が含まれているかもしれません。この記事では,記事更新時点での最新版であるPkgTemplates v0.7.45を使用します。

パッケージのひな形を作る

PkgTemplatesの使い方

標準のgenerateは最小限のプロジェクトを作成しました。自分自身または身内だけで使う場合,この構成で十分です。

もしこのプロジェクトを標準的なパッケージとして公開するなら,このコマンドで生成されないファイルやディレクトリを用意する必要があります。このような「パッケージのひな形を作る」には,PkgTemplatesパッケージが利用できます。

具体的な手順は,cometscome_phys さんの記事で説明されています。Hiroshi Shinaokaさんも簡潔に説明しています。ここでは,今後の説明のために,小さなパッケージMyPackageを作ります。これはGithub上に置かれ,最終的には公式パッケージとして登録する可能性も考慮します。あらかじめデフォルト環境にPkgTemplatesをインストールしておきます。

PkgTemplatesのデフォルト挙動(Gitのグローバル設定を引き継ぐ)

Juliaのパッケージは(たとえ公開する予定がなくても)gitレポジトリとして作成される機会が多くなっています。これを反映して,PkgTemplatesが作成するひな形はgitレポジトリそのものであり,その作成の際にはgitのユーザ名を必要とします。システムにgitが存在するならば,PkgTemplatesgit config --globalのuser.nameとuser.emailを読み取り,新規作成するパッケージに引き継ぎます。もし,個別のユーザ名を使いたい場合,あるいは,gitがうまく呼び出せなくてエラーになる場合(さらにはgitが不要な場合)には,テンプレート内にそれらの情報を記述することで回避できます。このための設定は後述します。

ひとまずここでは,gitのグローバル設定を引き継いで,パッケージのひな形を作成します。REPLで以下のコマンドを入力します。なお,各項目の説明は,?Templateで表示できます。

# パッケージをロードします
julia> using PkgTemplates

# gitのグローバル設定に基づいてテンプレートを作ります
julia> tmpl = Template(;
      user="mametank",
      authors=["Mametank <mametank@example.com>"],
      dir=".",
      julia=v"1.9",
      plugins=[
        License(; name="MIT"),
        Git(; ssh=true),
        GitHubActions(),
      ],
  )

# テンプレートに基づいてファイルとディレクトリを作成します
julia> tmpl("MyPackage")

なお,plugins=[]内には,さまざまな追加機能や設定を記述します。上記の例では,License()がライセンスを設定し,Git()がgitの詳細な設定を行います。GitHubActions()は,Github ActionsによるCIをサポートします。AppVeyorやTravisCIを使いたい場合,それらを適切な書式で記述します(詳しくは?AppVeyorまたは?TravisCIを参照してください)。このほかにも,多数の機能がサポートされています。

さて,上記のコマンドを実行すると,以下のディレクトリとファイルが生成されます。

C:.
└───MyPackage
    │   .gitignore
    │   LICENSE
    │   Manifest.toml
    │   Project.toml
    │   README.md
    │
    ├───.git
    │   (Gitリポジトリの情報)
    │
    ├───.github
    │   └───workflows
    │           CI.yml
    │           CompatHelper.yml
    │           TagBot.yml
    │
    ├───src
    │       MyPackage.jl
    │
    └───test
            runtests.jl

このパッケージは,カレントディレクトリ直下にgitレポジトリとして作られます。もしgitやGithubを使う予定がなければ,不要なファイルは削除するか,単に無視してかまいません。

ユーザ名とemailの個別指定

グローバルなgit設定とは異なるユーザ名を使いたい場合,あるいは,gitがうまく呼び出せなくてエラーになる場合には,テンプレートのGit内にそれらの情報を記述することで回避できます。また,gitを使う予定がない場合にも,適当なユーザ名とメールドレスを記入しておけば,以下の方法でひな形が作成できます。上記のコマンドを以下のように変更します。

# テンプレートの作成時に,gitのuserとemailを指定してしまう
julia> tmpl = Template(;
      user="mametank",
      authors=["Mametank <mametank@example.com>"],
      dir=".",
      julia=v"1.9",
      plugins=[
        License(; name="MIT"),
        Git(; name="mametank", email="mametank@example.com", ssh=true),
        GitHubActions(),
      ],
  )

# テンプレートに基づいてファイルとディレクトリを作成します
julia> tmpl("MyPackage")

ひな形作成後の設定変更

ファイルを生成したあとで設定を変えたいときは,Project.tomlをエディタで開いて修正し,さらにgitレポジトリの設定を変える必要があります。これらの修正は,Julia上のshellモードで行うか,Juliaコマンドを実行することで実現できます。

以下,コマンドを使って修正する場合のサンプルを示します。パス表記がWindowsのものですので,適宜読み替えてください。

# エディタでファイルを開く
julia> edit("MyPackage\\Project.toml")

# gitリポジトリのユーザ情報を変更する
julia> run(`git -C MyPackage config --local user.name mametank`)

julia> run(`git -C MyPackage config --local user.email mametank@example.com`)

ここまで作成できれば,あとは必要に応じてファイルを追加・編集できます。また,gitによるcommitやpushも実行できます。

依存するパッケージを追加

新規作成したばかりのパッケージは,独立した環境を持ちます。この環境で使える依存パッケージを登録するには,pkgモードでactivateした上で,addコマンドを実行します。詳しくは前回の記事を参照してください

たとえば,新規作成したパッケージ内でLinearAlgebraを使うとしましょう。この場合,Pkgモードで以下のように入力します。

# 開発中のパッケージのパスを指定し,その環境に切り替える
(v1.1) pkg> activate MyPackage

# 依存パッケージを明示する
(MyPackage) pkg> add LinearAlgebra
 Resolving package versions...
  Updating `c:\Users\home\local\src\julia\MyPackage\Project.toml`
  [37e2e46d] + LinearAlgebra
  Updating `c:\Users\home\local\src\julia\MyPackage\Manifest.toml`
  [8f399da3] + Libdl
  [37e2e46d] + LinearAlgebra

# 追加が終わったらグローバル環境に戻る
(MyPackage) pkg> activate

# 表示がもとに戻ったことを確認する
(v1.1) pkg>

このプロジェクトが直接依存するパッケージ名はProject.tomlに,すべてのパッケージの依存関係はManifest.tomlに保存されます。これらはパッケージマネージャがうまく処理してくれますので,ユーザが気にする必要はありません。

developコマンドで開発をスタートする

まずはじめに,パッケージを新規開発し,それをローカルに保存するケースを考えます。続けて,Githubに配置したパッケージの開発も紹介します。

なぜdevelopコマンドか

通常,Juliaでパッケージを利用可能にするには,Pkgモードでaddコマンドを実行し,Juliaにそれを認識させる必要があります。正確に言えば,パッケージを使うには,その情報をProject.tomlManifest.tomlに追加しなければなりません。たとえそれが開発途中のパッケージで,その内容がすぐに変更されるにしても,です。

しかし,開発中のパッケージをaddで追加することは,実用的ではありません。addを使うべきでない(使おうにも使えない)理由は,以下のとおりです。

  1. addは,1つの環境にパッケージの複数のバージョンをインストールできない。すでに完成してインストールされているパッケージ修正したい場合,開発版を一時的にaddしようとすれば,既存のバージョンをアンインストールする(あるいは開発版で上書きする)必要が生じる。
  2. addが読み込めるローカルなパッケージは,gitレポジトリだけである。パッケージをPkgTemplatesで作成した場合には問題ないが,generateで生成した生のパッケージは読み込めない。

パッケージを開発するために,Juliaにはdevelop(短縮してdev)コマンドが用意されています。これはaddと同様に,開発中のパッケージをJuliaにインストールしますが,上記の欠点を克服し,より柔軟に動作します。

ローカルで新規パッケージを開発する

さて,上の例で作ったMyPackageディレクトリを,devコマンドでJuliaに認識させましょう。まずは,自分がデフォルト環境にいることを確認します。続けて,devコマンドに,このプロジェクトのフルパスを与えて実行します(プロジェクト名だけ与えると,JuliaはGithubを読みに行き,エラーを返すかもしれません)。

(v1.1) pkg> dev c:\\Users\\home\\local\\src\\julia\\MyPackage
 Resolving package versions...
  Updating `C:\Users\home\.julia\environments\v1.1\Project.toml`
 [no changes]
  Updating `C:\Users\home\.julia\environments\v1.1\Manifest.toml`
 [no changes]

これで,現在の環境(デフォルト環境)で,自作パッケージを認識させることができました。もし,自作パッケージをactivateしたまま,このdevを実行すると,以下のエラーが出ます。

(v1.1) pkg> activate MyPackage

(MyPackage) pkg> dev C:\Users\home\local\src\julia\MyPackage
ERROR: Cannot develop package with the same name or uuid as the project

あとは,REPLのコマンドモードに戻り,usingでパッケージを読み込みます。これで,開発中のパッケージの機能が使えるようになります。

julia> using MyPackage

このように追加したパッケージを削除するには,通常と同じくrmを使います。1

(v1.1) pkg> rm MyPackage
  Updating `C:\Users\home\.julia\environments\v1.1\Project.toml`
  [a1239a20] - MyPackage v0.1.0 [`c:\\Users\\home\\local\\src\\julia\\MyPackage`]
  Updating `C:\Users\home\.julia\environments\v1.1\Manifest.toml`
  [a1239a20] - MyPackage v0.1.0 [`c:\\Users\\home\\local\\src\\julia\\MyPackage`]

Github上の既存パッケージに手を入れる

ここまで説明した方法は,Github上のパッケージの開発でも共通です。ここからは,すでにパッケージがGithub上にある場合に,これを修正するケースを考えます。そして,その自作パッケージがすでにインストールされている環境で,このパッケージを修正していきます。Julia公式のテスト用のパッケージExampleを用いて,実際の手順を見てみましょう。

まず,デフォルト環境にExampleをインストールします。

(v1.1) pkg> add Example
 Resolving package versions...
  Updating `C:\Users\home\.julia\environments\v1.1\Project.toml`
  [7876af07] + Example v0.5.1
  Updating `C:\Users\home\.julia\environments\v1.1\Manifest.toml`
  [7876af07] + Example v0.5.1
  [2a0f44e3] + Base64
  [8ba89e20] + Distributed
  [b77e0a4c] + InteractiveUtils
  [56ddb016] + Logging
  [d6f4376e] + Markdown
  [9a3f8284] + Random
  [9e88b42a] + Serialization
  [6462fe0b] + Sockets
  [8dfed614] + Test

(v1.1) pkg> status
    Status `C:\Users\home\.julia\environments\v1.1\Project.toml`
  [7876af07] Example v0.5.1

このパッケージを修正するには,以下のdevコマンドを使います。これもデフォルト環境で実行します。

(v1.1) pkg> dev Example
[ Info: Path `C:\Users\home\.julia\dev\Example` exists and looks like the correct package, using existing path
 Resolving package versions...
  Updating `C:\Users\home\.julia\environments\v1.1\Project.toml`
  [7876af07]  Example v0.5.1  v0.5.1+ [`C:\Users\home\.julia\dev\Example`]
  Updating `C:\Users\home\.julia\environments\v1.1\Manifest.toml`
  [7876af07]  Example v0.5.1  v0.5.1+ [`C:\Users\home\.julia\dev\Example`]

(v1.1) pkg> status
    Status `C:\Users\home\.julia\environments\v1.1\Project.toml`
  [7876af07] Example v0.5.1+ [`C:\Users\home\.julia\dev\Example`]

このパッケージがcloneされ,規定の.julia/dev/Exampleディレクトリに保存されました。Exampleのバージョン番号に+が表示され,これが開発中であることを意味しています。つまり,この環境には,既存のバージョンと開発版のバージョンの両方がインストールされていますが,開発中のバージョンが優先して使われています。そのパッケージを使うには,REPLでusing Exampleとします。

julia> using Example
[ Info: Recompiling stale cache file C:\Users\home\.julia\compiled\v1.1\Example\lLvWP.ji for Example [7876af07-990d-54b4-ab0e-23690620f79a]

これ以降,パッケージの中身を変更してゆき,修正を適時git commitします。この開発版のパッケージは,ずっと.julia/dev以下に居座ります。パッケージマネージャ自身がはその中身を変更することはありません。また,修正をgitレポジトリにpushするかどうかは,開発者の判断です。開発を終えて,これを削除するには,pkgモードの(rmではなく)freeコマンドを使います。

(v1.1) pkg> free Example
 Resolving package versions...
  Updating `C:\Users\home\.julia\environments\v1.1\Project.toml`
  [7876af07]  Example v0.5.1+ [`C:\Users\home\.julia\dev\Example`]  v0.5.1
  Updating `C:\Users\home\.julia\environments\v1.1\Manifest.toml`
  [7876af07]  Example v0.5.1+ [`C:\Users\home\.julia\dev\Example`]  v0.5.1

(v1.1) pkg> status
    Status `C:\Users\home\.julia\environments\v1.1\Project.toml`
  [7876af07] Example v0.5.1

開発版を解放したことで,以前のように,開発版ではない(+のない)オリジナルのパッケージが利用できるようになりました。

ReviseでJuliaの再起動を避ける

パッケージ開発で最大の障害は,usingで読み込んだパッケージは,REPLを再起動するまで変更できないことです。いちどusingしてしまうと,パッケージ内のファイルを修正して再びusingしても,その変更は現在のセッションには反映されません。Reviseパッケージは,この制限を廃し,ファイルの変更をすぐに現在のセッションに反映させるものです。なお,開発環境であるJunoは,これとは別の仕組みで,これと同等の機能を提供しています。

使い方は簡単で,開発中のパッケージをusingする前に,using Reviseを実行するだけです。あとは何もする必要はありません。これをスタートアップスクリプトに書いておくと,実行忘れを防げます。

実際にさきほどのMyPackageReviseで更新してみましょう。devで開発を始めたあと,ReviseMyPackageの順にパッケージを読み込みます。

# これを最初に実行する
julia> using Revise

# 続けてパッケージを読み込む
julia> using MyPackage

さて,ここでsrc/MyPackage.jlに含まれるgreet()関数を実行してみましょう。これはexportされていないので,パッケージ名をつけて明示的に実行する必要があります。

# exportされていないのでエラーになる
julia> greet()
ERROR: UndefVarError: greet not defined
Stacktrace:
 [1] top-level scope at none:0

# パッケージ名を含めると実行できる
julia> MyPackage.greet()
Hello World!

ここでsrc/MyProject.jlをエディタで開き,greet()をexportするように修正しましょう。

module MyPackage

greet() = print("Hello World!")

# この行を新たに追加する
export greet

end # module

REPLに戻り,今度は単にgreet()を実行してみましょう。

# 少し待ちます
julia> greet()
Hello World!

コマンド入力後,更新ファイルを検知してプログラムを再コンパイルするので,少し待たされます。その後,この関数が問題なく実行されるでしょう。

テストを書く

パッケージにはテストのためのスクリプトを含めることができます。このスクリプト名は決め打ちで,test/runtests.jlです。この中で別のファイルをincludeできますので,ファイルを分割することができます。

テストの書き方は,いくつかのサイト(たとえば,Julia - パッケージ作りJulia v1.0 でユニットテスト)でわかりやすく紹介されています。ここでは,さきほどの続きで,Reviseを利用した状態で test/runtests.jl を編集し,これが正しく動くことを確認します。

テストファイルtest/runtests.jlを開き,その内容を以下のように編集して保存します。

using MyPackage
using Test

@testset "Function tests" begin
    @test_nowarn greet()
end

このテストは完全に独立な環境下で実施されるので,必要なパッケージをすべてusingで読み込む必要があります。@testset "テストグループ名" begin ... end は,テストグループを定義するマクロで,その中に複数のテストを記述します。異なる種類のテストを行う場合には,また別の@testsetを定義します。

テストの鍵である@test_nowarnは,引数の命令文がwarningを出さないかをテストし,出さない場合にtrue(テスト成功)を返すマクロです。もし命令文がbooleanを返すなら,単に@testマクロが利用できます(真ならばテスト成功)。

さて,このファイルはJuliaスクリプトですので,REPLからそのまま実行できます。ですが,これはすでにパッケージの一部ですので,pkgモードで実行するほうが統一的です。testコマンドのあとにパッケージ名をつけて実行します。

# パッケージ内の test/runtests.jl を実行する
(v1.1) pkg> test MyPackage
   Testing MyPackage
 Resolving package versions...
    Status `C:\Users\home\AppData\Local\Temp\jl_2C17.tmp\Manifest.toml`
  [a1239a20] MyPackage v0.1.0 [`c:\Users\home\local\src\julia\MyPackage`]
  [2a0f44e3] Base64  [`@stdlib/Base64`]
  [8ba89e20] Distributed  [`@stdlib/Distributed`]
  [b77e0a4c] InteractiveUtils  [`@stdlib/InteractiveUtils`]
  [8f399da3] Libdl  [`@stdlib/Libdl`]
  [37e2e46d] LinearAlgebra  [`@stdlib/LinearAlgebra`]
  [56ddb016] Logging  [`@stdlib/Logging`]
  [d6f4376e] Markdown  [`@stdlib/Markdown`]
  [9a3f8284] Random  [`@stdlib/Random`]
  [9e88b42a] Serialization  [`@stdlib/Serialization`]
  [6462fe0b] Sockets  [`@stdlib/Sockets`]
  [8dfed614] Test  [`@stdlib/Test`]
Hello World!Test Summary: | Pass  Total
Function tests |    1      1
   Testing MyPackage tests passed

もちろん警告は出ませんので,テストは成功します。このスクリプトは,テストグループを1つだけ(Function tests)含むので,その結果だけが表示されています。Reviseを実行していれば,このテストスクリプトの変更も,即座にtestコマンドに反映されます。

パッケージを公開する

以上の手順を繰り返し,パッケージを開発します。開発者の判断で,git commitとgit pushを行います。ここには示しませんでしたが,依存ライブラリはdepsに,ドキュメントはdocにそれぞれ保存します。そのほかの公開に必要なファイルは,すでにPkgTemplatesが用意してくれました。このgitレポジトリをGithubに配置すれば,非公式パッケージの完成です。

これを公式パッケージとして登録するには,CIを実行してビルドをパスしている必要があります。その後,JuliaRegistratorでパッケージを登録します。以下の記事がその方法を丁寧に説明してくれています。

まとめ

  • 最初にパッケージのひな形をつくる。PkgTemplatesはパッケージをgitレポジトリとして準備する。
  • パッケージ開発は,pkgモードのdevelopコマンドで開始する。
  • 開発中のパッケージを読み込む前にusing Reviseを実行する。
  • テストはtest/runtests.jlに書き,pkgモードでtestコマンドを実行する。
  • 以上を繰り返して,パッケージを仕上げる。
  • これを公式パッケージにするには,CIをパスし,JuliaRegistratorを呼び出す。
  1. パッケージ開発を紹介するブログやマニュアルではfreeを使う例が多く出てきます。これは,後述するような,以前のバージョンと開発版のバージョンが両方存在する場合に,開発版だけを除去するコマンドです。この例では,開発中のバージョンだけが環境に存在するので,rmで除去できます。

57
48
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57
48

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?