競技プログラミング用にRustコードを一つの.rsファイルにバンドルするCargoサブコマンドです。
更新情報はCHANGELOG.mdにあります。
```toml [package] name = "solve" version = "0.0.0" edition = "2018"
[dependencies] ac-library-rs-parted = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-convolution = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-dsu = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-fenwicktree = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-lazysegtree = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-math = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-maxflow = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-mincostflow = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-modint = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-scc = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-segtree = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-string = { git = "https://github.com/qryxip/ac-library-rs-parted" } ac-library-rs-parted-twosat = { git = "https://github.com/qryxip/ac-library-rs-parted" } qryxip-competitive-fastout = { git = "https://github.com/qryxip/competitive-programming-library" } qryxip-competitive-input = { git = "https://github.com/qryxip/competitive-programming-library" } qryxip-competitive-tonelli-shanks = { git = "https://github.com/qryxip/competitive-programming-library" }
```
```rust
extern crate input as _;
extern crate fastout as _;
use aclmodint::ModInt; use tonellishanks::ModIntBaseExt as _;
fn main() { input! { yps: [(u32, u32)], }
for (y, p) in yps {
ModInt::set_modulus(p);
if let Some(sqrt) = ModInt::new(y).sqrt() {
println!("{}", sqrt);
} else {
println!("-1");
}
}
} ```
↓
console
❯ cargo equip --resolve-cfgs --remove comments docs --rustfmt --check -o ./bundled.rs
Running `/home/ryo/.cargo/bin/rustup run nightly cargo udeps --output json -p solve --bin solve`
Checking solve v0.0.0 (/home/ryo/src/local/play-cargo-equip/solve)
Finished dev [unoptimized + debuginfo] target(s) in 1.76s
info: Loading save analysis from "/home/ryo/src/local/play-cargo-equip/solve/target/debug/deps/save-analysis/solve-99d680d49de8dec4.json"
Running `/home/ryo/.rustup/toolchains/1.42.0-x86_64-unknown-linux-gnu/bin/cargo check --message-format json -p 'solve:0.0.0' --bin solve`
Checking solve v0.0.0 (/home/ryo/src/local/play-cargo-equip/solve)
Finished dev [unoptimized + debuginfo] target(s) in 1.75s
Bundling the code
Running `/home/ryo/.rustup/toolchains/1.42.0-x86_64-unknown-linux-gnu/bin/cargo check`
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
warning: declaring `extern crate .. as ..` in a root module is not recommended: ` extern crate __acl_internal_math as internal_math`
Checking cargo-equip-check-output-gasbxby00cjlqd1x v0.1.0 (/tmp/cargo-equip-check-output-gasbxby00cjlqd1x)
Finished dev [unoptimized + debuginfo] target(s) in 0.40s
Submit Info #33161 - Library-Checker
nightlyツールチェインとcargo-udepsもインストールしてください。
console
❯ rustup update nightly
console
❯ cargo install cargo-udeps
console
❯ cargo install cargo-equip
masterconsole
❯ cargo install --git https://github.com/qryxip/cargo-equip
バイナリでの提供もしています。
cargo-equipで展開できるライブラリには以下の制約があります。
各crate rootには#[macro_export]したマクロと同名なアイテムが存在しないようにする。
cargo-equipはmod lib_name直下にpub use crate::{ それらの名前 };を挿入するため、展開後のuseで壊れます。
bin側ではマクロは#[macro_use]で使ってください。
```rust // in main source code
extern crate input as _; ```
bin内のextern crateはコメントアウトされます。
```rust // in main source code
/#[macro_use]
extern crate input as _;/ // as _でなければuse crate::$name;が挿入される
```
Rust 2015に展開する場合のみ、共に展開する予定のクレートを使うときにextern preludeから直接名前を解決しない。
ルートモジュール以外のモジュールでextern crateを宣言してマウントし、そこを相対パスで参照してください。
cargo-equipは--exclude <SPEC>..., --exclude-atcoder-crates, --exclude-codingame-cratesで指定されたクレートを除いて、
extern crateをuse crate::extern_crate_name_in_main_crate;に置き換えます。
lib同士をexter crateで参照する場合、誤って直接使わないように対象の名前はリネームしておくことを強く推奨します。
```diff mod extern_crates {
pub(super) use crate::another_lib; }
use self::externcrates::anotherlib::foo::Foo; // Prepend self:: to make compatible with Rust 2015
```
AOJ ~~やyukicoder~~ 等のRustが2018が利用できないサイトにこのツールを使用しないなら不要です。
2018向けにはcargo-equipは各ライブラリにこのようなmod __pseudo_extern_preludeを作り、extern preludeの代用にします。
このmod __pseudo_extern_prelude自体はRust 2015でもコンパイルできますが、Rust 2015はuse another_lib::A;を解決できません。
```diff +mod _pseudoextern_prelude {
マクロ内ではcrateではなく$crateを使う。
macro_rules!内の$crateは$crate::extern_crate_name_in_main_crateに置き換えられます。
macro_rules!内のcrateは置き換えられません。
3.以外の場合も可能な限り絶対パスを使わない。
cargo-equipはpathのcrateはcrate::extern_crate_name_in_main_crateに、pub(crate)はpub(in crate::extern_crate_name_in_main_crate)に置き換えます。
ただしこの置き換えは必ず上手くいくかどうかがわかりません。
できる限りcrate::よりもself::とsuper::を使ってください。
diff
-use crate::foo::Foo;
+use super::foo::Foo;
可能な限りライブラリを小さなクレートに分割する。
cargo-equipは「クレート内のアイテムの依存関係」を調べることはしません。 AtCoder以外に参加する場合は、出力結果を制限内(たいてい64KiB程度)に収めるためにできるだけ小さなクレートに分割してください。
console
.
├── input
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── output
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
⋮
ライブラリが用意できたら、それらをbin側のCargo.tomlの[dependencies]に加えてください。
コンテスト毎にツールでパッケージを自動生成しているならそれのテンプレートに加えてください。
rust-lang-ja/ac-library-rsを使いたい場合、qryxip/ac-library-rs-partedを使ってください。
本物のac-library-rsを ~~custom-build内で自動で加工する~~ スクリプトで加工したクレートです。
~~custom-build部分はAtCoder環境と同様のCargo.lockを壊さないためにsyn 1.0.17とproc-macro2 1.0.10で書かれています。~~
やっぱり小さいといってもdependencyが数十個付いてきてCI等で煩わしいのでやめました。
現在のこれらのクレートは外部の依存クレートを持たず、瞬時にビルド可能です。
toml
[dependencies]
ac-library-rs-parted = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-convolution = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-dsu = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-fenwicktree = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-lazysegtree = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-math = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-maxflow = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-mincostflow = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-modint = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-scc = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-segtree = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-string = { git = "https://github.com/qryxip/ac-library-rs-parted" }
ac-library-rs-parted-twosat = { git = "https://github.com/qryxip/ac-library-rs-parted" }
準備ができたらコードを書いてください。
bin側の制約は以下の2つです。
useしない。qualified pathで使うか#[macro_use]で使う。bin内にmodを作る場合、その中ではextern preludeから展開予定のライブラリの名前を解決しない。```rust
extern crate input as _;
use std::io::Write as _;
fn main() { input! { n: usize, }
buffered_print::buf_print(|out| {
macro_rules! println(($($tt:tt)*) => (writeln!(out, $($tt)*).unwrap()));
for i in 1..=n {
match i % 15 {
0 => println!("Fizz Buzz"),
3 | 6 | 9 | 12 => println!("Fizz"),
5 | 10 => println!("Buzz"),
_ => println!("{}", i),
}
}
});
} ```
コードが書けたらcargo equipで展開します。
--bin {binの名前}か--src {binのファイルパス}でbinを指定してください。
パッケージ内のbinが一つの場合は省略できます。
ただしdefault-runには未対応です。
console
❯ cargo equip --bin "$name"
コードはこのように展開されます。
extern_crate_nameがbin側から与えられていないクレートは__package_name_0_1_0のような名前が与えられます。
diff
diff
+//! # Bundled libraries
+//!
+//! - qryxip-competitive-buffered-print 0.0.0 (path+█████████████████████████████████████████████████████████████████████████████████████) published in https://github.com/qryxip/competitive-programming-library licensed under CC0-1.0 as crate::buffered_print
+//! - qryxip-competitive-input 0.0.0 (path+████████████████████████████████████████████████████████████████████████████) published in https://github.com/qryxip/competitive-programming-library licensed under CC0-1.0 as crate::input
-#[macrouse] -extern crate input as _; +/*#[macrouse] +extern crate input as _;*/
use std::io::Write as _;
fn main() { input! { n: usize, }
buffered_print::buf_print(|out| {
macro_rules! println(($($tt:tt)*) => (writeln!(out, $($tt)*).unwrap()));
for i in 1..=n {
match i % 15 {
0 => println!("Fizz Buzz"),
3 | 6 | 9 | 12 => println!("Fizz"),
5 | 10 => println!("Buzz"),
_ => println!("{}", i),
}
}
});
}
+
+// The following code was expanded by cargo-equip.
+
+#[allow(deadcode)]
+mod bufferedprint {
+ // ...
+}
+
+#[allow(dead_code)]
+mod input {
+ // ...
+}
cargo-equipがやる操作は以下の通りです。
bin側
#![cfg_attr(cargo_equip, cargo_equip::skip)]を発見した場合、以下の処理をスキップして--checkの処理だけ行い出力mod $name;をすべて再帰的に展開する。このとき各モジュールをインデントする。ただし複数行にまたがるリテラルが無い場合はインデントしないextern crateを処理libを下部に追加lib側
mod $name;をすべて再帰的に展開するcrateを処理extern crateを処理macro_rules!を処理mod __pseudo_extern_prelude { .. }とuse (self::|$(super::)*)__pseudo_extern_prelude::*;を挿入--resolve-cfgオプションを付けた場合、#[cfg(常にTRUEのように見える式)]のアトリビュートと#[cfg(常にFALSEのように見える式)]のアトリビュートが付いたアイテムを消去--remove docsオプションを付けた場合、doc commentを消去--remove commentsオプションを付けた場合、commentを消去--minify allオプションを付けた場合コード全体を最小化する--rustfmtオプションを付けた場合Rustfmtでフォーマットするcargo-equipはwattを使った手続き型マクロを展開する機能を持っています。
TODO
--resolve-cfgs#[cfg(恒真)] (e.g. cfg(feature = "enabled-feature"))のアトリビュートを消去します。#[cfg(恒偽)] (e.g. cfg(test), cfg(feature = "disable-feature"))のアトリビュートが付いたアイテムを消去します。これは次の割り当てで判定されます。
test: falseproc_macro: falsecargo_equip: truefeature: bin側から見て有効化されているもののみtrue```rust
pub mod a { pub struct A;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
} ```
↓
```rust
pub mod a { pub struct A; } ```
--remove <REMOVE>...--remove docsでDoc comment (//! .., /// .., /** .. */, #[doc = ".."])を--remove commentsでコメント (// .., /* .. */)を除去します。
```rust
pub mod a { //! A.
/// A.
pub struct A; // aaaaa
} ```
↓
```rust
pub mod a { pub struct A; } ```
--minify <MINIFY>--minify libで展開後のライブラリをそれぞれ一行に折り畳みます。
--minify allでコード全体を最小化します。
ただ現段階では実装が適当なのでいくつか余計なスペースが挟まる場合があります。
--rustfmt出力をRustfmtでフォーマットします。
--checkバンドルしたコードを出力する前にtarget directoryを共有した一時パッケージを作り、それの上でcargo checkします。
#![cfg_attr(cargo_equip, cargo_equip::skip)]でスキップした場合も有効です。
console
❯ cargo equip --check -o /dev/null
Running `/home/ryo/.cargo/bin/rustup run nightly cargo udeps --output json -p solve --bin solve`
Checking solve v0.0.0 (/home/ryo/src/local/a/solve)
Finished dev [unoptimized + debuginfo] target(s) in 0.13s
info: Loading save analysis from "/home/ryo/src/local/a/solve/target/debug/deps/save-analysis/solve-4eea33c8603d6001.json"
Bundling the code
Checking cargo-equip-check-output-6j2i3j3tgtugeaqm v0.1.0 (/tmp/cargo-equip-check-output-6j2i3j3tgtugeaqm)
Finished dev [unoptimized + debuginfo] target(s) in 0.11s
MIT or Apache-2.0のデュアルライセンスです。