kkAyatakaのメモ帳。

誰かの役に立つかもしれない備忘録。

Sphinx拡張を作ったときに参考にしたモノ

sphinxcontrib-xlsxtableを作る際に参考にしたところ。 最終的にはdocutilsのソースコードまで見ました。

あと、開発時は見てないけど、以下がもっそい参考になりそう。

いろいろとWebを漁りながらやっていましたが、どうも非公式のドキュメントを結構見てたみたいです。 リファレンスに全然書いてねぇ...とか思ってたけど、そもそも公式じゃなかった。 もうちょっとdocutilsの公式リファレンスを漁ったほうがよかった。

sphinxcontrib-xlsxtableはいうなれば、CSVファイルがExcelになったようなモノなので、 CSV Table DirectiveとTable Directiveを参考にしました。

特にソースを追いかけたのはTable DirectiveのRSTTableクラス。 作成したクラスの継承元でもありますが、そもそもTable Directiveがかなりシンプルな構文なので、 参考にしやすいです。

# https://sourceforge.net/p/docutils/code/HEAD/tree/trunk/docutils/docutils/parsers/rst/directives/tables.py
# RSTTable classのrunメソッド
def run(self):
    ...

    # Table Captionの取得とかはそのまま流用
    title, messages = self.make_title()
    # この辺を順番にいじったり、デバッグしながら動作を確認
    node = nodes.Element()
    self.state.nested_parse(self.content, self.content_offset, node)

    ...

    table_node = node[0]
    table_node['classes'] += self.options.get('class', [])
    self.set_table_width(table_node)
    if 'align' in self.options:
        table_node['align'] = self.options.get('align')
    tgroup = table_node[0]

    ...

    self.add_name(table_node)
    if title:
        table_node.insert(0, title)
    return [table_node] + messages

見返すと結構見るべきドキュメントを取りこぼしてる感じだけど、 当初は理解の仕方を理解してなかったところがあるので、理解するのは無理だったかも。 一から理解して~というよりも、とりあえず動かしてみたいってのも強かった。

XcodeとgcovrでC++コードのカバレッジを計測する

Xcode単体では可視化できないブランチカバレッジを取り扱える。 Xcodeへのシームレスな統合は失われるが、設定・実行は簡単で、手順はシンプルになる。

f:id:kkAyataka:20200617210900j:plain

環境

手順

  1. Xcodeカバレッジファイルの出力設定を有効にする
  2. テストを実行する
  3. gcoverをインストールして実行する

Xcodeカバレッジファイルの出力設定を有効にする

とりあえずはXcodeの出力ロケーションを相対にしておく。 カバレッジファイルがDerivedData (の結構深いところ) にできるので、相対的に参照できるパスが何かと都合が良い。

f:id:kkAyataka:20200617211038j:plain

2つのビルド設定を有効にする。

  • Instrument Program Flow (GCC_INSTRUMENT_PROGRAM_FLOW_ARCS)
  • Generate Legacy Test Coverage Files (GCC_GENERATE_TEST_COVERAGE_FILES)

Instrument Program Flow (GCC_INSTRUMENT_PROGRAM_FLOW_ARCS)

gcc-fprofile-arcs。flowとかarcsで検索すると出てくる。

f:id:kkAyataka:20200617211210j:plain

Generate Legacy Test Coverage Files (GCC_GENERATE_TEST_COVERAGE_FILES)

gcc-ftest-coverage。coverageで検索すると出てくる。 Xcode 11だとLegacy扱い。標準のはclangの設定。

f:id:kkAyataka:20200617211414j:plain

テストを実行する

unit_testに設定したので、単純にunit_testを実行する。 失敗するようならクリーンにするとかDerivedDataを消すとかすれば解消されると思われ。

gcovrをインストールして実行する

Pythonのツールなので、pipでインストール。

% pip install gcovr

試した時はプロジェクト以下に仮想環境を作ったけど、流石にグローバルインストールでいい気がしてる。

