Rime on Windows10
競技プログラミング作問支援ツール Rime を Windows10 で使えるように改造しましょう!
Mac なんかに……負けないっ……!
おことわり
- 作業にあたって Rime 非公式ドキュメント を大いに参考にさせていただきました。基本的にはここのチュートリアルに従い、windowsで正しく動かなかったら適宜書き換えていく、という方針で作業を行います。
- 自己責任でお願いします。そんなにパソコン全体に影響のあるような作業はしないので大丈夫だとは思いますが……
- 動かない原因をコードを読みながら延々と試行錯誤した結果なので、書き換えなくていい場所や、もっと効率のいい方法があるかもしれません。
- 「同じように作業をしたのにここが違う!」みたいなことを言われても何もわからないので自分でコードと公式リファレンス読んでください。
- Powershell を使用します。
インストール
PS C:\home> pip install git+https://github.com/icpc-jag/rime Collecting git+https://github.com/icpc-jag/rime ...(略)...
インストールが完了したことを確かめましょう。
PS C:\home> rime --help
コマンドが走らず、スクリプトファイル自体を開こうとしてしまいます。どうやらMacではファイル名を入力するだけでPythonを起動できるようなのですが、Windowsではご存じのように
python file.py
のように入力しなければなりません。ではそのようにしてみましょう。
PS C:\home> python rime --help C:\Users\Personal\AppData\Local\Programs\Python\Python39\python.exe: can't open file 'C:\home\rime': [Errno 2] No such file or directory
どうやら二番目以降の引数はパスが通っていても認識してくれないようです。まったくもう
これをフルパスで入力するとちゃんと動きます。
PS C:\home> python 'C:\Users\Personal\AppData\Local\Programs\Python\Python39\Scripts\rime' --help rime.py <command> [<options>...] [<args>...] Rime is a tool for programming contest organizers to automate usual, boring and error-prone process of problem set preparation. It supports various programming contest styles like ACM-ICPC, TopCoder, etc. by plugins. To see a brief description and available options of a command, try: rime.py help <command> Commands: ...(略)...
ただ毎回こんなことはやっていられないので、起動しやすい場所に持ってくることにします。
Rimeで作問をする専用のディレクトリを作成し、その中にさっきの場所にあるrimeとrime_initをコピーしてきましょう。エクスプローラを使ってマウスでやっても大丈夫です。(そのほうが速いかも)
PS C:\home> mkdir Rime ディレクトリ: C:\home Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2021/05/01 17:25 Rime PS C:\home> cd Rime PS C:\home\Rime> cp 'C:\Users\Personal\AppData\Local\Programs\Python\Python39\Scripts\rime' . PS C:\home\Rime> cp 'C:\Users\Personal\AppData\Local\Programs\Python\Python39\Scripts\rime_init' . PS C:\home\Rime> ls ディレクトリ: C:\home\Rime Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2021/05/01 17:11 172 rime -a---- 2021/05/01 17:11 2421 rime_init
こうするとほぼ本家と同じように書くことができます。
PS C:\home\Rime> py rime --help rime.py <command> [<options>...] [<args>...] Rime is a tool for programming contest organizers to automate usual, boring and error-prone process of problem set preparation. It supports various programming contest styles like ACM-ICPC, TopCoder, etc. by plugins. To see a brief description and available options of a command, try: rime.py help <command> Commands: ...(略)...
以上のように、コマンドすべての頭に py
を付ければOKです。 py
を登録していない人は python
に置き換えてください。
これでインストールは完了です。
チュートリアル
ディレクトリの初期化
PS C:\home\Rime> py rime_init --git Initialized empty Git repository in C:/home/Rime/.git/ [master (root-commit) fe093c2] Initial commit 3 files changed, 5049 insertions(+) create mode 100644 .gitignore create mode 100644 PROJECT create mode 100644 common/testlib.h
新規問題の作成
subprocess
PS C:\home\Rime> py rime add . problem aplusb Note: Running Rime under Windows will be unstable. Traceback (most recent call last): File "c:\users\personal\appdata\local\programs\python\python39\lib\site-packages\rime\core\main.py", line 144, in Main ...(中略)... FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。
出力ログを見ると、途中で call
という関数を呼んでいることが分かります。これは subprocess
というモジュールの関数なのですが、このモジュールでシェルコマンドを呼び出すためには shell=True
という引数を与える必要があります。Rime では subprocess
から call()
のほかに check_output()
と Popen()
を使っています。これらについても shell=True
を与える必要があります。
一度、Rime の中身のコードが置かれているディレクトリに移動します。
PS C:\home\Rime> cd 'C:\users\personal\appdata\local\programs\python\python39\lib\site-packages\rime' PS C:\..\..\rime>
以降、Rime 本体のコードに言及する時は基本的にこの中にあるものを指していると思ってください。スクリプト内で call()
を使っている箇所をすべて見つけるため、以下のコマンドで検索します。
PS C:\..\..\rime> findstr /s /n 'call\(' *.py plugins\plus\commands.py:63: call([EDITOR, filename])
これで 'call(' が書かれている箇所をすべて探すことができました。call()
は一度しか使われていないようです。 plugins\plus\commands.py
を見に行って、以下のように書き換えます。
def EditFile(filename, initial): EDITOR = os.environ.get('EDITOR', 'vi') files.WriteFile(initial, filename) call([EDITOR, filename], shell=True)
check_output()
, Popen()
についても同じように探して書き換えます。
comple(None, ...)
あたらめて実行しましょう。最初の実行時に作成できてしまったディレクトリは削除しておきます。
PS C:\home\Rime> py rime add . problem aplusb Note: Running Rime under Windows will be unstable. ERROR: aplusb: compile() arg 1 must be a string, bytes or AST object
これは core/targets.py
内の TargetBase.Load()
内部で、compile()
に None
を渡してしまっていることが原因です。そのため、以下のように条件分岐を付け加えておきます。
# Evaluate config. try: script = files.ReadFile(self.config_file) except IOError: raise ConfigurationError('cannot read file: %s' % self.config_file) if script is None: return try: code = compile(script, self.config_file, 'exec') self.PreLoad(ui) exec(code, self.exports, self.configs) self.PostLoad(ui) except ReloadConfiguration: raise # Passthru except Exception as e: # TODO(nya): print pretty file/lineno for debug raise ConfigurationError(e)
vim
PS C:\home\Rime> py rime add . problem aplusb Note: Running Rime under Windows will be unstable. 'vi' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。 [ ADD ] C:\home\Rime\aplusb/PROBLEM Error Summary: Total 0 errors, 0 warnings
問題ディレクトリの作成に成功すると自動で vim が起動するのですが、私は vim を入れていないので起動に失敗します。代わりに Visual Studio Code を指定します。
plugins/plus/commands.py
の EditFile()
を以下のように書き換えます。
def EditFile(filename, initial): EDITOR = os.environ.get('EDITOR', 'code') files.WriteFile(initial, filename) call([EDITOR, filename], shell=True)
お好きなエディタを指定してください。
ここまで済ませると無事に Rime/aplusb
が作成され、Rime/aplusb/PROBLEM
が自動で開きます!お疲れさまでした。まだまだ続きます。
PS C:\home\Rime> ls ディレクトリ: C:\home\Rime Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2021/05/01 18:21 aplusb d----- 2021/05/01 17:36 common -a---- 2021/05/01 17:36 72 .gitignore -a---- 2021/05/01 17:36 671 PROJECT -a---- 2021/05/01 17:11 172 rime -a---- 2021/05/01 17:11 2421 rime_init PS C:\home\Rime> ls aplusb ディレクトリ: C:\home\Rime\aplusb Mode LastWriteTime Length Name ---- ------------- ------ ---- -a---- 2021/05/01 18:21 408 PROBLEM
解答プログラムの作成
先ほど作成した
aplusb/
ディレクトリ内で、以下のコマンドを実行してみましょう。
と書かれていますが、Rime/
でもパスを指定する引数を書き換えれば同じことができるのでそうします。私は Rust が好きなので Rust で書きます。
PS C:\home\Rime> py rime add aplusb solution rust_correct Note: Running Rime under Windows will be unstable. [ ADD ] C:\home\Rime\aplusb\rust_correct/SOLUTION Error Summary: Total 0 errors, 0 warnings
Rime/aplusb/rust_correct/SOLUTION
が自動で開きます。Rust 用の行をコメントアウトして、ans.rs
を書きましょう。
fn main() { let mut io = IO::new(); input!{ from io, a: i32, b: i32 } println!("{}", a + b); }
SOLUTION
を見れば分かるように、コンパイルは rustc を通して行われるので、cargo を使ってほかのクレートを持ってくることができません。入出力関数なども自分で用意しましょう。
テスト用プログラムの作成
全部自分で書かなければならないので、generator と validator を Rust で作るのはかなり絶望的です。あきらめて C++ で書くことにします。
ここまで来たら
PS C:\home\Rime> py rime add aplusb testset tests Note: Running Rime under Windows will be unstable. [ ADD ] aplusb: C:\home\Rime\aplusb\tests/TESTSET Error Summary: Total 0 errors, 0 warnings
も問題なく実行できるはずなので、その後は本家に則って作業してください。私はコピペしました。
テストの実行
SIGXCPU
PS C:\home\Rime> py rime test aplusb Note: Running Rime under Windows will be unstable. [ COMPILE ] aplusb/tests: generator.cc ERROR: aplusb/tests: generator.cc: Compile Error (On compiling: module 'signal' has no attribute 'SIGXCPU') [ COMPILE ] aplusb/tests: validator.cc ERROR: aplusb/tests: validator.cc: Compile Error (On compiling: module 'signal' has no attribute 'SIGXCPU') Build Summary: aplusb ... in: 0B, diff: 0B, md5: d41d8cd98f00b204e9800998ecf8427e rust_correct RUST 236 lines, 5.1kB Test Summary: aplusb ... 1 solutions, 0 tests rust_correct FAIL Failed to build tests Error Summary: ERROR: aplusb/tests: generator.cc: Compile Error (On compiling: module 'signal' has no attribute 'SIGXCPU') ERROR: aplusb/tests: validator.cc: Compile Error (On compiling: module 'signal' has no attribute 'SIGXCPU') Total 2 errors, 0 warnings
python の signal というモジュールを使っているらしいのですが、SIGXCPU
という変数は存在しません。どうやら Python3.5 で SIG_DFL
という変数に統合されてしまったようです。先ほどと同様、 findstr
を使って検索してすべて書き換えましょう。
ここでは実行に時間が掛かりすぎている場合に処理を切り替えるような作業を行っていますが、コンパイルを行う際は必ず引っかかっているようです。これが正しい挙動なのかはわかりません。動けばよかろうなのです。だれか助けて~~~
seek
PS C:\home\Rime> py rime test aplusb Note: Running Rime under Windows will be unstable. [ COMPILE ] aplusb/tests: generator.cc ERROR: aplusb/tests: generator.cc: Compile Error (On compiling: 'int' object has no attribute 'seek') [ COMPILE ] aplusb/tests: validator.cc ERROR: aplusb/tests: validator.cc: Compile Error (On compiling: 'int' object has no attribute 'seek') Build Summary: aplusb ... in: 0B, diff: 0B, md5: d41d8cd98f00b204e9800998ecf8427e rust_correct RUST 236 lines, 5.1kB Test Summary: aplusb ... 1 solutions, 0 tests rust_correct FAIL Failed to build tests Error Summary: ERROR: aplusb/tests: generator.cc: Compile Error (On compiling: 'int' object has no attribute 'seek') ERROR: aplusb/tests: validator.cc: Compile Error (On compiling: 'int' object has no attribute 'seek') Total 2 errors, 0 warnings
rime/basic/codes.py
の CodeBase._ResetIO()
内で args
(stdin
, stdout
, stderr
) に対して seek()
を呼びますが、stderr
として渡される subprocess.STDOUT
はオブジェクトではなく、特殊な名前を持つただの整数です。そこで、以下のように書き換えます。
def _ResetIO(self, *args): for f in args: if f is None or f is subprocess.STDOUT: continue try: f.seek(0) f.truncate() except IOError: pass
UTF-8
PS C:\home\Rime> py rime test aplusb Note: Running Rime under Windows will be unstable. [ COMPILE ] aplusb/tests: generator.cc [ COMPILE ] aplusb/tests: validator.cc [ GENERATE ] aplusb/tests: generator.cc [ VALIDATE ] aplusb/tests: 02_random_01.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_02.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_03.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_04.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_05.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_06.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_07.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_08.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_09.in: PASSED [ VALIDATE ] aplusb/tests: 02_random_10.in: PASSED [ VALIDATE ] aplusb/tests: OK [ COMPILE ] aplusb/rust_correct [ REFRUN ] aplusb/rust_correct: 02_random_01.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_02.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_03.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_04.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_05.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_06.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_07.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_08.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_09.in: DONE [ REFRUN ] aplusb/rust_correct: 02_random_10.in: DONE [ REFRUN ] aplusb/rust_correct [ TEST ] aplusb/rust_correct ERROR: aplusb/rust_correct: 02_random_01.in: Wrong Answer judge log: C:\home\Rime\aplusb\rime-out\rust_correct\02_random_01.judge [ TEST ] aplusb/rust_correct: 02_random_01.in: Wrong Answer Build Summary: aplusb ... in: 40B, diff: 25B, md5: - rust_correct RUST 236 lines, 5.1kB Test Summary: aplusb ... 1 solutions, 10 tests rust_correct FAIL 02_random_01.in: Wrong Answer Error Summary: ERROR: aplusb/rust_correct: 02_random_01.in: Wrong Answer judge log: C:\home\Rime\aplusb\rime-out\rust_correct\02_random_01.judge Total 1 errors, 0 warnings
コンパイルはできましたが、どうやらジャッジが合わないようです。output を作成するコードと解答コードが同一なのに……どういうことでしょうか?ログに書かれている judge log を確認してみます。
'diff' �́A�����R�}���h�܂��͊O���R�}���h�A ����\�ȃv���O�����܂��̓o�b�` �t�@�C���Ƃ��ĔF������Ă��܂���B
ポンコツ太郎がよ……
Python の open()
はちゃんと文字コードを指定してあげないとこういうことが起こりがちなので、ちゃんと UTF-8 を指定してあげましょう。結構たくさんあるので頑張ってください。
def _ExecForCompile(self, args): with open(os.path.join(self.out_dir, self.log_name), 'w', encoding='utf-8') as outfile: yield (yield self._ExecInternal( args=args, cwd=self.src_dir, stdin=files.OpenNull(), stdout=outfile, stderr=subprocess.STDOUT))
こんな感じです。
diff
これでもう一度実行して judge log を見ます。
'diff' �́A�����R�}���h�܂��͊O���R�}���h�A ����\�ȃv���O�����܂��̓o�b�` �t�@�C���Ƃ��ĔF������Ă��܂���B
どうして……
私の力では judge log を出力しているのがどこなのか分からず、文字化けを治すことができませんでした。仕方がないのでここを直すのはあきらめて別エンコードで開くことにします。VSCode であれば下部の「UTF-8」と書かれた箇所をクリックすることで自分でエンコードを指定して開くことができます。
'diff' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。
subprocess
でコマンドを実行する際は Powershell ではなく cmd.exe を呼び出すのですが、残念ながら cmd.exe には diff
というコマンドはありません。代わりに fc
というコマンドがあるので、これを使えばよいです。rime/basic/codes.py
の InternalDiffCode.Run()
を書き換えます。
def Run(self, args, cwd, input, output, timeout, precise, redirect_error=False): parser = optparse.OptionParser() parser.add_option('-i', '--infile', dest='infile') parser.add_option('-d', '--difffile', dest='difffile') parser.add_option('-o', '--outfile', dest='outfile') (options, pos_args) = parser.parse_args([''] + list(args)) run_args = ('fc', options.difffile, options.outfile) # ここ with open(input, 'r', encoding='utf-8') as infile:
UTF-8 として比較するようなオプションがあり、それがないと非ASCII文字を出力する問題でバグりそうな予感がしますが、そのオプションを付けると改行が \n
か \n\r
かが区別され、正しい出力でも WA と判定されてしまうので付けないことにしました。
これで実行できるはずです。
PS C:\home\Rime> py rime test aplusb Note: Running Rime under Windows will be unstable. [ COMPILE ] aplusb/rust_correct: up-to-date [ TEST ] aplusb/rust_correct [ TEST ] aplusb/rust_correct: 02_random_01.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_02.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_03.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_04.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_05.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_06.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_07.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_08.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_09.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_10.in: PASSED [ TEST ] aplusb/rust_correct: max 0.03s, acc 0.29s Build Summary: aplusb ... in: 40B, diff: 25B, md5: - rust_correct RUST 236 lines, 5.1kB Test Summary: aplusb ... 1 solutions, 10 tests rust_correct OK max 0.03s, acc 0.29s Error Summary: Total 0 errors, 0 warnings
誤答が落ちることをテストする
せっかくなのでここも試してみます。今度はpythonで書いてみることにします。
PS C:\home\Rime> py rime add aplusb solution py_wa Note: Running Rime under Windows will be unstable. [ ADD ] C:\home\Rime\aplusb\py_wa/SOLUTION
SOLUTION
にも書かれているとおり、 shebang を忘れないようにしてください。フルパスで書かなければならず結構面倒です。ここもうまく書き換えれば shebang を省略できる気がするんですが、あんまり試してないので良く分かりません。
#!C:\Users\Personal\AppData\Local\Programs\Python\Python39\python.exe a, b = map(int, input().split()) if a % 5 == 0: a -= 1 print(a + b)
PS C:\home\Rime> py rime test aplusb Note: Running Rime under Windows will be unstable. [ TEST ] aplusb/py_wa [ TEST ] aplusb/py_wa: 02_random_01.in: PASSED [ TEST ] aplusb/py_wa: 02_random_02.in: PASSED [ TEST ] aplusb/py_wa: 02_random_03.in: PASSED [ TEST ] aplusb/py_wa: 02_random_04.in: PASSED [ TEST ] aplusb/py_wa: 02_random_05.in: PASSED [ TEST ] aplusb/py_wa: 02_random_06.in: Wrong Answer [ COMPILE ] aplusb/rust_correct: up-to-date [ TEST ] aplusb/rust_correct [ TEST ] aplusb/rust_correct: 02_random_01.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_02.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_03.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_04.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_05.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_06.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_07.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_08.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_09.in: PASSED [ TEST ] aplusb/rust_correct: 02_random_10.in: PASSED [ TEST ] aplusb/rust_correct: max 0.02s, acc 0.20s Build Summary: aplusb ... in: 40B, diff: 25B, md5: - rust_correct RUST 236 lines, 5.1kB py_wa SCRIPT 6 lines, 142B Test Summary: aplusb ... 2 solutions, 10 tests rust_correct OK max 0.02s, acc 0.20s py_wa OK 02_random_06.in: Wrong Answer Error Summary: Total 0 errors, 0 warnings
しっかり WA も判定できました
やった~~~~~~~!!!!!!!!!!!!!!!!!!!!!!
おわりに
とてもがんばりました。
よき作問ライフを!