おがさわらなるひこのオープンソースとかプログラミングとか印刷技術とか

おがさわらなるひこ @naru0ga が技術系で興味を持ったりなんだりしたことをたまーに書くブログです。最近はてなダイアリー放置しすぎて記事書くたびにはてな記法忘れるのではてなブログに移行しました。

クリエイティブ・コモンズ・ライセンス
特に断りがない場合は、本ブログの筆者によるコンテンツは クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

短期集中連載? LibreOfficeをWindowsで開発してみよう:補足その①ユニットテストについて

GWの間にLibO開発できる環境をWindowsで作ろうの連載をやりましたが、あれからようやっと修正パッチ投げるところまでやりましたので、その中で気づいたことなど。

過去記事:第1回 第2回 第3回 第4回 第5回

なお再度お断りですが、この一連の記事はLibreOfficeの開発について解説しようというものではないです。

というのは、解説記事をかけるほど自分詳しくないってことと、 LibreOfficeのように開発が活発なプロジェクトにおいて、 開発情報は将来において変わりうるので解説の寿命がそんなに長くないってことです*1

あくまでも、Wikiにある公式情報:

wiki.documentfoundation.org

を参照しつつ、私の記事は、 あーなるほどこんなところで引っかかったりするのかーみたいな一種のドキュメンタリーとして楽しんでいただけたら。

あともう一つ、第5回で、 問題のどこを直せばいいかというのはわかったつもりだけど、 どう直せばいいのか(仕様変更すべきなのか)がわかってないみたいに書きました。 それについては、メーリングリストに質問投げまして、 仕様変更になるかもだけど直した方がよかろうということに賛同いただきまして決着。 よかったよかった。

さてさて。前置き長くてすみません。本題。

実際に開発を進めていくにあたって、以下3点少し悩んだので。

  1. ユニットテストをどう書いてどう実行するか
  2. パッチをどうやって送るか
  3. ヘルプの更新の仕方

と、書いてたらむちゃくちゃ長くなったので、この記事はまずは①だけについて。②③は別記事にします。

1. ユニットテストをどう書いてどう実行するか

LibreOfficeのような、日々膨大な変更があって*2 リリース頻度も高いソフトウェアには、自動テストが欠かせません。

ロジックを何らか変えたら、 当然、テストを実行してリグレッション*3がないかどうかを確認し、 場合によっては、新たに実装したロジックが適切に動作するかどうかのテストを書かなくてはいけません。

LibreOfficeには自動テストのしくみがいくつかありますが、基本となるのはC++なロジックをC++なテストコードで確認するユニットテスト。 例によって公式Wikiに記載があります。

wiki.documentfoundation.org

さて。「どう書いて」については、この記事ではお伝えできる情報あんまり情報がないです……。 今回のように既存のロジックを変えましたよという場合、そのロジックに関するテストを探して、 必要ならそいつに書き足す……ってな感じでしょうね。

で、前回同様「DBNum」でプロジェクト丸ごと検索かけて、今回は関係しそうなテストは二つ。

一つは sc/qa/unit/subsequent_export-test.cxx の以下の部分:

void ScExportTest::testNatNumInNumberFormatXLSX()
{
    ScDocShellRef xDocSh = loadDoc("tdf79398_NatNum5.", FORMAT_ODS);
    CPPUNIT_ASSERT( xDocSh.is() );
    xDocSh = saveAndReload( &(*xDocSh), FORMAT_XLSX);  // Convert [NatNum5] to [DBNum2] in Chinese
    CPPUNIT_ASSERT( xDocSh.is() );

    xmlDocUniquePtr pDoc = XPathHelper::parseExport2(*this, *xDocSh, m_xSFactory, "xl/styles.xml", FORMAT_XLSX);
    CPPUNIT_ASSERT(pDoc);

    assertXPath(pDoc, "/x:styleSheet/x:numFmts/x:numFmt[3]", "formatCode", "[DBNum2][$-804]General;[RED][DBNum2][$-804]General");

    xDocSh->DoClose();
}

こいつは:

  • 名前の通りエクスポートのときに NatNum -> DBNum の変換がちゃんとおこわなれるかのテストで
  • あらかじめ用意したODSファイルをXLSX形式で保存しなおして
  • 出てる内容のXPathで正しさを確認する

ってものなのですけど…… うーん、データファイル作るの少し面倒だし、 XPathの書き方もよくわかってないので*4 こいつはいったん置きます。

もう一つは、 svl/qa/unit/svl.cxx の以下のコード。

void Test::testUserDefinedNumberFormats()
{
    ...
    {  // tdf#79399 tdf#101462 Native Number Formats
        sCode = "[NatNum5][$-0404]General\\ ";
        // Chinese upper case number characters for 120
        sExpected = u"\u58F9\u4F70\u8CB3\u62FE ";
        checkPreviewString(aFormatter, sCode, 120, eLang, sExpected);
        sCode = "[DBNum2][$-0404]General\\ ";
        checkPreviewString(aFormatter, sCode, 120, eLang, sExpected);
    ...

こいつはセルの書式画面にて書式コードを手で入力するときの「プレビュー」をもとに、 書式が期待通りに処理されるかということを確認。お、これはわかりやすいですね。

もうちょっと読んでみますと、書式指定文字列らしい [NatNum5][$-0404]General\\、 この [$-0404] ってなんじゃろ……と思って調べたら、 ヘルプ によると、 include/lang.h にある:

#define LANGUAGE_CHINESE_TRADITIONAL        LanguageType(0x0404)
...
#define LANGUAGE_JAPANESE                   LanguageType(0x0411)
...
#define LANGUAGE_KOREAN                     LanguageType(0x0412)

みたいですね。 今回の場合、 セル内の言語によって結果が変わることをテストしないといけないのでこれは大変に都合がよろしい。

で、 sExpected = u"\u58F9\u4F70\u8CB3\u62FE "; ですが、 これは適当なツール*5 UTF-16 デコードすると 壹佰貳拾 となって、 あーなるほど、checkPreviewString() の第3引数に渡してるやつの NatNum5 = DBNum2 の期待値なのですね。

ということでこのロジックはままコピれるみたいなので、 別個こんな感じでまとめました。ちょっと長いけど。

    { // tdf#130193 tdf#130140 Native Number Formats mapping for Chinese (Traditonal), Japanese, Korean
        // -- Traditional Chinese: DBNum1 -> NatNum4, DBNum2 -> NatNum5, DBnum3 -> NatNum6

        // DBNum1 -> NatNum4: Chinese lower case text for 123456789
        // 一億二千三百四十五萬六千七百八十九
        sExpected = u"\u4e00\u5104\u4e8c\u5343\u4e09\u767e\u56db\u5341\u4e94\u842c\u516d\u5343"
                    u"\u4e03\u767e\u516b\u5341\u4e5d ";
        sCode = "[NatNum4][$-0404]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum1][$-0404]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // DBNum2 -> NatNum5: Chinese upper case text
        // 壹億貳仟參佰肆拾伍萬陸仟柒佰捌拾玖
        sExpected = u"\u58f9\u5104\u8cb3\u4edf\u53c3\u4f70\u8086\u62fe\u4f0d\u842c\u9678\u4edf"
                    u"\u67d2\u4f70\u634c\u62fe\u7396 ";
        sCode = "[NatNum5][$-0404]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum2][$-0404]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // DBNum3 -> NatNum6: fullwidth text
        // 1億2千3百4十5万6千7百8十9
        sExpected = u"\uff11\u5104\uff12\u5343\uff13\u767e\uff14\u5341\uff15\u842c\uff16\u5343"
                    u"\uff17\u767e\uff18\u5341\uff19 ";
        sCode = "[NatNum6][$-0404]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum3][$-0404]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // -- Japanese: DBNum1 -> NatNum4, DBNum2 -> NatNum5, DBnum3 -> NatNum3

        // DBNum1 -> NatNum4: Japanese modern long Kanji text for 123456789
        // 一億二千三百四十五万六千七百八十九
        sExpected = u"\u4e00\u5104\u4e8c\u5343\u4e09\u767e\u56db\u5341\u4e94\u4e07\u516d\u5343"
                    u"\u4e03\u767e\u516b\u5341\u4e5d ";
        sCode = "[NatNum4][$-0411]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum1][$-0411]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // DBNum2 -> NatNum5: traditional long Kanji text
        // 壱億弐阡参百四拾伍萬六阡七百八拾九
        sExpected = u"\u58f1\u5104\u5f10\u9621\u53c2\u767e\u56db\u62fe\u4f0d\u842c\u516d\u9621"
                    u"\u4e03\u767e\u516b\u62fe\u4e5d ";
        sCode = "[NatNum5][$-0411]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum2][$-0411]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // DBNum3 -> NatNum3: fullwidth Arabic digits
        // 123456789
        sExpected = u"\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19 ";
        sCode = "[NatNum3][$-0411]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum3][$-0411]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // -- Korean: DBNum1 -> NatNum1, DBNum2 -> NatNum2, DBNum3 -> NatNum4, DBNum4 -> NatNum9

        // DBNum1 -> NatNum1: Korean lower case characters
        // 一二三四五六七八九
        sExpected = u"\u4e00\u4e8c\u4e09\u56db\u4e94\u516d\u4e03\u516b\u4e5d ";
        sCode = "[NatNum1][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum1][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // DBNum2 -> NatNum2: Korean upper case characters
        // 壹貳參四伍六七八九
        sExpected = u"\u58f9\u8cb3\u53c3\u56db\u4f0d\u516d\u4e03\u516b\u4e5d ";
        sCode = "[NatNum2][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum2][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // DBNum3 -> NatNum3: fullwidth Arabic digits
        // 123456789
        sExpected = u"\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19 ";
        sCode = "[NatNum3][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum3][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);

        // DBNum4 -> NatNum9: Hangul characters
        // 일이삼사오육칠팔구
        sExpected = u"\uc77c\uc774\uc0bc\uc0ac\uc624\uc721\uce60\ud314\uad6c ";
        sCode = "[NatNum9][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
        sCode = "[DBNum4][$-0412]General\\ ";
        checkPreviewString(aFormatter, sCode, 123456789, eLang, sExpected);
    }

さて、実行について。

さきのページにもありました通り、全部のテストを実行するには、

make check

するだけ。このときウィルスチェックがONだと失敗することがあるのでOFFっとくのがいいみたいです*6

ただ、LibOのユニットテストはとても膨大なので、毎度毎度全部のテストを流すのはホネが折れます。 なので狙ったテストだけ流す方法もあります。

 make CppunitTest_svl_qa_cppunit CPPUNIT_TEST_NAME="testUserDefinedNumberFormats"

たとえばこんな感じ。 今回書いたテストは svl/qa/unit/svl.cxx 以下にあるので、makeのターゲットは "svl_qa" となるというわけですね。 ……いや、正直にいうと、たぶん ls svl/CppunitTest*.mk してそれっぽいものを探しただけな気がします。

なお、事前にモジュールがあるフォルダーに cd しておくと、ちょっとだけテストの起動が早くなりますね。

で、失敗したとき。このときもとてもよくできてまして、失敗するとこんな感じのメッセージが出ます。

C:/cygwin/home/naruhiko/lode/dev/core/svl/qa/unit/svl.cxx:437:`anonymous namespace'::Test::testUserDefinedNumberFormats
equality assertion failed
- Expected: 一億二千三百四十五萬六千七百八十九
- Actual  : 一千二百三十四萬五千六百七十八

`anonymous namespace'::Test::testUserDefinedNumberFormats finished in: 736ms
C:/cygwin/home/naruhiko/lode/dev/core/svl/qa/unit/svl.cxx(437) : error : Assertion
Test name: `anonymous namespace'::Test::testUserDefinedNumberFormats
equality assertion failed
- Expected: 一億二千三百四十五萬六千七百八十九
- Actual  : 一千二百三十四萬五千六百七十八

Failures !!!
Run: 1   Failure total: 1   Failures: 1   Errors: 0
warn:unotools.config:1836:23384:unotools/source/config/configmgr.cxx:140: ConfigManager not empty

Error: a unit test failed, please do one of:
make CppunitTest_svl_qa_cppunit CPPUNITTRACE=TRUE # which is a shortcut for the following line
make CppunitTest_svl_qa_cppunit CPPUNITTRACE="'C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/Common7/IDE/devenv.exe' /debugexe" # for interactive debugging in Visual Studio
make CppunitTest_svl_qa_cppunit CPPUNITTRACE="drmemory -free_max_frames 20" # for memory checking (install Dr.Memory first, and put it to your PATH)

You can limit the execution to just one particular test by:

make CppunitTest_svl_qa_cppunit CPPUNIT_TEST_NAME="testXYZ" ...above mentioned params...

そう、ここに書いてある通り、

make CppunitTest_svl_qa_cppunit CPPUNITTRACE=TRUE

ってやると、Visual Studioが立ち上がって、この中でユニットテスト(と、もちろん、その中で呼び出されるLibOのコード)をデバッグできるんですね。 当然 ‘CPPUNIT_TEST_NAME` との併用も可能です。

これで、新規に作ったテストがちゃんと通るようになったら、あらためてユニットテストもgitにコミットして。

コミットしたコードで正しくテストが全部通るかどうか、 make check をもう一度やっておきましょう。 わたくしはこの工程をさぼったため、先に示した svl/qa/unit/svl.cxx のテストの実行がCIでfailしてあわわわわ……ってなりました。恥ずかしい*7

さてさて長くなりました。gitにコミットした内容をもとに、いよいよパッチの提出……は、次の記事にて。

*1:同様の理由で、 公式Wikiの開発情報のページ翻訳するの昔は抵抗があるんですよね……。 Readmeなんかで使ってるMediaWikiの翻訳プラグイン適用してくれないかなー。 そしたら原文更新されたパラグラフは英語になるので、うっかり古い情報を提供せずに済むので。

*2:プロジェクトの日々を知ることができるサイト https://dashboard.documentfoundation.org/ によれば一日当たりだいたい300弱のコミットがあるらしいですね。

*3:回帰ともいう。つまり、変更によって過去に動いてた機能が動かなくなったなど。

*4:今この記事を書くために再度読み直したらそんなに大変じゃないかも……。 たぶん [$-804] がExcel用の言語コードなので、生成されたXLSXをunzipして中のXML覗いて、これだけ直してあげればよさそう。 それで日本語・韓国語のテスト用ODSを作ってあげさえすれば。 レビューで指摘もらったら対応するかも。

*5:私はこいつ使いました。

*6:わたくしの場合はこのとき、masterからとってきただけのソースでも特定のテストがfailしました……この場合正しくは「どうなってるの?」とメーリングリストなりなんなりで聞くべきなのですが、わたくしはとりあえず無視で先に進みました……よくない。

*7:自分でテスト実行した後、手作業でちょっとだけ直してコミットしたコードに誤りがあったのです。

【野良翻訳】ミュンヘン市、「Public Money, Public Code!」にコミット

Free Software Foundatoin Eurpoe (FSFE)のブログ:

fsfe.org

の野良翻訳です。

ミュンヘン市は新たな連立合意で「Public Money? Public Code!」(公金は公的コードへ)原則にコミット。FSFEは新市政におけるこの決定を歓迎し、実現の進捗を緊密に監視します。

ミュンヘン市SPD*1 および緑の党は、3月の地方選を受けて、先の日曜日に連立協定に合意しました。 この中には、フリーソフトウェアの利用に対するポジティブな声明が含まれています:将来において「Public Money? Public Code!」原則が適用されるべきとのものです。 ミュンヘン市はこうしてFSFEからの要求に加わりました。

「FSFEはミュンヘン市政による「Public Money? Public Code!」ポリシーを歓迎します。 先のSPDCSU*2による市政は、 それ以前の革新的なフリーソフトウェア戦略から距離を置いていましたが、今や再びポジティブなシグナルが灯りました。 政府組織が「Public Money? Public Code!」原則に従うことは、ほかの公的団体との共同作業に利益となり、特定のベンダーからの独立を保ち、税金を節約できる可能性があり、イノベーションを進め、ITセキュリティのより良い基盤となります。」 と、Free Software Foundation Europe議長、 Matthias Kirschnerは述べました。

2014年、SPDCSUとの連立合意に加わり、Dieter Reiter (SPD) が新しいミュンヘン市長として当選しました。 ミュンヘン市は、独立したフリーソフトウェアGNU/Linuxオペレーティングしシステムに基づくITインフラストラクチャを開発する、 「LiMux」戦略を放棄し、プロプライエタリなソフトウェアへの依存への回帰を始めました。Free Software Foundatoinはこの「再移行」を過去に批判しました。 今、SPD緑の党による新たな合意によって、ミュンヘン市は彼らによる「Public Money? Public Code!」へのコミットメントに戻ろうとしているように見えます。 依然として、協定は、たとえば個人情報や機密情報が含まれていないソフトウェアに限定するなど、いくつかの典型的な抜け穴があり、改善の余地があります。 そのためFSFEは彼らの「Public Money? Public Code!」ポリシーの実施および、将来において調達手続きがどのように行われるかに対する緊密な監視を継続します。

「Public Money? Public Code!」イニシアチブは、フリーソフトウェアを、公的に投資されるソフトウェアの標準とするということを目標としています。 Free Software Foundation Europeは、180の市民団体および、27,000人の個人とともに、オープンレターに署名しました。 この署名をもとに私たちはヨーロッパの政策決定者、政府代表者に連絡を取り、公的なコードを標準とすることを説得しています。 より大きな影響を与えるために皆さんにも署名を呼び掛けています: https://publiccode.eu/

議論はこちらで。

短期集中連載? LibreOfficeをWindowsで開発してみよう:その⑤ 不具合直して? みたよ

GWの間にLibO開発できる環境をWindowsで作ろうの連載5回目。さてGWやらも本日で終わり、なのでこの短期集中連載もさしあたりの最終回。

過去記事:第1回 第2回 第3回 第4回

せっかく開発環境作ったのになんにも触らないのもアレなんで、適当なバグを直してみようと。ターゲットにしたのはこれ。

bugs.documentfoundation.org

この不具合なんですが、平たく言うとExcelで作成した「セルの書式設定で大字(12345……を壱弐参四五……って書くやつ)を指定したセルが、LibO Calcで開くと大字にならないってものです。

Excelで大字表示ってどうやるんだろ……って調べてみると、たとえばこのサイトがヒットしたんですけど:

www.kenzo30.com

書式記号に [DBNum2] って指定すればいいらしいです。

Excelの書式指定子(Modifier)、Calcでも指定できるので指定してみる……と、

f:id:naruoga:20200506210339p:plain
書式指定子 [DBNum2] 指定

ホントだ。大字にならないですね。ということでこれを追ってみることにします*1

ソースを検索して該当部分を探す

最初はやみくもにソースを検索して該当部分を探してみましょう*2

ソースの検索は OpenGrok でも git grep でもよいですが、まあせっかくなのでVS2019上で検索してみることにします。 Ctrl-Shift-F を押してソリューション内検索ダイアログを開いて "DBNum2" を検索します。

見つかったのはテストコード、コメントを除くと /svl/source/numbers/zformat.cxx だけ。名前からいってもそれっぽい。ということで中を覗いてみます。

デバッガを使いつつなんとなく眺める

パッと目に入るのは、 SvNumberformat のコンストラクタ。 こいつもまあ相当長いメソッドなのですが、要は書式指定子の文字列をパースして [DBNum2] なら 2 を取り出して保持しておく処理っぽいですね。

あと DBNum というキーワードでなんとなくコードを眺めると:

sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate )
sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate )

というペアのメソッドが目につきます。なにやら内部的に DBNum と NatNum なるものを相互に読み替えているらしい。 試しにデバッガで前者にブレークポイント設定して*3 DBNum2 を設定してみたら呼ばれてますね。なるほど。 私はこんな風にブレークポイントしかけて確かにこれが見るべき処理かって確認するのよくやります。 机上で追い込むの苦手なので……。

そこで、おっと思ったのが、さっきのテストでセルの書式設定に [DBNum2] とやったやつ。 あれを保存して再度読み込みなおすと、 [NatNum4] という書式指定に代わってしまう。

f:id:naruoga:20200506215618p:plain
書式指定が変わってしまう

ここでようやっと、仕様確認しようと思いつきました(遅い)。ヘルプを "DBNum2" で検索すると、このページがヒットしました。

help.libreoffice.org

「Displaying Numbers Using Native Characters」とあるとおり、NatNum というのはNative Numberなんでしょうね。 LibOの書式指定のネイティブはこっち。

で、Excel(OOXML)からインポートしたり、手打ちで書式指定 DBNum2 とかした場合は、適切なNatNumに変換して内部的にはそれを使って処理する……と。なるほどね。 エクスポートするときには逆に NatNum から DBNum に書き戻すのでしょう。

さてヘルプを見ると、…… DBNum2NatNum4 に対応するということですね。 それは、ODSで保存して読み込み直すときの結果と符合しますね。なのでここに間違いはないと*4

