tseki blog

my study room

AtCoder を `acr new abc400` ですぐに始められる Rust 製 CLI を作った話

AtCoder のコンテストを解くとき、毎回地味に面倒だったのが「開始前の準備」と「提出周り」でした。
ディレクトリを切って、Cargo.toml を書いて、サンプルケースをブラウザからコピペして、エディタとブラウザを並べて……

そんな面倒さを全部 1 コマンドに押し込んだ CLI ツールが acr です。

github.com

"AtCoder-Rust" を略して "acr" としました。 Rust 製のCLIで、acr new abc400 とだけ叩けば、Cargo ワークスペースが生成され、サンプル入力が全問分 fetch され、エディタと問題ページがブラウザで開き、あとは解き始めるだけ——そういう体験を目指しました。

この記事では、acr でできることを一通り紹介したうえで、開発中にハマった「Cloudflare Turnstile 問題」や、配布・リリースまわりの工夫なども書いていきます。

30 秒で見る acr

# インストール(Linux / macOS)
curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/t-seki/acr/releases/latest/download/acr-cli-installer.sh | sh

# 初期設定(エディタとブラウザを対話式に設定)
acr init

# ログイン(ブラウザから REVEL_SESSION クッキーを貼るだけ)
acr login

# コンテスト開始
acr new abc400

これだけで abc400/ 配下に A〜F 問題の Cargo プロジェクトが並び、abc400/a/src/main.rs がエディタで、問題 A のページがブラウザで開きます。

解き終わったら、その問題のディレクトリで:

acr test     # サンプルケースで一括テスト(色付き)
acr submit   # テストを通してからブラウザで提出画面を開く

主要機能

acr new — ワンコマンドのワークスペース生成

acr new abc400 は、裏で AtCoder の問題ページを叩いてサンプルケースを全部 scrape し、tests/1.intests/1.out 形式で保存します。
Cargo.toml には後述の "AtCoderで利用可能" な依存一覧がピン留めされ、src/main.rs はユーザーが ~/.config/acr/template.rs に置いたテンプレートから生成されます。

--at フラグ — 開始時刻まで待機する

コンテストに備えて、acr new abc400 --at 21:00 と打っておけば、指定時刻まで待機して、タスクが公開された瞬間にワークスペースを生成してくれます。
--at 直後は AtCoder 側も混み合うので、初回リトライの間隔を短くしたり 404 をリトライ対象に含めたり、このあたりはコンテスト実戦で試しながら何度か調整を入れました。

同じ待機メカニクスを使った acr virtual abc300 も用意しています。過去コンテストを選んで、任意の時刻からバーチャル参加を始められます。

"AtCoderで利用可能" な依存クレートのピン留め

ローカルではコンパイルできてもジャッジで CE になったら悲しいですよね。
acr new が生成する Cargo.toml は、AtCoder ジャッジが提供しているクレートセットとバージョンをそのままピン留めします。
ac-library-rsproconioitertoolsnumnalgebrandarraypetgraphrustc-hash などが最初から使えて、ローカルで通れば基本的にジャッジでも通ります。

ジャッジ側のクレートセットが更新されたときは、acr update -d で既存ワークスペースの Cargo.toml を追従させられます。

エディタ/ブラウザ統合

acr init でエディタとブラウザコマンドを設定でき、フラグ込みでも OK です:

acr config editor "code --new-window"
acr config browser "firefox --new-window"

WSL2 から Windows 側の Chrome を叩くような、パスにスペースが入るケースも shlex でパースしています。

テンプレート共有

~/.config/acr/template.rs がコードファイルを作成する際のテンプレートになります。
他人のテンプレートを使いたいときは:

acr template add https://gist.github.com/someone/abcdef1234
acr template add https://github.com/someone/dotfiles/blob/main/atcoder.rs
acr template show    # 現在のテンプレートを表示
acr template reset   # ビルトインに戻す

GitHub の blob URL や Gist のきれいな URL は自動で raw URL に書き換えます。
上書き前には .bak に退避するので、気軽に試せる設計にしています。

開発の裏側

なぜ Rust か

(1) 自分が AtCoder を解くなら Rust を使いたかった
(2) インストールやカスタマイズが容易なツールが欲しかった
(3) 単一バイナリで配れるので macOS / Linux / Windows のユーザーに同じ体験を届けやすい

という理由です。

Cloudflare Turnstile との戦い

開発中に一番大きかった方針転換は、AtCoder が Cloudflare Turnstile を導入していたことでした。
これにより、従来の CLI ツールが使っていた「フォームに username / password を POST して cookie を受け取る」タイプの自動ログインが、事実上できなくなってしまいました。

