ada_language_server =================== .. post:: Nov 5, 2024 :tags: gnat 近年はLanguage Server Protocol(以下LSP)のお陰でIDEやエディタを問わず補完や宣言等へのジャンプが使えるようになりました。 それでGNAT/gcc用のLSPサーバーであるada_language_serverを使おう、という話です。 これまでAdaで宣言等を探すのはエディタに外部ツールを登録するなどでカーソル位置をgnatfindやgnatxrefに渡すことで実現していました。 gnatfindやgnatxrefはgcc 12でなくなってしまいましたので代わりが必要なのです。 バイナリの入手方法 ------------------ ada_language_serverをビルドするのは大変です。 簡単にバイナリを手に入れる方法もいくつかあります。 Visual Studio Codeの拡張として入手 ++++++++++++++++++++++++++++++++++ 開発元であるAdaCore社が `Ada & SPARK `_ というVisual Studio Codeの拡張も作ってくださってますので、これをインストールすればコンパイル済みのada_language_serverも付いてきます。 ダウンロードされたものは例えば *~/.vscode/extensions/adacore.ada-25.0.20240915-linux-x64/x64/linux/ada_language_server* のような位置にあります。 他の言語のLSPサーバーも事情は似たりよったりですので、もしVisual Studio Codeを使わないとしてもダウンローダーとして利用するのは有りだと思います。 githubから入手 ++++++++++++++ https://github.com/AdaCore/ada_language_server/releases にもコンパイル済みバイナリが置かれています。 あれ、以前はソースコードだけでしたような。 ありがたいことです。 Alireでビルド +++++++++++++ とはいえ自分でビルドしたい場合もあるでしょう。 本当に自力でビルドするとなりますとライブラリを揃えて.gprファイルのパスを調整してと大変な手間がかかりますので `Alire `_ の力を借りることにします。 .. container:: gray-and-small 処理系ごとに再発明されたパッケージマネージャなんて覚えたくもありませんし大抵こなれてませんから酷い仕様がありますし他にビルド方法が用意されていなければミックスランゲージやクロスコンパイルの障害にもなりますしいいことはあまりないのですが、こうした巨大プロジェクトをとりあえず手元で動くようビルドするにはやっぱり便利ですね。 くっ。 Alireの環境設定 ^^^^^^^^^^^^^^^ しばらくぶりに触りましたが :doc:`Alireのインストール方法は以前とあまり変わっていませんでした `\ 。 ただ *~/.config* の中で全てを展開するクソ仕様は流石に\ `修正されていました `_\ ので ``cache.dir`` を設定しましょう。 ``cache.dir`` という名前ですがその下で実際にビルドも行われますので純粋なキャッシュではないです。 ビルドに失敗すれば中のファイルを修正する必要も出てきます。 また環境変数 ``ALIRE_SETTINGS_DIR`` で設定ファイルの位置も変更できますので .. code-block:: sh $ export ALIRE_SETTINGS_DIR=$PWD/alireconfig $ alr settings --global --set cache.dir $PWD/alirecache のようにすれば *~/.config* には何も置かずに済みます。 Alireの設定が終わりましたらまずツールチェインを選びまして。 .. code-block:: $ alr toolchain --select Welcome to the toolchain selection assistant In this assistant you can set up the default toolchain to be used with any crate that does not specify its own top-level dependency on a version of gnat or gprbuild. If you choose "None", Alire will use whatever version is found in the environment. ⓘ gnat is currently not configured. (alr will use the version found in the environment.) Please select the gnat version for use with this configuration 1. gnat_native=14.2.1 2. None 3. gnat_arm_elf=14.2.1 4. gnat_avr_elf=14.2.1 5. gnat_riscv64_elf=14.2.1 6. gnat_xtensa_esp32_elf=14.2.1 7. gnat_arm_elf=14.1.3 8. gnat_avr_elf=14.1.3 9. gnat_native=14.1.3 0. gnat_riscv64_elf=14.1.3 a. (See more choices...) Enter your choice index (first is default): > 1 ここでは1のネイティブコンパイラの最新版を選んでおきます。 ada_language_serverをビルド ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 事前にlibcの他にlibstdc++や `GMP `_ も環境にインストールされている必要がありますが、そもそもgccを動かすために必要なものばかりですので既に入っているはずです。 ではada_language_serverを取ってきてビルドしてみましょう。 .. code-block:: sh $ alr get ada_language_server $ cd ada_language_server_24.0.0_a8b6ee7c $ alr build コマンドとしてはこれだけです。 量が多いので時間がかかります。 各ライブラリのビルドは ``cache.dir`` の下の *builds* の中で、ada_language_server本体のビルドはada_language_serverをクローンしたディレクトリ *ada_language_server_24.0.0_a8b6ee7c* の中で行われます。 *.obj* というドット付きディレクトリになっていますので探しづらいものの最終的に実行ファイルは *./.obj/server/ada_language_server* に作られます。 (Alireの仕様ではなくada_language_serverの *gnat/lsp_server.gpr* の記述によるものです。) 実行ファイルとしての *ada_language_server* はデータファイルも必要とせずライブラリもAdaで書かれた分は全て静的リンクされていますので、好きな場所に移動しても動きます。 ところでこうして何もオプションをつけずにビルドした場合はデバッグ用ビルドになります。 それぞれの.gprファイル次第ではありますがだいたいは最適化オプションも ``-O0`` か ``-Og`` ですので実用のためにはリリース用ビルドをしたいところです。 リリース用にビルドするにはalr buildコマンドに ``--release`` オプションを渡します。 デバッグ用ビルドとリリース用ビルドでディレクトリを分けてくれているかそれとも混ざってしまうのかも.gprファイル次第になってしまいますのでオプションを変えてビルドをやりなおすには一度 ``alr clean`` でビルドしたものを削除したほうが確実です。 .. code-block:: sh $ alr clean $ alr build --release ……実はこれだけですとまだ駄目です。 **幸いにも**\ Adaの各プロジェクトはまだAlireへの依存が低いためAlire側でビルドオプションを変えても全てのプロジェクトでそれが使われるようにはなっていません。 これはAlireの使い勝手としては悪い反面、良い傾向とも言えます。 既にパッケージマネージャを用いないビルド方法が用意されていない酷いプロジェクトなんて溢れていますからね……。 gprbuildでは ``-X変数名=値`` の形で外から値を渡せます。 gprbuildを用いるほとんどのプロジェクトではこの方式で何らかのビルドオプションを指定できるようになっています。 alr buildコマンドでは ``--`` に続けて渡したオプションはそのままgprbuildに引き渡されますのでこれでビルドオプションを指定できます。 これもプロジェクト次第の話ですので例えばconfigureとmakeを用いる昔ながらのプロジェクトですとalr build経由でオプションを指定する方法が無かったりします。 そういうときはそれぞれのビルドディレクトリに入って個別対応する必要があります。 今回はada_language_server自体がAdaCore社製のプロジェクトということもあって全ての依存ライブラリでもgprbuildが使われていました。 それで適当にgrepしてみました結果、ほとんどは ``-XBUILD_MODE=prod`` で良さそうです。 vss(AdaCore社のUnicodeのライブラリ、Visual SourceSafeではない)のみ ``-XBUILD_PROFILE=release`` でした。 他の例外では *alire.toml* にも記述がなされておりalire buildの--releaseオプションに連動してくれました。 というわけで最終的なリリース用ビルドのやり方はこうなります。 .. code-block:: sh $ alr clean $ alr build --release -- -XBUILD_MODE=prod -XBUILD_PROFILE=release すんなりとうまくいった方はおめでとうございます。 私はリンクができませんでした。 依存ライブラリのひとつlibadasat.aの中身が存在していないかのようなエラーメッセージが出てきます。 nmしてもちゃんと必要な中身がありますのにリンカが見つけられないようです。 原因は上で選択したツールチェインのgnat_native 14.2.1に含まれるldがLTOに対応していないからでした。 *adasat.gpr* では ``BUILD_MODE`` が ``prod`` の時に ``-flto`` が有効になっていましたのでこれを削除することで無事にリンクできました。 ファイルの位置は ``cache.dir`` の下の *builds/adasat_24.0.0_46db8c9a/d497b5e492503ae325667c8793cc729c969f2a5fcfbee7c788ace2730761a029/adasat.gpr* です。 こうしてアドホックな修正が許されているって素晴らしいですね。 ネイティブコンパイラにリンクエラーは付きものですから。 .. role:: strike .. container:: gray-and-small :strike:`例えばOPAMのような最悪のパッケージマネージャではローカルでのアドホックな修正が許されておらず(以下略)` 修正ついでですのでada_language_serverの *gnat/lsp_server.gpr* に書かれていますリンクオプションに ``-Wl,--gc-sections`` を追加しちゃいましょう。 バイナリのサイズを減らすことができます。 Kateでの利用例 -------------- Visual Studio Codeから使うぶんには拡張を入れて説明通りに *settings.json* を書けば使えてしまいますので、他のエディタで使ってみましょう。 例として `Kate `_ を使用します。 QtCreaterやBBEdit等でも設定方法さえ見つけられれば同様に使えるはずです。 .. container:: gray-and-small :doc:`以前 `\ に使うのが難しいと愚痴ったのですけれども、うまく使えちゃえましたのでこれ書いてます。 まずはKateのLSPクライアントを有効にしてada_language_serverの存在を伝えるところからです。 設定ダイアログのPlugin ManagerからProject PluginとLSP Cientを有効にします。 .. image:: 2024_11_06_1_kateplugins.png するとLSP Clientの設定が現れますのでUser Server Settingsに次のような内容を入力します。 .. code-block:: json { "servers": { "ada": { "command": ["ada_language_server"], "highlightingModeRegex": "^Ada$", "root": "%{Project:NativePath}" } } } .. image:: 2024_11_06_1_kateservers.png シンタックスハイライトがAdaになっているファイルを開いたときにそのファイルが所属する「Kateのプロジェクト」のルートディレクトリをカレントディレクトリにして *ada_language_server* を実行する、という意味です。 Kateが「Kateのプロジェクト」と認識してくれる条件は\ `バージョン管理されていて.git/.svn/.hg等が存在するか、または.kateprojectファイルがある `_\ ことです。 大抵はバージョン管理しているでしょうから気にしなくていいです。 現在のプロジェクトはサイドバーのProjectsペインで表示できます。 さて、一応これでada_language_serverは起動してくれるようになりました。 しかしこれだけでは全機能を利用することはできません。 試しにこの状態で使ってみます。 .. image:: 2024_11_06_1_katenogpr.png プロジェクトファイルがないと言われました。 このプロジェクトファイルとは(*.kateproject* のようなエディタ固有の話ではなく).gprファイルのことです。 GNAT/gccはコンパイルの過程でクロスリファレンス情報を.aliファイルに出力します。 gnatfindもgnatxrefもそしてada_language_serverもこの情報を利用します。 .aliファイルは.oと同じディレクトリに作られますのでビルドに用いているディレクトリの位置が必要になりそれはビルドツールであるgprbuildと共有するのが自然、ということなのだと思います。 gnatfindやgnatxrefであればgnatmake同様のオプションを受け取ってくれますので.gprファイルは必須ではありませんでしたが、ada_language_serverでは.gprファイルは必須です。 というわけで.gprファイルを用意します。 .. code-block:: :caption: hello.gpr project Hello is for Object_Dir use "build"; for Source_Dirs use ("."); end Hello; ada_language_serverだけのためであれば実際にビルドできるように書く必要はありません。 ``Object_Dir`` と ``Source_Dirs`` だけで充分です。 少々複雑な例として `The Village of Vampire `_ [*]_ の場合も載せておきます。 .. code-block:: :caption: vampire.gpr project Vampire is for Object_Dir use "x86_64-linux-gnu.noindex"; for Source_Dirs use ( "source", "lib/openssl-ada/source", "lib/yaml-ada/source", "lib/web-ada/source"); for Runtime ("Ada") use "/opt/lib/drake/x86_64-linux-gnu/7.5.0"; end Vampire; ソースコードのディレクトリが複数ありAdaランタイムもgcc付属とは異なるものを利用しています。 ada_language_serverが動けばそれでいいので色々ハードコードしてしまっていますがちゃんと書くならアーキテクチャやバージョンは変数になるでしょう。 こうして用意した.gprファイルを、LSPの `initializationOptions `_ を通じて\ `位置を伝えてやる必要があります `_\ 。 Kateでは先程の設定にて `initializationOptionsの内容を指定することができます `_\ 。 .. code-block:: json { "servers": { "ada": { "command": ["ada_language_server"], "highlightingModeRegex": "^Ada$", "root": "%{Project:NativePath}", "initializationOptions": { "projectFile": "hello.gpr" } } } } しかしこれは……正直だめですよね。 全てのプロジェクトの.gprファイル名が同じなはずがないです。 プロジェクト毎に設定を変える必要があります。 これで長いこと悩んでいたのですが、方法を2つ見つけました。 .kateproject ++++++++++++ 大抵はバージョン管理しているでしょうから気にしなくていい、と書いた *.kateproject* ファイルですが\ `Kateのドキュメントをよくよく読み返してみますとLSPの設定を上書きすることができます `_\ 。 .. code-block:: json :caption: .kateproject { "name": "hello", "files": [{ "directory": "." }], "lspclient": { "servers": { "ada": { "command": ["ada_language_server"], "highlightingModeRegex": "^Ada$", "root": "%{Project:NativePath}", "initializationOptions": { "projectFile": "hello.gpr" } } } } } 他にもサイドバーのProjectsペインに表示するファイルをフィルタするような便利な機能もありますので *.kateproject* ファイルを作ることにしてしまえば何かと捗ります。 ada_language_serverの--configオプション +++++++++++++++++++++++++++++++++++++++ ada_language_serverには ``initializationOptions`` の内容を書いたファイルをコマンドライン引数で渡すことができます。 .. code-block:: $ ada_language_server --help Usage: ada_language_server [options] Options: --tracefile Full path to a file containing traces configuration --config Full path to a JSON file containing initialization options for the server (i.e: all the settings that can be specified through LSP 'initialize' request's initializattionOptions) --language-gpr Handle GPR language instead of Ada --version Display the program version -h, --help Display help information ですのでKate側の設定はこのようにしておいて .. code-block:: json { "servers": { "ada": { "command": [ "ada_language_server", "--config", "initializationOptions.json" ], "highlightingModeRegex": "^Ada$", "root": "%{Project:NativePath}" } } } 各プロジェクトのディレクトリに *initializationOptions.json* を置く、という方法です。 .. code-block:: json :caption: initializationOptions.json { "defaultCharset" : "UTF-8", "enableIndexing" : false, "projectFile" : "hello.gpr" } ハードコードしてしまいますと *initializationOptions.json* が無いとada_language_serverの起動に失敗することになりますので、ファイルがあるときだけオプションを足すようなラッパースクリプトを間に入れても良いかもしれません。 余計なファイルを置けないディレクトリ ++++++++++++++++++++++++++++++++++++ 余計なファイルを置きたくない、置けないディレクトリではどうしようもありませんのでその場合はラッパースクリプトに頼ることになります。 KateではOpen Folder...で任意のディレクトリをプロジェクト扱いで開くことができます。 ``root`` に ``%{Project:NativePath}`` と指定していますのでLSPサーバーはプロジェクトとして開いたディレクトリをカレントディレクトリにして起動されます。 ``command`` にラッパースクリプトを指定してその中で ``$PWD`` で分岐すればよいでしょう。 ラッパースクリプトを挟めば ``--config`` オプションに渡すファイルの置き場所や環境変数等の調整も含めて融通が利くようになります。 コンパイラの使い分けとPATH環境変数 ++++++++++++++++++++++++++++++++++ その他の注意事項としましてはada_language_serverは起動時点でPATHが通っているコンパイラを使います。 例えばAdaランタイムのソースコードにジャンプした場合はPATHの通っているコンパイラの *lib* 内に飛びます。 ランタイムライブラリと.aliファイルの内容が食い違えばうまく動かない機能もありそうです。 */usr/bin/gcc* だけを使うなら気にしなくてよいのですが、複数のバージョンを使い分けたり(Alire等で)他の場所にインストールしたツールチェインを使いたければ少し考える必要があります。 方法は色々あるとは思いますが *.kateproject* ファイルで解決する場合はenvコマンドが利用できます。 ``%{ENV:...}`` と書くことで環境変数を参照できます。 .. code-block:: json :caption: .kateproject { "name": "hello", "files": [{ "directory": "." }], "lspclient": { "servers": { "ada": { "command": [ "env", "PATH=/opt/ytomino/gcc-7.5.0/bin:%{ENV:PATH}", "ada_language_server" ], "highlightingModeRegex": "^Ada$", "root": "%{Project:NativePath}", "initializationOptions": { "projectFile": "hello.gpr" } } } } } .. image:: 2024_11_06_1_kategcc7.png ``Text_IO`` を選択してGo to Declarationでgcc-7.5.0のランタイムライブラリにジャンプできました。 まとめ ------ Kateとada_language_serverを例にしてきましたが、このようにLSPサーバーと ``initializationOptions`` を指定する方法さえあればKateに限らずお好きなエディタでAdaに限らずお好きな言語の補完やジャンプが使えるようになるはずです、たぶんきっと。 .. [*] 休止したまま放置しててごめんなさい……。