じゃあそうなると、書式指定をもとに実際の表示文字列を組み立てるところ、それが問題あるということでしょうか。 表示文字列を組み立てるところ、それって多分ですけど、普通の実装なら、 セルの内容を渡したら文字列が返ってくるメソッドを用意するんじゃないでしょうかね*5

ということで、ソースコードgrepして(ここはVS2019使わないんかい!) zformat.cxx の中で戻り値が OUString なものをgrepして、中のぞいて、あーそれっぽいと思ったのがこの二つ。

OUString SvNumberformat::impTransliterateImpl(const OUString& rStr,
                                              const SvNumberNatNum& rNum ) const
{
    css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );
    return GetFormatter().GetNatNum()->getNativeNumberStringParams(rStr, aLocale, rNum.GetNatNum(),
                                                                   rNum.GetParams());
}

void SvNumberformat::impTransliterateImpl(OUStringBuffer& rStr,
                                          const SvNumberNatNum& rNum ) const
{
    css::lang::Locale aLocale( LanguageTag( rNum.GetLang() ).getLocale() );

    OUString sTemp(rStr.toString());
    sTemp = GetFormatter().GetNatNum()->getNativeNumberStringParams(
        sTemp, aLocale, rNum.GetNatNum(), rNum.GetParams());
    rStr = sTemp;
}

どっちも GetnativeNumberStringParams() なるメソッドを読んでるわけですが、ここはVS2019でステップ実行でステップインして飛びますと、 最終的には i18npool/source/nativenumber/nativenumbersupplier.cxxNativeNumberSupplierService::getNativeNumberString() メソッドに来るわけですね。