最初はログインや提出もターミナルから行う実装を考えていましたが、Turnstile によるエラーがどうしても解消できませんでした。
ターミナルから提出・結果確認が出来たら嬉しいとは思いつつも、Headless Chrome を使ったりなどはしたくなかったので、「ログインと提出はブラウザに任せる」 方針に倒しました。

  • acr login は AtCoder のログインページをブラウザで開き、ユーザーが DevTools から REVEL_SESSION cookie 値を 1 回だけコピペする方式に
  • 提出 (acr submit) はテストを通したうえで、提出ページをブラウザで開く。コードは事前にクリップボード (arboard crate を使用) に載せておくので、提出画面で Ctrl+V → Submit の 2 アクションで送れる

「完全自動」を諦める代わりに、「CLI ツールが壊れ続けない」という安心感を取った形です。

配布パイプライン

個人ツールを真面目に配るのは初めてだったので、ここもけっこう勉強になりました。現状は 4 ルートで配っています:

  1. Prebuilt binary(shell / PowerShell インストーラ)— cargo-dist が GitHub Actions で macOS・Linux・Windows のバイナリを作って Release に添付
  2. Homebrew tapt-seki/acr)— 同じく cargo-dist が formula を生成
  3. cargo binstall — prebuilt を引いてくる
  4. cargo install acr-cli — ソースからビルド

リリース自体は release-please を使った Conventional Commits 駆動で、
feat:fix: を main にマージすると自動的に "Release PR" が立ち、マージするとタグと GitHub Release が作成されます。
タグ push をトリガーに cargo publish が走りますが、このとき crates.io の Trusted Publishing を使っていて、OIDC 経由で短命トークンを発行しているので、crates.io のトークンをリポジトリシークレットに置かずに済むのが便利でした。

この一式を組むまでに CI/CD の試行錯誤が地味に多く、特に release-please と cargo-dist のどちらに GitHub Release の所有権を持たせるかに関しては少々苦労しました。

これから

数週間このツールを使ってみて、使い心地に関してはわりと満足しています。

直近の課題としては、ローカルテスト実行時のパフォーマンス改善あたりを考えています。
もし要望や issue がありましたら GitHub リポジトリ までお願いします。

おわりに

acr は「コンテストに集中するための環境を整える」ことを目的に作った小さなツールですが、
Cloudflare 越しにどう対処するか、Rust 製 CLI をどう配るか、といった副産物の学びが多くて楽しかったです。
Rust で競プロやっている方、もしよかったら試してみていただけると嬉しいです。

私も AtCoderのレーティング上げ頑張ります。

curl で AWS の SigV4 署名ができるようになったらしいのでやってみた

  • きっかけ
  • HTTP API の作成
  • curl でアクセスする
  • --aws-sigv4 オプションについて

きっかけ

Tori さんのツイートを見て試してみたくなったのでやってみます。
HTTP API に IAM 認証を設定し、curl でアクセスしてみます。

続きを読む

Amazon Cognito で複数の Idp を使って1人のユーザーを認証する

課題

例えば「Googleでサインイン」を併用している場合、
特に対策を行わない状態では、
同じメールアドレスでも Cognito UserPool のユーザーと Google のユーザーが別々に作成されます。

f:id:aba_0921:20210620001511p:plain

f:id:aba_0921:20210620001939p:plain

こんな時に、
同じメールアドレスのユーザーが統合され、
1人のユーザーが複数の認証方法を使ってサインイン出来るようにする方法についてメモします。

続きを読む

ソースコードが読みにくい理由

GWも折返し地点に差し掛かりましたが、皆さんいかがお過ごしでしょうか。

やや話題の「ユニコーン企業のひみつ」を読んで
組織やビジョナリー・カンパニー的な仕事のモチベーションに想いを馳せつつ、
GWの前半は手元のコードを眺めていました。

プロジェクトで決められたことしかやらないうちに
負債が溜まったコードとはまさにこのこと。

今日は、いろいろと弄っているうちに感じた
「このコードが読みにくい理由」
を書き留めておきます。

読みやすさには個人差があるのと、
C# 以外のケースでは必ずしも当てはまらない内容かもしれません。

続きを読む

kubeadm & containerd on Vagrant(VirtualBox)

kubeadm を使った kubernetes クラスターの作成を行います。
dockershim が非推奨になることもあり、今回は containerd を使ってみたいと思います。

続きを読む