From 22e7b5aa81ec659245a77fe16b233f8a35ef36ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8A=E3=81=99/TwoSquirrels?= <47352965+TwoSquirrels@users.noreply.github.com> Date: Fri, 12 Jun 2026 16:40:21 +0900 Subject: [PATCH 1/7] feat: add --no-tree-shaking option Co-Authored-By: Claude Opus 4.8 --- src/cli.rs | 4 +++ src/commands/bundle.rs | 14 ++++---- tests/cli.rs | 79 +++++++++++++++++++++++++++++------------- 3 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 3985711..9dda28d 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -30,6 +30,10 @@ pub struct BundleArgs { #[arg(short = 'n', long = "no-check")] pub no_check: bool, + /// Expand the source without tree-shaking + #[arg(long = "no-tree-shaking")] + pub no_tree_shaking: bool, + /// C++ source file to bundle pub file: PathBuf, diff --git a/src/commands/bundle.rs b/src/commands/bundle.rs index 98ad259..d321bdf 100644 --- a/src/commands/bundle.rs +++ b/src/commands/bundle.rs @@ -45,13 +45,11 @@ pub fn run(args: BundleArgs) -> Result<()> { .file .canonicalize() .with_context(|| format!("failed to resolve {}", args.file.display()))?; - let unused = unused_origins( - &settings, - &inventory, - &compiler_args, - &preprocessed, - &target, - )?; + let unused = if settings.tree_shaking { + unused_origins(&settings, &inventory, &compiler_args, &preprocessed, &target)? + } else { + BTreeSet::new() + }; let target_dir = target.parent(); let bundled = rewrite::rewrite( &preprocessed, @@ -118,6 +116,7 @@ struct Settings { options: Vec, keep: BTreeSet, embed: bool, + tree_shaking: bool, } impl Settings { @@ -136,6 +135,7 @@ impl Settings { args.keep.iter().cloned().collect() }, embed: args.embed || config.embed, + tree_shaking: !args.no_tree_shaking, }) } } diff --git a/tests/cli.rs b/tests/cli.rs index 116539c..4e2e645 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -4,6 +4,8 @@ mod common; +use std::collections::BTreeSet; + use assert_cmd::prelude::*; use common::{Sandbox, compile_and_run}; use predicates::prelude::*; @@ -167,12 +169,14 @@ fn update_picks_up_new_files() { .stdout(predicate::str::contains("B")); } -#[test] -fn bundle_drops_unused_headers_and_keeps_used_ones() { +/// all.hpp が used/unused を両方束ねるが main は Used だけ使う、という小さなライブラリ +/// `mylib` を登録し、main.cpp を用意した sandbox を返す。tree-shaking の有無で未使用ヘッダーの +/// 残り方が反転することを対で検証するための共通 fixture。 +/// +/// メソッド名まで重ならないようにする。逆引きは定義識別子の一致で行うため、`value()` を共有すると +/// 未使用ヘッダーまで巻き込まれてしまう (それ自体は正しい挙動)。 +fn used_and_unused_library() -> Sandbox { let sandbox = Sandbox::new(); - // all.hpp が used/unused を両方束ねるが、main は Used だけ使う。 - // メソッド名まで重ならないようにする。逆引きは定義識別子の一致で行うため、`value()` を共有すると - // 未使用ヘッダーまで巻き込まれてしまう (それ自体は正しい挙動)。 sandbox.write( "mylib/used.hpp", "#pragma once\nstruct Used { int used_value() const { return 42; } };\n", @@ -194,17 +198,37 @@ fn bundle_drops_unused_headers_and_keeps_used_ones() { .assert() .success(); - let main = "#include \n#include \n\ - int main() { Used u; std::printf(\"%d\\n\", u.used_value()); return 0; }\n"; - sandbox.write("main.cpp", main); + sandbox.write( + "main.cpp", + "#include \n#include \n\ + int main() { Used u; std::printf(\"%d\\n\", u.used_value()); return 0; }\n", + ); + sandbox +} + +/// sandbox 内で `risundle main.cpp` を実行し、標準出力を返す。 +fn run_bundle(sandbox: &Sandbox, args: &[&str]) -> String { let output = sandbox .risundle() - .args(["-k", STD, "main.cpp"]) + .args(args) + .arg("main.cpp") .output() .expect("run bundle"); assert!(output.status.success(), "bundle failed"); - let bundled = String::from_utf8(output.stdout).expect("utf-8"); + String::from_utf8(output.stdout).expect("utf-8") +} + +/// ソース中の `struct` 定義行の集合。出力どうしの包含関係を比べるのに使う。 +fn struct_defs(src: &str) -> BTreeSet<&str> { + src.lines().filter(|line| line.contains("struct ")).collect() +} + +#[test] +fn bundle_drops_unused_headers_and_keeps_used_ones() { + let sandbox = used_and_unused_library(); + + let bundled = run_bundle(&sandbox, &["-k", STD]); assert!(bundled.contains("struct Used"), "使用ヘッダーは残るべき"); assert!( @@ -214,6 +238,25 @@ fn bundle_drops_unused_headers_and_keeps_used_ones() { assert_eq!(compile_and_run(&sandbox, &bundled).trim(), "42"); } +#[test] +fn bundle_no_tree_shaking_keeps_unused_headers() { + let sandbox = used_and_unused_library(); + + let shaken = run_bundle(&sandbox, &["-k", STD]); + let expanded = run_bundle(&sandbox, &["-k", STD, "--no-tree-shaking"]); + + // 無効版は有効版に残る定義をすべて含む上位集合で、さらに未使用の Unused も残す。 + assert!( + struct_defs(&shaken).is_subset(&struct_defs(&expanded)), + "tree-shaking 有効版の定義は無効版に包含されるべき" + ); + assert!( + expanded.contains("struct Unused"), + "tree-shaking 無効時は未使用ヘッダーも残るべき" + ); + assert_eq!(compile_and_run(&sandbox, &expanded).trim(), "42"); +} + #[test] fn bundle_keeps_transitively_required_headers() { let sandbox = Sandbox::new(); @@ -245,13 +288,7 @@ fn bundle_keeps_transitively_required_headers() { int main() { Mid m; std::printf(\"%d\\n\", m.mid_value()); return 0; }\n"; sandbox.write("main.cpp", main); - let output = sandbox - .risundle() - .args(["-k", STD, "main.cpp"]) - .output() - .expect("run bundle"); - assert!(output.status.success(), "bundle failed"); - let bundled = String::from_utf8(output.stdout).expect("utf-8"); + let bundled = run_bundle(&sandbox, &["-k", STD]); assert!(bundled.contains("struct Mid"), "使用ヘッダーは残るべき"); assert!( @@ -295,13 +332,7 @@ fn bundle_ignores_identifiers_in_comments_and_strings() { int main() { Real r; std::printf(\"%d\\n\", r.real_value()); (void)note; return 0; }\n"; sandbox.write("main.cpp", main); - let output = sandbox - .risundle() - .args(["-k", STD, "main.cpp"]) - .output() - .expect("run bundle"); - assert!(output.status.success(), "bundle failed"); - let bundled = String::from_utf8(output.stdout).expect("utf-8"); + let bundled = run_bundle(&sandbox, &["-k", STD]); assert!(bundled.contains("struct Real"), "使用ヘッダーは残るべき"); assert!( From e0dbfed1caefe805d378342be1fc969e09772f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8A=E3=81=99/TwoSquirrels?= <47352965+TwoSquirrels@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:14:33 +0900 Subject: [PATCH 2/7] docs: document --no-tree-shaking in README Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TwiopnwyFnRbSUyNoFLWAZ --- README.ja.md | 3 +++ README.md | 3 +++ 2 files changed, 6 insertions(+) diff --git a/README.ja.md b/README.ja.md index 6ca5984..c3f9fdc 100644 --- a/README.ja.md +++ b/README.ja.md @@ -60,10 +60,13 @@ risundle [OPTIONS] [-- ...] | --- | --- | | `-c`, `--compiler ` | 使用するコンパイラ (既定: `g++`) | | `-k`, `--keep ` | tree-shaking の対象外にするライブラリ ID (繰り返し可。既定: `std`) | +| `--no-tree-shaking` | tree-shaking を無効化し、すべて展開する (フォールバック用) | | `-e`, `--embed` | 元のソースを先頭にコメントとして埋め込む | | `-n`, `--no-check` | ライブラリ更新のハッシュ検証をスキップする | | `-- ...` | `--` 以降をコンパイラへそのまま渡す | +`--keep` はライブラリを展開せず `#include` のまま残しますが、`--no-tree-shaking` は全ライブラリを展開した上で tree-shaking を行いません。両者は別物です。 + ```bash # clang++ を使い、AC Library も展開せず #include のまま残す risundle -c clang++ -k std -k ac-library main.cpp > submission.cpp diff --git a/README.md b/README.md index f1d61a3..120cb7b 100644 --- a/README.md +++ b/README.md @@ -60,10 +60,13 @@ Bundles `` and writes the result to standard output. | --- | --- | | `-c`, `--compiler ` | Compiler to use (default: `g++`) | | `-k`, `--keep ` | Library ID to exclude from tree-shaking (repeatable; default: `std`) | +| `--no-tree-shaking` | Disable tree-shaking and expand everything (useful as a fallback) | | `-e`, `--embed` | Embed the original source as a comment at the top | | `-n`, `--no-check` | Skip the hash verification of library updates | | `-- ...` | Pass everything after `--` straight to the compiler | +`--keep` leaves a library unexpanded as an `#include`, whereas `--no-tree-shaking` expands every library but performs no tree-shaking. The two are different. + ```bash # Use clang++ and leave AC Library unexpanded, keeping it as #include risundle -c clang++ -k std -k ac-library main.cpp > submission.cpp From 092ad79b4e856a11e1061a2f0cf047fab95baa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8A=E3=81=99/TwoSquirrels?= <47352965+TwoSquirrels@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:14:33 +0900 Subject: [PATCH 3/7] docs: add task-oriented cheatsheet (ja/en) Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TwiopnwyFnRbSUyNoFLWAZ --- docs/cheatsheet.ja.md | 206 ++++++++++++++++++++++++++++++++++++++++++ docs/cheatsheet.md | 206 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 docs/cheatsheet.ja.md create mode 100644 docs/cheatsheet.md diff --git a/docs/cheatsheet.ja.md b/docs/cheatsheet.ja.md new file mode 100644 index 0000000..538627d --- /dev/null +++ b/docs/cheatsheet.ja.md @@ -0,0 +1,206 @@ +# risundle チートシート + +[English](cheatsheet.md) | 日本語 + +「〜したい」から引く逆引き集です。コマンドはそのままコピペで動きます。各機能の説明は [README](../README.ja.md)、細かい仕様は [spec.ja.md](spec.ja.md) へ。 + +- [最初に 1 回だけやること](#最初に-1-回だけやること) +- [提出するたびにやること](#提出するたびにやること) +- [提出ファイルを調整したい](#提出ファイルを調整したい) +- [ライブラリを触ったらやること](#ライブラリを触ったらやること) +- [毎回同じオプションを打ちたくない](#毎回同じオプションを打ちたくない) +- [うまくいかないとき](#うまくいかないとき) + +## 最初に 1 回だけやること + +### インストールしたい + +```bash +cargo install risundle +``` + +(Rust ツールチェーンと `g++` などの C++ コンパイラが必要です) + +### 自作ライブラリを使えるようにしたい + +```bash +risundle library add mylib ~/cp/library +``` + +`mylib` は好きな ID、`~/cp/library` はライブラリのルート (include の起点になるディレクトリ) に読み替えてください。標準ライブラリの登録は不要です (初回バンドル時に自動登録)。 + +### AC Library など他人のライブラリも使えるようにしたい + +自作と同じく `add` するだけです。 + +```bash +risundle library add ac-library ~/ac-library +``` + +## 提出するたびにやること + +### 解答を提出用の 1 ファイルにしたい + +```bash +risundle main.cpp > submission.cpp +``` + +`main.cpp` が実際に使っている部分だけが残った `submission.cpp` ができます。あとはジャッジに貼るだけ。 + +### ファイルを作らず直接クリップボードにコピーしたい + +```bash +risundle main.cpp | iconv -t cp932 | clip.exe # WSL (日本語 Windows) +risundle main.cpp | pbcopy # macOS +risundle main.cpp | xclip -sel clip # Linux (X11) +risundle main.cpp | wl-copy # Linux (Wayland) +``` + +clip.exe は Windows 側のコードページ (日本語環境では cp932) で入力を解釈するため、UTF-8 のまま流すと日本語コメントが文字化けし、改行が消えてコードの意味が変わることすらあります。`iconv -t cp932` を挟んで変換してください。 + +### テストから提出までまとめてやりたい (oj と組み合わせる) + +サンプルテストや提出の機能は、責任を絞るため risundle 自体には持たせていません。[oj](https://github.com/online-judge-tools/oj) など既存ツールと `&&` で繋いでください。 + +```bash +# サンプルが通ったらバンドルして、そのまま提出 +oj t && risundle main.cpp > submission.cpp && oj s submission.cpp +``` + +`&&` は直前のコマンドが成功したときだけ次を実行するので、サンプルが落ちたら提出まで進みません。 + +## 提出ファイルを調整したい + +### ジャッジに入っているライブラリは展開せず `#include` のまま残したい + +例: AtCoder Library がジャッジ側にあるので埋め込みたくない。 + +```bash +risundle -k std -k ac-library main.cpp > submission.cpp +``` + +`-k` に渡すのは登録時の ID です。`-k` を 1 つでも指定すると既定の `keep = ["std"]` は上書きされるため、`-k std` も一緒に指定してください (忘れると標準ライブラリまで展開されます)。このとき解答側の include は `#include "..."` ではなく `#include ` のような山括弧で書いてください (`"..."` だと keep が効かないことがあります)。 + +### ジャッジと同じコンパイラ・同じフラグで処理したい + +```bash +risundle -c clang++ main.cpp -- -std=gnu++20 -O2 +``` + +`-c` でコンパイラを変更、`--` より後ろはコンパイラへそのまま渡ります。`#ifdef` でコンパイラやフラグによって分岐するコードを書いているときに。 + +### コンパイルが通ることを確認してから提出したい (自動フォールバック) + +tree-shaking は近似なので、まれに必要な定義を削ることがあります。失敗時にどう退避するかは人それぞれのため、自動フォールバック機能は risundle 自体には持たせず、シェルで組む方式にしています。バンドル後に手元でコンパイル検証し、通らなければ全展開 (`--no-tree-shaking`) に自動で切り替えるには、`||` (直前のコマンドが失敗したときだけ実行) で繋ぎます。 + +```bash +risundle main.cpp > submission.cpp +g++ -fsyntax-only -std=gnu++20 submission.cpp || + risundle --no-tree-shaking main.cpp > submission.cpp +``` + +`-fsyntax-only` は構文チェックだけして実行ファイルを作らないので高速です。フラグはジャッジに合わせてください。 + +毎回打つのが面倒なら、シェルの設定ファイル (`~/.bashrc` など) に関数として書いておけます。 + +```bash +bundle() { + risundle "$1" > submission.cpp && + g++ -fsyntax-only -std=gnu++20 submission.cpp 2>/dev/null || + { echo 'tree-shaking に失敗したため全展開します' >&2 && + risundle --no-tree-shaking "$1" > submission.cpp; } +} +``` + +以後 `bundle main.cpp` だけで済みます。フォールバックが発動したら、tree-shaking の取りこぼしですので [Issue](https://github.com/TwoSquirrels/risundle/issues) で報告してもらえると助かります。 + +### 提出ファイルの先頭に元のソースをコメントで残したい + +```bash +risundle -e main.cpp > submission.cpp +``` + +コードを公開するジャッジで、tree-shaking 前の読みやすいコードを見せたいときに。 + +## ライブラリを触ったらやること + +### ライブラリを書き換えたので反映したい + +```bash +risundle library update mylib # 1 つだけ +risundle library update # 登録済みぜんぶ +``` + +反映し忘れてもバンドル時に「変更された」と止まって教えてくれるので、そのとき打てば大丈夫です。 + +### 何を登録したか確認したい + +```bash +risundle library list # ID とパスの一覧 +risundle library show mylib # 詳細 +risundle library show mylib -v # どのファイルがどの識別子を定義しているかまで +``` + +tree-shaking の結果が想定と違うとき、`-v` で「この関数はこのファイル由来」を確認できます。 + +### 登録をやめたい + +```bash +risundle library delete mylib +``` + +### g++ 以外のコンパイラでも標準ライブラリを解決したい + +```bash +risundle library add-std clang++ +``` + +呼ぶたびにコンパイラが追加されていくので、g++ と clang++ を両方登録して `-c` で使い分けられます。 + +## 毎回同じオプションを打ちたくない + +解答ファイルのあるディレクトリ (かその親、たとえばプロジェクトルート) に `.risundlerc.toml` を置くと、配下のバンドル全部に効きます。 + +```toml +[compiler] +path = "g++" +options = ["-std=gnu++20", "-O2", "-DONLINE_JUDGE", "-DATCODER"] + +[library] +keep = ["std", "ac-library"] +``` + +CLI オプションを併用した場合はそちらが優先されます。 + +## うまくいかないとき + +### 「ライブラリが変更された」と言われて止まる + +登録後にライブラリの中身が変わっています。反映するのが本筋、急ぐなら検証スキップ。 + +```bash +risundle library update # 本筋 +risundle -n main.cpp # 急場しのぎ (検証スキップ) +``` + +### バンドル後のファイルがコンパイルエラーになる + +まず tree-shaking を切って全部展開すれば、とりあえず提出できます。 + +```bash +risundle --no-tree-shaking main.cpp > submission.cpp +``` + +これで直る場合は tree-shaking が必要な定義を削っています ([Issue](https://github.com/TwoSquirrels/risundle/issues) で報告してもらえると助かります)。この切り替えを自動化したい場合は「[コンパイルが通ることを確認してから提出したい](#コンパイルが通ることを確認してから提出したい-自動フォールバック)」を参照してください。これでも直らない場合は、宣言と実装がファイル分割されたライブラリの可能性があります (v1.0 はヘッダーオンリーのみ対応)。 + +### `-k` したはずのライブラリが展開されてしまう + +解答側の include を `#include "atcoder/dsu"` のような `"..."` 形式で書いていませんか。`#include ` の山括弧形式に変えてください。 + +### `-k` を付けたら標準ライブラリまで展開されるようになった + +`-k` の指定は既定の `keep = ["std"]` を上書きします。`-k std` を並べて指定してください。 + +### エラーメッセージの行番号が元のファイルと合わない + +バンドル後のファイルには `#line` が入っており、コンパイラの診断は元ファイルの行を指します。ジャッジ上のエラー行番号も元ファイル基準で読んでください。 diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md new file mode 100644 index 0000000..1c5c749 --- /dev/null +++ b/docs/cheatsheet.md @@ -0,0 +1,206 @@ +# risundle cheatsheet + +English | [日本語](cheatsheet.ja.md) + +A task-oriented quick reference: find what you want to do, copy the command, done. See the [README](../README.md) for what each feature is, and [spec.md](spec.md) for the fine details. + +- [One-time setup](#one-time-setup) +- [Every time you submit](#every-time-you-submit) +- [Tweaking the submission file](#tweaking-the-submission-file) +- [After touching a library](#after-touching-a-library) +- [Tired of typing the same options](#tired-of-typing-the-same-options) +- [When things go wrong](#when-things-go-wrong) + +## One-time setup + +### Install + +```bash +cargo install risundle +``` + +(Requires the Rust toolchain and a C++ compiler such as `g++`.) + +### Make your own library available + +```bash +risundle library add mylib ~/cp/library +``` + +`mylib` is any ID you like; `~/cp/library` is the library root (the directory your includes are relative to). No need to register the standard library — it is registered automatically on the first bundle. + +### Make third-party libraries like AC Library available + +Just `add` them the same way. + +```bash +risundle library add ac-library ~/ac-library +``` + +## Every time you submit + +### Bundle a solution into a single file for submission + +```bash +risundle main.cpp > submission.cpp +``` + +You get a `submission.cpp` containing only the parts `main.cpp` actually uses. Paste it into the judge and you are done. + +### Copy straight to the clipboard without creating a file + +```bash +risundle main.cpp | clip.exe # Windows / WSL +risundle main.cpp | pbcopy # macOS +risundle main.cpp | xclip -sel clip # Linux (X11) +risundle main.cpp | wl-copy # Linux (Wayland) +``` + +Caution on WSL: `clip.exe` interprets its input in the Windows system code page, not UTF-8. If your code contains non-ASCII comments, they get garbled — and line breaks can even disappear, silently changing what the code means. Convert first with `iconv` (e.g. `risundle main.cpp | iconv -t cp932 | clip.exe` on Japanese Windows). + +### Run tests and submit in one go (combine with oj) + +To keep its responsibilities small, risundle itself has no test or submit features. Chain it with existing tools such as [oj](https://github.com/online-judge-tools/oj) using `&&`. + +```bash +# If the samples pass, bundle and submit right away +oj t && risundle main.cpp > submission.cpp && oj s submission.cpp +``` + +`&&` runs the next command only if the previous one succeeded, so a failing sample never reaches the submit step. + +## Tweaking the submission file + +### Leave a library the judge already has as `#include`, unexpanded + +Example: the judge provides the AtCoder Library, so you don't want it embedded. + +```bash +risundle -k std -k ac-library main.cpp > submission.cpp +``` + +`-k` takes the ID you used at registration. Specifying `-k` even once overrides the default `keep = ["std"]`, so pass `-k std` alongside it (forget it and the standard library gets expanded too). Also write the includes in your solution with angle brackets like `#include `, not `#include "..."` (with `"..."` the keep may not take effect). + +### Process with the same compiler and flags as the judge + +```bash +risundle -c clang++ main.cpp -- -std=gnu++20 -O2 +``` + +`-c` switches the compiler; everything after `--` is passed to the compiler as is. Useful when your code branches on compilers or flags with `#ifdef`. + +### Verify it compiles before submitting (automatic fallback) + +Tree-shaking is an approximation, so on rare occasions it can drop a needed definition. How to fall back on failure differs per person, so risundle itself has no automatic fallback — you compose it in the shell instead. To test-compile the bundle and switch to full expansion (`--no-tree-shaking`) when it fails, chain with `||` (which runs the next command only if the previous one failed). + +```bash +risundle main.cpp > submission.cpp +g++ -fsyntax-only -std=gnu++20 submission.cpp || + risundle --no-tree-shaking main.cpp > submission.cpp +``` + +`-fsyntax-only` only checks the syntax without producing a binary, so it is fast. Match the flags to your judge. + +If that is too much to type every time, put a function in your shell config (`~/.bashrc` etc.). + +```bash +bundle() { + risundle "$1" > submission.cpp && + g++ -fsyntax-only -std=gnu++20 submission.cpp 2>/dev/null || + { echo 'tree-shaking failed; expanding everything' >&2 && + risundle --no-tree-shaking "$1" > submission.cpp; } +} +``` + +From then on, `bundle main.cpp` is all you need. If the fallback ever fires, tree-shaking missed something — a report in the [issues](https://github.com/TwoSquirrels/risundle/issues) would be much appreciated. + +### Keep the original source as a comment at the top of the submission + +```bash +risundle -e main.cpp > submission.cpp +``` + +For judges that publish submissions, when you want readers to see the readable pre-tree-shaking code. + +## After touching a library + +### Reflect changes after editing a library + +```bash +risundle library update mylib # just one +risundle library update # everything registered +``` + +Even if you forget, bundling stops with a "library changed" error to remind you — just run it then. + +### Check what is registered + +```bash +risundle library list # IDs and paths +risundle library show mylib # details +risundle library show mylib -v # down to which file defines which identifiers +``` + +When tree-shaking output is not what you expected, `-v` tells you which file each identifier comes from. + +### Unregister + +```bash +risundle library delete mylib +``` + +### Resolve the standard library with compilers other than g++ + +```bash +risundle library add-std clang++ +``` + +Each call adds another compiler to the set, so register both g++ and clang++ and switch with `-c`. + +## Tired of typing the same options + +Put a `.risundlerc.toml` in the directory of your solutions (or a parent, e.g. the project root) and it applies to every bundle underneath. + +```toml +[compiler] +path = "g++" +options = ["-std=gnu++20", "-O2", "-DONLINE_JUDGE", "-DATCODER"] + +[library] +keep = ["std", "ac-library"] +``` + +CLI options take precedence when both are given. + +## When things go wrong + +### Bundling stops saying the library has changed + +The library contents changed after registration. Reflecting the change is the proper fix; skip verification if you are in a hurry. + +```bash +risundle library update # proper fix +risundle -n main.cpp # quick and dirty (skips verification) +``` + +### The bundled file fails to compile + +Turn off tree-shaking and expand everything — that gets you something submittable for now. + +```bash +risundle --no-tree-shaking main.cpp > submission.cpp +``` + +If this fixes it, tree-shaking dropped a needed definition (a report in the [issues](https://github.com/TwoSquirrels/risundle/issues) would be much appreciated). To automate this switch, see "[Verify it compiles before submitting](#verify-it-compiles-before-submitting-automatic-fallback)". If it still fails, the library may split declarations and implementations across files (v1.0 supports header-only libraries only). + +### A library you passed to `-k` gets expanded anyway + +Are you writing the include as `#include "atcoder/dsu"`? Switch to the angle-bracket form `#include `. + +### Adding `-k` made the standard library get expanded too + +Specifying `-k` overrides the default `keep = ["std"]`. Pass `-k std` alongside it. + +### Error line numbers don't match the original file + +The bundled file contains `#line` directives, so compiler diagnostics point at the original files. Read error line numbers on the judge as referring to your original file too. From 5d538a8f53c8ef5d082664bb10286dc462630aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8A=E3=81=99/TwoSquirrels?= <47352965+TwoSquirrels@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:22:06 +0900 Subject: [PATCH 4/7] style: apply rustfmt Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TwiopnwyFnRbSUyNoFLWAZ --- src/commands/bundle.rs | 8 +++++++- tests/cli.rs | 4 +++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/commands/bundle.rs b/src/commands/bundle.rs index d321bdf..919d155 100644 --- a/src/commands/bundle.rs +++ b/src/commands/bundle.rs @@ -46,7 +46,13 @@ pub fn run(args: BundleArgs) -> Result<()> { .canonicalize() .with_context(|| format!("failed to resolve {}", args.file.display()))?; let unused = if settings.tree_shaking { - unused_origins(&settings, &inventory, &compiler_args, &preprocessed, &target)? + unused_origins( + &settings, + &inventory, + &compiler_args, + &preprocessed, + &target, + )? } else { BTreeSet::new() }; diff --git a/tests/cli.rs b/tests/cli.rs index 4e2e645..24cf153 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -221,7 +221,9 @@ fn run_bundle(sandbox: &Sandbox, args: &[&str]) -> String { /// ソース中の `struct` 定義行の集合。出力どうしの包含関係を比べるのに使う。 fn struct_defs(src: &str) -> BTreeSet<&str> { - src.lines().filter(|line| line.contains("struct ")).collect() + src.lines() + .filter(|line| line.contains("struct ")) + .collect() } #[test] From c2362b26a68269049e267673e1df4fc210ed6fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8A=E3=81=99/TwoSquirrels?= <47352965+TwoSquirrels@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:48:54 +0900 Subject: [PATCH 5/7] fix: skip library verification with --no-tree-shaking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 識別子タグを使わないモードでは stale ハッシュ検証が不要であり、 フォールバック用途をエラーで妨げないようにする。あわせて CLI 専用の tree_shaking を Settings から外し、config + CLI マージという構造体の 契約を保つ。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TwiopnwyFnRbSUyNoFLWAZ --- src/commands/bundle.rs | 13 +++++++------ tests/cli.rs | 9 ++++++++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/commands/bundle.rs b/src/commands/bundle.rs index 919d155..e0cfcf5 100644 --- a/src/commands/bundle.rs +++ b/src/commands/bundle.rs @@ -6,6 +6,9 @@ //! 3. `` 由来部分から識別子を検出 → 逆引きで依存ヘッダーを特定 //! 4. 依存ヘッダーに `-M` を実行 → 必要集合を得て、出力中の不要ヘッダーを判定 //! 5. 不要行の削除・ダミー pragma の復元・クレジット/埋め込みを施して出力 +//! +//! `--no-tree-shaking` 時は識別子タグを一切使わないため、手順 1 のハッシュ検証と手順 3〜4 を +//! まるごとスキップする (不要ヘッダー無しとして扱う)。 use std::collections::{BTreeSet, HashMap}; use std::path::{Path, PathBuf}; @@ -34,7 +37,7 @@ pub fn run(args: BundleArgs) -> Result<()> { let settings = Settings::resolve(&args)?; let inventory = Inventory::load(&store, &settings.keep)?; warn_std_compiler(&settings.compiler, &inventory); - if !args.no_check { + if !args.no_check && !args.no_tree_shaking { inventory.verify()?; } @@ -45,7 +48,9 @@ pub fn run(args: BundleArgs) -> Result<()> { .file .canonicalize() .with_context(|| format!("failed to resolve {}", args.file.display()))?; - let unused = if settings.tree_shaking { + let unused = if args.no_tree_shaking { + BTreeSet::new() + } else { unused_origins( &settings, &inventory, @@ -53,8 +58,6 @@ pub fn run(args: BundleArgs) -> Result<()> { &preprocessed, &target, )? - } else { - BTreeSet::new() }; let target_dir = target.parent(); let bundled = rewrite::rewrite( @@ -122,7 +125,6 @@ struct Settings { options: Vec, keep: BTreeSet, embed: bool, - tree_shaking: bool, } impl Settings { @@ -141,7 +143,6 @@ impl Settings { args.keep.iter().cloned().collect() }, embed: args.embed || config.embed, - tree_shaking: !args.no_tree_shaking, }) } } diff --git a/tests/cli.rs b/tests/cli.rs index 24cf153..109df67 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -370,7 +370,7 @@ fn bundle_fails_for_missing_input_file() { } #[test] -fn bundle_detects_library_changes_and_no_check_bypasses_it() { +fn bundle_detects_library_changes_unless_verification_is_bypassed() { let sandbox = Sandbox::new(); let header = sandbox.write("mylib/algo.hpp", "#pragma once\nstruct Algo {};\n"); let lib_root = header.parent().unwrap().to_path_buf(); @@ -399,4 +399,11 @@ fn bundle_detects_library_changes_and_no_check_bypasses_it() { .args(["-k", STD, "--no-check", "main.cpp"]) .assert() .success(); + + // --no-tree-shaking は識別子タグを使わないため、検証自体が不要になり通る。 + sandbox + .risundle() + .args(["-k", STD, "--no-tree-shaking", "main.cpp"]) + .assert() + .success(); } From 8983e3650b8120fb25771564a81b9543d69c0566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8A=E3=81=99/TwoSquirrels?= <47352965+TwoSquirrels@users.noreply.github.com> Date: Tue, 7 Jul 2026 09:48:55 +0900 Subject: [PATCH 6/7] docs: correct --no-tree-shaking descriptions and fallback recipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - keep 指定ライブラリは --no-tree-shaking でも #include のまま残る旨を明記 - フォールバックレシピ: keep ライブラリには -I が必要な注意を追加、 2>/dev/null をやめて g++ のエラーを見せ、誤診を防ぐ - 英語版チートシートのクリップボード例に iconv 併記 - spec に --no-tree-shaking の挙動 (検証スキップ・設定ファイル非対応) を追記 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TwiopnwyFnRbSUyNoFLWAZ --- README.ja.md | 4 ++-- README.md | 4 ++-- docs/cheatsheet.ja.md | 12 ++++++------ docs/cheatsheet.md | 21 +++++++++++---------- docs/spec.ja.md | 8 +++++++- docs/spec.md | 8 +++++++- 6 files changed, 35 insertions(+), 22 deletions(-) diff --git a/README.ja.md b/README.ja.md index c3f9fdc..960770f 100644 --- a/README.ja.md +++ b/README.ja.md @@ -60,12 +60,12 @@ risundle [OPTIONS] [-- ...] | --- | --- | | `-c`, `--compiler ` | 使用するコンパイラ (既定: `g++`) | | `-k`, `--keep ` | tree-shaking の対象外にするライブラリ ID (繰り返し可。既定: `std`) | -| `--no-tree-shaking` | tree-shaking を無効化し、すべて展開する (フォールバック用) | +| `--no-tree-shaking` | tree-shaking を無効化し、keep 指定以外をすべて展開する (フォールバック用) | | `-e`, `--embed` | 元のソースを先頭にコメントとして埋め込む | | `-n`, `--no-check` | ライブラリ更新のハッシュ検証をスキップする | | `-- ...` | `--` 以降をコンパイラへそのまま渡す | -`--keep` はライブラリを展開せず `#include` のまま残しますが、`--no-tree-shaking` は全ライブラリを展開した上で tree-shaking を行いません。両者は別物です。 +`--keep` はライブラリを展開せず `#include` のまま残しますが、`--no-tree-shaking` は keep 指定を除く全ライブラリを展開した上で tree-shaking を行いません。両者は別物で、併用もできます。 ```bash # clang++ を使い、AC Library も展開せず #include のまま残す diff --git a/README.md b/README.md index 120cb7b..ce0e92c 100644 --- a/README.md +++ b/README.md @@ -60,12 +60,12 @@ Bundles `` and writes the result to standard output. | --- | --- | | `-c`, `--compiler ` | Compiler to use (default: `g++`) | | `-k`, `--keep ` | Library ID to exclude from tree-shaking (repeatable; default: `std`) | -| `--no-tree-shaking` | Disable tree-shaking and expand everything (useful as a fallback) | +| `--no-tree-shaking` | Disable tree-shaking and expand everything except kept libraries (useful as a fallback) | | `-e`, `--embed` | Embed the original source as a comment at the top | | `-n`, `--no-check` | Skip the hash verification of library updates | | `-- ...` | Pass everything after `--` straight to the compiler | -`--keep` leaves a library unexpanded as an `#include`, whereas `--no-tree-shaking` expands every library but performs no tree-shaking. The two are different. +`--keep` leaves a library unexpanded as an `#include`, whereas `--no-tree-shaking` expands every library except the kept ones but performs no tree-shaking. The two are different and can be combined. ```bash # Use clang++ and leave AC Library unexpanded, keeping it as #include diff --git a/docs/cheatsheet.ja.md b/docs/cheatsheet.ja.md index 538627d..0198632 100644 --- a/docs/cheatsheet.ja.md +++ b/docs/cheatsheet.ja.md @@ -99,20 +99,20 @@ g++ -fsyntax-only -std=gnu++20 submission.cpp || risundle --no-tree-shaking main.cpp > submission.cpp ``` -`-fsyntax-only` は構文チェックだけして実行ファイルを作らないので高速です。フラグはジャッジに合わせてください。 +`-fsyntax-only` は構文チェックだけして実行ファイルを作らないので高速です。フラグはジャッジに合わせてください。また、std 以外のライブラリを keep している場合、その `#include` は素の g++ からは見えないため、`-I ~/ac-library` のように場所を教えてください (教えないと検証が毎回失敗し、フォールバックが常に発動してしまいます)。 毎回打つのが面倒なら、シェルの設定ファイル (`~/.bashrc` など) に関数として書いておけます。 ```bash bundle() { risundle "$1" > submission.cpp && - g++ -fsyntax-only -std=gnu++20 submission.cpp 2>/dev/null || - { echo 'tree-shaking に失敗したため全展開します' >&2 && + g++ -fsyntax-only -std=gnu++20 submission.cpp || + { echo '検証に失敗したため、全展開版にフォールバックします' >&2 && risundle --no-tree-shaking "$1" > submission.cpp; } } ``` -以後 `bundle main.cpp` だけで済みます。フォールバックが発動したら、tree-shaking の取りこぼしですので [Issue](https://github.com/TwoSquirrels/risundle/issues) で報告してもらえると助かります。 +以後 `bundle main.cpp` だけで済みます。フォールバックが発動したときは、直前に表示された g++ のエラーを確認してください。自分のコードのミスならフォールバック版も通りませんし、tree-shaking の取りこぼし (消えた定義への未定義エラーなど) なら [Issue](https://github.com/TwoSquirrels/risundle/issues) で報告してもらえると助かります。 ### 提出ファイルの先頭に元のソースをコメントで残したい @@ -128,7 +128,7 @@ risundle -e main.cpp > submission.cpp ```bash risundle library update mylib # 1 つだけ -risundle library update # 登録済みぜんぶ +risundle library update # 登録済み全部 ``` 反映し忘れてもバンドル時に「変更された」と止まって教えてくれるので、そのとき打てば大丈夫です。 @@ -185,7 +185,7 @@ risundle -n main.cpp # 急場しのぎ (検証スキップ) ### バンドル後のファイルがコンパイルエラーになる -まず tree-shaking を切って全部展開すれば、とりあえず提出できます。 +まず tree-shaking を切れば (keep 指定以外が全部展開される)、とりあえず提出できます。 ```bash risundle --no-tree-shaking main.cpp > submission.cpp diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index 1c5c749..9017983 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -50,13 +50,14 @@ You get a `submission.cpp` containing only the parts `main.cpp` actually uses. P ### Copy straight to the clipboard without creating a file ```bash -risundle main.cpp | clip.exe # Windows / WSL -risundle main.cpp | pbcopy # macOS -risundle main.cpp | xclip -sel clip # Linux (X11) -risundle main.cpp | wl-copy # Linux (Wayland) +risundle main.cpp | clip.exe # Windows / WSL (ASCII-only sources) +risundle main.cpp | iconv -t cp932 | clip.exe # WSL on Japanese Windows (see below) +risundle main.cpp | pbcopy # macOS +risundle main.cpp | xclip -sel clip # Linux (X11) +risundle main.cpp | wl-copy # Linux (Wayland) ``` -Caution on WSL: `clip.exe` interprets its input in the Windows system code page, not UTF-8. If your code contains non-ASCII comments, they get garbled — and line breaks can even disappear, silently changing what the code means. Convert first with `iconv` (e.g. `risundle main.cpp | iconv -t cp932 | clip.exe` on Japanese Windows). +Caution on WSL: `clip.exe` interprets its input in the Windows system code page, not UTF-8. If your code contains non-ASCII comments, they get garbled — and line breaks can even disappear, silently changing what the code means. Convert first with `iconv -t ` as shown above (cp932 on Japanese Windows). ### Run tests and submit in one go (combine with oj) @@ -99,20 +100,20 @@ g++ -fsyntax-only -std=gnu++20 submission.cpp || risundle --no-tree-shaking main.cpp > submission.cpp ``` -`-fsyntax-only` only checks the syntax without producing a binary, so it is fast. Match the flags to your judge. +`-fsyntax-only` only checks the syntax without producing a binary, so it is fast. Match the flags to your judge. Also, if you keep a library other than std, its `#include` is invisible to plain g++ — tell it where the library lives with `-I ~/ac-library` or similar (otherwise the check always fails and the fallback fires every time). If that is too much to type every time, put a function in your shell config (`~/.bashrc` etc.). ```bash bundle() { risundle "$1" > submission.cpp && - g++ -fsyntax-only -std=gnu++20 submission.cpp 2>/dev/null || - { echo 'tree-shaking failed; expanding everything' >&2 && + g++ -fsyntax-only -std=gnu++20 submission.cpp || + { echo 'verification failed; falling back to full expansion' >&2 && risundle --no-tree-shaking "$1" > submission.cpp; } } ``` -From then on, `bundle main.cpp` is all you need. If the fallback ever fires, tree-shaking missed something — a report in the [issues](https://github.com/TwoSquirrels/risundle/issues) would be much appreciated. +From then on, `bundle main.cpp` is all you need. When the fallback fires, check the g++ error printed right above it: a mistake in your own code means the fallback output won't compile either, while an undefined reference to a dropped definition means tree-shaking missed something — a report in the [issues](https://github.com/TwoSquirrels/risundle/issues) would be much appreciated. ### Keep the original source as a comment at the top of the submission @@ -185,7 +186,7 @@ risundle -n main.cpp # quick and dirty (skips verification) ### The bundled file fails to compile -Turn off tree-shaking and expand everything — that gets you something submittable for now. +Turn off tree-shaking (everything except kept libraries gets expanded) — that gets you something submittable for now. ```bash risundle --no-tree-shaking main.cpp > submission.cpp diff --git a/docs/spec.ja.md b/docs/spec.ja.md index 09633fa..925c6b7 100644 --- a/docs/spec.ja.md +++ b/docs/spec.ja.md @@ -40,7 +40,7 @@ README より踏み込んだ、各コマンドの挙動・エラー条件・出 ### ライブラリの変更検知 -維持指定 (`keep`) していない `std` 以外のライブラリについて、登録時のハッシュと現在の内容を照合する。食い違う場合は `library update` を促してエラー終了する。`--no-check` を指定すると検証自体をスキップする。維持指定ライブラリと `std` は識別子情報を使わないため検証しない。 +維持指定 (`keep`) していない `std` 以外のライブラリについて、登録時のハッシュと現在の内容を照合する。食い違う場合は `library update` を促してエラー終了する。`--no-check` を指定すると検証自体をスキップする。維持指定ライブラリと `std` は識別子情報を使わないため検証しない。同じ理由で、`--no-tree-shaking` 指定時は全ライブラリを検証しない。 ハッシュは mtime ではなく内容ベースなので、`git clone` や `cp` による時刻変化では誤検知せず、ファイルの追加・削除・リネームは検知できる。 @@ -48,6 +48,12 @@ README より踏み込んだ、各コマンドの挙動・エラー条件・出 維持指定したライブラリは展開せず `#include` のまま残す (tree-shaking の対象外)。既定で `std` が含まれる。`std` を維持指定するとコンパイラに `-nostdinc` を付け、システムヘッダーをダミー経由で解決する。 +### tree-shaking の無効化 (`--no-tree-shaking`) + +`--no-tree-shaking` 指定時は、識別子の検出・依存ヘッダーの逆引き・`-M` による必要集合の計算を行わず、不要ヘッダー無しとして扱う。つまり維持指定を除く全ライブラリが展開されたまま残る。識別子タグを一切使わないため、ライブラリの変更検知もスキップする。維持指定 (keep) とダミー解決の挙動は通常時と変わらない。 + +tree-shaking がうまくいかないときの一時的なフォールバック用途を想定しており、`.risundlerc.toml` からは設定できない (設定ファイルで常用できるようにすると、CLI から tree-shaking を有効に戻すオプションが対で必要になり、仕様が複雑化するため)。 + ### 出力形式 - 先頭行に `// Bundled with risundle v` のクレジットを付ける。 diff --git a/docs/spec.md b/docs/spec.md index e1d0f28..14be413 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -40,7 +40,7 @@ Searches from the directory of `` toward its parents for `.risundlerc.toml ### Library change detection -For libraries other than `std` that are not marked to be kept (`keep`), the registration-time hash is compared against the current contents. If they differ, it prompts you to run `library update` and exits with an error. Specifying `--no-check` skips the verification itself. Kept libraries and `std` are not verified because they do not use identifier information. +For libraries other than `std` that are not marked to be kept (`keep`), the registration-time hash is compared against the current contents. If they differ, it prompts you to run `library update` and exits with an error. Specifying `--no-check` skips the verification itself. Kept libraries and `std` are not verified because they do not use identifier information. For the same reason, no library is verified when `--no-tree-shaking` is specified. The hash is content-based rather than mtime-based, so it does not false-positive on time changes from `git clone` or `cp`, while it can detect file additions, deletions, and renames. @@ -48,6 +48,12 @@ The hash is content-based rather than mtime-based, so it does not false-positive A kept library is not expanded and is left as `#include` (excluded from tree-shaking). `std` is included by default. Keeping `std` adds `-nostdinc` to the compiler and resolves system headers through a dummy. +### Disabling tree-shaking (`--no-tree-shaking`) + +With `--no-tree-shaking`, identifier detection, dependency-header reverse lookup, and the `-M` computation of the required set are all skipped, and no header is treated as unused. In other words, every library except the kept ones remains fully expanded. Because identifier tags are never consulted, library change detection is also skipped. Keep and dummy resolution behave the same as usual. + +This option is intended as a temporary fallback for when tree-shaking goes wrong, and it cannot be set from `.risundlerc.toml` (allowing it in the configuration file would require a paired CLI option to turn tree-shaking back on, complicating the specification). + ### Output format - The first line carries the credit `// Bundled with risundle v`. From 85705fc570464a1cb947aeae816bb8e67eb13148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8A=E3=81=99/TwoSquirrels?= <47352965+TwoSquirrels@users.noreply.github.com> Date: Tue, 7 Jul 2026 10:00:16 +0900 Subject: [PATCH 7/7] docs: address CodeRabbit review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - フォールバックレシピの検証をリンクまで通す実コンパイル (-o /dev/null) に 変更し、定義消失時の undefined reference も検出できるようにする - README に --no-tree-shaking がハッシュ検証も行わない旨を追記 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01TwiopnwyFnRbSUyNoFLWAZ --- README.ja.md | 2 +- README.md | 2 +- docs/cheatsheet.ja.md | 6 +++--- docs/cheatsheet.md | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.ja.md b/README.ja.md index 960770f..da5541d 100644 --- a/README.ja.md +++ b/README.ja.md @@ -65,7 +65,7 @@ risundle [OPTIONS] [-- ...] | `-n`, `--no-check` | ライブラリ更新のハッシュ検証をスキップする | | `-- ...` | `--` 以降をコンパイラへそのまま渡す | -`--keep` はライブラリを展開せず `#include` のまま残しますが、`--no-tree-shaking` は keep 指定を除く全ライブラリを展開した上で tree-shaking を行いません。両者は別物で、併用もできます。 +`--keep` はライブラリを展開せず `#include` のまま残しますが、`--no-tree-shaking` は keep 指定を除く全ライブラリを展開した上で tree-shaking を行いません。両者は別物で、併用もできます。なお `--no-tree-shaking` は識別子情報を使わないため、ライブラリ更新のハッシュ検証も行いません。 ```bash # clang++ を使い、AC Library も展開せず #include のまま残す diff --git a/README.md b/README.md index ce0e92c..ad5c7a3 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Bundles `` and writes the result to standard output. | `-n`, `--no-check` | Skip the hash verification of library updates | | `-- ...` | Pass everything after `--` straight to the compiler | -`--keep` leaves a library unexpanded as an `#include`, whereas `--no-tree-shaking` expands every library except the kept ones but performs no tree-shaking. The two are different and can be combined. +`--keep` leaves a library unexpanded as an `#include`, whereas `--no-tree-shaking` expands every library except the kept ones but performs no tree-shaking. The two are different and can be combined. Note that `--no-tree-shaking` also skips the hash verification of library updates, since it uses no identifier information. ```bash # Use clang++ and leave AC Library unexpanded, keeping it as #include diff --git a/docs/cheatsheet.ja.md b/docs/cheatsheet.ja.md index 0198632..9da9ade 100644 --- a/docs/cheatsheet.ja.md +++ b/docs/cheatsheet.ja.md @@ -95,18 +95,18 @@ tree-shaking は近似なので、まれに必要な定義を削ることがあ ```bash risundle main.cpp > submission.cpp -g++ -fsyntax-only -std=gnu++20 submission.cpp || +g++ -std=gnu++20 -o /dev/null submission.cpp || risundle --no-tree-shaking main.cpp > submission.cpp ``` -`-fsyntax-only` は構文チェックだけして実行ファイルを作らないので高速です。フラグはジャッジに合わせてください。また、std 以外のライブラリを keep している場合、その `#include` は素の g++ からは見えないため、`-I ~/ac-library` のように場所を教えてください (教えないと検証が毎回失敗し、フォールバックが常に発動してしまいます)。 +リンクまで通すことで、定義だけが消えたときの undefined reference も検出できます (実行ファイルは要らないので `-o /dev/null` に捨てています)。フラグはジャッジに合わせてください。また、std 以外のライブラリを keep している場合、その `#include` は素の g++ からは見えないため、`-I ~/ac-library` のように場所を教えてください (教えないと検証が毎回失敗し、フォールバックが常に発動してしまいます)。 毎回打つのが面倒なら、シェルの設定ファイル (`~/.bashrc` など) に関数として書いておけます。 ```bash bundle() { risundle "$1" > submission.cpp && - g++ -fsyntax-only -std=gnu++20 submission.cpp || + g++ -std=gnu++20 -o /dev/null submission.cpp || { echo '検証に失敗したため、全展開版にフォールバックします' >&2 && risundle --no-tree-shaking "$1" > submission.cpp; } } diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index 9017983..88e31cb 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -96,18 +96,18 @@ Tree-shaking is an approximation, so on rare occasions it can drop a needed defi ```bash risundle main.cpp > submission.cpp -g++ -fsyntax-only -std=gnu++20 submission.cpp || +g++ -std=gnu++20 -o /dev/null submission.cpp || risundle --no-tree-shaking main.cpp > submission.cpp ``` -`-fsyntax-only` only checks the syntax without producing a binary, so it is fast. Match the flags to your judge. Also, if you keep a library other than std, its `#include` is invisible to plain g++ — tell it where the library lives with `-I ~/ac-library` or similar (otherwise the check always fails and the fallback fires every time). +Going all the way through linking also catches undefined references when only a definition got dropped (the binary itself is not needed, so it is discarded to `-o /dev/null`). Match the flags to your judge. Also, if you keep a library other than std, its `#include` is invisible to plain g++ — tell it where the library lives with `-I ~/ac-library` or similar (otherwise the check always fails and the fallback fires every time). If that is too much to type every time, put a function in your shell config (`~/.bashrc` etc.). ```bash bundle() { risundle "$1" > submission.cpp && - g++ -fsyntax-only -std=gnu++20 submission.cpp || + g++ -std=gnu++20 -o /dev/null submission.cpp || { echo 'verification failed; falling back to full expansion' >&2 && risundle --no-tree-shaking "$1" > submission.cpp; } }