で、こいつをデバッグ実行しつつ追うと、

    switch (nNativeNumberMode)
    {
        ...
        case NativeNumberMode::NATNUM4: // Text, Lower, Long
            number = &natnum4[langnum];
            break;

という処理があって、ここで number という変数に入ってくるのは、すでに大字ではない通常の漢数字……ふむぅ。……ん?

ここで仕様(というかヘルプ)を再度確認

はい、私のよくなかったところ。 おもむろにソースを見る前に、「解くべき問題は果たして何か(何が正しいふるまいか)」を確認する必要がありましたね。

で、ヘルプ再度確認すると、いやー間抜け:

f:id:naruoga:20200506230700p:plain
NatNum4 は modern long Kanji text !

NatNum4 は modern だって書いてあるじゃー、ないですか!

なのでヘルプの記述が仕様として正しいなら、今の動作は完全に正しい。そして、古いヘルプ:

help.libreoffice.org

を見ると、この動作は3.3から……というかOpenOffice.orgのころから変わっていない、と思われます。

でもおかしいですよね、Excelでは DBNum2 は大字なのだとしたら、当然LibOではそれに対応する NatNum5 じゃないと、バグ起票されたようにExcelとの互換性がないということになってしまう。うーん。わからん。

目をつぶって直す

まあいいや、もし仮にこいつ直すとしたらどうするか。

結果としては DBNumXNatNunX の割り当てだけが問題ってことになったので、前述の:

sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang, bool bDate )
sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLang, bool bDate )

この二つを直せばいいということになります。

本来ならすべてのマッピングを見直す方が適切なのかもわからないですが、さしあたりはこの問題だけ直してみます。 以下diffだけだと若干情報足りないので手で補った嘘diffです。

