builderscon tokyo 2018 に行ってきた

「知らなかった、を聞く」でおなじみ、builderscon tokyo 2018 に行ってきた。初参加!

行けば何か面白い話が聞けるだろうという感じで講演内容はあまり事前に確認してなかった。適当すぎた。 しかし期待を裏切らず、面白い話をたくさん聞けた。

個人的に特に興味深かったのが bokuweb さんの「ファミコンエミュレータの創り方」(発表資料)。若干の興味はありつつも自分でガッツリ調べるまでは行かない分野だったので、聞けてよかった。

また、今回わかったのは、いくら「知らなかった、を聞く」と言えども、基礎もまったくわかってない話を聞いても何もわからないということ。まあ当然なんだけど。

とあるセッションにうっかり間違えて入ったのだけど(スマヌ)、間違えたとは言え知らないから聞いてみるかって聞いてたけど全然わからなくてとてもつらかった…。他に移るにしてもほとんどの部屋は満席なので移動も困難。ちゃんとある程度はわかりそうなセッションを選ばないといけない。これ以降、セッション選びに慎重になった。

どのセッションでも最後に質疑応答タイムがあって、こういう場って質問を募っても質問がないってことも割とある気がしてるんだけど、builderscon ではどのセッションでもちゃんと毎回質問が出ていた。結構すごい気がする。あれ、でも私が知らんだけで最近はそういう感じなんだろうか。わからなくなってきた。

ランチセッションは行ってるとごはん食べてる時間がなくなって午後に死ぬので、諦めました。残念。

1日目のランチは、事前にカレー好きの同僚に日吉に行くと言ったら、ではここに行ってくださいと紹介されたハイ,ハウ アー ユーへ行った。早めの時間だったからかほぼ並ばずに入れて良かった。 ハーフ&ハーフでキーマカレーとチキンカレーを選んで、温泉玉子をトッピングしてみた。キーマが結構辛かったけどうまかった。

f:id:thinca:20180907115631j:plain

2日目は、ノープランで会場を出たところでバッタリ purintai さんと会い、お昼のお店まだ決めてないと言ったら、ココがオススメって言われたのでとんかつ 和栗へ。ここまで来てケチっても仕方ないだろって言って頼んだのが特上ヒレかつカレー。またカレーか。かつめっちゃうまかった。

f:id:thinca:20180908125022j:plain

と言うわけで何もまとまりのない感想を書きつらねてみた。普段あまり大規模なカンファレンスはなかなか行かないのだけど(疲れちゃうし)、やっぱりこういうところで話を聞くと自分もやらないとって気持ちになるので、いいですね。あとはこういう場で得たエネルギーをちゃんと活用できるといいのだけど…がんばろう。

Docker Hub で Vim の最新版をタグ付きで自動ビルドするようにした話

今まで Docker Hub 上で最新の Vim のビルドが行われるように設定していたが、ふと思い立って過去のバージョンについても一通りタグに残ってると良さそうだと思って設定した際の記録。

ちなみにこの作業を開始したのは 2018 年 1 月です。どんだけかけとるんだ。

作業前の状態

Docker には Build Trigger 機能があり、URL を叩くことで任意のタイミングでビルドを走らせることができる。

この機能を使い、IFTTT の RSS Feed サービスで Vim のコミット Feed を監視して、新しいコミットがあった際にトリガーを実行し、Webhooks サービスを使って Docker の Build Trigger の URL を叩くことで、latest が常に最新の Vim のバージョンになるようにしていた。

これだと常に最新版の Vim のイメージは作られるが、過去のバージョンについては参照できない。

目標

  • Vim の公式リポジトリに新しいコミットがあったら、自動で Docker Hub 向けにビルドを行う
    • バージョンでタグを付け、過去のビルドも参照できるようにする
  • Vim 7.4 以降の過去のバージョンについてもビルドを行い、参照できるようにする。
  • Docker Hub 側で自動ビルドを行う (自前のマシンでビルドして push はしない)
  • トリガー部分も無料でがんばる
    • 個人的には自宅サーバを使う手もあるが、これは使わないでやる

Dockerfile

まずは指定のバージョンの Vim をビルドできるように ARG として VIM_VERSION を追加した

今までは常に最新の Vimソースコードを取得してビルドしていたが、引数によって特定の Vim のバージョン(Git リポジトリ上の reference)のビルドができるようになった。

また、せっかくなので各種外部インターフェースの有効無効も環境変数で設定できるようにしてみた。デフォルトは全てオフで、VIM_ENABLE_ALL を空以外にすると外部インターフェースが有効になる。

Docker Hub 側の準備

次に Docker Hub で前述のタグを利用したビルドを行えるようにする。

Docker Cloud には実際のビルド処理をスクリプトで置き換える機能があり、実はこれは Docker Hub でも使える1

autobuild のリポジトリ内の Dockerfile と同じディレクトリに hooks/ ディレクトリを作成し、その中にbuild という名前の実行可能なスクリプトを置いておけばよい。今回の場合は以下のようなスクリプトを用意した。

