`bundle install --clean`を実行するときの注意点

bundle install --clean を実行するときの注意点として、バッドノウハウを書き残しておきます。

これは、以前 pocke さんが CircleCI の実行を速くするのに bundle install --clean を使う方法を記されていたことで思い出したことです。

pocke.hatenablog.com

bundle install --clean は Bundler 管理下で不要な gem とそのバージョンを削除します。pocke さんの伝える CI 上の gem のキャッシュを削除する用途以外に、イメージ化していないデプロイ環境で bundle update している環境なんかでも同一 gem に対して複数バージョンがインストールされていたりします。そういった環境で bundle install --clean を実行すると、不要バージョンを削除してガツっと容量を空けることができたりします。

以上を踏まえて、本題の注意点です。問題となるのは Gemfile に記載せずにサーバーに直接 gem install passenger とか gem install unicorn といった感じでデプロイ環境に直接サーバーをインストールして、Gemfile / Gemfile.lock にサーバーの gem が記されていないケースです。その状態で bundle install -clean するとサーバーが消えます。文字列に起こすといかにも間抜けですが、やらかすと悲しいことになるというか、かつてなったことがあるのでここに記しておきます。Unicorn で書き忘れはない気がしますが、Passenger なんかは Apache モジュールとしてコンパイルしたりした後で、うっかり Gemfile に書き忘れていたりしないか気をつけると良いです。

そうそう踏む人はいないと思いますが、サーバー含めてインストールする gem はきちんと Gemfile に記しておきましょうという話でした。

Rails 6.1でサードパーティーのArel visitorが外される

Active Record でサポートしているコアアダプターの MySQL, PostgreSQL, SQLite 以外のサードパーティーRDBMS に対する Arel visitor がコアから外されることになった。

github.com

SQL Server は以前から独自で梱包していたようで、Oracle は今回対応した PR を Acitive Record Oracle enhanced adapter のリポジトリに開いている。

github.com

この変更によってメンテナンスされているアダプターのユーザーに対して直接的な影響はないが、アダプター開発者は Rails リポジトリactiverecord/lib/active_record/connection_adapters に加えて、activerecord/lib/arel の変更もウォッチ対象になるだろう。

余談だが今回 Arel visitor を Oracle enhanced adapter に輸入する際の手順で、過去に書き残しておいた記事がちょっと役立った。

koic.hatenablog.com

git status -s | grep '^DU ' | cut -d ' ' -f 2 | xargs git rm が思い出せなかった。べんり。

Ruby 2.8.0-devビルドのためclangを更新した

TL;DR としては手元の macOS の clang のバージョン更新を怠っていたので、Ruby の head (2.8.0-dev) でコンパイルエラーが起きていた。久しぶりの Ruby 2.8.0-dev でコンパイルエラーが出たら clang を更新してみましょう。

以下ログ込みで記しておく。 Ruby 2.8.0-dev に話題の Endless method definition が入ったこともあり、一昨日あたりに数日ぶりのアップデートをしたら rbenv install 2.8.0-devコンパイルエラーが起きるようになっていたので、ruby/rubyリポジトリで再現するか見たところ期待どおり同様のエラーになった。

% cd path/to/ruby/ruby
% make distclean
(snip)

% make check
        BASERUBY = echo executable host ruby is required.  use --with-baseruby option.; false
        CC = clang -fdeclspec
        LD = ld
        LDSHARED = clang -fdeclspec -dynamiclib
        CFLAGS = -O3 -ggdb3 -Wall -Wextra -Werror=deprecated-declarations -Werror=division-by-zero -Werror=implicit-function-declaration -Werror=implicit-int -Werror=pointer-arith -Werror=shorten-64-to-32 -Werror=write-strings -Wmissing-noreturn -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Werror=extra-tokens -std=gnu99  -pipe
        XCFLAGS = -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fno-strict-overflow -DRUBY_DEVEL=1 -fvisibility=hidden -DRUBY_EXPORT -fPIE -DCANONICALIZATION_FOR_MATHN -I. -I.ext/include/x86_64-darwin17 -I./include -I. -I./enc/unicode/12.1.0
        CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT
        DLDFLAGS = -Wl,-undefined,dynamic_lookup -Wl,-multiply_defined,suppress -fstack-protector-strong -Wl,-pie -framework Security -framework Foundation
        SOLIBS = -lpthread -lgmp -ldl -lobjc
        LANG = ja_JP.UTF-8
        LC_ALL =
        LC_CTYPE = ja_JP.UTF-8
        MFLAGS =
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
compiling ./main.c
In file included from ./main.c:21:
In file included from ./include/ruby.h:33:
In file included from ./include/ruby/ruby.h:46:
./include/ruby/3/scan_args.h:264:47: error: use of undeclared identifier 'fmt'; did you mean 'fma'?
    RUBY3_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) <  0,    "bad scan arg format",                    "error")
                                              ^~~
                                              fma
