2014-15年を振り返る / 2016年の抱負

あけましておめでとうございます。2年ぶりの抱負エントリです。
去年は正月の時期を逃してしまい、仕方がないから旧正月に書くことにするか!と思っていたら仕事が忙しくて完全にタイミングを逸してしまいました。

2014年の抱負 として挙げたのは

  • blockdiag のバグ/タスクを減らす
  • なにか使えるツールをリリースする (少なくとも一本以上)
  • 技術書を読み続ける
  • 新しい言語でなにかツールを書く
  • コードを書く時間を維持する
  • 環境整備をしよう
  • あたらしいツール/フレームワーク/サービスに触る
  • 体重を落とす (目標 -3kg)

の8個でした。

この 2年間は遊んで過ごしていたこともあって、あまり成果らしい成果はでていないので
振り返るほどでもないのですが、反省を兼ねてざっと振り返ります。

blockdiag のバグ/タスクを減らす:✕

完全に放置状態ですね。2年間まるまる手を付けていません。

なにか使えるツールをリリースする (少なくとも一本以上):△

調べてみると

  • 2014
    • sphinxcontrib-cacoo
    • sphinxcontrib-astah
    • sphinxcontrib-visio
    • numfig feature (Sphinx)
  • 2015
    • flake8-config
    • sphinxcontrib-imagehelper
    • testing.elasticsearch
    • sphinxcontrib-apiblueprint
    • sphinxcontrib-markdown

とパッケージを作っていたみたい。
どれもちょっとしたツールで、使えるツールかと言われると悩ましいですね。

そういえば、Sphinx の numfig を作った流れでコミッターになりました。
あまり活動していなかったのですが、年末からぱたぱたと活動を再開しています。

あと、ずっと git 嫌いだったのですが bitbucket の凋落っぷりをみて、あきらめて git + github にスイッチしました。

技術書を読み続ける:◯

安定して新宿 Book-a-thon を開催しています。

読んだのは以下の本らしい。

読んだ中では日本語組版処理の要件が本当におもしろかった。
普段慣れ親しんでいる書籍について、考え方やルールを理解できてとてもいい資料です。
(厳密には本ではないけど、まあいいじゃない)

あと、読む方ではなく、書く方もちょっとやりました。
Software DesignSphinx の連載記事に 2本ほど寄稿しました。

新しい言語でなにかツールを書く:✕

golang を使ってみようとちょっと触ってみたけど、完成していないし、あまり身についた気がしませんね。

コードを書く時間を維持する:✕

サボってました。

環境整備をしよう:△ or ✕

ローカルにあれこれ入れたくないとずっと嫌がっていた homebrew を入れることにしたのは
ビッグチェンジだったかもしれません。

あたらしいツール/フレームワーク/サービスに触る:✕

あまり触っていません。
数少ないいじってみたサービスのうち、手に馴染んでるのは slack と trello ぐらいでしょうか。

体重を落とす (目標 -3kg):◯

がくっと落ちました。-5kg ぐらい。
完全に ingress のおかげです。
最近は忙しくて ingress をやる時間がとれないのですが、その後安定しているのでリバウンドはなさそうな感じ。

まとめ / 2016年の抱負

一昨年の抱負の成果は ○ 2つ、△ 2つ、× 4つでした。
ダメダメですね。


というわけで、さっくり今年の抱負。

  • Sphinx のバグ/タスクを減らす
    • しばらくコミッタ活動しようと思います
  • blockdiag のバグ/タスクを減らす
    • 今年こそ…
  • なにか使えるツールをリリースする (少なくとも一本以上)
    • API blueprint や sphinxcontrib-markdown の v1.0 リリースをしたいところ。
  • 技術書を読み続ける
    • 引き続きもの
  • コードを書く時間を維持する
    • 最近さぼっているので引き続き。
  • 体重維持

tox で環境変数をセットする方法

先日リリースされた tox-2.0.0 から、コマンドを実行する際に環境変数がフィルタされるようになりました。

(new) introduce environment variable isolation: tox now only passes the PATH and PIP_INDEX_URL variable from the tox invocation environment to the test environment and on Windows also SYSTEMROOT, PATHEXT, TEMP and TMP whereas on unix additionally TMPDIR is passed.
http://tox.readthedocs.org/en/latest/changelog.html

いままでテストの制御に環境変数を使ったりすることがありましたが、
tox-2.0.0 ではそのままでは動作しないことになりました。

$ RUN_ALL_TESTS=1 tox   # すべてのテストを実行する
...
$ SKIP_FOOBAR_TEST=1 tox   # あるテストをスキップする
...

ここで登場したのが ``passenv`` という設定です。
参照する環境変数を tox.ini に列挙します。

[testenv]
passenv= RUN_ALL_TESTS SKIP_FOOBAR_TEST

* などを用いた glob マッチにも対応しているそうです。
なお、複数列挙する場合はスペース区切りで並べる必要があります。
他の設定項目のように空行区切りをしてしまうと正しく反映されません。

余談

Travis CI 上で coveralls を実行するとトークンの設定とかそういったものが不要で簡単なのですが、
coveralls コマンドは環境変数を見て情報を得ているらしく、tox 経由で実行する場合は tox.ini に

[testenv]
passenv= TRAVIS*

という設定をしておかないと

You have to provide either repo_token in .coveralls.yml, or launch via Travis

と言われてカバレッジデータをアップロードしてくれないことに気づきました。
oh...

古い pip で --use-wheel を指定してパッケージを入れるとコマンドが生成されないことがある件。

古い pip には wheel まわりの扱いに問題があるようで、
--use-wheel オプションを指定してパッケージを入れたときに
コマンドが生成されないことがあるようです。

具体的には drone.io の環境に入っている pip-1.4.1 では問題が発生しました。
そのため、

pip install --use-mirrors --upgrade wheel
pip install --use-mirrors --use-wheel detox

detox

のようなスクリプトを実行すると

/home/ubuntu/.build.sh: line 52: detox: command not found

などと言われてしまいます。

解決策は pip を新しくすることなので、wheel を入れるときに pip も新しくするとよいでしょう。

pip install --use-mirrors --upgrade pip wheel

ちなみに新しい pip を --use-wheel でアップデートすると、
pip コマンドが使えなくなるというオチが待っているので、
pip の更新は wheel を使わずに行いましょう。

sphinxcontrib-googleanalytics-0.1 と sphinx-1.3 の組み合わせで Sphinx が落ちる件。

表題の組み合わせだと、こんなエラーを吐いて Sphinx さんが死ぬ。

Traceback (most recent call last):
File "/Users/tkomiya/work/blockdiag.com/lib/python2.7/site-packages/sphinx/cmdline.py", line 244, in main
opts.warningiserror, opts.tags, opts.verbosity, opts.jobs)
File "/Users/tkomiya/work/blockdiag.com/lib/python2.7/site-packages/sphinx/application.py", line 143, in __init__
self.setup_extension(extension)
File "/Users/tkomiya/work/blockdiag.com/lib/python2.7/site-packages/sphinx/application.py", line 449, in setup_extension
if not ext_meta.get('version', None):
AttributeError: 'Sphinx' object has no attribute 'get'

Sphinx-1.3 から、Sphinx 拡張の `setup()` 関数は「拡張モジュールのメタデータ(dict)」か
「何も返さない」ことを期待しているのだけど、
sphinxcontrib-googleanalytics はなぜか Sphinx オブジェクトを返しているので
こんなエラーを吐くことになる。
Sphinx-1.3 以前は返り値はとくに見ていなかったので何も起きなかったのだけど、
バージョンアップですれ違いが起きてしまった。

根本的な解決は sphinxcontrib-googleanalytics が Sphinx-1.3 に対応して、
いい感じの返り値を返すように修正されることなんだけど、
このモジュール、長らくメンテされていないのでいつになるのかとても怪しい…。

というわけで、conf.py でさっくり monkey patch を当てることにした。

# adhoc: Fix sphinxcontrib-googleanalytics does not work with Sphinx-1.3
from sphinxcontrib import googleanalytics
original_setup = googleanalytics.setup

googleanalytics.setup = lambda app: original_setup(app) and None

とてもひどい。けど動く。でもひどい。

しかたがないので、あとで Sphinx 側を直すことにしよう。

rails-erd でいい感じの ER図を出す

諸事情で ER図をとても作りたくなることってよくありますよね。

rails で開発しているプロジェクトだと rails-erd というツールを使うと
モデル定義から簡単に ER図っぽいものを生成できるのですが、
なにもオプション指定をしないと複雑で読みづらいものができてしまうことがあります。

  • Form オブジェクトが表示されてしまう
  • 使っている gem のクラス (audit など) が表示されてしまう
  • created_by や updated_by など、users モデルの参照がごちゃごちゃしてしまう
  • has_many :through の関係が点線で表示されている
  • カラムの表示がアルファベット順になっている

また、表示されている内容をよく見ると id や created_at などの標準カラムや、
foreign_key が貼られている参照カラムなども表示されていません。

このあたり、ちょっと整理しないと **ER図** としてはちょっと読みづらいですよね。

というわけで、ちょっと手を入れて見やすい図にする工夫をしてみました。

