Chienomi

ArchlinuxのVSCodeでCaching Gemが進まない問題の解決

Live With Linux::trouble shooting

この記事はLinuxカテゴリです!

本文はLinux上での話をしています

Linuxに関する疑問解消や共有は日本語Linuxers

ArchlinuxのVSCode(Code OSS (code)やVSCodium (codium))でSolargraphを使うと

Caching gem

の表示が出続け、全く進捗せずCPUだけを浪費するという問題がある。

これはSolargraphの問題というわけではなく、Ruby-LSPを使っていてもそれなりに問題がある。

また、

bundle exec solargraph gems --rebuild

としても落ちる。

これは、大きくわけて2つの問題があり、そのうち1つはArchlinuxに固有の問題(Manjaro等にも引き継がれている)である。

今回のコードはCodebergに置いてある。

ArchlinuxのRubyパッケージの問題

概要

Archlinuxのrubyパッケージはかなり問題のあるもので、目立つ点としてはかなり長い間EOLになった3.0に留まって放置されていたり、現在も3.4系のままだったりする。

そして、分割パッケージングしているのにruby-default-gemsはすべてのDefault Gemを含んでいないし、ruby-bundled-gemsに至ってはbundlerしか含んでいない。

今回の問題が起きる原因ものパッケージングのまずさにある。 例としてabbrevを挙げよう。

ruby-abbrevはDefault Gemで、Archlinuxでは分割パッケージになっている。

そして、その定義は/usr/lib/ruby/gems/3.4.0/specifications/default/abbrev-0.1.2.gemspecにある。

# -*- encoding: utf-8 -*-
# stub: abbrev 0.1.2 ruby lib

Gem::Specification.new do |s|
  s.name = "abbrev".freeze
  s.version = "0.1.2".freeze

  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? 
  s.metadata = { "homepage_uri" => "https://github.com/ruby/abbrev", "source_code_uri" => "https://github.com/ruby/abbrev" } if s.respond_to? 
  s.require_paths = ["lib".freeze]
  s.authors = ["Akinori MUSHA".freeze]
  s.bindir = "exe".freeze
  s.date = "2025-03-22"
  s.description = "Calculates a set of unique abbreviations for a given set of strings".freeze
  s.email = ["knu@idaemons.org".freeze]
  s.files = [".github/dependabot.yml".freeze, ".github/workflows/test.yml".freeze, ".gitignore".freeze, "Gemfile".freeze, "LICENSE.txt".freeze, "README.md".freeze, "Rakefile".freeze, "abbrev.gemspec".freeze, "bin/console".freeze, "bin/setup".freeze, "lib/abbrev.rb".freeze]
  s.homepage = "https://github.com/ruby/abbrev".freeze
  s.licenses = ["Ruby".freeze, "BSD-2-Clause".freeze]
  s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze)
  s.rubygems_version = "3.6.2".freeze
  s.summary = "Calculates a set of unique abbreviations for a given set of strings".freeze
end

だが、 対応する/usr/lib/ruby/gems/3.4.0/gems/abbrev-0.1.2はない。 普通に/usr/lib/ruby/3.4.0/abbrev.rbに直置きされている。

このため、Solargraphはabbrevをドキュメンテーションしようとするが、あるべきファイルがないので失敗する。

解決策

ArchlinuxのRubyはパッケージングが腐っているので、システムパッケージを使わないのが一番簡単な解決策。

人気があるのはrbenvだと思うが、rbenvは使いづらすぎて私は好きではない。 asdfという選択肢もあるけれど、asdfもうまく動かないことがそれなりにある。

私はのおすすめはRust製のmiseだ。extraリポジトリにある。 Ruby以外も様々な処理系を扱うことができる。

まずは導入する

pacman -S mise

欲しい処理系のインストール。自前ビルドとプレビルドが選べる。 またメジャーバージョンのみ指定、マイナーバージョンまでの指定、リビジョン/パッチバージョン指定まで任意にできる。

mise install ruby@4.0

ユーザーデフォルトを指定。 これは~/.config/mise/config.tomlに書かれる。

mise use -g ruby@4.0

-gをつけない場合はカレントディレクトリにmise.tomlが書かれる。 これが許容できるならプロジェクトルートに置くことでバージョン指定が可能。 ただ、miseはGemfile.ruby-versionを読むので実際に必要な場合は少ない。

これを利用するにはmiseの環境変数が読まれている必要がある。 Zshならrcファイルにこんな感じ

eval "$(mise activate zsh)"

端末から起動するならこれでいいけれど、Nemo Actionsを使うには?

mise execがmiseをロードした上で実行してくれるので、こうすればいい

