Ruby extのためのVisual Studio Codeのclang extension用設定を書き出す

Visual Studio Codeの C/C++ Clang extensionのためのいくらかの設定をすると、Ruby extensionを開発するときに補完が効くようになり、開発が楽になります。

C/C++ Clang - Visual Studio Marketplace

ところでRuby extensionのCFLAGSなどの設定はわりと難しくて、実際のMakefileを読み込んでmakeを実行するのがてっとりばやいです。

というわけで、extconfを起動してMakefileをつくり、そこから必要なマクロを抜き出して .vscode/settings.json に書きだすためのタスクをつくってみました。

# additional Rake task for "ext/foo"
namespace :vscode do
  desc "Setup Visual Studio Code C/C++ settings (.vscode/setting.json)"
  task :setup do
    require "json"
    require "shellwords"

    FileUtils.mkdir_p("tmp/vscode")

    cflags = Dir.chdir("tmp/vscode") do
      # to create Makefile
      system("ruby", File.join(__dir__, "ext/foo/extconf.rb"))

      # Makefile macro to get CFLAGS for editors / IDEs
      task = "\n\nshow-cflags:\n\t$(ECHO) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS)\n\n"
      IO.write("Makefile", task, mode: "a")

      Shellwords.split(`make show-cflags`)
    end

    settings = {
      "clang.cflags" => cflags,
      "clang.completion.triggerChars" => [
        ".", ":", ">", "_"
      ],
    }

    FileUtils.mkdir_p(".vscode")
    IO.write(".vscode/settings.json", JSON.pretty_generate(settings))
  end
end

こんな感じで補完が出ます。

f:id:gfx:20170115121534p:plain

しかし、補完が出るのに数秒かかるので思ったほど快適ではない… 😔


追記: Microsoft公式のvscode.cpptoolsだと補完が快適でした。それ用のtaskはこんな感じです。

namespace :vscode do
  desc "Setup Visual Studio Code C/C++ settings (.vscode/setting.json)"
  task :setup do
    require "json"
    require "shellwords"

    FileUtils.mkdir_p("tmp/vscode")

    cflags = Dir.chdir("tmp/vscode") do
      # to create Makefile
      system("ruby", *Dir.glob(File.join(__dir__, "ext/**/extconf.rb")))

      # Makefile macro to get CFLAGS for editors / IDEs
      task = "\n\nshow-cflags:\n\t$(ECHO) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS)\n\n"
      IO.write("Makefile", task, mode: "a")

      Shellwords.split(`make show-cflags`)
    end

    include_path = cflags.select do |flag|
      flag.start_with?('-I')
    end.map do |flag|
      flag.sub(/\A-I/, '')
    end

    # http://code.visualstudio.com/docs/languages/cpp
    configurations = [
      {
        name: "Mac",
        includePath: include_path,
      },
      {
        name: "Linux",
        includePath: include_path,
      },
      {
        name: "Win32",
        includePath: include_path,
      },
    ]

    FileUtils.mkdir_p(".vscode")
    IO.write(".vscode/c_cpp_properties.json", JSON.pretty_generate(configurations: configurations))
  end
end

Android SDK 25.2.3から付属のsdkmanager(1)でパッケージのインストールをする

Android SDK 25.2.3にsdkmanagerとういコマンドが新しくく追加されていたようです。

これは、CLIでAndroid SDK packageを管理するためのツールのようです。Gradle Android pluginと同様にlicensesファイルに対応しているので、CIなどで扱いやすいですし、なによりandroidコマンドにはなかったパッケージのアップデートがサポートされています。このため、androidコマンドよりは扱いやすく工夫の余地が増えてると考えてよさそうです。

使い方はこんな感じです:

# homebrewだとまだパスが通らないので絶対パスで参照する

# インストール済みパッケージと利用可能なパッケージを出力する
$ANDROID_HOME/tools/bin/sdkmanager --list

# platform-toolsと旧名extra-android-m2repositoryをインストールする
$ANDROID_HOME/tools/bin/sdkmanager "platform-tools" "extras;android;m2repository"

# インストール済みパッケージをアップデートする
$ANDROID_HOME/tools/bin/sdkmanager --update

Circle CIでの導入はこんな感じです:

https://github.com/gfx/Android-Orma/pull/365

Circle CIにインストール済みのAndroid SDKは25.2.3より古いため、まずandroidコマンドでSDK toolsのアップデートをしないとsdkmanagerが使えません。このためCircle CIのAndroid SDKがアップデートされるまではandroidコマンドから脱却はできません。残念。

Ormaの2016年振り返りと2017年の展望

https://github.com/gfx/Android-Orma

2015年末に開発をはじめたOrmaも、この一年でだいぶバージョンが進んで、年明け時点で v0.20.0だったところが、年末時点でv4.0.2でした。v0.20.0...v4.0.2で21,768 additions and 6,328 deletionsということで、結構書いたなあという感じです。そのおかげでv1.0.0の頃とくらべると格段に安定していますし、機能も増えてます。

