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

ククログ

«前月 最新
タグ:

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 /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 /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-13
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

«前月 最新
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|
タグ: