Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve the interface #3

Open
kuretchi opened this issue Nov 19, 2020 · 4 comments
Open

Improve the interface #3

kuretchi opened this issue Nov 19, 2020 · 4 comments

Comments

@kuretchi
Copy link
Owner

kuretchi commented Nov 19, 2020

今までは「ライブラリクレートに対して実行し,必要なコードを抜粋する」コマンドだったのを,「(ライブラリに依存する) バイナリクレートに対して実行し,提出可能なコードを生成する」コマンドにしたい.

Before:

cargo simple-bundler --manifest-path /path/to/lib/one/Cargo.toml -e main.rs >one.rs
cargo simple-bundler --manifest-path /path/to/lib/two/Cargo.toml -e main.rs >two.rs
cat one.rs two.rs main.rs >bundled.rs

After:

cargo simple-bundler --crates one,two >bundled.rs

  • ライブラリ同士の依存関係を考えるか
    • 考えない場合はそれぞれのライブラリに対して今までの処理をしてくっつけるだけで済む
  • ライブラリのソースコードをどのように持ってくるか
    • path (や git) のみ対応 / cargo-clone などを使う
  • 「このライブラリについては依存関係の解析をスキップ」ができるようにする?
@qryxip
Copy link
Contributor

qryxip commented Nov 20, 2020

私が知っていることを書いておきます。

まず依存グラフおよび依存先のパッケージの情報はcargo metadataで簡単に取れます。(この"kind": nullcargo_metadata::DependencyKind::Normalにあたります)

cargo metadata --format-version 1 | jq '.resolve.root as $r | .resolve.nodes[] | select(.id == $r) | .deps[0]'
{
  "name": "anyhow",
  "pkg": "anyhow 1.0.33 (registry+https://github.com/rust-lang/crates.io-index)",
  "dep_kinds": [
    {
      "kind": null,
      "target": null
    }
  ]
}
  • ライブラリ同士の依存関係を考えるか

少なくとも依存先の方はクレート全体が十分に小さいと考えて、無条件でroot moduleに依存するとみなすという手があると思います。

また展開方法ですが、cargo-equipではこのようにlib間のextern_crate_nameを解決しています。

+// このような"pseudo extern prelude"を挿入。
+mod __pseudo_extern_prelude {
+    // `extern_crate_name`を翻訳
+    pub(super) use crate::{another_lib1, another_lib2};
+}
+use self::__pseudo_extern_prelude::*;
+
 use another_lib1::A;
 use another_lib2::B;

ただこれはRust 2015ではコンパイルできない(継ぎ木が無理)ので、ライブラリを2015-compatibleにしたい場合ユーザーがマーカーとしてextern crateを書くことになります。

 mod extern_crates {
-    pub(super) extern crate __another_lib as another_lib;
+    pub(super) use crate::another_lib;
 }

 use self::extern_crates::another_lib::foo::Foo; // Prepend `self::` to make compatible with Rust 2015

ただバージョンが1.31+なのにエディションが2015なサイトに片っぱしから要望を出す方が早いかもしれません。私はこの前Library-CheckerにPRを出し、yukicoderにも昨日要望を出しました。

  • ライブラリのソースコードをどのように持ってくるか

cargo metadataを打った時点で、というより依存グラフが構築された時点で依存クレートはすべてローカルにダウンロードされています。

cargo metadata --format-version 1 | jq '.packages[] | select(.name == "anyhow") | .manifest_path'
"/home/ryo/.cargo/registry/src/github.com-1ecc6299db9ec823/anyhow-1.0.33/Cargo.toml"

ソースコードを編集したやつを保存したいとなればどこかにcp -rする必要がありますが。(私が考えているやつ。permutohedronとかrustc-hashとかを適切なcopyright notice付きで使えないかなーと)

「このライブラリについては依存関係の解析をスキップ」ができるようにする?

関係あるかわかりませんがAtCoderで使えるクレートについては私はハードコードしてしまいました。ただpermutohedronとか今のままでも普通に展開できそうなんですよね...

@kuretchi
Copy link
Owner Author

ありがとうございます.助かります.

とりあえずの要求として考えていたのは

  • ジャッジ環境で使えるクレートにしか依存していないライブラリを複数使いたい
  • ジャッジ環境で使えるクレートは展開したくない

だったので,(すべての依存先を展開するのではなく) ユーザーが明示的に (依存先のうち) どのクレートを展開するか選択するようにしようというつもりでした.なんですが,これだとクレートを細かく分割するスタイルのライブラリに対応しようとなった場合には困ったことになりますね…

cargo metadataを打った時点で、というより依存グラフが構築された時点で依存クレートはすべてローカルにダウンロードされています。

これは知りませんでした.これなら簡単ですね.

@qryxip
Copy link
Contributor

qryxip commented Nov 27, 2020

なんですが,これだとクレートを細かく分割するスタイルのライブラリに対応しようとなった場合には困ったことになりますね…

cargo-equip(と今online-judge-tools/verification-helperに出している途中のpr)では私がcollaboratorをやっているcargo-udepsを使って「深さ1」のクレートを絞り込んでます。