採用事例や紹介エントリも増えてきました。

GitHub search などをみるといくつか実際のユースケースをみることもできます。

2017年の展望

2016年に比べるとやることは少なくなってきましたが、まだいくつか重要なタスクが残っています。

  • マイグレーションで使っているSQLiteパーサの変更
    • 現行のANTLR4ベースのパーサは、メモリの非常に少ない端末でたまにエラーになるという問題があります
    • 現在JavaCCでSQLiteパーサを書き直しており、1~2ヶ月後くらいには実験的に使えるようにしたいところです
    • 以前の検証によるとJavaCCによるパーサはANTLR4によるパーサよりも数倍高速なので、低スペック端末を切るとしてもやる価値はあるという判断です
  • has-many / many-to-many のよりよいサポート
    • has-many関係は宣言こそ自然にできるものの、INSERTやSELECTがわりと難しいのでもうちょっとなんとかしたい
  • ドキュメント
    • 特に英語圏でのマーケティングのため

今後ともよろしくお願いします。

PR: 3月のDroidKaigi 2017ではOrmaを含めた最近のAndroidのORM事情について話す予定です!!

ライブラリのバージョニングのしかた

セマンティックバージョニングは守るとして、だいたいこんなポリシーでやってます。

  • 0.0.1 - proof of concept / minimum viable product
  • 0.1.0 - とりあえずリリースしてプロダクションに組み込んでみる
  • 1.0.0 - プロダクションに組み込んだ
  • 2.0.0 - セマンティックバージョニングに従うので、メジャーバージョンアップは機能ではなく単にAPI互換性を失うという印

あとは、alpha, beta, rcなどを接尾詞としてつけることもあります。

  • *-alpha - 開発中
  • *-beta - 安定してきた
  • *-rc - release candidate. プロダクションに組み込んでもOK

Suffix ArrayをRustで実装した

suffix arrayを一番簡単なアルゴリズムで実装する - アルゴリズム学習(その6) - $shibayu36->blog; を読んで、ちょうど自分も何らかの形で全文検索を一部実装してみようと思っていたのでRustで真似してみました。

Rustを選んだ理由は、以下の理由からです。

  • 実際に全文検索を実装するのに耐えうるパフォーマンスであること
  • パッケージマネージャなどのエコシステムが完備されていること

Rustについてはそれほど詳しくはないのですが、GCや例外がないとのこと。であればパフォーマンスチューニングがC言語並にやりやすい可能性がありますし、一度真面目に勉強してみたいと思っていました。Goと異なり、ジェネリクスがあるのも魅力的です。

というわけでコードこんな感じになりました:

pub fn make_suffix_array(s: &str) -> Vec<i64> {
  use std::collections::HashMap;

  let mut map = HashMap::new();

  for i in 0..s.len() {
    map.insert(&s[i..s.len()], i as i64);
  }

  use std::iter::FromIterator;

  let mut suffixes = Vec::from_iter(map.keys());
  suffixes.sort();

  return suffixes.iter().map(|suffix| *map.get(*suffix).unwrap()).collect();
}

#[test]
fn test_make_suffix_array() {
  let a = make_suffix_array("banana");
  assert_eq!(a, vec![5, 3, 1, 0, 4, 2]);
}

Rustに慣れていないので、この数十行を書くのも大変でしたがとりあえず動きます。エディタはIntelliJ Rustを使いました。あまり賢くはないもののコード補完があり、普段使っているRubyMineやAndroid Studioに近い感覚でコードを書けるのでわりと好印象です。

MBP2016が購入直後に故障したので返品してMBP2015を注文した

Macbook Pro 2016 13インチ タッチバー付きモデルを先月購入しましたが、3週間ほど経って充電できなくなる現象に見舞われました。Apple Storeのジーニアスバーに持ち込んでみてもらったところ、「ハードウェアの不具合と思われるが現在このモデルは部品がなく修理できない」とのことで、返品することにしました。代わりにMBP2015を注文し、これは来週届く見込みです。

故障をきっかけとして返品ということになりましたが、結果的には、むしろMBP2015に交換できてよかったと思っています。下記のとおり大きな不満点がありましたし、同様の故障が観測範囲内でも散見されるので、それならばMBP2015のほうが心配なく使えるという判断です。MBP2016を検討している方は参考にしてください。