#!/bin/bash

# 実際にはもっと色々している
# VIM_VERSION に指定している ${SOURCE_BRANCH} については後述
docker build --build-arg "VIM_VERSION=${SOURCE_BRANCH}" --build-arg "VIM_ENABLE_ALL=" -t "${IMAGE_NAME}" .
docker build --build-arg "VIM_VERSION=${SOURCE_BRANCH}" --build-arg "VIM_ENABLE_ALL=yes" -t "${IMAGE_NAME}-full" .

通常ビルド(外部インターフェースなし)と、フルビルドをそれぞれ用意することにした。追加のイメージができるので、push 時に追加で push する必要がある。このために、動作を置き換えるのではなく追加の処理をするために post_push スクリプトを用意する。通常の push のあとに実行される。

中身は v8.0.xxxx-full のような full 付きのタグを push しているのと、現在のビルドが最新版だった場合は同じイメージを latest としても push している。

この結果、以下のようなファイル配置になった。

|- Dockerfile
`- hooks/
   |- build
   `- post_push

ビルドのトリガー方法

Vim のコミット Feed を参照して、新しいコミットがあったらビルドする点は変わらない。ただし、今までは常に最新版をビルドしていたが、今回からは指定したバージョンをビルドする必要がある。

ビルドトリガーから任意のパラメータを環境に渡す方法は見付けられなかった。そこで一般的なフローと同様に、タグが push された際にそのタグをビルドするようにした。これは先ほどのスクリプト内で参照している ${SOURCE_BRANCH} で取れる。

厄介なのは、Autobuild で連携しているリポジトリを取得する部分は変更できなさそうだということだ。どうも Docker Hub の Autobuild は Dockerfile があるリポジトリとプロダクトのリポジトリが同一である想定であるらしく、連携しているリポジトリにタグが push されるとビルドがトリガーされてそこから対象タグを clone し、Dockerfile を参照してビルドする。

つまりどういうことかと言うと、今回のようにプロダクトのリポジトリDockerfile を管理しているリポジトリが別になっている場合、Dockerfile しかないリポジトリに対して v8.0.0000 のようなバージョンのタグを無駄に push することになる。 仕方ないので今回はこの方法で行くことにした。つまり、Dockerfile を管理しているリポジトリに対して Vim のバージョンのタグを push すると、Docker Hub 上でそのバージョンの Vim のビルドが走る仕組みになる。

ビルドをトリガーする

Web hook を叩く方法から Git リポジトリにタグを作る方法になったので、GitHub API でタグを作ることにする。

これまでと同じように IFTTT でやりたいところだが、GitHub API は User-Agent の指定を必須としているところ、IFTTT は User-Agent を送らず、また HTTP リクエストヘッダを自由にカスタマイズすることができない。つまり IFTTT は今回は使えない。

そこで今回採用したのが Google Apps Script だ。採用した理由は、無料だから。

で、今回のために作ったのが以下。

https://github.com/thinca/gas-docker-vim-updater

せっかくの機会なので TypeScript に入門してみた。ので、余計に時間がかかった…。

RSS フィードを読む機能が必要だったため、今回のためにそのためのライブラリを自作した。過去に読んだフィードは Google スプレッドシートに記録し、新着を抽出する。

Google Apps Script には、定期的に関数を実行する機能があるため、これを使って RSS フィードをチェックし、更新があれば GitHub API を叩いてタグを作成するようにした。

不要になったタグを削除する

タグはビルドのトリガーにするために必要なもので、ビルドが終わったタグについては不要になる。これは消したい。

Docker Hub には、ビルドが終わって push された際にそのイベントを WebHooks で通知する機能がある。そして Google Apps Script は Web アプリとしてエントリーポイントを持つことができる。これを利用して、ビルドが終わって Docker イメージが push されたらそのイベントを Google Apps Script に通知し、そこから GitHub API を叩いてタグを消すようにした。

過去のバージョンをビルドする

最新版はビルドされるようになった。しかしこれだと過去のバージョンはビルドされない。

そこで少しずつタグを作っていく Ruby スクリプトを書いて、これを走らせた。実際には運用しながら少しずつスクリプトを修正、改善していった。

一気にタグを作らなかったのは、そうすると最新版が来たときにキューが詰まっていてなかなかビルドされなくなるからである。実際に全てビルドし終えるのに1ヶ月以上かかった。また、キューを大量に積むとなんとなくまずそうな気がしたというのもある。

中にはそもそもビルドが通らないバージョンが存在したり、Docker Hub が謎のエラーでビルドを失敗させたりしてかなりカオスだった。

課題 - 失敗したビルドに対応する

過去のバージョンをビルドしていてわかったことだが、前項で少し触れたように、ビルドできるはずのバージョンがビルドに失敗することは割とある。例えば apk でパッケージの更新に失敗すると言ったわかりやすいネットワーク由来のものから、コンテナの構築になぜか失敗するというよくわからないものまで様々な理由で失敗する場合がある。これらの失敗は多くの場合、再ビルドを実行すれば問題なくビルドが通る。

さて、これに対応したいところなのだが、難しいことに、Vim はビルドできないバージョンがリリースされることがありえる。これは今でも Bram さんの手による暖かみのあるパッチリリースが行われている賜物である。

そう、つまりこれだと、Docker Hub 由来でたまたま失敗したのか、そもそもビルドが通らないのかがわからない。従って、失敗していたら再実行するというソリューションは使うのが難しい。やるとしても回数制限してやる感じになりそう。

というわけでつらいので今のところやってない。せめて Vim のリリースが全てビルドが通ることが保証されてればなー2

やってみての感想

結果的にかなりあれこれすることになって大仕事になった割にどれだけ活用できるか不明だけど、あれこれ触れたのはよかった。しかし、時間をかけすぎた感がやばい…。

CI とかバグ調査等に使える可能性がなきにしもあらずなので使いたい方はどんどん使ってください。


  1. Docker Cloud と Docker Hub の違いをよくわかってない…。

  2. とは言えコンパイラのバージョンとか外部インターフェースのバージョンの依存で落ちる場合までは保証できないだろうしどちらにしてもつらそう。

Vim の問題を調査したときの記録

先日私の環境で起きた Vim に関する問題を調査した際の記録。一例なので汎用的に使える手法ではないけど、こういう感じのことをしているよというのを書き留めておきます。

結果的に無駄だった工程も書いています。1 本道で調査が進むことの方が珍しく、だいたい回り道します。

問題

cgn で検索のマッチ対象を別のキーワードに置き換えたあと . で繰り返すと、Bell が発生する。」

cgnc + gn で、gn は検索にマッチした範囲です。つまり検索にマッチした内容を c で書き換え、. で次のマッチ対象にも同様の変更を行います。1 つずつ確認しながら置換したい場合などによく使われる操作です。

問題について

Vim では不正な操作をした場合など様々な場面で警告としてベルを鳴らします。 例えばノーマルモードでそれ以上移動できない方向に移動しようとした場合など、割と些細なことでも鳴ったりします。

人によっては煩わしくて鳴らないように設定している人もいると思います。プラグインの利用者であればそれも良いかと思います。無効にしてしまえば今回の問題は解決します。

しかし、ベルを無効にすることはコンパイラの警告を全て無視しているようなものです。私は Vim プラグインの開発をしているので、些細な問題にも気付けるようにベルは有効にしています。

調査

ベルの厄介なところは、「何か問題が起きた」ということしかわからないところです。メッセージすらない…ハードモードです。

何由来のベルか調べてみる

ベルは 'belloff' オプションを使うことで種類ごとに抑制できます。これを使えば何の操作でのベルか絞れるかも…? と思って設定してみることに。

たぶんこれかな…と思い最初に error を設定してみると、見事エラーは抑制されました。 error は「その他のエラー」。何もわからないということがわかった…1。結果的にこの調査は完全に無意味でした。そういうこともある。

キーマッピングを確認する

. でエラーが起きるので、. に何か機能が割り当てられていないか確認。repeat.vim をインストールしていたのでこの辺りかもしれないと睨みました。 しかし実際に nmap . で設定を調べてみると、何も割り当てられておらず。repeat.vim にはいくつか派生バージョンがありますが、私が使っているものは repeat#set() が呼ばれるまでキーマッピングの設定をしないようです。

またもあてが外れましたが、徐々に絞れてきています。

autocmd を確認する

. がデフォルトのままだとすると、そこでエラーが起きるとすれば autocmd の発動によるものである可能性が高いです。 . で起きるイベントとなると…たぶん TextChanged かな? ということで TextChanged について調べることに。

:autocmd TextChanged
--- Auto-Commands ---
ALERunOnTextChangedGroup  TextChanged
    *         call ale#Queue(g:ale_lint_delay)
anzu  TextChanged
    *         call anzu#clear_search_cache()
vimconsole  TextChanged
    *         :call <sid>text_changed()
plugin-wtrans  TextChanged
    wtrans://source/*
              call wtrans#buffer#translate(expand('<amatch>'))
neosnippet  TextChanged
    *         call neosnippet#handlers#_restore_unnamed_register()
plugin-unite  TextChanged
    <buffer=2>
              call unite#handlers#_on_text_changed()

内容は記事執筆時点で再現のために出したものなので調査時とは少し違うかもしれません。 ともあれこの中に原因があると踏んで、1 つずつイベントを削除して、問題が再現するか確認します。ただし plugin-wtransplugin-unite はパターン的に発動しないことが明確なので最初から除外可能です。

:autocmd! ALERunOnTextChangedGroup TextChanged

1 つずつ消して、問題が再現するか試していきます。しかし全部消してもエラーは出続けました。むむむ。

実際に起きている autocmd を確認する

ざっくり雑に TextChanged かな、と疑ってみましたが、そもそもこの予想が外れていそうです。実際に起きているイベントを確認してみることに。 こういう場合は 'verbosefile' を使うと便利です。