gcovrを適当に呼び出すだけで検出してくれる (この時DerivedDataにできるカバレッジファイルを参照するのに、相対だと都合が良い)。 ただ、不必要な (例えばテストコード自体の) カバレッジまで出力されるので、 適当にフィルタされるようにオプションを指定して実行する。

% gcovr -f '.*plusaes.hpp' -r .
------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
include/plusaes/plusaes.hpp                  372     365    98%   212,222,407,432,451,558,712
------------------------------------------------------------------------------
TOTAL                                        372     365    98%
------------------------------------------------------------------------------

HTML形式にも出力できる。結果を見て何か作業する時はこっち。ブランチカバレッジの数値も自動で出る。

% gcovr -f '.*plusaes.hpp' -r . --html --html-details -o cov/coverage.html

f:id:kkAyataka:20200617210900j:plain

--html-detailsをつけるとソースコードの詳細をビジュアルで確認できる。

f:id:kkAyataka:20200617210758j:plain

XcodeでC++のカバレッジを計測する

厳密にはラインカバレッジらしい。また、ブランチカバレッジは取れない。

f:id:kkAyataka:20200614170723j:plain

環境

ヘッダーオンリーライブラリとして開発したファイルの単体テストカバレッジを測ります。

測定方法

  1. Test TargetをObjective-Cで作る
  2. テストコードを作成する
  3. カバレッジ計測オプションを有効にする
  4. hppファイルをコンパイル対象にする
  5. 実行して結果を見る

Test TargetをObjective-Cで作る

Test Navigatorから新しいTest Targetを作成する。

f:id:kkAyataka:20200614163744j:plain

Test言語はObjective-C。あとで設定を変えるが、実行するのはObjective-C++としてビルドする。単体テストGoogle Testで記述してあるので、それをなるべくそのまま実行できるようにする。

f:id:kkAyataka:20200614164121j:plain

テストコードを作成する

テストコードを編集します。ここではGoogle Testのメイン実行関数をそのまま呼び出すようにしています。また、この時Unit Testのコードが実行されるように、Test Targetの設定 (ソースファイルの追加やヘッダー検索パスの設定) なども行います。

- (void)testExample {
    NSArray * args = [[NSProcessInfo processInfo] arguments];
    int argc = (int)args.count;
    char const * argv[10] = {};
    for (int i = 0; i < argc; ++i) {
        argv[i] = [[args objectAtIndex: i] cStringUsingEncoding: NSUTF8StringEncoding];
    }

    // Google Testを実行
    testing::InitGoogleTest(&argc, (char**)argv);
    RUN_ALL_TESTS();
}

この時、C++のコードをそのまま呼び出すことになるため、テストソースのファイルタイプをObjective-C++に設定します。

f:id:kkAyataka:20200614165507j:plain

カバレッジ計測オプションを有効にする

Unit Testのスキーマ設定画面を開いて、テストのところに作成したTest Targetを追加します。

f:id:kkAyataka:20200614213640j:plain

そして、カバレッジ計測を有効にするためにOptionsを開いて、Code Coverageを有効にします。

f:id:kkAyataka:20200614165057j:plain

hppファイルをコンパイル対象にする

計測対象がhppなどのヘッダファイルの場合は、コンパイル対象として設定します。コンパイル対象にしないとカバレッジ計測の対象となりません。

f:id:kkAyataka:20200614170443j:plain

実行して結果を見る

Unit Test (Test Targetではなく) を実行します。Build・実行ボタンを長押しすると実行方法が選べるので、ここでテストを選ぶと、スキーマで設定したテストが実行されます。

実行後、結果画面から結果を見ます。「By Group表記」の方が個人的には見やすい。

「Show Test Bundles」を有効にすると、Test Targetで実装したコードのカバレッジを確認できます。C++ソースのカバレッジが計測されていることが確認できます。

f:id:kkAyataka:20200614213750j:plain

