本日は
Julia アドベントカレンダー 2025 の記事 $N=16$ です.
Julia のパッケージ開発では何を使っていますか?私は VS Code または Cursor を使っています.
Julia のランゲージサーバ JETLS.jl と VS Code extension である jetls-client を使ってJuliaのソースコードリーディングを捗る方法を共有します.
有名な language-julia extension でもある程度できます.ただ,パッケージ開発をしているときに src/MyPkg.jl と同階層のファイルにある関数の定義参照はできますが,テストファイルを書いているときに test/runtests.jl 内のファイルにあるユーザ定義関数の定義参照がなぜか出来ていない問題が手元で発生しています.
一方で jetls-client を入れるとそれが解決できなのでこの記事で共有しようと思います.
環境構築
前提
- VS Code または Cursor などの派生エディタを使っていること
- ランゲージサーバ JETLS.jl はさまざまなエディタを提供していますが,ここでは VS Code ユーザを対象にします
- Sublime でもいけました!
- Julia 1.12
- Julia 1.12 から導入された機能を使って運用しているため
- macOS
- 他のOSでも動くと思うけれど念のため記載
~/.julia/bin を PATH 環境変数に追加
JETLS.jl は jetls というコマンドを ~/.julia/bin に入れます.これは
https://pkgdocs.julialang.org/v1/apps/ に書かれている機能を使うからです.この機能は平たく言えば Julia のアプリケーションパッケージのエントリーポイントをCLIコマンドとして簡単に呼び出せる機能です.細かいことは置いておいてとりあえず.パスを追加します:
export PATH="$HOME/.julia/bin:$PATH"
jetls コマンドを呼び出せるようにする.
ドキュメント https://aviatesk.github.io/JETLS.jl/release/ に従って進めます.ターミナルを開いて下記のコマンドを実行します.
julia -e 'using Pkg; Pkg.Apps.add(; url="https://github.com/aviatesk/JETLS.jl", rev="release")'
~/.julia/bin/jetls があることを確認し,jetls コマンドが叩ければ O.K. のようです.
testrunner の導入
ボタンポチッとをスト @testset 単位でテストを実行してくれます.ReTestItems.jl をご存知の方は @testitem でやっていたことを標準ライブラリの Test.jl を用意するだけでできる機能と理解することができます.
ターミナルを開いて下記を実行します.
julia -e 'using Pkg; Pkg.Apps.add(url="https://github.com/aviatesk/TestRunner.jl")'
jetls-client の導入
VS Code の Marketplace https://marketplace.visualstudio.com/items?itemName=aviatesk.jetls-client に移動して Install するだけです.Cmd + Shift + x で extension の管理する画面からインストールしても O.K. です.
(混乱を避けるために一時的に) language-julia extension を無効にする
jetls-client が動いていることを確認するために languge-julia extension を無効にしておきます.
(2,3時間使ってる限り,jetls-client, language-julia は両方インストールしていても問題はなさそうです.)
動作例(定義参照)
Example.jl をローカル環境にクローンして試すこともできますが,ここではせっかく作った NumFactor.jl パッケージで試してみます.
test/runtests.jl を開くと下記のような実装になっていましたね.
マウスを下記のラインに当ててみます.
NumFactor.main(["13"])
下記画像のように NumFactor.main の詳細を見ることができます.初回は少々時間がかかります.5秒ほどすると機能が使えるようになります.
macOS 環境だと Cmd キーを押しながら main の部分をポチッとクリックすると定義にジャンプすることができます.
無事ジャンプできました.
余談ですが,Sublime Text だと下記のようになります.
.ipynb ファイル上での定義参照
.ipynb ベースでコードを書いている Jupyter ユーザであれば朗報でしょう.
domath の docstring もみれますし domath 関数の定義参照も可能です.
制約
.jl を.ipynbとして読み込む jupytext 系の extension でノートブックを開くと jetls-client の機能は使うことができませんでした.動いてたらめっちゃ嬉しかったのですが......
また ReTestItems.jl が提供する @testitem マクロ内で書かれているコードのシンボルの参照がうまく働かないようです.個人的にはこれがサポートされると嬉しい(ReTestItems.jl を使ってコードを書いてたりするので).
@testitem "Example" begin
using Example
@test hello("Julia") == "Hello, Julia"
@test domath(2.0) ≈ 7.0
end
上記の例ですと domath にカーソルを当てても Binding domath does not exist で処理が止まってるようです.
動作例(@testset 単位でのテスト実行)
下記画面の 5 行目付近に Run "main with a prime number" という薄い灰色の文字が見えるでしょう.
このボタンを押すと当該の箇所のみをテスト実行してくれます.これは個人的に嬉しい.
また,テストが失敗する行も教えてくれます.
@testset "math" begin
@test 1 + 1 == 2
@test 1 + 1 == 3 # ここだけテストが失敗する.
@test 1 + 1 == 2
end
Run ボタンを押すと 1 + 1 == 3 の箇所に赤線が出てその行のテストが失敗することがわかりますね.
TestRunner インテグレーションのページを見ると@testsetマクロが入れ子になってるケースでもできそうなのでお試しあれ.
Limitation
ReTestItems.jl ベースで書かれてるテストコード環境とは相性が悪いです.
上記のケースだとRun "API" のボタンを叩くとテストはパスしていても失敗するケースとして扱われています.
═══ Test Setup ═══
Info: Julia version: 1.12.2
Info: Julia executable: /Users/terasaki/.julia/juliaup/julia-1.12.2+0.aarch64.apple.darwin14/bin
Info: Active environment: /Users/terasaki/tmp/Example.jl/test/Project.toml
Project path: /Users/terasaki/tmp/Example.jl/test/Project.toml
═══ Test Configuration ═══
Info: File: /Users/terasaki/tmp/Example.jl/test/runtests.jl
Info: Patterns:
[1] String: "API"
Info: Filter lines: 6
═══ Running Tests ═══
runtests.jl: Error During Test at /Users/terasaki/.julia/packages/TestRunner/62IDR/src/app.jl:453
Got exception outside of a @test
can not merge projects
Stacktrace:
[1] pkgerror(msg::String)
@ Pkg.Types ~/.julia/juliaup/julia-1.12.2+0.aarch64.apple.darwin14/share/julia/stdlib/v1.12/Pkg/src/Types.jl:68
[2] activate(pkg::String; allow_reresolve::Bool)
@ TestEnv ~/.julia/packages/TestEnv/i9lgt/src/julia-1.11/activate_set.jl:44
[3] activate (repeats 2 times)
@ ~/.julia/packages/TestEnv/i9lgt/src/julia-1.11/activate_set.jl:9 [inlined]
[4] (::ReTestItems.var"#_runtests##0#_runtests##1"{ReTestItems.TestItemFilter{Returns{Bool}, Nothing, Nothing}, Tuple{String}, ReTestItems._Config, String})()
@ ReTestItems ~/.julia/packages/ReTestItems/5fo5Q/src/ReTestItems.jl:391
[5] with_logstate(f::ReTestItems.var"#_runtests##0#_runtests##1"{ReTestItems.TestItemFilter{Returns{Bool}, Nothing, Nothing}, Tuple{String}, ReTestItems._Config, String}, logstate::Base.CoreLogging.LogState)
@ Base.CoreLogging ./logging/logging.jl:540
[6] with_logger(f::Function, logger::Base.CoreLogging.ConsoleLogger)
@ Base.CoreLogging ./logging/logging.jl:651
[7] _runtests(ti_filter::Function, paths::Tuple{String}, cfg::ReTestItems._Config)
@ ReTestItems ~/.julia/packages/ReTestItems/5fo5Q/src/ReTestItems.jl:377
[8] runtests(shouldrun::Returns{Bool}, paths::String; nworkers::Int64, nworker_threads::String, worker_init_expr::Expr, testitem_timeout::Float64, retries::Int64, memory_threshold::Float64, debug::Int64, name::Nothing, tags::Nothing, report::Bool, logs::Symbol, verbose_results::Bool, test_end_expr::Expr, validate_paths::Bool, timeout_profile_wait::Int64, gc_between_testitems::Bool, failfast::Bool, testitem_failfast::Bool, failures_first::Bool)
@ ReTestItems ~/.julia/packages/ReTestItems/5fo5Q/src/ReTestItems.jl:347
[9] runtests(shouldrun::Function, paths::String)
@ ReTestItems ~/.julia/packages/ReTestItems/5fo5Q/src/ReTestItems.jl:298
[10] #runtests#36
@ ~/.julia/packages/ReTestItems/5fo5Q/src/ReTestItems.jl:276 [inlined]
[11] runtests
@ ~/.julia/packages/ReTestItems/5fo5Q/src/ReTestItems.jl:273 [inlined]
[12] runtests(pkg::Module)
@ ReTestItems ~/.julia/packages/ReTestItems/5fo5Q/src/ReTestItems.jl:272
[13] runtest
@ ~/.julia/packages/TestRunner/62IDR/src/TestRunner.jl:94 [inlined]
[14] macro expansion
@ ~/.julia/juliaup/julia-1.12.2+0.aarch64.apple.darwin14/share/julia/stdlib/v1.12/Test/src/Test.jl:1776 [inlined]
[15] runtest_internal(filename::String, patterns::Vector{Any}, filter_lines::Set{Int64}, verbose::Bool, project::String)
@ TestRunner.TestRunnerApp ~/.julia/packages/TestRunner/62IDR/src/app.jl:453
[16] runtest_json(filename::String, patterns::Vector{Any}, filter_lines::Set{Int64}, verbose::Bool, project::String)
@ TestRunner.TestRunnerApp ~/.julia/packages/TestRunner/62IDR/src/app.jl:466
[17] runtest_app
@ ~/.julia/packages/TestRunner/62IDR/src/app.jl:503 [inlined]
[18] main(args::Vector{String})
@ TestRunner.TestRunnerApp ~/.julia/packages/TestRunner/62IDR/src/app.jl:136
[19] _start()
@ Base ./client.jl:556
Test Summary: | Pass Error Total Time
runtests.jl | 2 1 3 4.8s
API | 2 2
まぁ,ReTestItems.jl に頼らず標準の Test.jl の機能でテストを書いて JETLS.jl, TestRunner.jl のエコシステムにマイグレーションすれば良いと言えば良いのかもしれません.
ReTestItems.jl が良いと思ってる理由
ReTestItems.jl が好きなのは VS Code の Test 機能を経由して利用できているからなんですよね.pytest でも愛用しています.
一方でちょっと嬉しいこと
Sublime Text でコードを読み書きがしやすくなった!!!
Sublime Text は個人的な好みなエディタだったので JETLS で強化できるのは嬉しいです.
language-julia は不要か?(まだ欲しい)
仮に JETLS.jl が language server として覇権を取っても language-julia extension の魅力は残り続けると思います.例えば,profview マクロを使いパフォーマンスの向上・ボトルネックを調査するためこの機能には大変お世話になってます.
今の所両方の extension を入れていても壊れないのでひとまずは両方入れて開発していこうかなと思います.
まとめ
JETLS.jl jetls-client で遊びました.定義参照がマトモに動いたのは嬉しいです.JETLS.jl は JuliaCon 2025 の発表でもあったので認知はしていたのですが,まだ開発途上で使えないと誤った理解をしていました.
ちなみに実際に動作するよーという情報, 特に jetls-client の存在は先日ありました JuliaLang Japan 2025 の意見交換会で開発者から直接知ったものなのでJuliaLang Japan 2025に参加してよかったです.