:set verbosefile=/home/thinca/log.txt
:8verbose normal .
:set verbosefile=

起きたイベントがファイルに記録されます。ログレベル2をもっと上げれば、実行された全てのスクリプトの行が記録されるので、それを使うこともありますが…今回はベル。ベルは記録されていないので、全てのログを出してもどこで問題が起きたかはわかりません。そこで今回はイベントだけ出します。

と言うわけで実行してみた結果(一部抜粋):

InsertEnter Auto commands for "*" を実行しています
InsertLeave Auto commands for "*" を実行しています

InsertEnterInsertLeave でした!そもそも TextChanged は実行されてなかった。

考えてみれば cgn は一旦挿入モードに入ってテキストを変更するので、. でリピートしたとしてもそこは変わらないわけですね。

というわけで今度こそ。

InsertEnter のイベントを確認する

先ほどやったように 1 つずつイベントを消して、再現するかどうか確認します。すると、以下の箇所で問題が発生していることがわかりました。

neocomplete  InsertEnter
    *         call neocomplete#handler#_do_auto_complete('InsertEnter')

ここまでわかればあとは該当コードを追っていけば良さそうです。

該当コードを追ってみる

この先はおまけ。該当コードは以下のようなもの。

function! neocomplete#handler#_do_auto_complete(event) abort "{{{
  if s:check_in_do_auto_complete(a:event)
    return
  endif

  if g:neocomplete#auto_complete_delay > 0 && has('timers')
        \ && (!has('gui_macvim') || has('patch-8.0.95'))
    if exists('s:timer')
      call timer_stop(s:timer.id)
    endif
    if a:event !=# 'Manual'
      let s:timer = { 'event': a:event }
      let s:timer.id = timer_start(
            \ g:neocomplete#auto_complete_delay,
            \ function('s:complete_delay'))
      return
    endif
  endif

  return s:do_auto_complete(a:event)
endfunction"}}}

+timer が利用可能な場合に少し待ったのちに s:complete_delay() を呼び出しています。s:complete_delay() 内では、補完処理を行っています。

ここで実際に起きている処理を思い出して見ます。cgn. で繰り返すと、一瞬挿入モードに入ってすぐに抜けるのでした。つまり s:complete_delay() が呼び出される頃にはすでに挿入モードを抜けています。今回の問題は、ノーマルモードで補完処理を行おうとして起きていたものでした。

ちなみに、この問題はすでに修正してもらえました。Shougo さん、ありがとうございます!

まとめ

Vim で問題が起きた際の調査の一例でした。

今回の例は再現率が 100 % だったのでなんとかなりました。トリガーがわからないとその分難しくなります。

他に汎用的な手法として、プラグインを少しずつ無効にしていってどのプラグインが原因か調べる方法もあります。プラグイン数が多いと大変ですが、確実性は高いです。問題を起こしているプラグインさえ特定できれば、最悪原因がわからなくても最小構成での再現を作ることでバグレポートを出すこともできます。

バグ調査は地道な作業ですが、快適な編集環境のためにできることからやっていきましょう。


  1. 正確にはその他のエラー以外のエラーではないことがわかった。

  2. 例で設定しているのは8。詳細は :help 'verbose'

進捗キャンプに行ってきた

書くのが遅くなってしまったけど、先週末に金曜を休みにして 4 連休を利用して友人達と進捗キャンプに行ってきた。

旅館に引き籠もってひたすら進捗を出す会。たまに休憩をしつつ、たまに近くにいる利点を活かして相談しあったり、進捗のマサカリを投げあったりして和気あいあいとしてかなり楽しかった。

環境もかなりよくて、広い部屋で思い思いの姿勢でだらーと開発。私は仰向けとかでだらっと開発していて、とても人様に見せられる格好ではなかったので知ってる人しかいなくてよかった。

以下に成果を簡単に。

プラグインを 1 つリリース

wtrans と言う node.js 製の CLI ツールと、それを使う wtrans.vim をリリースした。とは言っても、これ春だか夏だか(忘れるくらい昔)に作り始めたやつで、1週間くらいでサクッと作るかって思って今に至るのでだいぶ引っ張った。悪い癖だ…。

完全に個人用に作ったやつなのでどんなツールかの解説はあえてしない。簡単なドキュメントは書いてあるので気になる人はそっちを見てください。

vimrc読書会 bot の改修

特に Promise の then() を使いまくっていてつらかった部分を、どうせ hubot で Node.js なのでバージョン上げて async/await に書き換える作業などをしてた。 ろくにテストを書いていなかったので恐る恐るだったけど、なんとか無事動いたのでよかった…。テスト書きたいけど、書くのつらい…。

あとはちょっとした新機能の追加とかした。たぶん次回辺りにお披露目される。本当はまだ追加したい機能とか整理したいところとかいっぱいあってつらい…。

vital.vim 周りのあれこれとか

特に外部モジュールについていい加減つらいという事案があるので、この辺りとかを投げられたのでやろうと思っている。

ウデマエが全て S になった