この状態でソースコードをひらけば、実行されていない箇所をハイライトで確認することができます。

f:id:kkAyataka:20200614171016j:plain

sphinxcontrib-xlsxtableのモジュール実行

  • sphinxcontrib-xlsxtableをCLIで呼び出し可能にした
  • グリッドテーブル文字列を一旦ファイルに書き出してから、ドキュメントに組み込むことができる
  • グリッドテーブルがファイルに残るので、Git運用との相性がよさそう

モジュール実行

sphinxcontrib-xlsxtableをモジュール実行できるようにした。 CLIから呼び出すことでreStructuredTextのグリッドテーブル文字列を生成することができる。

$ python -m sphinxcontrib.xlsxtable --header-rows=1 sample.xlsx
+----+-------+-----------+--------+
| A1 | B1    | C1        | D1     |
+====+=======+===========+========+
| A2 | B2:B3 | C2        | D2     |
+----+       +-----------+--------+
| A3 |       | C3:D3              |
+----+-------+-----------+--------+
| A4 | B4    | C4        | - D4-1 |
|    |       |           | - D4-2 |
+----+-------+-----------+--------+

rsSTファイルに書き出してからドキュメントに組み込む

これを使うとExcelファイルから直接reSTに埋め込むのではなく、グリッドテーブルを出力したファイル経由でreSTに展開できる。

table.rstに書き出したとして...

$ python -m sphinxcontrib.xlsxtable --header-rows=1 sample.xlsx > table.rst

.. includeで取り込む。

.. table:: Table Caption

   .. include:: table.rst

グリッドテーブルテキストがファイルに残る

テキストとして残るため、Git管理との相性がよく、PR運用での差分確認が楽になる。 Excelファイルと二重管理にはなるものの、Excelファイルの差分確認はかなり手間であるため、メリットのほうが大きい。

スクリプトを用意すれば出力そのものはそれほど手間にはならないため、Excelファイルを直接reSTに埋め込むよりは、より現実的な運用のように思う。

sphinxcontrib-xlsxtable

SphinxExcelファイルから表 (テーブル) を埋め込むSphinx拡張を作りました。

機能的に不十分だったり、Pythonモジュールよくわかってなかったり、PyPIの理解も不十分だったりしますが、まあ、動いとる。

以下が特徴で、これはそのまま自分が必要とした機能であり、既存の拡張機能が肌に合わなかった理由になります。

  • Excelファイルの制御はOpenPyXLを使用しており、xlsxファイルのサポート範囲が広い
  • Sphinx標準表記の表 (テーブル) として出力される
  • 結合セル対応

使い方

pipで入れて、

$ pip install sphinxcontrib-xlsxtable

conf.pyに設定します。

# conf.py
extensions = [
    'sphinxcontrib.xlsxtable',
]

reStructuredTextのディレクティブは次の通りです。現状はほとんどオプションはありませんが、いくらか追加予定です。

ドキュメント化していませんが、一応シート指定にも対応してます。

.. xlsx-table:: Table Caption
   :file: path/to/xlsx/file.xlsx
   :header-rows: 1
   :sheet: Sheet

これによって、以下のExcelファイルから、

f:id:kkAyataka:20200314113454p:plain
Excel

以下の結果が得られます。

f:id:kkAyataka:20200314113537p:plain
結果

セル内の文字列はそのままreSTとして処理されるので、上記の通り「- テキスト」形式で書かれた部分は、箇条書きとして処理されています。

内部でGrid Table文字列を生成

仕組みとしては、OpenPyXLExcelファイルを解析して、内部でreSTのGrid Tableの文字列を作っています。

上記のExcelファイルだと、以下のような感じ。

+----+-------+-----------+--------+
| A1 | B1    | C1        | D1     |
+====+=======+===========+========+
| A2 | B2:B3 | C2        | D2     |
+----+       +-----------+--------+
| A3 |       | 日本語              |
+----+-------+-----------+--------+
| A4 | B4    | C4        | - D4-1 |
|    |       |           | - D4-2 |
+----+-------+-----------+--------+

