みなさまあけましておめでとうございます。
こちらのブログではお久しぶりにございます。
もうすっかり姫踊子草の開発からは遠ざかっていますが、今でも自分では使っておりますし、今回年末年始の気の迷いでちょっとだけ関係のありそうなことをやったのでその記録がてらに戻ってまいりました。
お題は昨年12月4日に出た「Visual Studio 2019 Preview」を使ってGoogle日本語入力の辞書縮小+オープンソース版の「mozc」をビルドしてみよう、いうことでございまして。
Mozc - a Japanese Input Method Editor designed for multi-platform
現状の結論だけ先に書いておきますと次のとおり。
- キーボードからの日本語入力は無事できるところまで来た。
- 手書き入力は常にエラー表示になって使えない。
- Debugビルドができない。Releaseビルドだけ。
- unittestで2件ほど失敗する。
unittestではどちらも構造化例外が出ておりますので、動いているつもりのReleaseビルドでもなにかの拍子に動かなかったりすることもあるかもしれません。
ビルド環境は次のとおり。
- Windows 10 Pro 64bit
- 別にHome Editionでも問題ないと思います。
- 32bit版Windowsではビルド不可とmozcの説明にあり。最近はさすがに減ってきたと思いますけど。
- CPU: AMD A8-3820。
- 6年位前に買ったパソコンゆえそろそろ新調したいところ。
- 自分でもやってみたいという方は CPU BOSS あたりでご自身のパソコンのCPUと性能比較を確認しておきましょう。
- SSD: 512GB。SUNEAST SSD SE800 512GB。
- 最近気の迷いで買っちまったやつ。この頃SSDが値下げ傾向らしいですよ。
- Visual Studio 2019 Preview
- 設置時、Visual C++っぽいところ一箇所にチェックを入れてください。初期設定のままでは入りません。C++指定時に自動でつく対象だけ設置すればOKです。
- Qt 5.12.0
- この版から「MSVC 2015 32bit」がなくなっていますが、「MSVC 2017 32bit」で代用できるようです。本件では64bitのQtは使えません。
- Gitやコマンドプロンプトなど
- それらが使える知識がある前提の文章なので念の為。基本的な知識だけでいいです。
概説
MozcのビルドはGYPでビルド手順を確定し、NINJAでコンパイラやリンカその他諸々を動作させて生成という流れになっております。基本的には次のURLの説明に従うことになりますが、そうではないところを要点として綴っていきます。
How to build Mozc in Windows
要点1: depot_tools.zip版の GYP は Preview版VS2019 に未対応
ということで、mozcとは別に最新版GYPをダウンロードします。なお depot_tools.zip も mozc の上記リンクに書いてあるとおりにダウンロード・展開・gclient二回実行、までやっておきます。
要点2: mozcから外部のGYPを指定できそうだが実はできない
これは修正が簡単で、(mozc)\src\build_mozc.py の一部を下のように書き換えます。
修正前(526行目あたり):
gyp_dir = os.path.abspath(options.gypdir)
gyp_command = [os.path.join(gyp_dir, 'gyp')]
else:
# Use third_party/gyp/gyp unless 'gypdir' option is specified. This is
修正後:
gyp_dir = os.path.abspath(options.gypdir)
if IsWindows():
gyp_command = [os.path.join(gyp_dir, 'gyp.bat')]
else:
gyp_command = [os.path.join(gyp_dir, 'gyp')]
else:
# Use third_party/gyp/gyp unless 'gypdir' option is specified. This is
要点3: Preview版VS2019は最新版GYPでも補足はしない
Preview版なんだから当然なのかもしれません。
これはGYP側の説明にあるとおり、環境変数を何らかの形で指定します。
たとえば本件関連のバッチファイルにはすべて次の記述を入れておくとか。
まだ2019には対応していないので、2017で偽装。
set GYP_MSVS_OVERRIDE_PATH=C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview
set GYP_MSVS_VERSION=2017
要点4: mozcにVisual C++ 2015決め打ちのところがある
さっきのとも関係ありますが、こちらも (mozc)\src\build_mozc.py を修正します。
修正前(703行あたり):
if IsWindows():
gyp_options.extend(['-G', 'msvs_version=2015'])
if (target_platform == 'Linux' and
修正後:
if IsWindows():
gyp_options.extend(['-G', 'msvs_version=2017'])
os.environ['GYP_MSVS_OVERRIDE_PATH'] = 'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Preview'
os.environ['GYP_MSVS_VERSION'] = '2017'
if (target_platform == 'Linux' and
決め打ちのままなんでそれはそれでどうかと思いますが。
要点5: mozc の C++ コードに問題を抱えているところがある
そのままでビルドを始めたりすると、C2678という一見複雑そうなコンパイルエラーが出ます。
エラー内容:
C:\Program Files (x86)\Microsoft Visual Studio\2019\Preview\VC\Tools\MSVC\14.20.27027\include\xutility(3248): error C2678: 二項演算子 '*': 型 'const _InIt' の左オペランドを扱う演算子が見つかりません (または変換できません) (新しい動作; ヘルプを参照)。
with
[
_InIt=mozc::IteratorAdapter<const int *,mozc::storage::louds::`anonymous-namespace'::ZeroBitAdapter>
]
(以下略、全30行くらい)
これはconst vector<const int *>から要素の参照を得るときに非constの関数を経由しようとしているのが原因らしく、(mozc)\src\base\iterator_adapter.h の次の関数をコピーしてconst付きの同名同内容のものを作ってください。
119行あたり(下の関数を書き加える):
value_type operator*() {
return adapter_(iter_);
}
value_type operator*() const {
return adapter_(iter_);
}
要点6: GYPがコマンドライン引数にほぼ問答無用で os.path.normpath をかけている
これが目下のところ大問題です。いずれ不合理仕様ということで除かれるんじゃないかと思いますが。
当たり前ですが引数に渡されるものは単体の、またローカルマシンのパスであるとは限りません。
mozcの例では次のような引数があります。
--input=../../data/dictionary_oss/dictionary00.txt
pos_matcher:32:..\..\out_win\Debug\gen\data_manager\chromeos\pos_matcher.data
これらを os.path.normpath で変換すると
data\dictionary_oss\dictionary00.txt
out_win\Debug\gen\data_manager\chromeos\pos_matcher.data
になってしまってパスの前の部分がいずれも消えてしまい、使い物になりません。
mozcサイドでもメーリングリストでの報告は行っているようですが、すでに3ヶ月前の話。
動いた様子がないので困ったものです。
msvs_emulation.py destroy relative pathsvs_emulation.py destroy relative path
どうするかというと、話題の (gyp)/pylib/gyp/msvs_emulation.py を書き換えます。
残念ながら見ての通りどう考えても無理があります。ああ、今回私が書いた部分はこの記事ごとBSD三項ライセンスでいいから。
pylib/gyp/msvs_emulation.py | 40 ++++++++++++++++++++++++++++++++++++++--
1 file changed, 38 insertions(+), 2 deletions(-)
diff --git a/pylib/gyp/msvs_emulation.py b/pylib/gyp/msvs_emulation.py
index 63d40e63..4ff51a5e 100644
--- a/pylib/gyp/msvs_emulation.py
+++ b/pylib/gyp/msvs_emulation.py
@@ -23,10 +23,45 @@ try:
except NameError:
basestring = str
+enable_normpath = 1 # 0 to False, 1 to True, but cannot build with 1? 0? or both?
windows_quoter_regex = re.compile(r'(\\*)"')
+def OptionRemainedNormpathForRspFile(arg):
+ """Apply os.path.normpath except the option declaration part.
+ Problematic case is '--optionname=../relpath/arg' or
+ '-o../relpath/arg'. When the name of option and argument for the
+ option is separated with spaces, the caller of this function split them
+ and no problems occur. Also, if arg doesn't have relative path,
+ os.path.normpath works without problems.
+
+ We should consider arg is 'some_option:param_for_option:path' style.
+ os.path.normpath('a:b:..\\just_child_relative_path') returns same but
+ os.path.normpath('a:b:..\\..\\more_deep_relative_path') returns
+ 'more_deep_relative_path'. We should avoid it too.
+ """
+ if not enable_normpath:
+ return arg
+ if arg.find('\\') == -1:
+ # Because that can be a full of, or a part of URL.
+ return arg
+ if arg[-1:] == '/':
+ # Because that can be required last slash to join another string.
+ # os.path.normpath('somepath/') -> 'somepath'
+ return arg
+ mo = re.search(r'--[^=]*=|-[^.-]?', arg)
+ if mo and mo.start() == 0:
+ return mo.group(0) + OptionRemainedNormpathForRspFile(arg[mo.end(0):])
+ rpos = arg.rfind(':')
+ if rpos == len(arg) - 1:
+ # Because os.path.normpath('') returns '.'
+ return arg
+ if rpos >= 0:
+ return arg[:rpos + 1] + os.path.normpath(arg[rpos + 1:])
+ return os.path.normpath(arg)
+
+
def QuoteForRspFile(arg):
"""Quote a command line argument so that it appears as one argument when
processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for
@@ -38,8 +73,9 @@ def QuoteForRspFile(arg):
# use that function to handle command line arguments.
# Use a heuristic to try to find args that are paths, and normalize them
+ # NOTE: if arg doesn't contain backslashs, no changes will be made.
if arg.find('/') > 0 or arg.count('/') > 1:
- arg = os.path.normpath(arg)
+ arg = OptionRemainedNormpathForRspFile(arg)
# For a literal quote, CommandLineToArgvW requires 2n+1 backslashes
# preceding it, and results in n backslashes + the quote. So we substitute
@@ -71,7 +107,7 @@ def EncodeRspFileList(args):
if not args: return ''
if args[0].startswith('call '):
call, program = args[0].split(' ', 1)
- program = call + ' ' + os.path.normpath(program)
+ program = call + ' ' + OptionRemainedNormpathForRspFile(program)
else:
program = os.path.normpath(args[0])
return program + ' ' + ' '.join(QuoteForRspFile(arg) for arg in args[1:])
これを diff ファイルとしてどうにかこうにかして適用。
ビルドできない問題点としてはこれで全部のはず。あと、(gyp)/pylib/gyp/generator/ninja.jp で /showIncludes が指定されているせいでログファイルが14MBとかになっちゃうのであらかじめこれらを削っておくのが吉。
だんだん疲れてきたので記事が投げっぱなしになってますが、だいたいこれで通るはず。幸運を祈る。