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

f:id:nebocco:20210501171635p:plain
???

f:id:nebocco:20210501171653p:plain
?????

コマンドが走らず、スクリプトファイル自体を開こうとしてしまいます。どうやら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.pyEditFile() を以下のように書き換えます。

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

pythonsignal というモジュールを使っているらしいのですが、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.pyCodeBase._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

ポンコツ太郎がよ……

Pythonopen() はちゃんと文字コードを指定してあげないとこういうことが起こりがちなので、ちゃんと 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」と書かれた箇所をクリックすることで自分でエンコードを指定して開くことができます。

f:id:nebocco:20210501193656p:plain
ここ

'diff' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

subprocess でコマンドを実行する際は Powershell ではなく cmd.exe を呼び出すのですが、残念ながら cmd.exe には diff というコマンドはありません。代わりに fc というコマンドがあるので、これを使えばよいです。rime/basic/codes.pyInternalDiffCode.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 も判定できました

やった~~~~~~~!!!!!!!!!!!!!!!!!!!!!!

おわりに

とてもがんばりました。

よき作問ライフを!