@@ -170,7 +170,7 @@ sal_uInt8 SvNumberNatNum::MapDBNumToNatNum( sal_uInt8 nDBNum, LanguageType eLang
          switch ( nDBNum )
          {
          ...
          case 2:
             if ( eLang == primary(LANGUAGE_CHINESE))
                 nNatNum = 5;
             else if ( eLang == primary(LANGUAGE_JAPANESE) )
-                nNatNum = 4;
+                nNatNum = 5;
             else if ( eLang == primary(LANGUAGE_KOREAN) )
                 nNatNum = 2;
             break;

こちらは DBNum2NatNum4 にして、

@@ -238,7 +238,7 @@ sal_uInt8 SvNumberNatNum::MapNatNumToDBNum( sal_uInt8 nNatNum, LanguageType eLan
          switch ( nNatNum )
          {
          ...
          case 4:
             if ( eLang == primary(LANGUAGE_CHINESE) )
                 nDBNum = 2;
             else if ( eLang == primary(LANGUAGE_JAPANESE) )
-                nDBNum = 3;
+                nDBNum = 2;
             break;
         case 6:
             if ( eLang == primary(LANGUAGE_CHINESE) )

こちらは逆に NatNum4DBNum2 に戻しているだけ。あー、超簡単だ。

で、この変更を入れてビルドしたやつで、バグ報告についてたファイルを開いてみると……。

f:id:naruoga:20200506233209p:plain
6.4だと大字になってない

f:id:naruoga:20200506233429p:plain
直したやつだとちゃんと大字になる

直ってますね!

もし……仮にもし、これをパッチに出すとすると、

  • まずはあるべき仕様を日本コミュニティで議論(メーリングリストかな?)。
  • 上記に基づき、DBNum2 以外に対する処理も書く。
  • ユニットテストでこの処理を確認しているものがあるげなので、直すなり、もしこのケースを確認してないなら追加するなりする。
  • ヘルプも直す。
  • コミット整えて、gerrit に push

ってな感じですかねー。

まあともかく、開発環境作ってリアルな問題を(少なくとも技術的には)解けるところまではいったので、 ゴールデンウイークとしてはまあまあの成果かな?

せっかくなので、これっきりにしないで定期的に開発にかかわっていきたいですね!

*1:なお調査しなくてもhimajin100000さんがコメントで書いてくださってるんですが、それはなんというか、練習なので、カンニングしない方向で……。

*2:慧眼なる読者の皆様ははこれが誤った手順であることにお気づきかと思います……。詳細はのちほど。

*3:個人的な言葉づかいで「ブレークポイントをはる」っていうんですが、この「はる」は張るなのか貼るなのかそれ以外なのか。

*4:もう気付いている人はいらっしゃると思いますがこれも伏線……です。

*5:引数で渡したオブジェクトの参照に値詰めて戻る実装もあるけど、文字列なら普通returnします……よね?

短期集中連載? LibreOfficeをWindowsで開発してみよう:その④ Visual Studio開発落穂ひろい

GWの間にLibO開発できる環境をWindowsで作ろうの連載4回目。環境作るだけじゃなくてできればバグつぶしてコミットするところまで行きたい。

過去記事:第1回 第2回 第3回

今日……というか昨日か、はあんまり進んでません。 なぜならSNSなどでご覧になった方はいるかもしれませんが、開発用のノートPCのHDDをSSDに換装したら、環境が全部ふっとんじゃったからです。 それを戻すのに時間がかかってこっちの作業あんまり進められませんでした。

なのでその際に気づいたメモなどを書いてお茶を濁します。 いちおうソースコード眺め始めてはいますが、まだ目立った成果はないので……

  • ディスク換装したのですが \cygwin\ 以下をまるっと古いディスクからコピーすればいいかと簡単に考えてましたがダメだったみたいです
    • git リポジトリでどうやってもuntracked changeがなくならない……
    • checkout -fcleanstash / stash drop もダメ
    • しょうがないので dev 以下捨てて lode 直下で ./setup --dev --force やり直して再ビルドしました
  • VS2019ソリューションが読めない
    • 言語パック切り替えるの忘れてたので English にしたら通りました
    • ただこれ、言語パック忘れじゃなくて、なにか VS Installer でちゃんとインストールできてなかったものがあって、 言語パック切り替えでそれがちゃんと入ったとかなのかも
    • 実は今回 workload の導入にも chocolatey 使ってみたんですけど、そいつが途中で刺さっちゃったんでアボートしたんですよね
      • ビルドとおったので大丈夫かな? と思ってたのですが、大丈夫じゃなかったのかも
  • デバッグ実行起動遅い話は、SSD化でだいぶ改善しました。やっぱり金の力は偉大だ

今はそんだけです。夜の報告でコードの解析結果とかお見せできたらいいな。

LibreOffice Asia Conference 2019 Tokyo 基調講演の紹介

去年行われましたLibreOffice Asia Conference 2019 Tokyo

conf.libreoffice.jp

の基調講演2本の字幕を聞き取りなおして日本語訳もつけたので、 せっかくだからもっといろんな人に見てほしいということで宣伝エントリーを書くことにしました。

言わずもがなですがわたくしの個人ブログに書いてることからお分かりの通り、 ここに書いてあることはLibreOffice日本語チーム、あるいはTDFをいかなる意味でも代表した発言ではござりませぬ。 あくまでもLibreOfficeというプロジェクト、コミュニティに好感を持つ一個人の意見としてお読みくださいませ。

さておき。動画については字幕ONにして、言語を日本語にしてご覧くださいね。

Mark Hung: LibreOffice CJK Bugs, Fixes, and Stories.

www.youtube.com

Mark Hung氏は台湾のLibreOffice開発者です。 主にLibreOffice Writerにて、を日本語で利用するには欠かせない、 アジア言語(いわゆるCJK; Chinese, Japanese, Korean)ならではの組版規則、ルビ、縦書き、BMP外のUnicode文字の扱いなど、さまざまな不具合を修正してきました。 最近ではWriterだけでなく、Impressのアニメーションの改善などにも取り組んでいます。

この講演では、なぜ彼がLibreOfficeに取り組むことになったか、 そしてどういう問題に取り組んできたかを、ストーリー仕立てで話してくれました。

Markのすごみというかは、自分の解ける範囲の問題を着実に解いて、それを意識して広げているってことです。

例えばルビの不具合、彼が直してくれたというときには、そうかー台湾でもルビ使うのかーって素朴に思ってたんですけど、 そうじゃなくて、自分が使ってないけれども領域が近い(CJKな問題)、 直せそうな問題を選んで取り組んだということをこの講演で言ってて、 なんというかそういう姿勢がすごいですよね。

LibreOfficeが持つCJKなさまざまな機能について振り返るという意味でも面白いですし、 それから、彼のLibreOfficeのストーリーのきっかけとなる、 Apache OpenOfficeで数週間放っておかれた不具合がLibOでは翌日レビューされて、 それが理由で組織がLibOを採用した……なんて話も面白いです。

あんまり弁が立つほうじゃないので、TED的なうまさはないですし地味に聞こえるかもしれないですが、 私としては、淡々とした口調であるからこそ、 なおかつ自分の活動を声高に主張しないからこその、 強さがあるなあと思いました。

彼の活動を日本に紹介したい、だから日本で講演してほしい。 それはずっと思ってきたことだったので、 とっつきの派手さがないから動画見られていない、 SNSとかでもあまり言及がない、ということなら、 それはすごいもったいないと思うのですよねー。

ので、とにかく見てほしいです。OSSへの貢献のありかたという意味でも興味深いです!

Italo Vignoli: LibreOffice, the many different faces of a global community

youtu.be

  • タイトル邦訳:LibreOffice、グローバルコミュニティの多様な側面
  • 発表資料(PDF): 英語 日本語

Italo Vignoli氏は、LibreOfficeを法的・財務的に支える非営利組織であるThe Document Foundation(以下TDF)の創立メンバーで、 マーケティング・広報担当コンサルタントとしてTDFと契約しています*1。 TDFが行っているLibreOffice認定制度を行う認定委員会の共同委員長でもあります。

LibreOfficeプロジェクト、プロダクトの説明もしていますが、それよりも、 LibreOfficeを取り巻く世界と、そこにおいてLibreOffice/TDFは何を目指して何をしてきたかという話が、 とても興味深いです。

ソフトウェアライセンスの輸出入の多寡で見る世界地図というところは実に衝撃的で、 LibreOfficeを使ってなくても、日本においてソフトウェアにかかわっている人であれば、 この話を聞くだけのためにぜひ見てほしいです。

またLibreOfficeの前身プロジェクトであるOpenOffice.orgとSun、Oracleの関係、 OpenOffice.orgが商標を譲渡したApache OpenOfficeIBMの関係なんかも、 改めて聞くと面白いんじゃないかと。

まあItaloという人はとにかく弁が立つ人で(そういう意味ではオープニング基調講演のMarkとは対照的)、 若干あおりが入ってるところもありますが、 笑いが絶えない、funかつinterestingな講演でした。

Lothar Becker: Certified as a LibreOffice professional - a win/win/win situation for the community

youtu.be

※Italoの講演の続きとなります。

  • タイトル邦訳:LibreOffice 専門家として認定をうけること - コミュニティにとってWin / Win / Win な状況
  • 発表資料(PDF): 英語 日本語

Lothar Becker氏は、Italoと同じくTDFのLibreOffice認定委員会の共同委員長です。 OpenOffice.orgの時代から、ドイツでもっとも古い移行案件を実施し、またOpenOffice.orgの認定制度を立ち上げるなどの活動をしてきたそうです。 なお、この当時は違いますが、今はTDFの議長を勤めています。

で、この講演は、彼の実績から、LibreOfficeのビジネスとエコシステムとコミュニティ、 そして認定制度についてのものでした。

ええと、正直に申し上げます。わたくし、 Lotharの話、LibreOfficeのグローバルなカンファレンス(LibreOffice Conference、通常LibOCon)で聞いて、 いまいちピンとこなかったんですよね。 じゃっかん抽象論っぽく聞こえて。

というのは、やっぱりあれじゃないですか、 LibreOfficeへの移行を(その動機はさておき)検討した場合、 「いやそんなことできませんよ」となる理由の一つは、 専門家によるサポートが受けられないことだと思うんですよね。 で、なぜサポートできる専門家がいないかというと、 平たく言うとそれでご飯が食べられないからです。市場がないか小さいから。 これってまさに鶏と卵のジレンマですよね。 そこに対するよい答えが、ないように思ってたのです。

でも、今回書き起こしのために時間かけて聞いたら、 自分で自分に「日本市場には適用できない」って言い訳しちゃってたのかもしれないです。

うーん今のところ考えまとまってないのでうまいこと言えないですけど、 まあ自分としてもうちょっとできることあるんじゃないかな、 人のせいにしてないで、そういうことやらないとなーと思える講演ではありました。

また、LibreOfficeの認定制度についてですが、 オフィスソフトの認定制度というとMSさんのMOSとかそういうのがぱっと浮かびますが、 そういうものとは少し違うんですよね。 その点、ちゃんと説明があったのもよかったと感じました。

……ということで三つの基調講演を紹介しました。

それぞれ、少しずつベクトルが違って面白い講演だったと思います。

面白いな、と思ってくださったら、ぜひ高評価、よろしければチャンネル登録もお願いします*2

*1:人呼んでTDFのゴッドファーザー

*2:YouTuberか!

続きを読む

短期集中連載? LibreOfficeをWindowsで開発してみよう:その③ Visual Studioで開発できるようにする

今回も夜までの更新間に合わなくて翌日朝更新。だって試行錯誤のためにフルビルドやり直しになっちゃったんだもん……。

GWの間にLibO開発できる環境をWindowsで作ろうの連載3回目。環境作るだけじゃなくてできればバグつぶしてコミットするところまで行きたい。

過去記事:第1回 第2回

本日は、LibreOfficeはちゃんとIDE連携があるので、そいつを使ってVisual Studio 2019 Communityで開発できるようにしてみましょう。

先に結論:

  • ./autogen.sh 実行時に --enable-debug 忘れずに。
  • makeに vs2019-ide-integration ってターゲットがちゃんとあるのでそれを使おう。

VS2019向けソリューションの作成

LibreOfficeの開発環境の各種IDE連携については、TDFのWikiにちゃんと説明があります。

wiki.documentfoundation.org

で、Visual Studioの説明を見てみますと、どれどれ……。

For Visual Studio 2017:

make vs2017-ide-integration

For Visual Studio 2015:

make vs2015-ide-integration

あれ? VS2019の説明がない。

まあ、とりあえずやってみましょう。

make vs2019-ide-integration

お、通った! これはドキュメントが更新されてないってことですね*1

見ると、ちゃんとVSで読めるソリューションファイルできてる。素晴らしい。ではあとはVS 2019で読み込めばいいのかな?

って、Wikiを見ると、

Note:

You need to build with --enable-debug in order to generate the Visual Studio IDE integration.

ってあるけど……なくても通っちゃったから別にいいのかなー。

VS2019で読み込んでみる

ということで、生成されたソリューションを読み込んでみました……えいっ。

……いけました!

デバッグ実行を試してみたら、ちゃんと自前ビルドしたLibreOfficeも起動します。おーーー。 ただ、起動までが猛烈に遅いんですけどこれは仕方ないのかなあ……。

しかしデバッグができない……。

では、ためしに適当なところにブレークポイント張って、デバッグ実行できるか試してみましょう。

というのはもちろん、さっきのドキュメントの注意書き「--enable-debug しないとだめだよ」が気になってるからですが。

結果をいうとだめでした。 ロジック上はまず通るだろうと思うところにブレークポイント張っててもすこっと抜けちゃう。 ブレークポイントにマウスカーソル当ててみると「シンボル読みこめないからこのブレークポイント無効だよ」みたいなことが書いてある(細かな文言メモってなかったです、すみません)。

あーそうなんだー、やっぱ --enable-debug ないとだめかー。

ということで、まずはVSを閉じてから、

./autogen.sh \
  --with-lang=ALL \
  --enable-64-bit \
  --with-jdk-home=/cygdrive/c/Program\ Files/AdoptOpenJDK/jdk-8.0.252.09-hotspot/ \
  --enable-debug

としといてから、念のため make clean して、

make && make vs2019-ide-integration

して一晩放置しました。

再びチャレンジ……うまくいった、か?

さて一晩経って処理終わってたので、再びVS開いてソリューションを開きなおし、F5キー叩いてデバッグ実行……ん?!

なんか関係ないところで例外吐いてブレークするんですけど……?(OpenCLがどうとかこうとか)

が、しかし、例外吐いてブレークするということは、デバッグは正常に動いてるということになります。

なので、「一回デバッグなしで起動しておいて、起動後デバッグアタッチする」という方法でやってみたら……。

f:id:naruoga:20200504100733p:plain
デバッグブレークできた

おお、ちゃんとブレークしますね。

ということで、Visual Studio 2019で無事デバッグできるようになった。よかったよかった。

なお、さっき書いた「なんか関係ないところで例外吐いてブレークする」は、一度デバッグ実行できるようにあったら再発しなくなりました……なんぞこれ。 初回起動時にしか通らないロジックとかなのかなあ。 まあ、支障はないので、とりあえず放置することにします(弱い)。

難点

とにかくデバッグ実行の起動がすごく遅いです。

LibOのソースの構成は、内部でたくさんのサブコンポーネントがあるわけですけど(参照: Development/Code Overview - The Document Foundation Wiki)、 こいつらが全部DLLとなる関係で、実行のたびに各DLLのビルドプロセスが走るみたいで、 まあその数が50個ぐらいあるわけですから、ソースに変更がない(ビルド不要)という判定をするだけでもだいぶ待たされます。

これ、なんとかならんのかなあ……。そこらへんはおいおい調査ですかね。

できれば、今日は具体的にソースを読み解いて問題を解くところまでやりたいですね。頑張りましょう。

*1:Wikiだから書いちゃえばよさそうだけど、開発素人なので勇気が出ないです(苦笑)。

短期集中連載? LibreOfficeをWindowsで開発してみよう:その② ビルド通りました、小ネタ紹介

いきなり集中連載1日落としてるじゃねーかってすみません。だって、ビルド通すのに苦戦してたんですもの……。

そんなわけでGWの間にLibO開発できる環境をWindowsで作ろうの連載2回目。

過去記事:第1回

今回は前回にも増して行き当たりばったりなので、最適な答え出してる自信ないです……。

ビルドしてみよう!

前回で autogen.sh を通すところまではできているので、あとは同じディレクトリで:

make

するだけ……なんですが、2点ほどハマりました。

submodule "translations" の取得に失敗してビルドがこけたあと回復不能

これがねー、けっこう解決に時間要しました。

正直、LibOのソースコードが置いてあるgerritですけど、 こいつがどうも能力いっぱいいっぱいなのか、それとも我々極東から離れて遠いヨーロッパにサーバーがあるせいなのか、 大きなリポジトリのcloneがタイムアウトでコケることがあるんですよね*1

で、LibreOffice coreのリポジトリは三つのsubmodule*2 があります。 そのうちの一つ、ウェブ翻訳システムWeblate の成果を取り込む translations というサブモジュールがありまして、 こいつはcloneしたあとのファイル数が23000ぐらいある、まあまあでかいリポジトリです。

なので、5/1の記事を書いた後、寝る前に make 叩いてビルドして、朝起きて結果見たら…… translations を取ってこれなくてビルド落ちてたんですね。

これは、まー、あー、しょうがないか……で、もう一度 make 叩いたのですが。 何度か試してるうちに、なぜか translations が正常にとってこれなくて空っぽなままなのにビルドが進むようになってしまい、 当然 "translations" 以下のファイルがないので途中で落ちてしまう……えー。なんでー。

ここもいろいろ試行錯誤したんですけど、結論からいうと以下のコマンド叩いたら復活できました。

git submodule deinit translations
git submodule init translations

要はいちどsubmoduleと、リモートリポジトリの紐づけ外してもう一度付けなおすみたいな感じですね。

これでもう一度 make 叩いたら、無事、translations の取得はできて*3、ビルド流れ始めました。やった!

Visual Studio 2019のコマンドラインツール( cl.exe とか)のメッセージを英語にする

で、ビルドの途中経過見ると、

□□□□: □C□□□N□□□[□h □t□@□C□□:        C:/cygwin/home/naruhiko/lode/dev/core/workdir/UnoApiHeadersTarget/offapi/normal\com/sun/star/chart2/data/XDataSequence.hdl
□□□□: □C□□□N□□□[□h □t□@□C□□:         C:/cygwin/home/naruhiko/lode/dev/core/workdir/UnoApiHeadersTarget/offapi/normal\com/sun/star/chart2/data/LabelOrigin.hdl

こんな感じになっちゃってます。 あーなるほど、VS2019同梱のコンパイラ cl.exe とかが日本語吐くからだな。

じゃあ、英語にしましょう。 最初この↓ 記事を参考にしたんですが(VS 2017の記事だけど手順は一緒かなって)、

usagi.hatenablog.jp

うまくいかなかったので*4、 もうあたま来て、日本語の言語パック消して英語のに入れ替えちゃうことにしました。

言語パックの追加削除はVisual Studio Installerで行います。前回の記事だと

Visual Studio 2019起動して、「コードなしで続行」選んで、ツール > ツールと機能の取得 を開きまして。

という動線を紹介しましたが、素直にVisual Studio Installerを起動>「Visual Studio 2019 Community」を選ぶ、でもいいです。

で、「言語パック」タブにて「英語」をチェックして、代わりに「日本語」のチェックを外して「変更」。

f:id:naruoga:20200503075132p:plain
日本語を消して英語だけにしちゃう

試してみますと、

>"c:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.25.28610\bin\Hostx64\x64\cl.exe" /?
Microsoft (R) C/C++ Optimizing Compiler Version 19.25.28614 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

                         C/C++ COMPILER OPTIONS


                              -OPTIMIZATION-

/O1 maximum optimizations (favor space) /O2 maximum optimizations (favor speed)
/Ob<n> inline expansion (default n=0)   /Od disable optimizations (default)
/Og enable global optimization          /Oi[-] enable intrinsic functions
/Os favor code space                    /Ot favor code speed
/Ox optimizations (favor speed)
/favor:<blend|AMD64|INTEL64|ATOM> select processor to optimize for, one of:
    blend - a combination of optimizations for several different x64 processors
    AMD64 - 64-bit AMD processors
    INTEL64 - Intel(R)64 architecture processors

わーい。英語になった。

巻き添えとして? VS2019のGUIも英語になっちゃいますが、まあ、いいですよね*5

あと、今思ったのですが、一度ビルド通した後はVS2019統合で開発できる*6 から、この手順要らんかった気がするな……。

動いた!

まあそんなわけで待つこと……どれぐらいだろ? 1時間よりは長くて8時間よりかは短い*7 感じかなー。

前にビルドしてたのはThinkpad X220 (Ubuntu)で4時間ぐらいだったかな、マシン早くなったのでもうちょっと早いかと思ってましたがHDDなのがいけないのか。

ともかく私家版ビルド、無事動きました! ビルドしたcygwinターミナルにて instdir/program/soffice & で起動できます。

f:id:naruoga:20200503080026p:plain
ビルドしたLibreOfficeのAbout画面

このリリースはnaruhikoが提供しました。

が誇らしい? ですね。

まーよかったよかった。ではまた次回の連載記事をお待ちください!

*1:前回紹介したlodeの導入手順も、coreのコードcloneしてくるのでその可能性はありました。私は問題なくできましたが。

*2:submoduleがわからない人はgit submoduleでググりましょう。私submoduleの使い方自信なくて毎度ググってる……。

*3:ものすごーーーーーく時間かかりましたけど。

*4:VS2019の機能で開発コンソール開くと英語になるのでよしよしって思ったら、 コマンドプロンプトから実行すると日本語のまま。今考えると環境変数とか見てるのかもしれない。

*5:というか、最初からVS2019英語で導入すればよかったのでは……?

*6:……たぶん。さりげなく次回予告ですが今日はこれを試す予定。

*7:幅あってすみません、別のパソコンで作業してほっといてたら、いつの間にか終わってたので。