MBP2016を3週間使ってみてどうだったか

  • ポートがUSB Type Cしかなくアダプタも貧弱で品薄状態。結局、購入から返品まで外部ディスプレイには繋げないままだった
    • 他にもアダプタを多数持ち歩かなければならず、非常に煩雑だった
    • もちろん過渡期ゆえのつらさという一面はあると思うが、自分はこういうことを楽しめないタイプなので純粋に苦痛だった
  • 充電ポートはMagsafe2のほうがよい
    • Magsafe2は充電状態を示すLEDがついており、一目で充電状態がわかるが、USB Type C充電コネクタは充電状態がわからない
    • Magsafe2は少しの力で外れるので足を引っ掛けてもコネクタが外れるだけだが、USB Type C充電コネクタは片手では抜けないくらいしっかりと刺さる
  • Touch Barは対応アプリが少なく、プラスでもマイナスでもなかった。対応アプリが増えてくればメリットを感じるかもしれない
    • ESCはやや遠く感じるが、慣れればそれほど問題はなかった
  • 新しいキーボードは悪くない
    • パタパタうるさいのは少し気になる
    • 力はそれほどいらないので慣れればむしろ楽で、旧MBA/MBPのキーボードが重く感じるレベル
    • Macbookは使ったことがないので差はわからない
  • Touch IDはよかった。特に1passwordを簡単にアンロックできるので使用頻度は高かった

スペック

MBP2016:

MBP2015:

babel-nodeでたてるnode serverをguard-processで管理するときはkexec npm packageが必須

Babel CLIには babel-node というコマンドが付属していて、これはES2015+のスクリプトをそのまま実行できるという便利ツールです。

babel-nodeは大変便利なのですが、guardのpluginであるguard-process とは相性がわるく、babel-nodeで立ち上げたnode serverプロセスの再起動がうまくできません。これは nodeの process.spawn() まわりの問題だと思うのですが、詳細は調べていません。

ただし、babel-nodekexec がインストールされているとそちらを使います。そこで yarnpkg add -D kexec するだけでguardで管理できるようになります。これはドキュメントに乗っていないので、ここにメモしておきます。

該当コードはこのあたりです。

https://github.com/babel/babel/blob/master/packages/babel-cli/src/babel-node.js#L77-L94

Orma v4.0.0 - RxJava2 & SQLBrite-like query observables

GitHub - gfx/Android-Orma: A type-safe ORM for Android as a wrapper of SQLiteDatabase

v3.0.0からまだ一ヶ月くらいしかたってませんが、Ormaがv4.0.0となりました。

目玉機能は RxJava 2.x 対応で、これが後方互換性を維持したまま行うのが難しかったため、メジャーバージョンを上げることにしました。

RxJava 2.x 対応

機能的にはRxJava 1.x対応と同じ水準なはずです。

ただこれを機に、 Observable を返すものは AsObservable をサフィックスに、 SingleAsSingle に、 CompletableAsCompletableに…と、メソッド名を見ればRx observableのどの亜種を返すのかを分かるようにしました。

Query Observables

この機能は実験的です。いきなり消えることはないと思いますが、微妙にインターフェイスが変化したりする可能性があります。またこの機能の使い心地についてフィードバックを求めています!

SQLBriteのように、データセットの変更が流れるイベントストリームをRxJavaのobservableとして作成できます。このobservableを購読すると、Ormaによるデータの更新(INSERT, UPDATE, DELETE)を受け取れます。またその際Selectorを引数として受け取りますが、これはただのSelectorなので使っても使わなくてもかまいません。使い方の具体例としてはOrmaRecyclerViewAdapterなどがあります。

SQLBriteと違って完全にRx任せなので、デフォルトで同期的に変更通知が流れます。非同期で処理したい場合は、Rxのschedulersを使ってスレッドを調整してください。またこれもSQLBriteはsubscribe() した瞬間に最初のイベントがながれますが、Ormaは何も流れません。Ormaはあくまでも変更通知だけを流します。

READMEから抜粋:

// NOTE: Keep the observable instance. If it's released, the observable is disposed.

// create a query observable, which is a hot observable
Observable<Author_Selector> observable = db.relationOfAuthor()
        .createQueryObservable();

// subscribe the events
observable.flatMap(new Function<Author_Selector, Observable<Author>>() {
    @Override
    public Observable<Author> apply(Author_Selector selector) throws Exception {
        Log.d(TAG, "Author has been changed!");
        return selector.executeAsObservable();
    }
})
        .map(new Function<Author, String>() {
            @Override
            public String apply(Author author) throws Exception {
                return author.name;
            }
        })
        .subscribe(new Consumer<String>() {
            @Override
            public void accept(String name) throws Exception {
                Log.d(TAG, "name: " + name);
            }
        });

注意点としては、 createQueryObservable() の戻り値を強参照として保持しておく必要があります。そうでなければ、GCのタイミングで消えてしまいます。また、変更通知が伝播するのは同一のデータベースハンドル(OrmaDatabaseのインスタンス)の間だけです。つまり、プロセス間ではイベントが伝播しません。

