競技プログラミング用にRustコードを一つの.rsファイルにバンドルするCargoサブコマンドです。
```toml [package] name = "solve" version = "0.0.0" edition = "2018"
[dependencies] aclconvolution = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } acldsu = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclfenwicktree = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } acllazysegtree = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclmath = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclmaxflow = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclmincostflow = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclmodint = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclscc = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclsegtree = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } aclstring = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } acltwosat = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" } input = { path = "/path/to/input" } output = { path = "/path/to/output" } tonellishanks = { path = "/path/to/tonellishanks" }
```
```rust
extern crate input as _;
use aclmodint::ModInt; use std::io::Write as _; use tonellishanks::ModIntBaseExt as _;
fn main() { input! { yps: [(u32, u32)], }
output::buf_print(|out| {
macro_rules! println(($($tt:tt)*) => (writeln!(out, $($tt)*).unwrap()));
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 docs --minify libs --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/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-e3698634e08ed722.json"
Bundling the code
Checking cargo-equip-check-output-99qzpt0pl701s8e6 v0.1.0 (/tmp/cargo-equip-check-output-99qzpt0pl701s8e6)
Finished dev [unoptimized + debuginfo] target(s) in 0.34s
Submit Info #29030 - Library-Checker
nightlyツールチェインとcargo-udepsもインストールしてください。
console
❯ rustup update nightly
console
❯ cargo install --git https://github.com/est31/cargo-udeps # for est31/cargo-udeps#80
console
❯ cargo install cargo-equip
masterconsole
❯ cargo install --git https://github.com/qryxip/cargo-equip
バイナリでの提供もしています。
cargo-equipで展開できるライブラリには以下の制約があります。
各libには#[macro_export]したマクロと同名なアイテムが存在しないようにする。
cargo-equipはmod lib_name直下にpub use crate::{ それらの名前 }を挿入するため、展開後のuseで壊れます。
マクロは#[macro_use]で使ってください。
```rust // in main source code
extern crate input as _; ```
展開時にはコメントアウトされます。
```rust // in main source code
/#[macro_use]
extern crate input as _;/ // as _でなければuse crate::$name;が挿入される
```
共に展開する予定のクレートを使う場合、extern crateを宣言してそれを適当な場所にマウントし、そこを相対パスで参照する。
cargo-equipはitertools等のAtCoderやCodinGameで使えるクレートを除いて、
extern crateをuse crate::extern_crate_name_in_main_crate;に置き換えます。
誤って直接使わないようにlib → libの依存においては対象の名前はリネームしておくことを強く推奨します。
rust
extern crate __another_lib as another_lib;
注意点として、バンドル後のコードが2015 editionで実行される場合(AOJ, yukicoder, ~~Library-Checker~~)、相対パスで参照するときはself::を付けてください。
rust
use self::another_lib::foo::Foo;
可能な限り絶対パスを使わない。
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;
またマクロ内では$crateを使ってください。
macro_rules!内の$crateは$crate::extern_crate_name_in_main_crateに置き換えられます。
可能な限りライブラリを小さなクレートに分割する。
cargo-equipは「クレート内のアイテムの依存関係」を調べることはしません。 出力結果を64KiBに収めるためにはできるだけ小さなクレートに分割してください。
console
.
├── input
│ ├── Cargo.lock
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── output
│ ├── Cargo.lock
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
⋮
ライブラリが用意できたら、それらをbin側のCargo.tomlの[dependencies]に加えてください。
コンテスト毎にツールでパッケージを自動生成しているならそれのテンプレートに加えてください。
ac-library-rsを使いたい場合はこれらを使ってください。
toml
[dependencies]
acl_convolution = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_dsu = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_fenwicktree = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_lazysegtree = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_math = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_maxflow = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_mincostflow = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_modint = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_scc = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_segtree = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_string = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
acl_twosat = { git = "https://github.com/qryxip/ac-library-rs", branch = "split-into-separate-crates" }
準備ができたらコードを書いてください。
bin側の制約は以下の2つです。
useしない。qualified pathで使うか#[macro_use]で使う。bin内にmodを作る場合、その中ではExtern Preludeから名前を解決しない。```rust
extern crate input as _;
use std::io::Write as _;
fn main() { input! { yps: [(u32, u32)], }
output::buf_print(|out| {
macro_rules! println(($($tt:tt)*) => (writeln!(out, $($tt)*).unwrap()));
for (y, p) in yps {
ModInt::set_modulus(p);
if let Some(sqrt) = ModInt::new(y).sqrt() {
println!("{}", sqrt);
} else {
println!("-1");
}
}
});
} ```
コードが書けたらcargo equipで展開します。
--bin {binの名前}か--src {binのファイルパス}でbinを指定してください。
パッケージ内のbinが一つの場合は省略できます。
ただしdefault-runには未対応です。
console
❯ cargo equip --bin "$name"
コードはこのように展開されます。
extern_crate_nameがbin側から与えられていないクレートは"__internal_lib_0_1_0" + &"_".repeat(n)のような名前が与えられます。
``diff
+//! # Bundled libraries
+//!
+//! ##input(private)
+//!
+//! ###externcratename
+//!
+//!input
+//!
+//! ##output(private)
+//!
+//! ###externcratename
+//!
+//!output`
-#[macrouse] -extern crate input as _; +/*#[macrouse] +extern crate input as _;*/
use std::io::Write as _;
fn main() { input! { n: usize, }
output::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 input {
+ // ...
+}
+
+#[allow(deadcode)]
+mod output {
+ // ...
+}
```
cargo-equipがやる操作は以下の通りです。
bin側
mod $name;をすべて再帰的に展開する。このとき各モジュールをインデントする。ただし複数行にまたがるリテラルが無い場合はインデントしないextern crateを処理libを下部に追加lib側
mod $name;をすべて再帰的に展開するcrateを処理extern crateを処理macro_rules!を処理--resolve-cfgオプションを付けた場合、#[cfg(常にTRUEのように見える式)]のアトリビュートと#[cfg(常にFALSEのように見える式)]のアトリビュートが付いたアイテムを消去--remove docsオプションを付けた場合、doc commentを消去--remove commentsオプションを付けた場合、commentを消去--minify allオプションを付けた場合コード全体を最小化する--rustfmtオプションを付けた場合Rustfmtでフォーマットする--resolve-cfgs#[cfg(常にTRUEのように見える式)] (e.g. cfg(feature = "enabled-feature"))のアトリビュートを消去します。#[cfg(常にFALSEのように見える式)] (e.g. cfg(test), cfg(feature = "disable-feature"))のアトリビュートが付いたアイテムを消去します。```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します。
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のデュアルライセンスです。