作った文字列はそのままdocutils標準の解析器 / 生成器に渡しているだけ (のつもり) なので、結合セル、セル内のreST表記に対応し、標準の表のスタイルで表示することを実現しています。

Sphinx / reStructuredTextの表について

元々はSphinxで結合セルの表記をするならExcelから読み込みたい...というので作ったのだけど、使ってみたところ思いの外便利でした。

  • 大規模な表はCSV Table使おうと思ってたけど、CSVの編集が思ったより大変
    • Excelで開いても表示のレイアウトが維持されないので、割と辛い
  • 既存のExcelファイルをそのまま使えるのが強い
    • 既存ドキュメントのまとめ直しで、既存ファイルをそのまま使える
  • Excelで表作るのやっぱ楽。

opensslコマンドを使ってAPIと同じ結果を得る

% openssl aes-128-cbc -e -iv 00000000000000000000000000000000 -K 30313233343536373839414243444546 -nosalt -in data.txt -out out.txt
  • -ivはCBCモードの初期化ベクターで、16進数値で指定する
  • -Kは暗号化キーで、16進数値で指定する
  • -nosoltは暗号化データにsoltが設定されないようにする

opensslコマンドで簡単に暗号化できるけど、データにsoltを足したり、 デフォルトの初期化ベクターが入ったりする。 狙った暗号化 (のデータを得る) には、それらの値も合わせて設定 / 抑制する必要がある。

環境

  • macOS 10.15.3
  • openssl (LibreSSL 2.8.3)

ファイルのバイナリ表示

ファイルを1 byte単位でバイナリ表示するには以下。暗号化したデータの確認に使用する。

% od -t x1 out.txt
0000000    57  b1  cc  91  87  f6  d1  21  4b  cc  71  ab  56  40  3c  c4
0000020

OpenSSLを使った暗号化 (v1.1.1d)

2015-10-24にOpenSSLでの暗号化の記事を書いたけど、 最近になって試したところコンパイルできなくなっていたので、その修正版。

昔の記事、バージョン書いてなかったから差がわかんね。

環境

  • OpenSSL v1.1.1d (ソースからビルド)
  • macOS 10.15.3
  • Xcode 11.3.1

EVP_CIPHER_CTXの作成方法がEVP_CIPHER_CTX_newを使用するようになった

違いと言ってもEVP_CIPHER_CTXの作成方法が異なるだけ。 通常の構造体として使えば良かったのがオペークポインタ形式になっているので、 専用関数で作成する。で、専用関数でリリースする。

// 昔の
EVP_CIPHER_CTX ctx = {}; // 関数定義が非公開になったので、作れなくなった

// 新しいの
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); // newで作って
...
EVP_CIPHER_CTX_free(ctx); // freeで開放

暗号化処理に関してざっくり書くと、以下。EVP_CIPHER_CTXの作成方法が変わっただけで、 それ以外の部分は古いバージョンとの違いは無い。

#include <openssl/evp.h>

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
EVP_CIPHER_CTX_init(ctx);

// 暗号化の設定で、EVP_aes_128_ecb等いろいろ
const unsigned char iv[16] = {0};
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);

// ブロックサイズで割り切れる部分の処理
int outl = 0;
EVP_EncryptUpdate(ctx, encrypted, &outl, data, data_size);

// 最後のブロックの処理で、PKCSパディングされる。
int pad = 0;
EVP_EncryptFinal_ex(ctx, encrypted + outl, &pad);

EVP_CIPHER_CTX_cleanup(ctx);
EVP_CIPHER_CTX_free(ctx);

ドキュメント

OpenSSLの公式ページの構成が変わっているので、古いURLはリンク切れになった。 以下は完全ソースコードとv1.1.1のドキュメントへのリンク。