./include/ruby/3/scan_args.h:102:57: note: expanded from macro 'rb_scan_args_count'
#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
                                                        ^
./include/ruby/3/scan_args.h:98:29: note: expanded from macro 'rb_scan_args_count_lead'
    (!rb_scan_args_isdigit((fmt)[ofs]) ? \
                            ^
./include/ruby/3/scan_args.h:67:62: note: expanded from macro 'rb_scan_args_isdigit'
#define rb_scan_args_isdigit(c) (RUBY3_CAST((unsigned char)((c)-'0'))<10)
                                                             ^
./include/ruby/3/cast.h:31:28: note: expanded from macro 'RUBY3_CAST'
# define RUBY3_CAST(expr) (expr)
                           ^
./include/ruby/3/attr/diagnose_if.h:34:36: note: expanded from macro 'RUBY3_ATTR_DIAGNOSE_IF'
    __attribute__((__diagnose_if__(_, __, ___))) \
                                   ^
/usr/include/math.h:554:15: note: 'fma' declared here
extern double fma(double, double, double);
              ^
In file included from ./main.c:21:
In file included from ./include/ruby.h:33:
In file included from ./include/ruby/ruby.h:46:
./include/ruby/3/scan_args.h:264:28: error: subscript of pointer to function type 'double (double, double, double)'
    RUBY3_ATTR_DIAGNOSE_IF(rb_scan_args_count(fmt) <  0,    "bad scan arg format",                    "error")
    ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
./include/ruby/3/scan_args.h:102:33: note: expanded from macro 'rb_scan_args_count'
#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
                                ^
./include/ruby/3/scan_args.h:98:28: note: expanded from macro 'rb_scan_args_count_lead'
    (!rb_scan_args_isdigit((fmt)[ofs]) ? \
                           ^
./include/ruby/3/scan_args.h:67:62: note: expanded from macro 'rb_scan_args_isdigit'
#define rb_scan_args_isdigit(c) (RUBY3_CAST((unsigned char)((c)-'0'))<10)
                                                             ^

(snip)
./include/ruby/3/scan_args.h:102:57: note: expanded from macro 'rb_scan_args_count'
#define rb_scan_args_count(fmt) rb_scan_args_count_lead(fmt, 0, 0)
                                                        ^
./include/ruby/3/scan_args.h:99:29: note: expanded from macro 'rb_scan_args_count_lead'
     rb_scan_args_count_var(fmt, ofs, vari) : \
                            ^
./include/ruby/3/scan_args.h:89:31: note: expanded from macro 'rb_scan_args_count_var'
     rb_scan_args_count_trail(fmt, ofs, vari) : \
                              ^
note: (skipping 2 expansions in backtrace; use -fmacro-backtrace-limit=0 to see all)
./include/ruby/3/scan_args.h:75:29: note: expanded from macro 'rb_scan_args_count_block'
     rb_scan_args_count_end(fmt, (ofs)+1, (vari)+1))
                            ^
./include/ruby/3/scan_args.h:70:7: note: expanded from macro 'rb_scan_args_count_end'
    ((fmt)[ofs] ? -1 : (vari))
      ^
./include/ruby/3/attr/diagnose_if.h:34:36: note: expanded from macro 'RUBY3_ATTR_DIAGNOSE_IF'
    __attribute__((__diagnose_if__(_, __, ___))) \
                                   ^
/usr/include/math.h:554:15: note: 'fma' declared here
extern double fma(double, double, double);
              ^
fatal error: too many errors emitted, stopping now [-ferror-limit=]
20 errors generated.
make: *** [main.o] Error 1

自分の環境のことなので、clang のバージョンが古かろうと見てみたところ version 9.0.0 だった。

% clang --version
Apple LLVM version 9.0.0 (clang-900.0.39.2)
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

LLVM を更新する。

% brew upgrade llvm
(snip)

==> llvm
To use the bundled libc++ please add the following LDFLAGS:
  LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"

llvm is keg-only, which means it was not symlinked into /usr/local,
because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have llvm first in your PATH run:
  echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.zshrc

For compilers to find llvm you may need to set:
  export LDFLAGS="-L/usr/local/opt/llvm/lib"
  export CPPFLAGS="-I/usr/local/opt/llvm/include"

clang のバージョンが 10.0.0 になる。

% clang --version
clang version 10.0.0
Target: x86_64-apple-darwin17.7.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm/bin

rbenv install 2.8.0-dev でふたたびインストール可能になった。

% ruby -v
ruby 2.8.0dev (2020-04-12T05:58:13Z master a07cbacd23) [x86_64-darwin17]

話題の Endless method definition を動かすとこんな感じ。

% ruby -e 'def greet(name) = puts("Hello, #{name}!"); greet("John Smith")'
Hello, John Smith!

ついでに右代入してみる。

% ruby -e '1 + 1 => x; puts x'
2

Enjoy!

GitHub Packages と Gem の配布先

TL;DR 公開 gem の配布先は従来どおり rubygems.org のみで良いかなといまのところ思っています。


GitHub では GitHub Packages がリリースされて、gem を含めていくつかのパッケージに対してサポートされている。

help.github.com

gem に対する GitHub Packages への現時点での考え。

OSS など公開 gem のリリース先については、従来どおり rubygems.org のみで良いかなといまのところ思っています。理由としては、rubygem.org ではダウンロード数などの数字によって、その Gem やリリースがユーザーに与える影響への指標のひとつとして中央集権先として見ることができるためです。将来的に複数のホスティングに分散するメリットが明確になったらその時は改めて考えてみたいところ。

一方で、gem server を使った社内 Gem サーバーなどを運用している場合は、GitHubパッケージをホスティング先にすると便利そうです。その他、リリース時に rubygems.org が死んでいたら死ぬみたいな状況を回避したいようなシステムの冗長構成先として使えるかもしれません (DNSimple が死んで rubygems.org が引けなかったことが数年前にあったけれど、滅多にないと思う) 。

このエントリの出典は、RuboCop リポジトリに以下のような GitHub Packages 向けに Gem をリリースするタスク追加の PR が送られてきたやりとりの際に考えたことでした。

github.com

備忘を兼ねてここに書いておこしておきます。

RuboCop Faker 1.0 をリリースした

Faker::Base.unique メソッドが使われている際の false negative に対応した RuboCop Faker 1.0 をリリースした。

rubygems.org

もともと Faker 1 系から Faker 2 系へのインタフェースマイグレーションをゴールとした Gem で API としては安定していたので、今回のリリースを期にメジャーバージョン 1.0 を付与した。

今回は以下のイシューに Real world ユースケースが示されており、以下のような Fake::Base.unique が使われていた場合に false negative があった。

github.com

public_key { Faker::Lorem.unique.characters(255) }

これはユニーク値を生成する形で以下の API が呼び出されている (実装には method_missing が使われている) 。

Faker::Lorem.characters(255)

今回のリリースで RuboCop Faker でサポートしているひととおりの Faker のメソッドに対して unique が使われているときも検知できるように対応している。

ruby-build のパッケージ定義ファイルを生成する

Ruby の処理系がリリースされた際に rbenv/ruby-build に PR を開いている定義ファイルを生成するスクリプトを upstream に提案した。既に ruby-build には PR を開いていて統合済み。

github.com

過去の PR と見る人が見れば分かる定義ファイルだが、既に自動化されていたTruffleRuby 向けと Rubinius 向けを参考に CRuby 向けと JRuby 向けも自動化した。

頭の中のチェックメモはあって基本的な部分を含めて記すと以下になる。

  • 公式サイトでのリリースアナウンスをソースとする
  • CRuby と JRuby のいずれもダウンロードできるパッケージがいくつか公開されているので、それぞれ適したものを入手する
  • CRuby の場合は公式サイトで SHA256 が書かれているので一致を確認する (していない場合はどこかに問題がある場合があり公式サイトの値が違っていたことがある)
  • CRuby の場合は openssl のバージョンを他の定義ファイルと突き合わせる
  • JRuby の場合は require_java のバージョンを他の定義ファイルと突き合わせる
  • PR を開くことになる定義ファイルでインストールと Hello, world くらいが成功することを確認する

このあたりいくらか職人の温かみのあるセルフレビューの必要が残っているものの update-truffleruby と同様にスクリプトの実行で定義ファイルが作れる。

以下を ~Downloads ディレクトリにパッケージをダウンロード済みである状態で実行する。

# Ruby 2.7.0 の定義ファイルを生成する場合
% cd path/to/rbenv/ruby-build
% script/update-cruby 2.7.0 ~/Downloads

# JRuby 9.2.11.0 の定義ファイルを生成する場合
% script/update-jruby 9.2.11.0 ~/Downloads

注意点としては macOS を使っている人は SHA256 のコマンドが gsha256sumshasum -a 256 なんかだと思うので、sha256sum を symlink しておくなどのハックがいると思う。このあたり rbenv は type コマンドでうまいこと解決しているが、ベースの update-truffleruby の実装に合わせていることや利用者がその周辺で限定されているものだったのであえて対応せずコンパクトな形にしている。

RVM にもこういったのがあると便利だと以前から思っていはいるので、そのうち用意したいところ。

RuboCop の Cop 名をマイグレーションする

RuboCop では Cop が所属する先の部署や Cop 名が変更されることがある。

変更された際に所属先の部署や Cop 名をマイグレーションする仕組みが用意されているものについて紹介する。

Migration/DepartmentName cop

現状の RuboCop では部署名が省略されいても、一意に特定できれば動く仕組みが入っている。これはおそらく昔の RuboCop では部署が存在せず、部署導入時に移行をスムースにするための機構だったようだ。

rubocop-hq/rubocop#7717 にて以下のコメントがある。

RuboCop no longer supports ambiguous cop names (those without a department).

このとおり今後の RuboCop では部署名の省略は許可しないようになるだろう。これに向けて部署が省略された disabled コメントに部署名を補完する Cop が追加されている。それが Migration/DepartmentName cop である。

github.com

v0.75 でデフォルト無効で追加されているが、現状の v0.80 ではデフォルトで有効化している。今後、disabled コメントへの部署名の省略をランタイムで受け入れる機構を削除する可能性があることから、ユーザーに対して disabled コメントへの部署名の補完を推進する意味合いがある。

Migration/DepartmentName cop は auto-correct をサポートしているので、rubocop -a --only Migration/DepartmentName で以下のように部署名を補完することができる。

-def parse value # rubocop:disable MethodLength
+def parse value # rubocop:disable Metrics/MethodLength

また、以前の RuboCop から部署名が変わっているようなレガシー部署からの移行についても RuboCop 0.80 からサポートしているので、RuboCop のバージョンを上げる際にはその辺りも活用できると思う。

github.com

Mry

pocke さん作の gem 。Mry Migrates .Rubocop.Yml の頭文字で Mry とのこと。コマンドライン一発でマイグレーションできるのでとても便利。

github.com

.rubocop.yml に指定している Cop 名についてレガシーの名前を新しい名前に置換してくれる。

繰り返しになるが、将来的に部署名を省略した disabled コメントのサポートは消される方向性があるため、Migration/DepartmentName cop と組み合わせて 部署名/Cop名 の形式に移行しておくと良い。