一番の成果と言っていいかもしれない。今まで A+ 辺りをうろうろしていたので、ようやくここまで来たかという感じ。前作では S+ は一瞬だけ上がってあとはずっと S だったので、今回は先に行ってみたいところ。

以上

他何か色々やった気がしないでもないけど、忘れてしまった…。

本当はもっと進捗出したかった感はあるけど、とは言え普段よりかは捗った気がするので、かなりよかった。また行きたい。

VimConf 2017 を振り返る

VimConf 2017 に、スタッフとして参加したので、その簡単に振り返りなどを。この内容はあくまで個人的なものであり、運営チームとしての見解ではありません。

今年の VimConf の立ち上げ

例年の VimConf に参加してくれていた人ならわかると思いますが、今年の VimConf は規模が去年までとは違い、かなり大きくなりました。

そもそも今年の VimConf の企画立ち上げ当初、我々には課題がありました。VimConf では将来的に Bram さんを招待したいと謳っているのですが、昨年までのペースではとてもではないけどそれは実現できないだろう、ということです。これを達成するためには、VimConf は大きく変わる必要がありました。

規模を大きくするには多くの壁があります。多くの人が入る会場を確保し、資金を調達する必要があります。会場を大きくしたとして、人が集まるかどうかはわかりません。国際カンファレンスを銘打っているのに、国際感がないとの指摘も以前からありました。とても大変であろうことは想像に難くありませんでした。この時点で、例えば、Bram さんを招待するという目標を撤回して細々と続けていく、という選択肢もあり得ました。

しかし、我々は前に進むことに決めました。正直なところ私は乗っかっただけなのであまり偉そうなことは言えないんですが。この辺りの決断に関する話を秋葉原にある肉の万世でしたのは今でも覚えています。

会場について

今回の開催で、良かった点をいくつか挙げてみようと思います。まずは会場について。

今回会場として使わせてもらった富士ソフトさんのアキバプラザは素晴らしかったですね。全席電源付き、Wi-Fi あり、そして雛壇状になっているので後ろでも発表が見やすいと、参加者にとってもそうでしたが、様々なオプションが付いていたおかげで、なんとか少ないスタッフでも無事回すことができました。 例えば参加者に配布した通訳用のレシーバー、あれも会場側が用意してくれたものです。

Fatih さんの登壇および英語発表

Fatih さんに登壇してもらえたのは本当に幸運だったと思います。

先ほど、国際感がないという話をしましたが、Fatih さんの登壇によって一気に国際感が上がりました。

また、スピーカー募集時に意識したおかげもあってかはわかりませんが、英語で発表してくれるスピーカーさんも予想より多く、国際カンファレンスの名に恥じないイベントになったと思います。

この場を借りて、Fatih さんおよびスピーカーの皆さん(もちろん日本語で発表した人も!)にお礼を言いたいと思います。ありがとうございました!

スポンサー

正直に言うと、私は最初、Vim のイベントにスポンサーはそうそう付かないだろうと思っていました。

企業で特定技術を使っていれば、その特定技術のイベントのスポンサーになることで技術者にアピールすることは理解できるのですが、Vim は個人単位で利用を選択するレベルのもので、企業として Vim をやっていきます!ということにはならないわけなので、難しいのかな、と。しかし実際には多くの企業がスポンサーになってくれました。更には終わった後にスポンサーになり損ねたのを悔やむ声も。本当にありがたいことです。

今回の開催で、「Vim のイベントに企業スポンサーをつけるのは難しいのではないか」という私の疑念は完全に払拭されました。

チケット完売

チケットについても、不安がありました。規模が大きくなったとは言え、去年の倍以上の値段で、枠も増えています。実際にチケットを売り出すまでは、ちゃんと掃けてくれるのか心配でしたが、そんな心配をよそに、無事完売御礼となりました。買えなかった人には申し分けなかったですが、来年に繋がるかどうかはチケットがちゃんと売れるかどうか次第だったので、ホッとしました。

開催を終えて

まずはとにかく無事に終わって一安心と言ったところです。当日の様子や上がってくる感想記事などを見た感じだと評判も上々のようで、本当にやって良かったと思います。

一方で反省材料もあれこれと。ここで具体的には書きませんが、来年に活かしたいところです。

興奮冷めやらぬ週明け、スタッフメンバーでささやかな打ち上げをしました。会場は秋葉原肉の万世。お肉おいしいです。

ちなみにスタッフはみんなざっくりの担当はありつつも雑用もみんなしつつみたいな感じで、私は主にパンフレットおよびイベントロゴの作成、をしてくれるデザイナーさんとの連絡役でした。パンフレット、気に入って頂けたなら嬉しいです。

当日作っていたものについて

なんか真面目っぽい話をしてしまったので、最後に少し雑な話を。

皆さんの発表を聞いていて、感極まってきて何かしら作りたくなってきたので、以前からアイディアがあったもののプロトタイプを作ってみた。

https://gist.github.com/thinca/ac438e1139c054b70a740b8f29e5ba0d

これは、Vim の中で動くターミナル(:terminal)の中で動く zsh の中で、:FooBar のように : で始まるコマンドを実行すると、外側の Vim でそのコマンドを実行し、zsh のコマンドの実行結果として出力するというもの。ギリギリ動くものの、まだ実用レベルではない。また、ターミナルについて詳しくないのでこのアプローチが妥当かとかもよくわかってない。

懇親会の最後に飛び込み発表タイムがあったので披露してみたのだけど、開発した環境が ssh 先で、会場の Wi-Fi は提供が終了していたのでテザリングでやったら回線がうまいこと動かなくてちょくちょく固まってしまった。みんなゴメンよ…。

1点、実行コマンドがそのまま出ちゃうのがなんとかできれば割と実用レベルまで持って行ける可能性があるので、できそうならやってみたいところ。Vim 本体を直すか、アプローチを変えるか…。

RubyKaigi 2017 に行ってきた

3 日間に渡る国内最大の Ruby カンファレンス、RubyKaigi 2017 に行ってきた。初参加!

RubyKaigi 自体も初参加なのだけど、複数日に渡る技術系イベントにフル参加したこと自体がたしか初めて。遠方だったのもあってかなり疲れた…。

いつものように極めて雑な感想を書きますが、ぶっちゃけ聞いたけどわからなかったのとかもあったりするので(英語だったり難易度だったり…)、いくつかピックアップして書きます。 あと勝手な解釈でまとめを書いてたりするので間違ってたらゴメン。

1日目

Keynote - ゆるふわRuby生活 - nobu

発表自体が非常にゆるふわで、ふわふわしながら聞いていたのだけど、たぶん Ruby の開発に参加しませんかっていうお話のはず。

会期中にも幾度か話題に挙がった、右代入はあると便利そう。しかしこれ仮に入ったとして、同じ意味を持つコードの書き方が増える=スタイルが増えるので、しばらくはスタイルの統一に苦心するかも。新しい文法はすべからくその可能性があるので最初は仕方ないんだけど。

API Development in 2017 - @onk

Schema First で開発しようという話。

API 記述ツール回りは私も個人的に少し追っていたので、概ね想像通りの内容だった。GraphQL はまだ実際に試すところまで行けてないのでちょっと触ってみたい。

Official Partyの場で質問する機会があったので、以下のような感じなことを聞いてみた。

  • Q: 既存の API 記述形式はいずれも Request/Response の記述のみだが、昨今は WebSocket などもあり、特にゲーム等では双方向通信のための API 記述が必要になることが多い。双方向通信の API を記述するにはどうするのがよいのか
  • A: うちも困ってて悩み中 (雑要約)

ですよねーー!という感じだった。とりあえず悩んでるのが私だけでないとわかってよかった(?)。

別のセッションで出てくる Language Server Protocol はまさしくこの双方向通信を使っているものなんだけど、現状は Markdown 形式の仕様書 があるのみ。プログラムから再利用可能な形式の API 定義がほしい。

Handling mails on a text editor - @shugomaeda

発表資料: https://github.com/shugo/RubyKaigi2017

Ruby で書かれたテキストエディタ、Textbringer の実装の話。

バックグラウンド実行の話は興味深い。 Vim の場合は歴史的経緯もあってプログラム全体がシングルスレッド。外部インターフェースを使えばスレッドが作れるが、そこから Vim のバッファなどの情報を書き換えると当然クラッシュする恐れがある。怖い。

Gemification for Ruby 2.5/3.0 - @hsbt

発表資料: https://www.slideshare.net/hsbt/gemification-for-ruby-2530

標準ライブラリを外部 gem にしていく計画の話。

標準ライブラリを gem として外に出していくことで、Ruby 本体の更新を待たずに gem を更新して最新のパッケージを使えるようになるので、ユーザーとしては助かる。作る方は互換性の確保などの必要があって大変らしい。

Ruby 2.5 から bundler が標準ライブラリになって gem install bundler が不要になるらしい。便利。

How to optimize Ruby internal. - Watson

発表資料: https://speakerdeck.com/watson/how-to-optimize-ruby-internal

地道にパフォーマンス改善していく話。

ものすごく淡々と、基本に忠実にパフォーマンス改善していた。カッコイイ。 計測で使っていた iprofiler が便利そうだったけど、Mac 用のアプリらしい。この辺りのツール類は計測したくなったときに色々調べてみよう。

I quit my job to write my own language: Goby - Stan

発表資料: https://www.slideshare.net/LoStan/goby-and-its-compiler

飼い猫がかわいいという話 自作言語 Goby の話。

どうやらまだまだコンパイラの作り方の勉強中らしい。割と基本的なバイトコード最適化の部分を「難しい」って言ってた。がんばれー。

私も時間取って言語作ったりしてみたいなー。

2日目

Keynote - The Many Faces of Module - matz

Ruby のモジュールが持つ役割の分類について。

