VIMのaleでのrubocopを軽快に
rubocop軽快化のためのツール
前提
素のrubocopの起動時間を確認しておく。
$ time rubocop -v
0.80.1
rubocop -v 0.56s user 0.20s system 95% cpu 0.794 total
rubocop-daemon
rubocopをdaemonとして起動しておくことができる。
rubocop-daemon-wrapperを使うと、daemonを自動的に起動してくれて、その上でdaemonとやり取りするので起動時間分だけ実行時間が短くなる。
$ rubocop_daemon_wrapper=$(gem contents rubocop-daemon | grep rubocop-daemon-wrapper)
$ time $rubocop_daemon_wrapper -v
0.80.1
$rubocop_daemon_wrapper -v 0.69s user 0.26s system 96% cpu 0.988 total
$ time $rubocop_daemon_wrapper -v
0.80.1
$rubocop_daemon_wrapper -v 0.01s user 0.01s system 69% cpu 0.031 total
daemonはプロジェクトごとに起動されるようなので、使用しているrubocopのバージョンが異なる複数のプロジェクトがあっても問題ない(はず)。
spring-commands-rubocop
Railsでおなじみのspring経由でrubocopを実行できるようにする。
あらかじめgemをロードした状態のプロセスを用意しておいて、そこからrubocopを起動するのでロード分だけ実行時間が短くなる。
$ time spring rubocop -v
Running via Spring preloader in process 94344
0.80.1
spring rubocop -v 0.14s user 0.06s system 11% cpu 1.764 total
$ time spring rubocop -v
Running via Spring preloader in process 94377
0.80.1
spring rubocop -v 0.13s user 0.06s system 70% cpu 0.275 total
ただしプロジェクトによっては逆に遅くなったりすることもある。(rubocopに必要のないものまでロードした状態になるから?)
上の例はrails new
しただけの状態でのもの。
VIMのaleの設定
背景
aleではrubocopの実行コマンド名をg:ale_ruby_rubocop_executable
やb:ale_ruby_rubocop_executable
により指定できる。
しかし、これらの変数にはコマンド名しか指定できないので、springを経由させたいときにはspring rubocop
ではなくてbin/rubocop
のようにspring binstubを指定しなければならない。
また、rubocopをRailsプロジェクト以外でも使う場合、実行コマンド名をbin/rubocop
に固定するわけにはいかない。
さらに、プロジェクトの事情によってはbin/rubocop
がなかったり、Gemfileにrubocop-daemonを追加できなかったりすることもある。
そのため、プロジェクトごとに、あるいは特定のプロジェクトに属さないファイルに応じて、rubocopの実行コマンドを調整してやる必要が出てくる。
設定
というわけで設定を考えてみた。
aleとvim-bundlerをインストールしておく。
function! s:set_ale_ruby_rubocop_executable()
let l:project = bundler#project()
if empty(l:project) || !l:project.has('rubocop')
return
endif
let l:project_root = l:project['root']
let l:project_rubocop_daemon_wrapper = l:project_root.'/../rubocop-daemon-wrapper.rb'
let l:project_rubocop_spring_wrapper = l:project_root.'/../rubocop-spring-wrapper.sh'
let l:rubocop_binstub = l:project_root.'/bin/rubocop'
if executable(l:project_rubocop_daemon_wrapper)
unlet $RUBOCOP_DAEMON_USE_BUNDLER
let b:ale_ruby_rubocop_executable = l:project_rubocop_daemon_wrapper
elseif l:project.has('rubocop-daemon')
let $RUBOCOP_DAEMON_USE_BUNDLER = '1'
let b:ale_ruby_rubocop_executable = l:project.paths()['rubocop-daemon'].'/bin/rubocop-daemon-wrapper'
elseif executable(l:project_rubocop_spring_wrapper)
let b:ale_ruby_rubocop_executable = l:project_rubocop_spring_wrapper
elseif l:project.has('spring-commands-rubocop') && executable(l:rubocop_binstub)
let b:ale_ruby_rubocop_executable = l:rubocop_binstub
else
let b:ale_ruby_rubocop_executable = 'bundle'
endif
endfunction
augroup my_ale_ruby_rubocop_setting
au!
au FileType ruby :call s:set_ale_ruby_rubocop_executable()
augroup END
../rubocop-daemon-wrapper.rb
や../rubocop-spring-wrapper.sh
は、諸事情ある場合の回避策として必要に応じて作成しておく。たとえば以下のような内容でどうだろう。
#!/usr/bin/env ruby
# rubocop-daemon-wrapper.rb
require 'rubygems'
if $0 == __FILE__
bin_dir = Gem::Specification.find_by_name('rubocop-daemon')&.bin_dir
abort 'rubocop-daemon.gem not found' unless bin_dir
ENV['RUBYOPT'] = "-r #{File.expand_path(__FILE__).sub(/\.rb\z/, '')}"
exec(File.join(bin_dir, '../bin/rubocop-daemon-wrapper'), *ARGV)
end
def activate_rubocop_gems(lockfile_path)
in_gem = in_spec = false
File.foreach(lockfile_path) do |line|
case line
when /^Gem/
in_gem = true
when /^\S/
break if in_gem
when /^ specs:/
in_spec = in_gem
when /^ \S/
break if in_spec
when /^ (?<name>rubocop\S*) \((?<version>[.\d]+)\)$/
m = Regexp.last_match
gem m[:name], "=#{m[:version]}"
end
end
end
dir = Dir.pwd
while dir.start_with?(__dir__)
begin
activate_rubocop_gems("#{dir}/Gemfile.lock")
break
rescue SystemCallError
dir = File.dirname(dir)
end
end
#!/bin/sh
# rubocop-spring-wrapper.sh
exec spring rubocop "$@"
../rubocop-daemon-wrapper.rb
を使うと少し遅くなるが、それでも素のrubocopよりは速い。
$ time ../rubocop-daemon-wrapper.rb -v
0.80.1
../rubocop-daemon-wrapper.rb -v 0.61s user 0.23s system 97% cpu 0.866 total
$ time ../rubocop-daemon-wrapper.rb -v
0.80.1
../rubocop-daemon-wrapper.rb -v 0.07s user 0.05s system 88% cpu 0.137 total
(あれ、初回起動が素のrubocop-daemon-wrapperを実行するより速いのはなぜだろう? gemのアクティベートをはしょっているから?)