なんですがcargo-udeps(1)に加えてnightlyツールチェインが必要な上に吐くJSONの形式はドキュメント化してないんですよね。

{
  "success": false,
  "unused_deps": {
    "solve 0.0.0 (path+file:///home/ryo/src/local/play-cargo-equip/solve)": {
      "manifest_path": "/home/ryo/src/local/play-cargo-equip/solve/Cargo.toml",
      "normal": [
        "ac-library-rs-parted",
        "ac-library-rs-parted-convolution",
        "ac-library-rs-parted-dsu",
        "ac-library-rs-parted-fenwicktree",
        "ac-library-rs-parted-lazysegtree",
        "ac-library-rs-parted-math",
        "ac-library-rs-parted-maxflow",
        "ac-library-rs-parted-mincostflow",
        "ac-library-rs-parted-scc",
        "ac-library-rs-parted-segtree",
        "ac-library-rs-parted-string",
        "ac-library-rs-parted-twosat",
        "im-rc",
        "permutohedron"
      ],
      "development": [],
      "build": []
    }
  },
  "note": "Note: These dependencies might be used by other targets.\n      To find dependencies that are not used by any target, enable `--all-targets`.\nNote: They might be false-positive.\n      For example, `cargo-udeps` cannot detect usage of crates that are only used in doc-tests.\n      To ignore some dependencies, write `package.metadata.cargo-udeps.ignore` in Cargo.toml.\n"
}

JSON自体も必要最低限の情報しか詰めてないのでちょっと面倒かも。注意点としてはこのあたり?

  • 「未使用のクレート」は"name in toml"の形でしか含まれてないので、packageとの対応を取るのがほんの少しだけ面倒。取るとしたらこのようにする。

    1. Package::dependenciesDependency::renameHashSet<&str>で集める (explicit_name_in_tomls)
    2. Resolve::nodesNodeDep::nameを見る。
      • i.に含まれているなら、NodeDep::nameは"name in toml"に等しくこれとNodeDep::pkgが対応する。Package::nameが"name in toml"に等しいとは限らない(リネームされているため)
      • i.に含まれていないなら、逆にNodeDep::nameは"name in toml"に等しいとは限らない(lib.nameのリネームがあるため)。Package::nameが"name in toml"に等しい
    3. ii.で見つからないname in tomlについては、そもそもそれはライブラリではない。(「ライブラリでないパッケージ」を[dependencies]に加えるのは何ら問題はない。ただ我々の場合なら単にエラーにしてもいい)
  • (無いとは思いますが)複数のworkspace memberを扱う場合、unused_depsのkey部分のpackage idが意図せずして"human readable"表記の方になっちゃっているので、value側のmanifest_pathで区別する(これらは変なsymlinkを張ってない限り一意)。

あとパッケージ/クレートの「名前」については、このように解決されると私は理解しています。Cargoに詳しいわけではないので誤っているかもしれませんが。

  • PackagePackagename_in_toml

    1. リネームされているならそれ ('-'が含まれているとinvalid)
    2. リネームされていないならpackageの名前に等しい (そうでないCargo.tomlを書こうとしてもinvalid)
  • PackagePackageextern_crate_name == cargo_metadata::NodeDep::name

    1. Cargo.tomlの[*dependencies]でリネームされているなら、それ(== name_in_toml) ('-'が含まれているとinvalid)
    2. [*dependencies]でリネームされてないなら、高々1つあるlibターゲットのTarget::name.replace('-', "_")
      • Cargo.tomlでlib.nameが設定されているならそれ。('-'が含まれているとinvalid)
      • ↑が無ければpackage.name.replace('-', "_")
    3. libターゲットが無ければ、存在しない

@qryxip
Copy link
Contributor

qryxip commented Nov 27, 2020

「このライブラリについては依存関係の解析をスキップ」ができるようにする?

関係あるかわかりませんがAtCoderで使えるクレートについては私はハードコードしてしまいました。ただpermutohedronとか今のままでも普通に展開できそうなんですよね...

あと一応これについてですが、明示的に--exclude-atcoder-cratesを付ける形式に先程変えてしまいました

        --exclude <SPEC>...           Exclude library crates from bundling
        --exclude-atcoder-crates      Alias for `--exclude https://github.com/rust-lang/crates.io-index#alga:0.9.3 ..`
        --exclude-codingame-crates    Alias for `--exclude https://github.com/rust-lang/crates.io-index#chrono:0.4.9 ..`

破壊的変更なのでエラーメッセージも追加した上で。

note: attempted to bundle with the following crate(s), which are available on AtCoder. to exclude them from bundling, run with `--exclude-atcoder-crates`

- `im-rc 14.3.0 (registry+https://github.com/rust-lang/crates.io-index)`
- `rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)`

「CLIオプションで指定するパッケージ」を扱うとしたら krates::PkgSpecpackage ID specificationsを取るのがいいと思います。Cargoコマンドらしくなるし。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants