株式会社クリアコード > ククログ

ククログ


Python/CFFIを利用してC拡張を作成する (2/2)

本記事は全2回の連載の後編です。前回の記事はこちらから読めます。

前回の記事ではCFFIライブラリを使うことで、C関数のプロトタイプ宣言から Pythonの拡張モジュールを自動的に生成できることを見ました。

この後編ではCFFIライブラリの実務的な使い方を解説したいと思います。

(以下の内容は Ubuntu 16.04 / Python 3.5.1 で動作を確認しています)

1. C拡張作成の基本的な流れ

本節ではCFFIでC拡張モジュールを作成する際の基本的なフローを解説します。 前回に引き続いて、暗号ライブラリのlibsodiumを題材として具体的に手順を追っていきます。

1.1. CFFIをインストールする

まずはCFFIをインストールします。 たいていのディストリビューションでパッケージが用意されているので、 これを利用するのが最も手っ取り早いです。

$ sudo apt install python3-cffi

あわせて、C拡張のコンパイルに必要になるので、 gccとPythonのヘッダファイルもインストールしておいてください。

$ sudo apt install gcc python3-dev

なおCFFIそのものはPyPI経由でもインストール可能です。

$ pip install cffi
1.2. 対象のライブラリをインストールする

続いて、呼び出しの対象となるCライブラリをインストールします。 ここでは、共有ライブラリ本体だけではなく、ヘッダファイルも一緒にインストールします。

例えば、libsodiumの場合は次のようにインストールします:

# 次のパッケージをインストールする
# - libsodium18   ... 共有ライブラリ本体 (libsodium.so)
# - libsodium-dev ... ヘッダファイル (sodium.h)
$ sudo apt install libsodium18 libsodium-dev

他のライブラリも多くのケースで同様のパッケージングがされているので、 利用したいライブラリをapt searchコマンドで探してみてください。

1.3. CFFIにライブラリの情報を渡す

以下のようなビルド定義を作成し、builder.pyという名前で保存します。

from cffi import FFI

ffibuilder = FFI()

ffibuilder.cdef("""
    size_t crypto_stream_keybytes(void);
    size_t crypto_stream_noncebytes(void);
""")

ffibuilder.set_source("_sodium", """
    #include <sodium.h>
""", libraries=["sodium"])

if __name__ == "__main__":
    ffibuilder.compile(verbose=True)

このスクリプトはlibsodium前提の内容になってますが、 cdef()set_source()の二つのメソッドの呼び出しを書き換えることで、 他のライブラリに応用することができます。 この二つのメソッドの違いは一見すると分かりにくいのですが、 次のような住み分けがなされています。

ffi.cdef()

  • Pythonにエクスポートする関数のプロトタイプ宣言を渡します。
  • CFFIはここで定義された各関数についてPython向けの実装を自動生成します。

ffi.set_source()

  • cdefで渡した以外の、ビルドに必要なあらゆる情報を渡します。
  • それぞれの引数はdistutilsやコードジェネレータに引き継がれます。

各メソッドの詳しい使い方は2節に譲ります。

1.4. C拡張モジュールを生成する

定義したスクリプトをPythonに渡すと一連のビルド処理が走り、C拡張が生成されます。

$ python3 builder.py

カレントディレクトリにC拡張モジュール (*.so) が生成されていることが確認できたら、 実際にPythonから処理を呼び出してみましょう。

>>> from _sodium import ffi, lib
>>> lib.crypto_stream_keybytes()
32
>>> lib.crypto_stream_noncebytes()
24

これでPythonから共有ライブラリの処理を呼び出すことができるようになりました。

2. ライブラリの情報の渡し方

C拡張を生成する際にはcdef()set_source()の二つのインターフェイスを通じて、 必要な情報を渡すことになります。 各メソッドの取扱いには多少分かりづらい部分があるので、本節で要点を解説します。

2.1. cdef: 関数のプロトタイプ宣言を渡す

cdef()メソッドには、Pythonにエクスポートしたい関数のプロトタイプ宣言を渡します。 ここで渡した定義(関数名・型情報)をもとにC拡張のコードが自動的に生成されます。

# 例: libsodiumのストリーム暗号処理をエクスポートする
ffibuilder.cdef("""
  size_t crypto_stream_keybytes(void);
  size_t crypto_stream_noncebytes(void);
  int crypto_stream(unsigned char *c, unsigned long long clen,
                    const unsigned char *n, const unsigned char *k);
""")

定義を渡さなかった関数については、実装が生成されない点に注意してください。

作業時のポイント

cdef()に渡す関数名と入出力型はライブラリ側の定義と厳密にマッチする必要があります。 このため、実務的な作業としては、ライブラリ本体のヘッダファイルからプロトタイプ宣言を 一つ一つコピペしていくことになるのですが、作業にあたってはいくつかの注意点があります:

  1. この定義の中ではCの任意の文法が使えるわけではありません。

    • 例えば、#includeはサポートされていませんし、マクロも原則として使えません。
    • これはCFFI独自の処理系によって解析されることに由来する制約です。
  2. 定義の解釈にあたって、ライブラリ本体のヘッダファイルは参照されません。

    • 従って、定義が自己完結するように配慮する必要があります。
    • 例えば、関数定義にマクロが利用されている場合、手で展開する必要があります。

特殊な記法を使えば多少は融通を聞かせることもできるのですが、実際の取扱いでは、 ライブラリの定義に準拠した、単純な関数宣言のみで構成するのが最も障害が少ないです。

補足として、この中で構造体や型を定義することもできます。 本記事では取り扱わないので、これに関心のある方はcdef()のドキュメントを参照してください。

2.2. set_source: ビルドに必要な情報を渡す

set_source()メソッドにはビルドに必要なその他の情報を渡します。 名前からは非常に分かりづらいのですが、このメソッドを通じてコード生成からコンパイル までの一連のフローを制御できる設計になっています。

以下に主要な引数についてインラインで解説を加えます:

ffibuilder.set_source(
    # module_name: 生成されるC拡張モジュールの名称
    # 例えば'foo'とすると`import foo`でインポート可能になります。
    module_name="_sodium",

    # source: 自動生成時に埋め込むコード
    # この引数を通じて任意のC言語の処理を自動生成コードに埋め込むことができます。
    # (ただ現実の大半のケースではライブラリのヘッダをincludeするだけです)
    source="""#include <sodium.h>""",

    # source_extension: 生成されるソースファイルの拡張子
    # 具体的な利用例としては、C++の拡張を生成する場合に'.cpp'を指定します。
    source_extension='.c',

    # libraries: リンカに渡されるライブラリ情報
    # 以下の例ではリンカの実行オプションに`-lsodium`を追加しています。
    # この指定を省くとインポート時に未定義シンボルエラーが発生します。
    libraries=["sodium"]
)

このメソッドは実質的に「distutilsのプロキシ」という性格が強いです。 キーワード引数は原則としてdistutils.core.Extensionsにそのまま引き継がれる作りになっているので、 ビルドの細かい制御を行いたい場合は distutilsのリファレンス を参照して引数を調整してください。

3. より複雑な関数に対応する

CとPythonは基本的なセマンティクスが異なっているので、 単純にCの関数をPythonに機械的にエクスポートしただけでは、扱いづらい場合が少なくありません。

例えば、ランダムなバイト列を生成するrandombytes_buf()関数を考えてみましょう。

void randombytes_buf(void * const buf, const size_t size);

これまでの解説を用いれば、この関数をエクスポートすること自体は容易にできます。 問題は、具体的にどのようにこの関数をPythonからコールするかです。 例えば、単純にPythonのオブジェクトを引数に与えると、 Pythonオブジェクトの内部構造を上書きしてしまい、予期しない動作を引き起こします。

>>> from _sodium import ffi, lib
>>> buf = b'x' * 64
>>> lib.randombytes_buf(buf, 64)  # ???

この問題を解決するには、CFFIのffiというインターフェイスを利用する必要があります。

3.1. FFIインターフェイス

ffiインターフェイスの役割は、Python上でC言語のセマンティクスを部分的に再現することです。 提供されている主要なメソッドを以下に示します:

名称 機能
ffi.new() メモリ領域を確保する
ffi.cast() 型変換(キャスト)を行う
ffi.sizeof() データ型のサイズを取得する
ffi.memmove() メモリ領域をコピーする
ffi.string() メモリ領域をPythonのバイト列に変換する

メソッドの一覧はリファレンスマニュアルを参照してください。

具体的な利用例を以下に示します:

>>> from _sodium import ffi, lib
>>> buf = ffi.new('char[]', 64)   # メモリ領域を確保する
>>> lib.randombytes_buf(buf, 64)  # 関数に引き渡す
0
>>> ffi.string(buf, 64)           # バッファをPythonのバイト列に変換する
b'\x1d\xedw+\xf9}\x8d!\xa3...'

Cのポインタ操作と同等の処理がPythonでできるようになっている事が見て取れると思います。

3.2. メモリ管理について

確保したメモリ領域はPython上ではcdataというオブジェクトとして表現されます。

>>> buf = ffi.new('char[]', 64)
>>> print(buf)
<cdata 'char[]' owning 10 bytes>

重要なポイントとして、確保したメモリ領域は、 対応するcdataオブジェクトのライフサイクルに紐付けて自動的に管理されます。 オブジェクトがGCによって回収されるとメモリ領域も解放されるので、 C言語のように開発者の側で手動で解放する必要がなくなっています。

# Pythonオブジェクトが回収されると、確保したメモリ領域も自動的に解放される。
# 例えば、次の関数は`intp`を明示的に解放していないが、メモリリークは起きない。
del foo():
    intp = ffi.new('int *')
    return lib.somefunc(intp)
3.3. モジュール作成のヒント

モジュールを使うために毎回ffiインターフェイスを操作する必要があるのは非常に面倒です。 そのため、CFFIでC拡張モジュールを作成する時は、一緒にPythonのラッパ実装を作成しておくと、 Pythonモジュールとしての使い勝手がぐんと向上します。

一般的に使われるテクニックは、C拡張のモジュールをアンダースコア付きの名前で生成しておいて、 その上にPythonの実装をかぶせるという方式です。

_mymodule ... CFFIで生成した素のC拡張モジュール
mymodule  ... Pythonで作成したラッパモジュール

前節のコードを例にとると、次のようなラッパ実装を sodium.py という名前で保存します。

from _sodium import ffi,lib

def get_randombytes(size):
    buf = ffi.new('char[]', size)
    lib.randombytes_buf(buf, size)
    return ffi.string(buf)

これでモジュールの利用者はffiインターフェイスを意識せずに、 ライブラリの機能を利用できるようになります。

>>> from sodium import get_randombytes
>>> get_randombytes(10)
b'\x93\x13\xf9z\xaaE\xf8gb\x01'

4. まとめ

本記事では、実務面に焦点をあててCFFIライブラリの使い方を解説しました。 PythonからCの共有ライブラリを扱う場合の参考になりましたら幸いです。

タグ: Python
2018-04-05

td-agent3でGemfileベースのプラグイン管理

Fluentdのプラグイン管理はGemfileベースでやると、きちんとバージョンを管理できるのでよいです。 ドキュメントでもGemfileベースのプラグイン管理について書かれています。 しかし、記事執筆時点ではtd-agentでどのようにするのかは、書かれていませんでした。

td-agent3からsystemdに対応しているので、特に断りがない場合はsystemdについて書いています。 また、パッケージはいくつか種類がありますが主にdebパッージについて書きます。rpmの場合でも、serviceファイルなどの内容は同じなので、そのまま使える知識です。

使用するソフトウェアで特にバージョンを気にするものは以下の通りです。

  • td-agent 3.1.1
  • bundler 1.16.0
bundle install中にsudoを実行される

Fluentdは--gemfileオプションを指定すると、起動時に指定されたGemfileを使ってbundle installを実行します。 このとき、bundlerがある条件下でsudoを実行しようとしますが、td-agentを使用している場合、td-agentユーザーはsudoを使用できるようになっていないのでエラーになってしまいます。 なお、Fluentdは--gemfileオプションのみを指定すると、そのGemfileと同じディレクトリのvendor/bundleというディレクトリをBUNDLE_PATHとして使用します。