また、実験的に createEventObservable() というメソッドも用意していて、こちらはイベントの種類(INSERT / UPDATEなど)もとれます。ただし、@Deprecatedマークをしてあるとおり、これは本当に実験的で、これについてどこからも意見がなければどこかのバージョンで消すと思います。

vscodeの AlanWalk/markdown-toc extensionで目次の自動生成すると捗るぞ

先日のNode学園祭で standard-readme という規格が紹介されていました。

これを眺めていたら、ミニマムでも目次(ToC)は必要ですよということでした。たしかにToCは読むほうからすると便利なんですよね。

一方でToCのメンテナンスは人間のやる仕事じゃないですよね。機械的に生成してメンテフリーにしたい。

ということで、vscodeのMarkdown ToCのいい感じの拡張を探したところ、AlanWalk/markdown-toc というのがよさそうだったので使ってみました。

marketplace.visualstudio.com

この拡張をインストールしてREADME.mdに以下のように書いておくと:

## Table of Contents

<!-- TOC depthFrom:2 -->

<!-- /TOC -->

以降、保存するたびにToCが自動的に生成されてこのタグの中に埋められます。これは保存するたびに最新の状態に更新され続けるので、画面分割してToCを眺めながらセクションの構成を考えたりできます。

私の直近のプロジェクトだと、OrmaのREADME.md が1000行近くあってメンテするのがつらかったのですが、これのおかげでだいぶ楽になりました。おそらく読む方も楽でしょう。

Flowtypeの`flow suggest`がすごい!

flow(1)のコマンドラインオプションを眺めていたら、 suggest というサブコマンドがありました*1

これは、JavaScriptファイルを引数にとって、そのスクリプトにflowtype annotationを付けるパッチを生成するコマンドのようです。

たとえばこういうスクリプトに対して:

function add(a, b) {
    return a + b;
}

console.log(add(1, 2)));

次のようなパッチを生成しました:

--- old
+++ new
@@ -1,4 +1,4 @@
-function add(a, b) {
+function add(a: number, b: number) : number{
     return a + b;
 }

これは結構すごいんじゃないでしょうか。

Reactの1ファイルにも試してみました。

flow suggest src/isomorphic/modern/class/ReactComponent.js で以下のような出力です。

--- old
+++ new
@@ -13,7 +13,7 @@
 
 var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
 
-var canDefineProperty = require('canDefineProperty');
+var canDefineProperty: boolean = require('canDefineProperty');
 var emptyObject = require('emptyObject');
 var invariant = require('invariant');
 var warning = require('warning');
@@ -21,7 +21,7 @@
 /**
  * Base class helpers for the updating state of a component.
  */
-function ReactComponent(props, context, updater) {
+function ReactComponent(props, context, updater) : void{
   this.props = props;
   this.context = context;
   this.refs = emptyObject;
@@ -57,7 +57,7 @@
  * @final
  * @protected
  */
-ReactComponent.prototype.setState = function(partialState, callback) {
+ReactComponent.prototype.setState = function(partialState, callback) : void{
   invariant(
     typeof partialState === 'object' ||
     typeof partialState === 'function' ||
@@ -85,7 +85,7 @@
  * @final
  * @protected
  */
-ReactComponent.prototype.forceUpdate = function(callback) {
+ReactComponent.prototype.forceUpdate = function(callback) : void{
   this.updater.enqueueForceUpdate(this);
   if (callback) {
     this.updater.enqueueCallback(this, callback, 'forceUpdate');
@@ -98,7 +98,7 @@
  * modern base class. Instead, we define a getter that warns if it's accessed.
  */
 if (__DEV__) {
-  var deprecatedAPIs = {
+  var deprecatedAPIs: {isMounted: [string, string], replaceState: [string, string]} = {
     isMounted: [
       'isMounted',
       'Instead, make sure to clean up subscriptions and pending requests in ' +
@@ -110,10 +110,10 @@
       'https://github.com/facebook/react/issues/3236).',
     ],
   };
-  var defineDeprecationWarning = function(methodName, info) {
+  var defineDeprecationWarning = function(methodName: string, info) : void{
     if (canDefineProperty) {
       Object.defineProperty(ReactComponent.prototype, methodName, {
-        get: function() {
+        get: function() : void{
           warning(
             false,
             '%s(...) is deprecated in plain JavaScript React classes. %s',
@@ -125,7 +125,7 @@
       });
     }
   };
-  for (var fnName in deprecatedAPIs) {
+  for (var fnName: string in deprecatedAPIs) {
     if (deprecatedAPIs.hasOwnProperty(fnName)) {
       defineDeprecationWarning(fnName, deprecatedAPIs[fnName]);
     }

推論しきれてない部分もありますが、けっこうできているなという印象です。逆にいえば、これで補完されない部分は推論できないということなので、そういうところだけ人が書けばいいということですね。

*1:試したのはflow 0.35.0です