今まで当たり前のようにモジュールを使ってきたけど、言われてみればモジュールは様々な種類の役割を持っていることに気付かされる。今回の講演では既存の役割として6種類の使い方が紹介されて、また今後増える可能性も示唆された。Structural signature は Ruby 3 に向けて議論されている型システムにも通じるものがあるので非常に興味深い。

そしてやはり最後、Ruby はもはや Matz さんだけの言語ではなく、我々の言語であると言っていたのが印象深かった。

An introduction and future of Ruby coverage library - mame

Rubyカバレッジライブラリの現状と今後について。

現状はラインカバレッジしか取れないので、もっと細かく取れるようにしていくとのこと。ブランチカバレッジも取れるようにしたいって話だったので、こんな風に行内の式のどの部分が実行されてないかわかるようになるといいなぁ。

ところで、カバレッジ結果の出し方について悩んでいるという話があった。ここでの話はライブラリの API の戻り値の話だったけど、それを更にファイルに出力する際のファイルのフォーマットって各言語の各ツールがみんなそれぞれ好き勝手にやってて全然統一されてないっぽい。Codecov の API リファレンスにある、受け付けるファイルフォーマットの種類はリストアップされているだけで21種類あり、実際にはもっとあると書かれている。まさにカオス。

カバレッジの結果って行やカラムなどのファイルの位置情報に終始していて言語の文法に依存することって基本なさそうなので、統一できそうなんだけどなぁ(ちゃんと作らないと行単位しか表せなかったりだと困るのでどの程度の表現力が必要かという話はあるだろうけど)…デファクトがないのがつらい。

Automated Type Contracts Generation for Ruby - Valentin Fondaratov

発表資料: https://speakerdeck.com/valich/automated-type-contracts-generation-1

Ruby のコードの型情報を生成する手法について。

Ruby は動的な言語なので、静的解析には限界がある。他のセッションでも、完全な型情報を型を書かずに静的に決定するのは不可能という話があったりした。 動的な言語なんだから実際に実行しないとわからない。なら走らせてしまえ、というアプローチがおもしろい。やっぱり動かすしかないよね…。

任意の gem 内で定義されているメソッドが、テストコードによってどの型で呼び出されたかという情報を蓄積して、これにより型を得る。集めたデータはクラウドに蓄積し、public な gem に関してはユーザー間で共有する。これはすごい。

セッションの紹介ページをよく見るとわかるのだけど、これは RubyMine で使われている手法。Vim でもできるようになるといいなぁ。

Type Checking Ruby Programs with Annotations - @soutaro

アノテーションを書くことによって型情報を与え、その情報を元に型チェックを行う手法について。

型情報を書かずに、完璧な型チェックを行うことは不可能と結論していて、わかる…という感じ。どこかを妥協しないといけない。 steep という gem でできるようにしていて、Ruby 本体に手を加えずにできるので、試金石としてかなり良さそう。

Ruby Language Server - @mtsmfm

発表資料: https://speakerdeck.com/mtsmfm/ruby-language-server

Language Server Protocol の紹介と、Ruby 版の実装の解説。

Language Server Protocol についてはその前身の 1 つである OmniSharp の Vim 版クライアント omnisharp-vim のメンテナをしている(メンテナンスしているとは言っていない)のもあってかなり関心が高い。 Language Server が提供するものの 1 つに補完があるけど、この辺りの話題は今回の RubyKaigi で本当に多い。そしてそれが難しいと言う話も何度も出ていて、やはり現状では本当に簡単な補完しかまだできない模様。 この辺りは今後もっと周辺の環境が整っていきそうな気配があるので、要注目。

3日目

Exploring Memory in Ruby - Building a Compacting GC - @tenderlove

RubyGC にコンパクションを実装する話。

日本語でやってくれたのも大きいけど、感動的なくらいに話が非常にわかりやすい。GC 回りの基礎知識を私が持っていたのもあるかもしれない。

資料ではコンパクションは明示的に呼び出している模様。モチベーションの起源を考えると、それで十分なのかな。将来的に Ruby 本体に入れることを考えているかどうかはわからないけど、もし入れるのであればある程度自動で走ってほしいところ。しかしそうするとインクリメンタル GC との相性などもあるので、かなりハードルが上がりそうだ。

最後に話していた、不可能であるという思い込みを捨てろというのは割と刺さった。よいなぁ。

Ruby Parser In IRB 20th Anniversary…Now Let Time Resume - @aycabta

RDoc のレガシーと戦った話。

全てはこの PR のために。

私は某所で個人的に彼が大変な思いをしながらレガシーと戦っていたのを知っていたので、完全に感極まった。おめでとう!!!

Writing Lint for Ruby - Pocke

発表資料: https://speakerdeck.com/pocke/writing-lint-for-ruby

RuboCop の仕組みおよび新しいチェッカの追加方法などの解説。

質疑応答の中で紹介されていた --parallel オプションは知らなかったので収穫だった。 AST Visitor パターンの Lint の仕組みについては知識があったが、RuboCop 自体についてはそこまで詳しくなかったので、こういうのがポロッと知れるのはよい。