mise exec -- code -n %P

今回は決め打ちを避けたいし、今までのcode/codiumの分岐もあるから、スクリプトにしてしまおう。

[Nemo Action]
Name=Open VSCode
Comment=Visual Studio Code(ly) open on here

Exec=<abstract-code.zsh %P>

Icon-Name=code

Selection=Any
Extensions=any;

abstract-code.zshactionsディレクトリに置いて実行権限を与える。 ついでに、Ruby-LSPを使いたい場合もサポート。

#!/bin/zsh

profile_opt=()
code_cmd=code

# includes Ruby-LSP?
if [[ -f Gemfile.lock ]]
then
  if grep -E -q "^\s*ruby-lsp \(" Gemfile.lock 2>/dev/null && [[ -e "$HOME/.config/vscode_rubylsp" ]]
  then
    profile_opt+=(--user-data-dir "$HOME/.config/vscode_rubylsp")
  fi
fi

if (( $+commands[codium] ))
then
  code_cmd=codium
fi

if (( $+commands[mise] ))
then
  mise exec -- $code_cmd $profile_opt -n "$@"
else
  $code_cmd $profile_opt -n "$@"
fi

一度

code --user-data-dir=$HOME/.config/vscode_rubylsp

のようにしてから、

~/.config/vscode_rubylsp/User/settings.json -> ~/.config/Code - OSS/User/settings.json

みたいなシンボリックリンクにすればいい。あとは拡張機能のインストールで使い分けることができる。

bundle vs gem問題

概要

実はこれでもまだうまく動かない。ここから先はディストリビューションに依存しない問題だ。

実はSolargraphに限ってみてもプロジェクトは次のパターンがある。

  1. bundlerを使っていて、GemfileにSolargraphが含まれている
  2. bundlerを使っているが、GemfileにSolargraphが含まれていない
  3. bundlerを使っておらず、ユーザー環境のgemを使っている
  4. bundlerを使っておらず、ユーザー環境のgemも使っていない

3, 4のケースでは単にmiseを使えば解決する。 一方、それでは$GEM_PATHの問題でbundlerでインストールされているファイルが読めないので、1, 2のケースで問題が起きる。

1のケースだけを考えるのであればVSCodeのSolargraph拡張の設定からuseBundler: trueすればいい話なのだが、それだと2から4までがカバーできない。

特定の形式のプロジェクトしか触らないという想定は相当考えが浅いが、現状VSCodeはそうなっている。

そして、2のケースが最も難しい。

解決策

Geminiが完璧なコードを書いてくれたので、そのまま掲載。

#!/bin/bash

# 1. すでにVSCode側(ワークスペース設定など)が bundle exec を付与して呼び出している場合
if [ -n "$BUNDLE_BIN_PATH" ] || [ -n "$BUNDLE_GEMFILE" ]; then
    exec solargraph "$@"
fi

if [ -f "Gemfile.lock" ]; then
    # Gemfile.lock に solargraph 本体が含まれているかチェック
    if grep -E -q "^\s*solargraph \(" Gemfile.lock 2>/dev/null; then
        # パターンA: Bundler + Solargraphあり
        # Gemfileに含まれているならbundle execで起動するのが確実
        exec bundle exec solargraph "$@"
    else
        # パターンB: Bundler + Solargraphなし(★問題のケース)
        # Bundler管理下のgemパスだけを抽出し、環境変数 GEM_PATH に追加する
        BUNDLED_GEM_PATH=$(bundle exec ruby -e 'puts Gem.path.join(":")' 2>/dev/null)
        echo $BUNDLED_GEM_PATH > ~/tmp/sol
        if [ $? -eq 0 ] && [ -n "$BUNDLED_GEM_PATH" ]; then
            export GEM_PATH="$BUNDLED_GEM_PATH:$GEM_PATH"
        fi
        
        # グローバルの(miseの)Solargraphを、BundlerのGEM_PATHが見える状態で起動
        exec solargraph "$@"
    fi
else
    # パターンC & D: Bundlerなし
    exec solargraph "$@"
fi

VSCodeのSolargraph拡張はcode -n path_to_dirで開いた場合はpath_to_dirをカレントディレクトリにする。 一方、ワークスペース機能を使って開いた場合は、ワークスペースごとに固有のLSPインスタンスを起動し、それぞれがワークスペースのルートをカレントディレクトリにする。

このため、Gemfile.lockがあるディレクトリをプロジェクトルートとして開いているのであれば、./Gemfile.lockで見つけることができる。

あとはSolargraph拡張のCommand Pathにこれを設定すれば良い。