--gemfile /etc/td-agent/Gemfileのみ指定したとすると、以下のように動作します。

  1. BUNDLE_PATHは/etc/td-agent/vendor/bundle
  2. BUNDLE_PATHが存在しないので/etc/td-agentをパーミッションチェックの起点とする
  3. /etc/td-agent/build_info/* と /etc/td-agent/* がtd-agentユーザーで書き込み可能かどうかチェックする
  4. ↑で書き込み不可なファイルやディレクトリが一つでもあれば、sudoが必要と判定する

今の場合、td-agentをインストールした後、すぐにGemfileを置いてtd-agentを再起動したとすると以下のファイルやディレクトリのパーミションをチェックします。

  • /etc/td-agent
  • /etc/td-agent/td-agent.conf
  • /etc/td-agent/plugin

そのパーミッションは以下の通りで、pluginディレクトリとtd-agent.confはtd-agentユーザーから書き込みできません。

$ ls -laR /etc/td-agent/
/etc/td-agent/:
total 16
drwxr-xr-x  3 td-agent td-agent 4096 Mar  1 08:44 .
drwxr-xr-x 57 root     root     4096 Mar  5 06:14 ..
drwxr-xr-x  2 root     root     4096 Mar  1 08:43 plugin
-rw-r--r--  1 root     root     2381 Mar  1 08:44 td-agent.conf

よって、bundlerはsudoが必要だと判定してしまいます。

どのようにすればよいかは後述します。

td-agentユーザーのホームディレクトリが存在しない

これはdebパッケージ限定の問題ですが、masterでは修正済みです。 ホームディレクトリが存在しない場合、/tmp/bundler/home/vagrantのようなディレクトリをかわりに使用するため、マシンを再起動するとbundlerのキャッシュが消えます。 また、起動するたびにログにメッセージが残るのも本来着目すべきログを見つけづらくなります。

td-agent 3.1.1のメンテナスクリプトでは、以下のようにホームディレクトリ(/home/td-agent)を作成していませんでした。

adduser --group --system --no-create-home td-agent

ホームディレクトリを /var/lib/td-agent に変更して作成します。 /var/lib/td-agentにしたのはRPMに合わせるためです。また、システムユーザーは/var/lib以下にホームディレクトリを持つことが多いためです。

$ sudo usermod -d /var/lib/td-agent td-agent
$ sudo mkdir -p /var/lib/td-agent
$ sudo chown -R td-agent:td-agent /var/lib/td-agent
systemdのドロップインファイルについて

td-agentはsystemdに対応しているので、systemdの場合はtd-agentのオプションをカスタマイズするのに/etc/default/td-agent*1や/etc/sysconfig/td-agent*2は使いません。

ドロップインファイルというものを使用します。

$ sudo systemctl edit td-agent.service

を実行するとエディターが起動するので、内容を書いて保存すると/etc/systemd/system/td-agent.service.d/override.confというファイルを作成することができます。

td-agent 3.1.1 の場合は以下のようにすると、td-agentのオプションを変更することができます。

[Service]
Environment='TD_AGENT_OPTIONS=--gemfile=/etc/td-agent/Gemfile'
ExecStart=
ExecStart=/opt/td-agent/embedded/bin/fluentd --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid $TD_AGENT_OPTIONS

masterではExecStartに$TD_AGENT_OPTIONSが追加されているので以下のようにEnvironmentだけ指定すれば十分です。

[Service]
Environment='TD_AGENT_OPTIONS=--gemfile=/etc/td-agent/Gemfile'

参考: systemd - ArchWiki

設定例
td-agent 3.1.1

td-agent 3.1.1では次のようにします。

debの場合、td-agentユーザーのホームディレクトリを作成してください。

$ sudo usermod -d /var/lib/td-agent td-agent
$ sudo mkdir -p /var/lib/td-agent
$ sudo chown -R td-agent:td-agent /var/lib/td-agent

bundlerがsudoを必要としないドロップインファイルは以下の通りです。--gem-fileオプションでGemfileを指定し、--gempathオプションでtd-agentユーザーが書き込むことができるパスを指定します。

[Service]
Environment='TD_AGENT_OPTIONS=--gemfile=/etc/td-agent/Gemfile  --gempath=/var/lib/td-agent/vendor/bundle'
ExecStart=
ExecStart=/opt/td-agent/embedded/bin/fluentd --log /var/log/td-agent/td-agent.log --daemon /var/run/td-agent/td-agent.pid $TD_AGENT_OPTIONS
td-agent の次のリリースでは

bundlerがsudoを必要としないドロップインファイルは以下の通りです。

td-agentユーザーのホームディレクトリはパッケージによって作成済みなので、ドロップインファイルで環境変数を指定すればよいだけです。 環境変数の内容はtd-agent 3.1.1と同じです。

[Service]
Environment='TD_AGENT_OPTIONS=--gemfile=/etc/td-agent/Gemfile  --gempath=/var/lib/td-agent/vendor/bundle'
トレードオフ

Gemfileでプラグインを管理する場合、td-agent起動時にbundle installを実行するため、td-agentにバンドルされているgemであってもgemパッケージをダウンロードします。 Gemfileでプラグインのバージョンを固定できるというメリットとのトレードオフです。

まとめ

td-agent3でGemfileを使ってFluentdのプラグインを管理する方法を紹介しました。 きちんと設定すればtd-agentユーザーにsudoできる権限を与えずにGemfileを使ってFluentdのプラグインを管理することができます。

*1 debの場合

*2 rpmの場合

タグ: Fluentd
2018-04-06

fluent-plugin-elasticsearch v2.8.6/v1.13.4からのX-Pack向けの認証情報の設定方法の注意点

はじめに

fluent-plugin-elasticsearchはよく使われているプラグインの一つです。 このプラグインをメンテナンスするためには、Fluentdの知識だけでなく、Elasticsearchのエコシステムも知っておく必要があります。 Elasticsearch 5.xからnginxのリバースプロキシの認証だけでなく、Elastic Stack自体で認証をコントロールする仕組みがプラグインとして提供されています。

X-Packには、旧Shieldの機能であるBasic認証などの認証の仕組みがあります。 この認証の仕組みへの対応はElasticsearchのRubyクライアントであるelasticsearch-rubyも提供しています。

認証情報の設定方法の注意点

fluent-plugin-elasticsearchのこれまでの挙動はhost毎に認証情報を紐付ける形で設定していました。 v2.8.6/v1.13.4以降ではクラスタ毎に設定する設定ファイルの書き方が推奨されるようになります。*1 そのため、以下の設定方法は推奨されなくなることに注意してください。 X-Packを設定せずに運用している場合は設定変更の必要はありません。

hosts https://username:password@host-custom.com:443

という設定は推奨されなくなり、

hosts https://host-custom.com:443
user username
password password

という書き方が推奨されます。 前者の書き方ではElasticsearchのクラスタの情報の再読み込み時に認証情報が抜け落ちてしまうということが起きるためです。 後者の書き方では、クラスタの情報の再読み込み時に認証情報が抜け落ちてしまうということを防ぐ対策が動作するようになります。

まとめ

fluent-plugin-elasticsearch v2.8.6/v1.13.4 では、再読み込み時に接続先のElasticsearchの認証情報を保持することが可能となる対策が入っています。 X-PackでElasticsearchクラスタのBasic認証を有効化していて、リロード時に接続情報が失われて困っている運用者の方は是非バージョンアップしてみてください。

*1 これまで困っている方々が結構いたようです。例として https://github.com/uken/fluent-plugin-elasticsearch/issues/307 https://github.com/uken/fluent-plugin-elasticsearch/issues/257 などが報告されていました。

タグ: Fluentd
2018-04-10

Firefoxの法人向けカスタマイズのレシピ集の使い方

はじめに

クリアコードでは、Webブラウザ「Mozilla Firefox」のサポートサービスを行っています。
また、サポート等を通じて得られた知見はククログのMozillaカテゴリにまとめています。

今回は、Mozilla Firefoxの法人向けカスタマイズを通じて得られた知見をまとめたレシピ集である、「firefox-support-common」とその使い方について紹介します。

firefox-support-commonとは

法人向けで必要とされるカスタマイズの項目とその設定をまとめたファイルから構成されています。
GitHubのfirefox-support-commonリポジトリにて成果物を公開しています。

現在は次期ESRであるFirefox 60ESR向けに内容の整備を進めています。

このファイルには複数のシートが存在します。

  • customization-items
  • menuitem-shortcut-disable-items
  • misc-ui-disable-items
  • verify-targets

それぞれどのような内容か説明します。

customization-items

よくカスタマイズで用いられる設定項目をカテゴリー別にまとめた設定シートです。
以下のような項目から構成されています。

  • カテゴリー
  • カスタマイズ項目(目的)
  • サイト別設定の可否
  • 選択肢
  • 設定方法
  • 設定内容(Firefox 60)

必要に応じてカスタマイズ内容を選択し、設定方法にしたがって設置内容を反映すればお望みのカスタマイズを実現できます。

例えば、「Firefoxの自動更新を禁止したい」という場合には、検証対象IDの「Update-1-3」を参照します。

  • カテゴリー:自動更新
  • カスタマイズ項目:Firefoxの自動更新の可否
  • サイト別設定の可否:-
  • 選択肢:禁止する
  • 設定方法:MCDで設定を反映する

これに該当する設定内容(Firefox 60)は以下の通りです。

lockPref("app.update.enabled", false);
lockPref("app.update.auto", false);
lockPref("app.update.mode", 0);
// Firefox自体自体の自動更新のURL
lockPref("app.update.url", "");
lockPref("app.update.url.details", "");
lockPref("app.update.url.manual", "");

MCD( autoconfig.cfg )に上記設定を反映するとよいことがわかります。インストーラーへ反映するやりかたについては別記事がありますのでそちらを参照してください。

menuitem-shortcut-disable-items

メニューやショートカットのカスタマイズに関する設定シートです。
ただし、Firefox 60ESRでは従来アドオンで実現していた項目が多かったため、Policy Engineで設定可能なものを除いては大半が廃止となりました。

misc-ui-disable-items

その他非表示にしたいメニューのカスタマイズに関する設定シートです。
ただし、Firefox 60ESRでは従来アドオンで実現していた項目であったため、すべて廃止となりました。

verify-targets

選択したカスタマイズ内容をリストアップするためのシートです。
customization-itemsシートのA列で選択した項目が列挙されます。*1

まとめ

今回は、Mozilla Firefoxの法人向けカスタマイズを通じて得られた知見をまとめたレシピ集である、「firefox-support-common」とその使い方について紹介しました。

カスタマイズが実際にきちんと反映され、意図通りになっているのかを確認するためには、検証手順に則った確認が必要です。
firefox-support-commonの設定シートを利用して検証手順書を作成することもできるのですが、これについてはまた別の機会に紹介します。

*1 このシートは検証手順書を作成するために活用することになります。

2018-04-11

FirefoxのMCD設定ファイルの記述ミスを検出するには

はじめに

以前、Firefoxの法人向けカスタマイズのレシピ集の使い方として、レシピ集を使ったFirefoxのカスタマイズ方法を紹介しました。 MCDの設定ファイル(autoconfig.cfg)に設定値を反映していくのですが、記述に誤りがあっても問題のある箇所を特定しにくいことがあります。 今回は、そんなときに便利なチェック方法を紹介します。

記述にミスがあるとどうなるのか

autoconfig.cfgに記述ミスがある状態でFirefoxを起動すると次のようなメッセージが表示され、正常に起動することなく終了します。

設定ファイルが読み込まれない場合

記述ミスに気付きにくい事例

ありがちなのが、二重引用符のミスです。たとえば、以下の文字を使っているとエラーになりますが、ぱっと見ただけでは気付きにくいかもしれません。

  • U+201C “ LEFT DOUBLE QUOTATION MARK
  • U+201D ” RIGHT DOUBLE QUOTATION MARK

例えば、以下のようなautoconfig.cfgの問題を検出してみることにしましょう。

//

// unexpected character (LEFT DOUBLE QUOTATION MARK and RIGHT DOUBLE QUOTATION MARK)
lockPref(app.unexpected.character, true);

// expected double quote
lockPref("app.valid.doublequote", true);

フォントによっては区別しやすいかもしれませんが、あまりやりやすくはありません。

eslintによる記述ミスの検出

目視ではなく、ツールにより検出することにします。 今回はeslintを使ってみます。次のようにすると必要なモジュールをインストールできます。

% npm install -g eslint eslint-plugin-mozilla espree estraverse eslint-scope globals eslint-plugin-no-unsanitized

インストールできたら、設定ファイルを用意します。おすすめの設定が公開されているので、それをベースにするとよいでしょう。

Windows向けの設定ファイルのチェックをしたい場合には、改行コードの指定をデフォルトから変更しておきます。 また、MCDの設定向けに lockPrefpref の設定を追加するには次のような差分を適用します。

diff -u recommended.js .eslintrc.js
--- recommended.js       2018-04-13 15:13:25.183202023 +0900
+++ .eslintrc.js        2018-04-11 17:13:31.099435429 +0900
@@ -61,7 +61,10 @@
     // Specific to Firefox
     // eslint-disable-next-line max-len
     // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/uneval
-    "uneval": false
+    "uneval": false,
+    // for MCD
+    "lockPref": true,
+    "pref": true,
   },
 
   "overrides": [{
@@ -159,7 +162,7 @@
     "keyword-spacing": "error",
 
     // Unix linebreaks
-    "linebreak-style": ["error", "unix"],
+    "linebreak-style": ["error", "windows"],
 
     // Don't enforce the maximum depth that blocks can be nested. The complexity
     // rule is a better rule to check this.

上記の変更を含めた .eslintrc.js の内容は以下のとおりです。

"use strict";

module.exports = {
  "env": {
    "browser": true,
    "es6": true
  },

  "globals": {
    "AddonManagerPermissions": false,
    "BroadcastChannel": false,
    "BrowserFeedWriter": false,
    "CSSPrimitiveValue": false,
    "CSSValueList": false,
    "Cc": false,
    "CheckerboardReportService": false,
    // Specific to Firefox (Chrome code only).
    "ChromeUtils": false,
    "ChromeWorker": false,
    "Ci": false,
    "Components": false,
    "Cr": false,
    "Cu": false,
    "DOMRequest": false,
    "Debugger": false,
    "DedicatedWorkerGlobalScope": false,
    "DominatorTree": false,
    "HeapSnapshot": false,
    "IDBFileRequest": false,
    "IDBLocaleAwareKeyRange": false,
    "IDBMutableFile": false,
    "ImageDocument": false,
    "InstallTrigger": false,
    // Specific to Firefox
    // eslint-disable-next-line max-len
    // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/InternalError
    "InternalError": true,
    "KeyEvent": false,
    "MatchGlob": false,
    "MatchPattern": false,
    "MatchPatternSet": false,
    "MenuBoxObject": false,
    // Specific to Firefox (Chrome code only).
    "SharedArrayBuffer": false,
    "SimpleGestureEvent": false,
    // Note: StopIteration will likely be removed as part of removing legacy
    // generators, see bug 968038.
    "StopIteration": false,
    "StructuredCloneHolder": false,
    "WebAssembly": false,
    "WebExtensionContentScript": false,
    "WebExtensionPolicy": false,
    "WebrtcGlobalInformation": false,
    // Non-standard, specific to Firefox.
    "XULElement": false,
    "console": true,
    "dump": true,
    "openDialog": false,
    "saveStack": false,
    "sizeToContent": false,
    // Specific to Firefox
    // eslint-disable-next-line max-len
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/uneval
    "uneval": false,
    // for MCD
    "lockPref": true,
    "pref": true,
  },

  "overrides": [{
    // Turn off use-services for xml files. XBL bindings are going away, and
    // working out the valid globals for those is difficult.
    "files": "**/*.xml",
    "rules": {
      "mozilla/use-services": "off"
    }
  }, {
    // Turn off browser env for all *.jsm files, and turn on the jsm environment.
    "env": {
      "browser": false,
      "mozilla/jsm": true
    },
    "files": "**/*.jsm",
    "rules": {
      "mozilla/mark-exported-symbols-as-used": "error",
      "no-unused-vars": ["error", {
        "args": "none",
        "vars": "all"
      }]
    }
  }],

  "parserOptions": {
    "ecmaVersion": 9
  },

  // When adding items to this file please check for effects on sub-directories.
  "plugins": [
    "mozilla",
    "no-unsanitized"
  ],

  // When adding items to this file please check for effects on all of toolkit
  // and browser
  "rules": {
    // Require spacing around =>
    "arrow-spacing": "error",

    // Braces only needed for multi-line arrow function blocks
    // "arrow-body-style": ["error", "as-needed"]

    // Always require spacing around a single line block
    "block-spacing": "error",

    // No newline before open brace for a block
    "brace-style": ["error", "1tbs", { "allowSingleLine": true }],

    // No space before always a space after a comma
    "comma-spacing": ["error", {"after": true, "before": false}],

    // Commas at the end of the line not the start
    "comma-style": "error",

    // Warn about cyclomatic complexity in functions.
    // XXX Get this down to 20?
    "complexity": ["error", 34],

    // Don't require spaces around computed properties
    "computed-property-spacing": ["error", "never"],

    // Functions must always return something or nothing
    "consistent-return": "error",

    // Require braces around blocks that start a new line
    // Note that this rule is likely to be overridden on a per-directory basis
    // very frequently.
    // "curly": ["error", "multi-line"],

    // Encourage the use of dot notation whenever possible.
    "dot-notation": "error",

    // Always require a trailing EOL
    "eol-last": "error",

    // No spaces between function name and parentheses
    "func-call-spacing": "error",

    // Require function* name()
    "generator-star-spacing": ["error", {"after": true, "before": false}],

    // Two space indent
    // "indent": ["error", 2, { "SwitchCase": 1 }],

    // Space after colon not before in property declarations
    "key-spacing": ["error", {
      "afterColon": true,
      "beforeColon": false,
      "mode": "minimum"
    }],

    // Require spaces before and after keywords
    "keyword-spacing": "error",

    // Unix linebreaks
    "linebreak-style": ["error", "windows"],

    // Don't enforce the maximum depth that blocks can be nested. The complexity
    // rule is a better rule to check this.
    "max-depth": "off",

    // Maximum depth callbacks can be nested.
    "max-nested-callbacks": ["error", 10],

    "mozilla/avoid-removeChild": "error",
    "mozilla/import-browser-window-globals": "error",
    "mozilla/import-globals": "error",
    "mozilla/no-compare-against-boolean-literals": "error",
    "mozilla/no-define-cc-etc": "error",
    "mozilla/no-import-into-var-and-global": "error",
    "mozilla/no-useless-parameters": "error",
    "mozilla/no-useless-removeEventListener": "error",
    "mozilla/use-cc-etc": "error",
    "mozilla/use-chromeutils-import": "error",
    "mozilla/use-default-preference-values": "error",
    "mozilla/use-includes-instead-of-indexOf": "error",
    "mozilla/use-ownerGlobal": "error",
    "mozilla/use-services": "error",

    // Always require parenthesis for new calls
    // "new-parens": "error",

    // Use [] instead of Array()
    "no-array-constructor": "error",

    // Disallow use of arguments.caller or arguments.callee.
    "no-caller": "error",

    // Disallow modifying variables of class declarations.
    "no-class-assign": "error",

    // Disallow assignment operators in conditional statements
    "no-cond-assign": "error",

    // Disallow modifying variables that are declared using const.
    "no-const-assign": "error",

    // Disallow control characters in regular expressions.
    "no-control-regex": "error",

    // Disallow the use of debugger
    "no-debugger": "error",

    // Disallow deleting variables
    "no-delete-var": "error",

    // No duplicate arguments in function declarations
    "no-dupe-args": "error",

    // Disallow duplicate class members.
    "no-dupe-class-members": "error",

    // No duplicate keys in object declarations
    "no-dupe-keys": "error",

    // No duplicate cases in switch statements
    "no-duplicate-case": "error",

    // If an if block ends with a return no need for an else block
    "no-else-return": "error",

    // No empty statements
    "no-empty": ["error", {"allowEmptyCatch": true}],

    // No empty character classes in regex
    "no-empty-character-class": "error",

    // Disallow empty destructuring
    "no-empty-pattern": "error",

    // Disallow eval and setInteral/setTimeout with strings
    "no-eval": "error",

    // No assigning to exception variable
    "no-ex-assign": "error",

    // Disallow unnecessary calls to .bind()
    "no-extra-bind": "error",

    // No using !! where casting to boolean is already happening
    "no-extra-boolean-cast": "error",

    // No double semicolon
    "no-extra-semi": "error",

    // No overwriting defined functions
    "no-func-assign": "error",

    // Disallow eval and setInteral/setTimeout with strings
    "no-implied-eval": "error",

    // No invalid regular expressions
    "no-invalid-regexp": "error",

    // No odd whitespace characters
    "no-irregular-whitespace": "error",

    // Disallow the use of the __iterator__ property
    "no-iterator": "error",

     // No labels
    "no-labels": "error",

    // Disallow unnecessary nested blocks
    "no-lone-blocks": "error",

    // No single if block inside an else block
    "no-lonely-if": "error",

    // no-tabs disallows tabs completely.
    // "no-mixed-spaces-and-tabs": "error",

    // No unnecessary spacing
    "no-multi-spaces": ["error", { exceptions: {
      "ArrayExpression": true,
      "AssignmentExpression": true,
      "ObjectExpression": true,
      "VariableDeclarator": true
    } }],

    // No reassigning native JS objects
    "no-native-reassign": "error",

    // Nested ternary statements are confusing
    "no-nested-ternary": "error",

    // Use {} instead of new Object()
    "no-new-object": "error",

    // Dissallow use of new wrappers
    "no-new-wrappers": "error",

    // No Math() or JSON()
    "no-obj-calls": "error",

    // No octal literals
    "no-octal": "error",

    // No redeclaring variables
    "no-redeclare": "error",

    // Disallow multiple spaces in regular expressions
    "no-regex-spaces": "error",

    // Disallows unnecessary `return await ...`.
    "no-return-await": "error",

    // Disallow assignments where both sides are exactly the same
    "no-self-assign": "error",

    // No unnecessary comparisons
    "no-self-compare": "error",

    // No declaring variables from an outer scope
    // "no-shadow": "error",

    // No declaring variables that hide things like arguments
    "no-shadow-restricted-names": "error",

    // Disallow sparse arrays
    "no-sparse-arrays": "error",

    // Disallow tabs.
    "no-tabs": "error",

    // No trailing whitespace
    "no-trailing-spaces": "error",

    // No using undeclared variables
    "no-undef": "error",

    // Error on newline where a semicolon is needed
    "no-unexpected-multiline": "error",

    // Disallow the use of Boolean literals in conditional expressions.
    "no-unneeded-ternary": "error",

    // No unreachable statements
    "no-unreachable": "error",

    // Disallow control flow statements in finally blocks
    "no-unsafe-finally": "error",

    // No (!foo in bar) or (!object instanceof Class)
    "no-unsafe-negation": "error",

    // No unsanitized use of innerHTML=, document.write() etc.
    // cf. https://github.com/mozilla/eslint-plugin-no-unsanitized#rule-details
    "no-unsanitized/method": "error",
    "no-unsanitized/property": "error",

    // No declaring variables that are never used
    "no-unused-vars": ["error", {
      "args": "none",
      "vars": "local"
    }],

    // No using variables before defined
    // "no-use-before-define": ["error", "nofunc"],

    // Disallow unnecessary .call() and .apply()
    "no-useless-call": "error",

    // Don't concatenate string literals together (unless they span multiple
    // lines)
    "no-useless-concat": "error",

    // Disallow redundant return statements
    "no-useless-return": "error",

    // Disallow whitespace before properties.
    "no-whitespace-before-property": "error",

    // No using with
    "no-with": "error",

    // Require object-literal shorthand with ES6 method syntax
    "object-shorthand": ["error", "always", { "avoidQuotes": true }],

    // Require double-quotes everywhere, except where quotes are escaped
    // or template literals are used.
    "quotes": ["error", "double", {
      "allowTemplateLiterals": true,
      "avoidEscape": true
    }],

    // No spacing inside rest or spread expressions
    "rest-spread-spacing": "error",

    // Always require semicolon at end of statement
    "semi": ["error", "always"],

    // Require space before blocks
    "space-before-blocks": "error",

    // Never use spaces before function parentheses
    "space-before-function-paren": ["error", {
      "anonymous": "never",
      "asyncArrow": "always",
      "named": "never"
    }],

    // No space padding in parentheses
    // "space-in-parens": ["error", "never"],

    // Require spaces around operators
    "space-infix-ops": ["error", { "int32Hint": true }],

    // ++ and -- should not need spacing
    "space-unary-ops": ["error", {
      "nonwords": false,
      "overrides": {
        "typeof": false // We tend to use typeof as a function call
      },
      "words": true
    }],

    // Requires or disallows a whitespace (space or tab) beginning a comment
    "spaced-comment": "error",

    // No comparisons to NaN
    "use-isnan": "error",

    // Only check typeof against valid results
    "valid-typeof": "error"
  }
};

eslintで記述ミスを検出する

準備ができたので、実際に設定ファイルをチェックしてみましょう。次のようなコマンドでチェックします。

% eslint -c .eslintrc.js autoconfig.cfg 

autoconfig.cfg
  4:10  error  Parsing error: Unexpected character '“'

✖ 1 problem (1 error, 0 warnings)

4行目に '“' が含まれているのが問題であることが検出できました。

まとめ

今回は、eslintを使ってMCDの設定ファイルの記述ミスを検出する方法を紹介しました。 設定ファイルが正常に読み込まれずFirefoxを起動できない場合の対策の参考にしてください。

2018-04-13

Firefoxでセカンドサーチを使って効率よく検索するには

はじめに

Firefox/Thunderbirdの挙動を調べるために、DXRを利用してソースコードレベルで調査をすることがあります。 検索フォームに対してキーワードを変更して検索し、実際の挙動の裏付けをソースコードを確認して行うというのが典型的な利用例です。 その場合には、最新版のソースコードとESR版のソースコードを切り替えて検索したりというのが頻繁にあります。 今回は、そんなときに便利なセカンドサーチを使った検索方法を紹介します。

セカンドサーチとは

スマートキーワードをFirefoxからより使いやすくしたアドオンです。スマートキーワードがどういうものかについては、アドレスバーからニコニコ動画や Wikipedia などのサイト内を検索するにはを参照するとよいでしょう。

スマートキーワードを使う場合アドレスバーにスマートキーワードそのものを入力する必要がありますが、セカンドサーチを使う場合には専用の検索窓を使うのでその手間がありません。

セカンドサーチをインストールするには

Add-ons for Firefoxの「セカンドサーチ」のページからアドオンをインストールできます。

セカンドサーチ

スマートキーワードを設定する

セカンドサーチから使えるように、スマートキーワードを設定します。

例として、バージョンの異なるFirefoxのソースコードをセカンドサーチを使って検索できるようにします。

  • Firefoxの最新版(central)のソースコードを検索したい
  • Firefox 52ESRのソースコードを検索したい

上記を行えるようにするために、スマートキーワードを2つ設定してみましょう。

Firefoxの最新版(central)のソースコードを検索できるようにするためには、 https://dxr.mozilla.org/mozilla-central/source/ を開き、検索フォームで右クリックして「この検索にキーワードを設定」をクリックします。

この検索にキーワードを設定

スマートキーワードcentral

項目
名前 dxr: fx central
キーワード fxcentral

保存をクリックしてスマートキーワードを保存します。

Firefox 52ESRのソースコードを検索できるようにするためには、 https://dxr.mozilla.org/mozilla-esr52/source/ を開き、検索フォームで右クリックして「この検索にキーワードを設定」をクリックします。

スマートキーワードfxesr52

項目
名前 dxr: fx esr52
キーワード fxesr52

保存をクリックしてスマートキーワードを保存します。

セカンドサーチを使って検索する

スマートキーワードの設定ができたので、実際に検索してみましょう。

検索するにはツールバー上の虫眼鏡に星が付いたアイコンをクリックするか、Ctrl+Shift+Lを入力します。するとセカンドサーチの検索窓と、その下にスマートキーワードが登録済みのブックマーク名(=検索エンジン)表示されます。 検索したいキーワード「pocket」を入力してから、検索エンジンを候補から↑↓キーまたはマウスで選択します。

pocketで検索

すると、選択した検索エンジンで検索した結果が新しいタブで表示されます。

pocketの検索結果

まとめ

今回は、Firefoxでセカンドサーチを使って効率よく検索する方法を紹介しました。 特定の検索フォームで検索することが頻繁にある場合には、セカンドサーチを使って検索するのがおすすめです。

2018-04-17

Vimで単語を囲む方法をいくつか

横山です。

社内から「Vimで単語をクォーテーション("..."など)で囲むのによい方法はないか」という質問をもらったので、調べたことをまとめました。

主に4つくらいのやり方がありそうだったので、順番に紹介します。

  • 普通に入力
  • ciw
  • 置換
  • プラグイン

普通に入力

普通にiでインサートモードに切り替えて入力する方法です。

ポイントは、単語単位で移動することと、ドットコマンドを活用することです。

  1. bで単語の先頭に移動
  2. "を入力
  3. eで単語の末尾に移動
  4. lで右側に移動
  5. .(ドット)を入力(直前の編集操作が繰り返されて、"が入力される)

以下は、phw/peekwavexx/screenkey(どちらもAPTでインストール可能)を使って取得したGIFアニメです。

normal

ciw

cから始まるコマンドを使うことで、文字列を削除(カット)しつつインサートモードに切り替えることができます。

削除対象範囲は以下のように柔軟に指定できます(以下は一例です)。

  • cw: カーソル位置から単語の末尾まで
  • cb: カーソル位置から単語の先頭まで
  • ciw: カーソル位置の単語の先頭から末尾まで
  • c0: カーソル位置から行頭まで
  • c$: カーソル位置から行末まで
  • cG: カーソル位置からファイル末尾まで

削除した部分はレジスタに保持されているので、ペーストすることができます。よって、囲みたい文字を続けて入力した後で、その間にペーストするという方法が使えます。囲みたい文字は同じ文字であることが多いので、続けて入力できるとだいぶ楽になります。

  1. ciwでカーソル位置の単語の先頭から末尾までをカットしてインサートモードに切り替え
  2. "を2つ(開始と終了)入力
  3. Escでノーマルモードに切り替え
  4. Pでカーソル位置の左側にカットした単語をペースト

ci

よく使う括弧の種類や対象範囲が決まっている場合、以下のようにマッピングすると便利です。

nnoremap <Leader>s" ciw""<Esc>P
nnoremap <Leader>s' ciw''<Esc>P
nnoremap <Leader>s` ciw``<Esc>P
nnoremap <Leader>s( ciw()<Esc>P
nnoremap <Leader>s{ ciw{}<Esc>P
nnoremap <Leader>s[ ciw[]<Esc>P

参考: Vimの生産性を高める12の方法 | POSTD

置換

同じ文字で囲みたい単語が複数ある場合は、置換を使うと時間短縮になって、漏れも防げます。

置換には正規表現が使えますが、Vimの正規表現はエスケープ対象の文字が少し特殊なので注意が必要です。 例えば、+()をメタ文字として扱うときはエスケープが必要です。

以下の例は、カーソルがある行の単語([0-9A-Za-z_]+)をすべて"..."で囲みます。

例: :s/\(\w\+\)/"\1"/g

正規表現のオプションにcを付けると、逐一置換するかどうかの確認をすることができます。ファイル全体を対象とする場合などは、意図しない置換を防げて安全かもしれません。

例: :%s/\(\w\+\)/"\1"/gc

また、いちいち入力するのは面倒なので、コマンド履歴を活用するのがおすすめです。 同じ置換を繰り返すのであれば、:でコマンドモードに切り替えた後に<C-p>で履歴をたどれます。 少し違うことをしたいときなどは、コマンドモードに切り替えた後に<C-f>でコマンドウィンドウを開けば、履歴を修正してから実行することもできます。

以下の例は、対象を丸括弧*1内の文字列のみにしてから置換を実行しています。

例: :%s/(\(\w\+\))/("\1")/gc

sub

プラグイン

プラグインを使って実現することもできます。有名なのはtpope/vim-surroundですが、後発のrhysd/vim-operator-surroundも、ドットコマンドで繰り返せるなどの利点があっておすすめです。

参考: 犬製 Vim プラグイン紹介3本立て - はやくプログラムになりたい

設定例:

  1. インストール
  2. <Leader>sなどにマッピング(sはsurroundのs)
    • 例: map <Leader>s <Plug>(operator-surround-append)
    • <Leader>sw"でカーソル位置から単語の末尾まで囲める
    • b<Leader>sw"でカーソル位置の単語の先頭から末尾まで囲める
    • <Leader>s$"でカーソル位置から行末まで囲める
    • (などは自動で閉じる文字を判別してくれる

まとめとおしらせ

ClearCode.vimの一環として、Vimについて調べたことをまとめました。ClearCode.vimはGitterとGitHubでやっているので、質問などがある方は社内外を問わずお気軽にコメントください。リアルタイムで質問したい方は、毎週水曜日の18:30-19:00はほぼ確実に見ているので、この時間にGitterまでお越しください。(反応が遅れてもよければ、いつ書き込んでもらっても大丈夫です。)

*1 昔は、中括弧({})や大括弧([])と特に区別したいときは小括弧と言っていた記憶があるのですが、最近はそれぞれ丸括弧、波括弧、角括弧と言うらしいです。

タグ: Vim
2018-04-23

OSDNのファイルストレージ機能を使ってAPTリポジトリやYUMリポジトリを作成する方法

OSDNのファイルリリース機能をAPI経由で使うにはという記事では、APTリポジトリやYUMリポジトリをOSDNでは作成できないと紹介していましたが、少し前にニュース: rsync, scp, sftpで操作する新ファイルストレージ機能を追加 - OSDN運営・管理 - OSDNで、rsyncコマンドでファイルのアップロードができるようになったことを知ったので試してみました。

FileStorage_Guide - OSDN ヘルプ - OSDNドキュメント管理 - OSDNにも使い方が書かれているので参考にしてください。

sourceforge.netに既に構築されているAPTリポジトリやYUMリポジトリをosdn.netにも作ってみました。 既に構築されているリポジトリを対象にするので、APTリポジトリやYUMリポジトリの作成方法については割愛します。

まず、sourceforge.net側からAPTリポジトリとYUMリポジトリを丸ごとダウンロードします。 例えば、以下のようなコマンドを実行します。詳細はsourceforge.netのドキュメント等を参照してください。

$ rsync -avz --progress --delete username@frs.sourceforge.net:/home/frs/project/c/cu/cutter/debian/ ./debian/
$ rsync -avz --progress --delete username@frs.sourceforge.net:/home/frs/project/c/cu/cutter/centos/ ./centos/

Cutterの場合は、debian以下にDebian用のAPTリポジトリ、centos以下にCentOS用のYUMリポジトリを作成していました。

あとは、そのままOSDNにアップロードすればOSDNの豊富なグローバルミラーネットワークでファイルを配布することができます。

Cutterの場合は以下のようにしました。初回の作業だったので手でコマンドを実行しました。

$ rsync -avz --progress ./debian/ username@storage.osdn.net:/storage/groups/c/cu/cutter/debian/
$ rsync -avz --progress ./centos/ username@storage.osdn.net:/storage/groups/c/cu/cutter/centos/

この場合、APT lineは以下の通りです。

deb https://osdn.net/projects/cutter/storage/debian/ stretch main
deb-src https://osdn.net/projects/cutter/storage/debian/ stretch main

また、ウェブブラウザから https://osdn.net/projects/cutter/storage/ にアクセスして確認することもできます。

このようにして作成したAPTリポジトリYUMリポジトリ*1からパッケージをインストールできることを確認しました。

なお、自動化したスクリプトやウェブサイトの移行についてはhttps://github.com/clear-code/cutter/pull/37で進めています。

*1 YUMリポジトリのセットアップはcutter-releaseというパッケージ経由で行うのが楽なので、CentOSユーザーはCutterの次のバージョンがリリースされるのを待つのが良いでしょう

2018-04-24

«前月 最新記事 翌月»
タグ:
年・日ごとに見る
2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|