bundle exec rake erd filetype=dot attributes=foreign_keys,primary_keys,timestamps,inheritance,content indirect=false sort=false
ruby -e '\
    dot = open("erd.dot").read
    dot.gsub!(/^\s*".*?::.*?;\n/m, "")  # remove Ruby classes (Form, Audit, ...)
    dot.gsub!(/^.*? -> ".*?::.*?;\n/, "")  # remove relation to Ruby classes
    dot.gsub!(/^(\s*m_User -> .*?;\n)\1{2}/m, "")  # remove created/updated/deleted_by releations
    print dot
' > output.dot
dot -Tpdf -o erd.pdf output.dot
dot -Tpng -o erd.png output.dot

工夫してるところは以下のとおりです。

  • rails_erd
    • 出力内容を加工するために画像ではなく dot 形式で出力
    • attributes オプションを指定して、すべてのカラムを表示させる
    • indirect=False を指定して、has_many :throught の点線を表示しない
    • sort=False を指定して、カラムをソートさせない
  • フィルタ処理
    • Form オブジェクトや gem など、Ruby のクラスっぽいもの (:: を含むもの)を除外
    • User モデルへの参照は読みづらいので削除


大したことはやってないのですが、あとで忘れそうなので備忘として。

※ 諸事情でお手伝いしたやつなので、具体的なサンプルはお見せできません。あしからず。

flake8 で coding: utf-8 の有無をチェックする flake8-coding を作った

# -*- coding: utf-8 -*-

というファイルエンコーディングを示す magic comment の有無を lint しようと思ったのだけど
調べてみると Jenkins でやる方法ぐらいしか見当たりませんでした。

drone.io やら travis ci やらでサクッとチェックできるように flake8-coding という flake8 plugin を作りました。
このプラグインを入れると flake8 を実行するだけで magic comment の有無をチェックできます。

$ flake8 setup.py
setup.py:0:1: C101 Coding magic comment not found

Jenkins + Warnings Plugin + flake8 で静的解析

ざっとググってもそれっぽい記事が見当たらなかったのでメモを残しておきます。

Jenkins の Warnings Plugin で flake8 の静的解析の結果を集計するには次のように設定します。

flake8 用のパーサーを追加する

Jenkins の管理 > システム設定 ページの コンパイラの警告 セクションで flake8 用のパーサーを追加します。

項目
名前 flake8
リンク名 flake8
推移レポート名 flake8
正規表現 (.):(\d+):(\d+): (.)
マッピングスクリプト (下記参照)
import hudson.plugins.warnings.parser.Warning
import hudson.plugins.analysis.util.model.Priority

String fileName = matcher.group(1);
int lineNumber = Integer.parseInt(matcher.group(2));
String category = null;
String type = "flake8";
String message = matcher.group(4);
Priority priority = null;

if (message.startsWith('E')) {
    category = "pep8 errors";
    priority = Priority.HIGH;
} else if (message.startsWith('W')) {
    category = "pep8 warnings";
    priority = Priority.NORMAL;
} else if (message.startsWith('F')) {
    category = "PyFlakes codes";
    priority = Priority.NORMAL;
} else if (message.startsWith('C9')) {
    category = "McCabe complexity plugin mccabe";
    priority = Priority.LOW;
} else if (message.startsWith('N8')) {
    category = "Naming Conventions plugin pep8-naming";
    priority = Priority.HIGH;
} else {
    category = "Unknown Error";
    priority = Priority.HIGH;
}

return new Warning(fileName, lineNumber, category, type, message, priority);

priority を決めるところは適当に決めたので、導入時にアレンジしてください。

  • E ではじまるエラー(PEP8 Error) は HIGH
  • W ではじまるエラー(PEP8 Warning) は NORMAL
  • F ではじまるエラー (PyFlakes Error) は NORMAL
  • C9 ではじまるエラー (McCabe) は LOW
  • N8 ではじまるエラー (pep8-naming error) は HIGH
  • その他のエラー(未知)は HIGH

プロジェクトの設定で flake8 を呼び出す

シェルの実行 やその他の方法で、いい感じに flake8 コマンドを実行します。

flake8 src/ --max-complexity=12 --exclude=path/to/tests/ > flake8.log || :
cat flake8.log

flake8 でエラーが検出されても、そのまま処理を進めるように || : とかを挟んでいます。 あと、console output を見て何が起きているのか把握するため、ログを cat しています。

  • プロジェクトの設定でコンパイラ警告の集計を設定する

同じくプロジェクトの設定ページで、Warnings plugin の設定を進めます。

項目
集計するファイル flake8.log
パーサー flake8

flake8 コマンドの出力結果ファイルと、システム設定で追加したパーサーを指定します。

さらに静的解析の結果で通知を制御するために、 高度な設定で ビルドステータスの閾値 を次のように設定してみました。f:id:tk0miya:20150309163845p:plain

まとめ

flake8 の解析内容によって success と unstable、 failure を切り替えたかったので、 Jenkins を細かく設定してみました。

追記

Warnings Plugin に PR した方がいいかしら、とリポジトリを眺めていたところ、 パッケージに含まれている PEP8 パーサーは既に flake8 に対応しているようです。

上記で登録したスクリプトとの違いは priority の決め方ぐらいです。

  • E ではじまるエラー(PEP8 Error) は HIGH
  • W ではじまるエラー(PEP8 Warning) は HIGH
  • F ではじまるエラー (PyFlakes Error) は HIGH
  • R ではじまるエラー (??) は NORMAL
  • その他のエラー(未知)は LOW

なんだかぞわぞわする…