あとはセッションと別の機会で Pocke さん達と、RuboCop 遅いよね、という話題が上がったけどまあ雑談程度でいい案が浮ぶわけもなく。実際 Cop が増えれば増えるほど原理上遅くなるので、何か解消できる方法があるといいのだけど、何かないかなぁ。

Towards Ruby 3x3 performance - @vnmakarov

発表資料: https://vmakarov.fedorapeople.org/VMakarov-RubyKaigi2017.pdf

英語だったのと内容が高度すぎたのもあって理解がかなり怪しい…。スライドの英語と Twitter のデキる皆さんのツイートを頼りに解読した程度の理解度。

とにかく絶対難しいやつなのにサラッと作ったみたいなこと言ってるのがやばい。そして実際速くなってるとのこと。このセッション以外のアプローチもあるようだし、3x3 は割と見えてきている感じなのかな。すごい。

雑感

  • 英語のセッションが全然わからなくてつらい…。リスニングだけでももうちょっとなんとかしないといけないんかなぁ。
  • 初日に電源が提供されていることに全く気付かず、ないもんだと思って2日目コードを持って行かなかったら実はあったというアレ。3日目は存分に享受させてもらった。
  • 去年はあったとの噂の弁当ランチが今年はないとか、広島平和記念資料館にタダで入れるとか、当日にならないとわからない情報が割とあった。事前に公式サイトで確認できると嬉しかった(実は書いてあったとかだったらゴメンナサイ)
  • 前夜祭から始まり、毎日パーティがあって完全に食べすぎた…
    • 翌日に響いてウトウトしちゃったセッションもあったり
  • LT の感想はいくつか書きたかったけど力尽きた

とにかくめちゃくちゃ刺激を受けた。来年も是非行きたい!

lemonade でマルチバイト文字がコピーできなかった現象とその顛末

TL;DR $LANG 設定しろ。


某月某日。私はとある問題に頭を抱えていた。

「なんでマルチバイト文字を含む文字列だけコピーできないんだ!」

私は lemonade を使い、ssh 先から文字列をコピーしようとしていた。

lemonade については過去の記事に紹介を譲る。

ところが、マルチバイト文字を含む場合のみ、クリップボードが空になってしまうのだ。ASCII 文字だけならばなんの問題もないというのに。

最初は lemonade のバグを疑ったが、仮に報告をしたとして、現在 lemonade はメンテナを募集している状態だ。修正される可能性は低いだろう。何より困っているのは私自身だ。

「自分で調査するしかないか…」

lemonade は Go 言語で実装されている。Go 言語は不慣れだが、幸いにも勉強しようと思っていたところだ。既存のソフトウェアのソースコードを読むのは大変勉強になる。ある意味丁度いい。

原因を調査するために、ソースコードを変更してログを仕込む。しかし、私の lemonade サーバは Mac で動いており、以下のような設定でデーモンとして起動している。

~/Library/LaunchAgents/lemonade.server.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>lemonade.server</string>
  <key>KeepAlive</key>
  <true/>
  <key>ProgramArguments</key>
  <array>
    <string>lemonade</string>
    <string>server</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
</dict>
</plist>
$ launchctl load ~/Library/LaunchAgents/lemonade.server.plist

このままでは標準出力に吐かれるログが見れない。いや、見る方法はあるのかもしれないが、そこまでは調べなかった。

ここはデーモンを止めて、コマンドラインから実行してしまうのがはやい。

$ launchctl unload ~/Library/LaunchAgents/lemonade.server.plist
$ lemonade server

これでログを見ることができる。

愚直に print デバッグを繰り返していると、とあることに気付く。

「あれ? …普通にマルチバイト文字送れてるぞ?」

そう、実はこの状態であれば lemonade はご機嫌に動作したのだ。

ここまで来ると原因が絞り込めてくる。この状態で動いているということは、lemonade のバグの線は消えたと言っていい。あとは環境の違いということになる。デーモンではダメで、手元のターミナルから直接実行だと動く。そして起きている現象は、マルチバイト文字に絡む…。

環境変数 $LANG 辺りが怪しいな」

そう思い立つと、適当に環境変数をファイルに出すプログラム sh -c 'env > env.txt' をデーモンに仕込んで、デーモン環境での環境変数を確認してみる。

思った通り、環境変数 $LANG が設定されていない。恐らくこれだろう。ということで、デーモンに環境変数を設定するように plist ファイルを以下のように書き換えた。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>lemonade.server</string>
  <key>KeepAlive</key>
  <true/>
  <key>ProgramArguments</key>
  <array>
    <string>lemonade</string>
    <string>server</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>EnvironmentVariables</key>
  <dict>
    <key>LANG</key>
    <string>ja_JP.UTF-8</string>
  </dict>
</dict>
</plist>

これで動かしたところ、無事、マルチバイト文字のコピーに成功した。世界に平和が訪れた。

後で知ったが、launchctl setenv などでデーモン全体の環境変数を設定することも可能なようだ。こちらでも良さそうだが、試していない。