Skip to main content

cargo_equip/
lib.rs

1#![forbid(unsafe_code)]
2#![warn(rust_2018_idioms)]
3#![recursion_limit = "256"]
4
5mod cargo_udeps;
6mod process;
7mod ra_proc_macro;
8mod rust;
9mod rustfmt;
10pub mod shell;
11mod toolchain;
12mod workspace;
13
14use crate::{
15    ra_proc_macro::ProcMacroExpander,
16    rust::CodeEdit,
17    shell::Shell,
18    workspace::{
19        Edition, MetadataExt as _, PackageExt as _, PackageIdExt as _, ResolveBehavior,
20        TargetExt as _,
21    },
22};
23use anyhow::Context as _;
24use cargo_metadata as cm;
25use indoc::indoc;
26use itertools::{iproduct, Itertools as _};
27use krates::PkgSpec;
28use maplit::{btreeset, hashmap, hashset};
29use petgraph::{
30    graph::{Graph, NodeIndex},
31    visit::Dfs,
32};
33use prettytable::{cell, format::FormatBuilder, row, Table};
34use quote::quote;
35use std::{
36    cmp,
37    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
38    fmt::Debug,
39    path::{Path, PathBuf},
40    rc::Rc,
41    str::FromStr,
42};
43use structopt::{clap::AppSettings, StructOpt};
44
45// We need to prepend " " to `long_help`s.
46// https://github.com/BurntSushi/ripgrep/blob/9eddb71b8e86a04d7048b920b9b50a2e97068d03/crates/core/app.rs#L533-L539
47
48#[allow(clippy::large_enum_variant)]
49#[derive(StructOpt, Debug)]
50#[structopt(
51    author("Ryo Yamashita <qryxip@gmail.com>"),
52    about("Please run as `cargo equip`, not `cargo-equip`."),
53    bin_name("cargo"),
54    global_settings(&[AppSettings::DeriveDisplayOrder, AppSettings::UnifiedHelpMessage])
55)]
56pub enum Opt {
57    #[structopt(
58        about(indoc! {r#"
59
60            A Cargo subcommand to bundle your code into one `.rs` file for competitive programming.
61
62            Use -h for short descriptions and --help for more detials.
63        "#}),
64        author("Ryo Yamashita <qryxip@gmail.com>"),
65        usage(
66            r#"cargo equip [OPTIONS]
67    cargo equip [OPTIONS] --lib
68    cargo equip [OPTIONS] --bin <NAME>
69    cargo equip [OPTIONS] --example <NAME>
70    cargo equip [OPTIONS] --src <PATH>"#,
71        )
72    )]
73    Equip(OptEquip),
74}
75
76#[derive(StructOpt, Debug)]
77pub struct OptEquip {
78    /// Bundle the lib/bin/example target and its dependencies
79    #[structopt(
80        long,
81        value_name("PATH"),
82        conflicts_with_all(&["lib", "bin", "example"]),
83        long_help(indoc! {r#"
84            Bundle the lib/bin/example target and its dependencies.
85
86            This option is intended to be used from editors such as VSCode. Use `--lib`, `--bin` or `--example` for normal usage.
87        "#})
88    )]
89    src: Option<PathBuf>,
90
91    /// Bundle the library and its dependencies
92    #[structopt(long, conflicts_with_all(&["bin", "example"]))]
93    lib: bool,
94
95    /// Bundle the binary and its dependencies
96    #[structopt(long, value_name("NAME"), conflicts_with("example"))]
97    bin: Option<String>,
98
99    /// Bundle the binary example and its dependencies
100    #[structopt(long, value_name("NAME"))]
101    example: Option<String>,
102
103    /// Path to Cargo.toml
104    #[structopt(long, value_name("PATH"))]
105    manifest_path: Option<PathBuf>,
106
107    /// Exclude library crates from bundling
108    #[structopt(long, value_name("SPEC"))]
109    exclude: Vec<PkgSpec>,
110
111    /// Alias for `--exclude {crates available on AtCoder}`
112    #[structopt(
113        long,
114        long_help(Box::leak(
115            format!(
116                "Alias for:\n--exclude {}\n ",
117                ATCODER_CRATES.iter().format("\n          "),
118            )
119            .into_boxed_str(),
120        ))
121    )]
122    exclude_atcoder_crates: bool,
123
124    /// Alias for `--exclude {crates available on AtCoder 202301}`
125    #[structopt(
126        long,
127        long_help(Box::leak(
128            format!(
129                "Alias for:\n--exclude {}\n ",
130                ATCODER_202301_CRATES.iter().format("\n          "),
131            )
132            .into_boxed_str(),
133        ))
134    )]
135    exclude_atcoder_202301_crates: bool,
136
137    /// Alias for `--exclude {crates available on CodinGame}`
138    #[structopt(
139        long,
140        long_help(Box::leak(
141            format!(
142                "Alias for:\n--exclude {}\n ",
143                CODINGAME_CRATES.iter().format("\n          "),
144            )
145            .into_boxed_str(),
146        ))
147    )]
148    exclude_codingame_crates: bool,
149
150    /// Do not include license and copyright notices for the users
151    #[structopt(
152        long,
153        value_name("DOMAIN_AND_USERNAME"),
154        long_help(
155            concat!(
156                indoc! {r#"
157                    Do not include license and copyright notices for the users.
158
159                    Supported formats:
160                    * github.com/{username}
161                    * gitlab.com/{username}
162                "#},
163                ' ',
164            )
165        )
166    )]
167    mine: Vec<User>,
168
169    /// [Deprecated] Alias for `--toolchain-for-udeps`
170    #[structopt(long, value_name("TOOLCHAIN"), conflicts_with("toolchain_for_udeps"))]
171    toolchain: Option<String>,
172
173    /// `nightly` toolchain for `cargo-udeps`
174    #[structopt(long, value_name("TOOLCHAIN"), default_value("nightly"))]
175    toolchain_for_udeps: String,
176
177    /// Toolchain for expanding procedural macros
178    #[structopt(long, value_name("TOOLCHAIN"))]
179    toolchain_for_proc_macro_srv: Option<String>,
180
181    /// Expand the libraries to the module
182    #[structopt(long, value_name("MODULE_PATH"), default_value("crate::__cargo_equip"))]
183    mod_path: CrateSinglePath,
184
185    /// Remove some part [possible values: docs, comments]
186    #[structopt(
187        long,
188        value_name("REMOVE"),
189        possible_values(Remove::VARIANTS),
190        hide_possible_values(true),
191        long_help(concat!(
192            indoc! {r#"
193                Removes
194                * doc comments (`//! ..`, `/// ..`, `/** .. */`, `#[doc = ".."]`) with `--remove docs`.
195                * comments (`// ..`, `/* .. */`) with `--remove comments`.
196
197                ```
198                #[allow(dead_code)]
199                pub mod a {
200                    //! A.
201
202                    /// A.
203                    pub struct A; // aaaaa
204                }
205                ```
206
207                ↓
208
209                ```
210                #[allow(dead_code)]
211                pub mod a {
212                    pub struct A;
213                }
214                ```
215            "#},
216            ' ',
217        ))
218    )]
219    remove: Vec<Remove>,
220
221    /// Minify part of the output before emitting [default: none]  [possible values: none, libs, all]
222    #[structopt(
223        long,
224        value_name("MINIFY"),
225        possible_values(Minify::VARIANTS),
226        hide_possible_values(true),
227        default_value("none"),
228        hide_default_value(true),
229        long_help(concat!(
230            indoc! {r#"
231                Minifies
232                - each expaned library with `--minify lib`.
233                - the whole code with `--minify all`.
234
235                Not that the minification function is incomplete. Unnecessary spaces may be inserted.
236            "#},
237            ' ',
238        ))
239    )]
240    minify: Minify,
241
242    /// Do not resolve `cfg(..)`s
243    #[structopt(long)]
244    no_resolve_cfgs: bool,
245
246    /// Do not format the output before emitting
247    #[structopt(long)]
248    no_rustfmt: bool,
249
250    /// Do not check the output before emitting
251    #[structopt(long)]
252    no_check: bool,
253
254    /// Write to the file instead of STDOUT
255    #[structopt(short, long, value_name("PATH"))]
256    output: Option<PathBuf>,
257
258    /// [Deprecated] Alias for `--minify`
259    #[structopt(
260        long,
261        value_name("MINIFY"),
262        possible_values(Minify::VARIANTS),
263        default_value("none")
264    )]
265    oneline: Minify,
266
267    /// [Deprecated] No-op
268    #[structopt(long, conflicts_with("no_resolve_cfgs"))]
269    resolve_cfgs: bool,
270
271    /// [Deprecated] No-op
272    #[structopt(long, conflicts_with("no_rustfmt"))]
273    rustfmt: bool,
274
275    /// [Deprecated] No-op
276    #[structopt(long, conflicts_with("no_check"))]
277    check: bool,
278}
279
280#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
281pub enum User {
282    Github(String),
283    GitlabCom(String),
284}
285
286impl FromStr for User {
287    type Err = &'static str;
288
289    fn from_str(s: &str) -> Result<Self, &'static str> {
290        return if let Some(username) = s.strip_prefix("github.com/") {
291            Ok(Self::Github(username.to_owned()))
292        } else if let Some(username) = s.strip_prefix("gitlab.com/") {
293            Ok(Self::GitlabCom(username.to_owned()))
294        } else {
295            Err(MSG)
296        };
297
298        static MSG: &str = indoc! {r"
299            Supported formats:
300            * github.com/{username}
301            * gitlab.com/{username}
302        "};
303    }
304}
305
306#[derive(Debug, derive_more::Display)]
307#[display(fmt = "crate::{}", _0)]
308pub struct CrateSinglePath(syn::Ident);
309
310impl FromStr for CrateSinglePath {
311    type Err = &'static str;
312
313    fn from_str(s: &str) -> Result<Self, &'static str> {
314        (|| {
315            let syn::Path {
316                leading_colon,
317                segments,
318            } = syn::parse_str(s).map_err(|_| ())?;
319            match (leading_colon, &*segments.into_iter().collect::<Vec<_>>()) {
320                (None, [p1, p2]) if p1.ident == "crate" => Ok(Self(p2.ident.clone())),
321                _ => Err(()),
322            }
323        })()
324        .map_err(|()| "expected `crate::$ident`")
325    }
326}
327
328#[derive(Clone, Copy, PartialEq, Debug)]
329pub enum Remove {
330    Docs,
331    Comments,
332}
333
334impl Remove {
335    const VARIANTS: &'static [&'static str] = &["docs", "comments"];
336}
337
338impl FromStr for Remove {
339    type Err = &'static str;
340
341    fn from_str(s: &str) -> Result<Self, &'static str> {
342        match s {
343            "docs" => Ok(Self::Docs),
344            "comments" => Ok(Self::Comments),
345            _ => Err(r#"expected "docs", or "comments""#),
346        }
347    }
348}
349
350#[derive(Clone, Copy, PartialEq, Debug)]
351pub enum Minify {
352    None,
353    Libs,
354    All,
355}
356
357impl Minify {
358    const VARIANTS: &'static [&'static str] = &["none", "libs", "all"];
359}
360
361impl FromStr for Minify {
362    type Err = &'static str;
363
364    fn from_str(s: &str) -> Result<Self, &'static str> {
365        match s {
366            "none" => Ok(Self::None),
367            "libs" => Ok(Self::Libs),
368            "all" => Ok(Self::All),
369            _ => Err(r#"expected "none", "libs", or "all""#),
370        }
371    }
372}
373
374pub struct Context<'a> {
375    pub cwd: PathBuf,
376    pub cache_dir: PathBuf,
377    pub shell: &'a mut Shell,
378}
379
380static ATCODER_CRATES: &[&str] = &[
381    "https://github.com/rust-lang/crates.io-index#alga:0.9.3",
382    "https://github.com/rust-lang/crates.io-index#ascii:1.0.0",
383    "https://github.com/rust-lang/crates.io-index#bitset-fixed:0.1.0",
384    "https://github.com/rust-lang/crates.io-index#either:1.5.3",
385    "https://github.com/rust-lang/crates.io-index#fixedbitset:0.2.0",
386    "https://github.com/rust-lang/crates.io-index#getrandom:0.1.14",
387    "https://github.com/rust-lang/crates.io-index#im-rc:14.3.0",
388    "https://github.com/rust-lang/crates.io-index#indexmap:1.3.2",
389    "https://github.com/rust-lang/crates.io-index#itertools:0.9.0",
390    "https://github.com/rust-lang/crates.io-index#itertools-num:0.1.3",
391    "https://github.com/rust-lang/crates.io-index#lazy_static:1.4.0",
392    "https://github.com/rust-lang/crates.io-index#libm:0.2.1",
393    "https://github.com/rust-lang/crates.io-index#maplit:1.0.2",
394    "https://github.com/rust-lang/crates.io-index#nalgebra:0.20.0",
395    "https://github.com/rust-lang/crates.io-index#ndarray:0.13.0",
396    "https://github.com/rust-lang/crates.io-index#num:0.2.1",
397    "https://github.com/rust-lang/crates.io-index#num-bigint:0.2.6",
398    "https://github.com/rust-lang/crates.io-index#num-complex:0.2.4",
399    "https://github.com/rust-lang/crates.io-index#num-derive:0.3.0",
400    "https://github.com/rust-lang/crates.io-index#num-integer:0.1.42",
401    "https://github.com/rust-lang/crates.io-index#num-iter:0.1.40",
402    "https://github.com/rust-lang/crates.io-index#num-rational:0.2.4",
403    "https://github.com/rust-lang/crates.io-index#num-traits:0.2.11",
404    "https://github.com/rust-lang/crates.io-index#ordered-float:1.0.2",
405    "https://github.com/rust-lang/crates.io-index#permutohedron:0.2.4",
406    "https://github.com/rust-lang/crates.io-index#petgraph:0.5.0",
407    "https://github.com/rust-lang/crates.io-index#proconio:0.3.6",
408    "https://github.com/rust-lang/crates.io-index#proconio:0.3.7",
409    "https://github.com/rust-lang/crates.io-index#proconio:0.3.8",
410    "https://github.com/rust-lang/crates.io-index#rand:0.7.3",
411    "https://github.com/rust-lang/crates.io-index#rand_chacha:0.2.2",
412    "https://github.com/rust-lang/crates.io-index#rand_core:0.5.1",
413    "https://github.com/rust-lang/crates.io-index#rand_distr:0.2.2",
414    "https://github.com/rust-lang/crates.io-index#rand_hc:0.2.0",
415    "https://github.com/rust-lang/crates.io-index#rand_pcg:0.2.1",
416    "https://github.com/rust-lang/crates.io-index#regex:1.3.6",
417    "https://github.com/rust-lang/crates.io-index#rustc-hash:1.1.0",
418    "https://github.com/rust-lang/crates.io-index#smallvec:1.2.0",
419    "https://github.com/rust-lang/crates.io-index#superslice:1.0.0",
420    "https://github.com/rust-lang/crates.io-index#text_io:0.1.8",
421    "https://github.com/rust-lang/crates.io-index#whiteread:0.5.0",
422];
423
424static ATCODER_202301_CRATES: &[&str] = &[
425    "https://github.com/rust-lang/crates.io-index#ac-library-rs:0.1.1",
426    "https://github.com/rust-lang/crates.io-index#once_cell:1.18.0",
427    "https://github.com/rust-lang/crates.io-index#static_assertions:1.1.0",
428    "https://github.com/rust-lang/crates.io-index#varisat:0.2.2",
429    "https://github.com/rust-lang/crates.io-index#memoise:0.3.2",
430    "https://github.com/rust-lang/crates.io-index#argio:0.2.0",
431    "https://github.com/rust-lang/crates.io-index#bitvec:1.0.1",
432    "https://github.com/rust-lang/crates.io-index#counter:0.5.7",
433    "https://github.com/rust-lang/crates.io-index#hashbag:0.1.11",
434    "https://github.com/rust-lang/crates.io-index#pathfinding:4.3.0",
435    "https://github.com/rust-lang/crates.io-index#recur-fn:2.2.0",
436    "https://github.com/rust-lang/crates.io-index#indexing:0.4.1",
437    "https://github.com/rust-lang/crates.io-index#amplify:3.14.2",
438    "https://github.com/rust-lang/crates.io-index#amplify_derive:2.11.3",
439    "https://github.com/rust-lang/crates.io-index#amplify_num:0.4.1",
440    "https://github.com/rust-lang/crates.io-index#easy-ext:1.0.1",
441    "https://github.com/rust-lang/crates.io-index#multimap:0.9.0",
442    "https://github.com/rust-lang/crates.io-index#btreemultimap:0.1.1",
443    "https://github.com/rust-lang/crates.io-index#bstr:1.6.0",
444    "https://github.com/rust-lang/crates.io-index#az:1.2.1",
445    "https://github.com/rust-lang/crates.io-index#glidesort:0.1.2",
446    "https://github.com/rust-lang/crates.io-index#tap:1.0.1",
447    "https://github.com/rust-lang/crates.io-index#omniswap:0.1.0",
448    "https://github.com/rust-lang/crates.io-index#multiversion:0.7.2",
449    "https://github.com/rust-lang/crates.io-index#num:0.4.1",
450    "https://github.com/rust-lang/crates.io-index#num-bigint:0.4.3",
451    "https://github.com/rust-lang/crates.io-index#num-complex:0.4.3",
452    "https://github.com/rust-lang/crates.io-index#num-integer:0.1.45",
453    "https://github.com/rust-lang/crates.io-index#num-iter:0.1.43",
454    "https://github.com/rust-lang/crates.io-index#num-rational:0.4.1",
455    "https://github.com/rust-lang/crates.io-index#num-traits:0.2.15",
456    "https://github.com/rust-lang/crates.io-index#num-derive:0.4.0",
457    "https://github.com/rust-lang/crates.io-index#ndarray:0.15.6",
458    "https://github.com/rust-lang/crates.io-index#nalgebra:0.32.3",
459    "https://github.com/rust-lang/crates.io-index#alga:0.9.3",
460    "https://github.com/rust-lang/crates.io-index#libm:0.2.7",
461    "https://github.com/rust-lang/crates.io-index#rand:0.8.5",
462    "https://github.com/rust-lang/crates.io-index#getrandom:0.2.10",
463    "https://github.com/rust-lang/crates.io-index#rand_chacha:0.3.1",
464    "https://github.com/rust-lang/crates.io-index#rand_core:0.6.4",
465    "https://github.com/rust-lang/crates.io-index#rand_hc:0.3.2",
466    "https://github.com/rust-lang/crates.io-index#rand_pcg:0.3.1",
467    "https://github.com/rust-lang/crates.io-index#rand_distr:0.4.3",
468    "https://github.com/rust-lang/crates.io-index#petgraph:0.6.3",
469    "https://github.com/rust-lang/crates.io-index#indexmap:2.0.0",
470    "https://github.com/rust-lang/crates.io-index#regex:1.9.1",
471    "https://github.com/rust-lang/crates.io-index#lazy_static:1.4.0",
472    "https://github.com/rust-lang/crates.io-index#ordered-float:3.7.0",
473    "https://github.com/rust-lang/crates.io-index#ascii:1.1.0",
474    "https://github.com/rust-lang/crates.io-index#permutohedron:0.2.4",
475    "https://github.com/rust-lang/crates.io-index#superslice:1.0.0",
476    "https://github.com/rust-lang/crates.io-index#itertools:0.11.0",
477    "https://github.com/rust-lang/crates.io-index#itertools-num:0.1.3",
478    "https://github.com/rust-lang/crates.io-index#maplit:1.0.2",
479    "https://github.com/rust-lang/crates.io-index#either:1.8.1",
480    "https://github.com/rust-lang/crates.io-index#im-rc:15.1.0",
481    "https://github.com/rust-lang/crates.io-index#fixedbitset:0.4.2",
482    "https://github.com/rust-lang/crates.io-index#bitset-fixed:0.1.0",
483    "https://github.com/rust-lang/crates.io-index#proconio:0.4.5",
484    "https://github.com/rust-lang/crates.io-index#text_io:0.1.12",
485    "https://github.com/rust-lang/crates.io-index#rustc-hash:1.1.0",
486    "https://github.com/rust-lang/crates.io-index#smallvec:1.11.0",
487];
488
489static CODINGAME_CRATES: &[&str] = &[
490    "https://github.com/rust-lang/crates.io-index#chrono:0.4.19",
491    "https://github.com/rust-lang/crates.io-index#itertools:0.10.0",
492    "https://github.com/rust-lang/crates.io-index#libc:0.2.93",
493    "https://github.com/rust-lang/crates.io-index#rand:0.8.3",
494    "https://github.com/rust-lang/crates.io-index#regex:1.4.5",
495    "https://github.com/rust-lang/crates.io-index#time:0.2.26",
496];
497
498pub fn run(opt: Opt, ctx: Context<'_>) -> anyhow::Result<()> {
499    let Opt::Equip(OptEquip {
500        src,
501        lib,
502        bin,
503        example,
504        manifest_path,
505        exclude,
506        exclude_atcoder_crates,
507        exclude_atcoder_202301_crates,
508        exclude_codingame_crates,
509        mine,
510        toolchain: deprecated_toolchain_opt,
511        toolchain_for_udeps,
512        toolchain_for_proc_macro_srv,
513        mod_path: CrateSinglePath(cargo_equip_mod_name),
514        remove,
515        minify,
516        no_resolve_cfgs,
517        no_rustfmt,
518        no_check,
519        output,
520        oneline: deprecated_oneline_opt,
521        resolve_cfgs: deprecated_resolve_cfgs_flag,
522        rustfmt: deprecated_rustfmt_flag,
523        check: deprecated_check_flag,
524    }) = opt;
525
526    let minify = match (minify, deprecated_oneline_opt) {
527        (Minify::None, oneline) => oneline,
528        (minify, _) => minify,
529    };
530
531    let exclude = {
532        let mut exclude = exclude;
533        if exclude_atcoder_crates {
534            exclude.extend(ATCODER_CRATES.iter().map(|s| s.parse().unwrap()));
535        }
536        if exclude_atcoder_202301_crates {
537            exclude.extend(ATCODER_202301_CRATES.iter().map(|s| s.parse().unwrap()));
538        }
539        if exclude_codingame_crates {
540            exclude.extend(CODINGAME_CRATES.iter().map(|s| s.parse().unwrap()));
541        }
542        exclude
543    };
544
545    let toolchain_for_udeps = deprecated_toolchain_opt
546        .as_ref()
547        .unwrap_or(&toolchain_for_udeps);
548
549    let Context {
550        cwd,
551        cache_dir,
552        shell,
553    } = ctx;
554
555    if deprecated_toolchain_opt.is_some() {
556        shell.warn("`--toolchain` was renamed to `--toolchain-for-udeps`")?;
557    }
558    if deprecated_resolve_cfgs_flag {
559        shell.warn("`--resolve-cfgs` is deprecated. `#[cfg(..)]`s are resolved by default")?;
560    }
561    if deprecated_rustfmt_flag {
562        shell.warn("`--rustfmt` is deprecated. the output is formatted by default")?;
563    }
564    if deprecated_check_flag {
565        shell.warn("`--check` is deprecated. the output is checked by default")?;
566    }
567
568    let manifest_path = if let Some(manifest_path) = manifest_path {
569        cwd.join(manifest_path.strip_prefix(".").unwrap_or(&manifest_path))
570    } else {
571        workspace::locate_project(&cwd)?
572    };
573
574    let metadata = workspace::cargo_metadata(&manifest_path, &cwd)?;
575
576    let (root, root_package) = if lib {
577        metadata.lib_target()
578    } else if let Some(bin) = bin {
579        metadata.bin_target_by_name(&bin)
580    } else if let Some(example) = example {
581        metadata.example_target_by_name(&example)
582    } else if let Some(src) = src {
583        metadata.target_by_src_path(&cwd.join(src))
584    } else {
585        metadata.exactly_one_target()
586    }?;
587
588    if root_package.edition() == Edition::Edition2015 {
589        shell.warn("Rust 2015 is unsupported")?;
590    }
591    let resolve_behavior = workspace::resolve_behavior(root_package, &metadata.workspace_root)?;
592    if resolve_behavior >= ResolveBehavior::V2 {
593        shell.warn(
594            "Currently cargo-equip only support Feature Resovler v1, and may go search more crates \
595             than real Cargo does. Please watch https://github.com/qryxip/cargo-equip/issues/94",
596        )?;
597    }
598
599    let libs_to_bundle = {
600        let unused_deps = &if root.is_lib() {
601            hashset!()
602        } else {
603            match cargo_udeps::cargo_udeps(root_package, root, toolchain_for_udeps, shell) {
604                Ok(unused_deps) => unused_deps,
605                Err(warning) => {
606                    shell.warn(warning)?;
607                    hashset!()
608                }
609            }
610        };
611        let mut libs_to_bundle =
612            metadata.libs_to_bundle(&root_package.id, root.is_example(), unused_deps, &exclude)?;
613        if root.is_lib() {
614            libs_to_bundle.insert(&root_package.id, (root, root.crate_name()));
615        }
616        libs_to_bundle
617    };
618
619    let error_message = |head: &str| {
620        let mut msg = head.to_owned();
621
622        msg += "\n\n";
623        msg += &libs_to_bundle
624            .iter()
625            .map(|(package_id, (_, pseudo_extern_crate_name))| {
626                format!(
627                    "- `{}` as `crate::{}::crates::{}`\n",
628                    package_id, cargo_equip_mod_name, pseudo_extern_crate_name,
629                )
630            })
631            .join("");
632
633        let crates_available_on_atcoder = iproduct!(libs_to_bundle.keys(), ATCODER_CRATES)
634            .filter(|(id, s)| s.parse::<PkgSpec>().unwrap().matches(&metadata[id]))
635            .map(|(id, _)| format!("- `{}`\n", id))
636            .join("");
637
638        if !crates_available_on_atcoder.is_empty() {
639            msg += &format!(
640                "\nnote: attempted to bundle with the following crate(s), which are available on \
641                 AtCoder. to exclude them from bundling, run with `--exclude-atcoder-crates`\n\n{}",
642                crates_available_on_atcoder,
643            );
644        }
645
646        msg
647    };
648
649    let code = bundle(
650        &metadata,
651        if root.is_lib() {
652            RootCrate::Lib(root_package, root)
653        } else {
654            RootCrate::BinLike(root_package, root)
655        },
656        &libs_to_bundle,
657        &mine,
658        &cargo_equip_mod_name,
659        !no_resolve_cfgs,
660        &remove,
661        minify,
662        !no_rustfmt,
663        toolchain_for_proc_macro_srv.as_deref(),
664        &cache_dir,
665        shell,
666    )
667    .with_context(|| error_message("could not bundle the code"))?;
668
669    if !no_check {
670        workspace::cargo_check_using_current_lockfile_and_cache(
671            &metadata,
672            root_package,
673            root,
674            &exclude,
675            &code,
676        )
677        .with_context(|| error_message("the bundled code was not valid"))?;
678    }
679
680    if let Some(output) = output {
681        let output = cwd.join(output);
682        cargo_util::paths::write(output, code)
683    } else {
684        write!(shell.out(), "{}", code)?;
685        Ok(())
686    }
687}
688
689#[allow(clippy::too_many_arguments)]
690fn bundle(
691    metadata: &cm::Metadata,
692    root_crate: RootCrate<'_>,
693    libs_to_bundle: &BTreeMap<&cm::PackageId, (&cm::Target, String)>,
694    mine: &[User],
695    cargo_equip_mod_name: &syn::Ident,
696    resolve_cfgs: bool,
697    remove: &[Remove],
698    minify: Minify,
699    rustfmt: bool,
700    toolchain_for_proc_macro_srv: Option<&str>,
701    cache_dir: &Path,
702    shell: &mut Shell,
703) -> anyhow::Result<String> {
704    let cargo_check_message_format_json = |toolchain: &str, shell: &mut Shell| -> _ {
705        let (package, krate) = root_crate.split();
706        workspace::cargo_check_message_format_json(toolchain, metadata, package, krate, shell)
707            .map(Rc::new)
708    };
709
710    let active_toolchain = &*toolchain::active_toolchain(root_crate.package().manifest_dir())?;
711    let toolchain_for_proc_macro_srv = toolchain_for_proc_macro_srv.unwrap_or(active_toolchain);
712
713    let has_custom_build = libs_to_bundle
714        .keys()
715        .any(|p| metadata[p].has_custom_build());
716    let has_proc_macro = libs_to_bundle.keys().any(|p| metadata[p].has_proc_macro());
717
718    let (cargo_messages_for_out_dirs, cargo_messages_for_proc_macro_dll_paths) =
719        if has_custom_build && has_proc_macro && active_toolchain == toolchain_for_proc_macro_srv {
720            let cargo_messages = cargo_check_message_format_json(active_toolchain, shell)?;
721            (cargo_messages.clone(), Some(cargo_messages))
722        } else {
723            let cargo_messages_for_out_dirs = has_custom_build
724                .then(|| cargo_check_message_format_json(active_toolchain, shell))
725                .unwrap_or_else(|| Ok(Default::default()))?;
726
727            let cargo_messages_for_proc_macro_dll_paths = has_proc_macro
728                .then(|| cargo_check_message_format_json(toolchain_for_proc_macro_srv, shell))
729                .transpose()?;
730
731            (
732                cargo_messages_for_out_dirs,
733                cargo_messages_for_proc_macro_dll_paths,
734            )
735        };
736
737    let out_dirs = workspace::list_out_dirs(metadata, &cargo_messages_for_out_dirs);
738
739    let macro_expander = cargo_messages_for_proc_macro_dll_paths
740        .as_ref()
741        .map(|cargo_messages_for_proc_macro_dll_paths| {
742            let proc_macro_srv_exe = &toolchain::find_rust_analyzer_proc_macro_srv(
743                root_crate.package().manifest_dir(),
744                toolchain_for_proc_macro_srv,
745            )?;
746
747            let proc_macro_crate_dylibs = &ra_proc_macro::list_proc_macro_dylibs(
748                cargo_messages_for_proc_macro_dll_paths,
749                |p| libs_to_bundle.contains_key(p),
750            );
751
752            ProcMacroExpander::spawn(proc_macro_srv_exe, proc_macro_crate_dylibs)
753        })
754        .transpose()?;
755
756    let proc_macro_names = macro_expander
757        .as_ref()
758        .map(|macro_expander| {
759            let mut proc_macro_names = HashMap::<_, BTreeSet<_>>::new();
760            for (pkg, macro_names) in macro_expander.macro_names() {
761                for macro_name in macro_names {
762                    proc_macro_names
763                        .entry(pkg)
764                        .or_default()
765                        .insert(macro_name.to_owned());
766                }
767            }
768            proc_macro_names
769        })
770        .unwrap_or_default();
771
772    let resolve_nodes = metadata
773        .resolve
774        .as_ref()
775        .map(|cm::Resolve { nodes, .. }| &nodes[..])
776        .unwrap_or(&[])
777        .iter()
778        .map(|node| (&node.id, node))
779        .collect::<HashMap<_, _>>();
780
781    let mut code = if let Some((_, bin_target)) = root_crate.bin_like() {
782        let code = cargo_util::paths::read(bin_target.src_path.as_ref())?;
783        if rust::find_skip_attribute(&code)? {
784            shell.status("Found", "`#![cfg_attr(cargo_equip, cargo_equip::skip)]`")?;
785            return Ok(code);
786        }
787        code
788    } else {
789        "".to_owned()
790    };
791
792    shell.status("Bundling", "the code")?;
793
794    if let Some((bin_package, bin_target)) = root_crate.bin_like() {
795        code = rust::process_bin(
796            cargo_equip_mod_name,
797            &bin_target.src_path,
798            { macro_expander }.as_mut(),
799            |extern_crate_name| {
800                metadata
801                    .dep_lib_by_extern_crate_name(&bin_package.id, extern_crate_name)
802                    .map(|_| extern_crate_name.to_owned())
803            },
804            |extern_crate_name| {
805                matches!(
806                    metadata.dep_lib_by_extern_crate_name(&bin_package.id, extern_crate_name),
807                    Some(lib_package) if libs_to_bundle.contains_key(&lib_package.id)
808                )
809            },
810            || (bin_target.crate_name(), &bin_package.id.repr),
811        )?;
812    }
813
814    let libs = libs_to_bundle
815        .iter()
816        .map(|(pkg, (krate, pseudo_extern_crate_name))| {
817            let mut edit = CodeEdit::new(cargo_equip_mod_name, &krate.src_path, || {
818                (krate.crate_name(), &pkg.repr)
819            })?;
820            if let Some(out_dir) = out_dirs.get(pkg) {
821                edit.expand_includes(out_dir)?;
822            }
823            Ok((*pkg, (*krate, &**pseudo_extern_crate_name, edit)))
824        })
825        .collect::<anyhow::Result<BTreeMap<_, _>>>()?;
826
827    let (graph, indices) = normal_non_host_dep_graph(&resolve_nodes, libs_to_bundle);
828
829    let libs_using_proc_macros = {
830        let mut crates_using_proc_macros = BTreeMap::<_, HashSet<_>>::new();
831        for (pkg, names) in &proc_macro_names {
832            let (_, pseudo_extern_crate_name) = &libs_to_bundle[pkg];
833            for name in names {
834                crates_using_proc_macros
835                    .entry(&**name)
836                    .or_default()
837                    .insert(&**pseudo_extern_crate_name);
838            }
839        }
840        for goal in libs_to_bundle.keys() {
841            if metadata[goal].has_proc_macro() {
842                let mut dfs = Dfs::new(&graph, indices[goal]);
843                while let Some(next) = dfs.next(&graph) {
844                    let (_, pseudo_extern_crate_name) = &libs_to_bundle[graph[next]];
845                    for name in &proc_macro_names[goal] {
846                        crates_using_proc_macros
847                            .entry(name)
848                            .or_default()
849                            .insert(pseudo_extern_crate_name);
850                    }
851                }
852            }
853        }
854        crates_using_proc_macros
855    };
856
857    let libs_with_local_inner_macros = {
858        let mut libs_with_local_inner_macros = libs
859            .keys()
860            .map(|pkg| (*pkg, btreeset!()))
861            .collect::<HashMap<_, _>>();
862        for (goal, (_, pseudo_extern_crate_name, edit)) in &libs {
863            if edit.has_local_inner_macros_attr() {
864                libs_with_local_inner_macros
865                    .get_mut(*goal)
866                    .unwrap()
867                    .insert(&**pseudo_extern_crate_name);
868                let mut dfs = Dfs::new(&graph, indices[goal]);
869                while let Some(next) = dfs.next(&graph) {
870                    libs_with_local_inner_macros
871                        .get_mut(graph[next])
872                        .unwrap()
873                        .insert(&**pseudo_extern_crate_name);
874                }
875            }
876        }
877        libs_with_local_inner_macros
878    };
879
880    let libs = libs
881        .into_iter()
882        .map(
883            |(lib_package, (lib_target, pseudo_extern_crate_name, mut edit))| {
884                let lib_package: &cm::Package = &metadata[lib_package];
885
886                if let Some(names) = proc_macro_names.get(&lib_package.id) {
887                    debug_assert_eq!(["proc-macro".to_owned()], *lib_target.kind);
888                    let names = names
889                        .iter()
890                        .map(|name| {
891                            let rename = format!(
892                                "{}_macro_def_{}_{}",
893                                cargo_equip_mod_name, pseudo_extern_crate_name, name,
894                            );
895                            (name, rename)
896                        })
897                        .collect::<Vec<_>>();
898                    let crate_mod_content = format!(
899                        "pub use crate::{}::macros::{}::*;{}",
900                        cargo_equip_mod_name,
901                        pseudo_extern_crate_name,
902                        names
903                            .iter()
904                            .map(|(name, rename)| {
905                                let msg = format!(
906                                    "`{}` from `{} {}` should have been expanded",
907                                    name, lib_package.name, lib_package.version,
908                                );
909                                format!(
910                                    "#[macro_export]macro_rules!{}\
911                                     {{($(_:tt)*)=>(::std::compile_error!({});)}}",
912                                    rename,
913                                    quote!(#msg),
914                                )
915                            })
916                            .join("")
917                    );
918                    let macro_mod_content = format!(
919                        "pub use crate::{}{}{};",
920                        if names.len() == 1 { " " } else { "{" },
921                        names
922                            .iter()
923                            .map(|(name, rename)| format!("{} as {}", rename, name))
924                            .format(","),
925                        if names.len() == 1 { "" } else { "}" },
926                    );
927                    return Ok((
928                        pseudo_extern_crate_name,
929                        (
930                            lib_package,
931                            crate_mod_content,
932                            macro_mod_content,
933                            "".to_owned(),
934                        ),
935                    ));
936                }
937
938                let cm::Node { features, .. } = resolve_nodes[&lib_package.id];
939
940                let translate_extern_crate_name = |dst: &_| -> _ {
941                    let dst_package =
942                        metadata.dep_lib_by_extern_crate_name(&lib_package.id, dst)?;
943                    let (_, dst_pseudo_extern_crate_name) =
944                        libs_to_bundle.get(&dst_package.id).unwrap_or_else(|| {
945                            panic!(
946                                "missing `extern_crate_name` for `{}`. generated one should be \
947                                 given beforehead. this is a bug",
948                                dst_package.id,
949                            );
950                        });
951                    Some(dst_pseudo_extern_crate_name.clone())
952                };
953
954                edit.translate_crate_path(pseudo_extern_crate_name)?;
955                edit.translate_extern_crate_paths(translate_extern_crate_name)?;
956                edit.process_extern_crates_in_lib(translate_extern_crate_name, shell)?;
957                let macro_mod_content = edit.modify_declarative_macros(pseudo_extern_crate_name)?;
958                let prelude_mod_content = edit.resolve_pseudo_prelude(
959                    pseudo_extern_crate_name,
960                    &libs_with_local_inner_macros[&lib_package.id],
961                    &{
962                        metadata
963                            .libs_with_extern_crate_names(
964                                &lib_package.id,
965                                &libs_to_bundle.keys().copied().collect(),
966                            )?
967                            .into_iter()
968                            .map(|(package_id, extern_crate_name)| {
969                                let (_, pseudo_extern_crate_name) =
970                                libs_to_bundle.get(package_id).with_context(|| {
971                                    "could not translate pseudo extern crate names. this is a bug"
972                                })?;
973                                Ok((extern_crate_name, pseudo_extern_crate_name.clone()))
974                            })
975                            .collect::<anyhow::Result<_>>()?
976                    },
977                )?;
978                if resolve_cfgs {
979                    edit.resolve_cfgs(features)?;
980                }
981                if remove.contains(&Remove::Docs) {
982                    edit.allow_missing_docs();
983                    edit.erase_docs()?;
984                }
985                if remove.contains(&Remove::Comments) {
986                    edit.erase_comments()?;
987                }
988
989                let crate_mod_content = edit.finish()?;
990
991                Ok((
992                    pseudo_extern_crate_name,
993                    (
994                        lib_package,
995                        crate_mod_content,
996                        macro_mod_content,
997                        prelude_mod_content,
998                    ),
999                ))
1000            },
1001        )
1002        .collect::<anyhow::Result<Vec<(&str, (&cm::Package, String, String, String))>>>()?;
1003
1004    if !libs.is_empty() {
1005        if !root_crate.package().authors.is_empty() {
1006            shell.warn(
1007                "`package.authors` are no longer used to skip Copyright and License Notices",
1008            )?;
1009            shell.warn("instead, add `--mine github.com/{your username}` to the arguments")?;
1010        }
1011
1012        code = rust::insert_prelude_for_main_crate(&code, cargo_equip_mod_name)?;
1013
1014        code =
1015            rust::allow_unused_imports_for_seemingly_proc_macros(&code, |mod_name, item_name| {
1016                matches!(
1017                    libs_using_proc_macros.get(item_name), Some(pseudo_extern_crate_names)
1018                    if pseudo_extern_crate_names.contains(mod_name)
1019                )
1020            })?;
1021
1022        let doc = &{
1023            fn list_packages<'a>(
1024                doc: &mut String,
1025                title: &str,
1026                cargo_equip_mod_name: &syn::Ident,
1027                contents: impl Iterator<Item = (Option<&'a str>, &'a cm::Package)>,
1028            ) {
1029                let mut table = Table::new();
1030
1031                *table.get_format() = FormatBuilder::new()
1032                    .column_separator(' ')
1033                    .borders(' ')
1034                    .build();
1035
1036                let contents = contents.collect::<Vec<_>>();
1037                let any_from_local_filesystem = contents.iter().any(|(_, p)| p.source.is_none());
1038
1039                for (pseudo_extern_crate_name, package) in contents {
1040                    let mut row = row![format!("- `{}`", package.id.mask_path())];
1041
1042                    if any_from_local_filesystem {
1043                        row.add_cell(if package.source.is_some() {
1044                            cell!("")
1045                        } else if let Some(repository) = &package.repository {
1046                            cell!(format!("published in {}", repository))
1047                        } else {
1048                            cell!("published in **missing**")
1049                        });
1050                    }
1051
1052                    row.add_cell(if let Some(license) = &package.license {
1053                        cell!(format!("licensed under `{}`", license))
1054                    } else {
1055                        cell!("licensed under **missing**")
1056                    });
1057
1058                    if let Some(pseudo_extern_crate_name) = pseudo_extern_crate_name {
1059                        row.add_cell(cell!(format!(
1060                            "as `crate::{}::crates::{}`",
1061                            cargo_equip_mod_name, pseudo_extern_crate_name,
1062                        )));
1063                    }
1064
1065                    table.add_row(row);
1066                }
1067
1068                if !table.is_empty() {
1069                    if !doc.is_empty() {
1070                        *doc += "\n";
1071                    }
1072                    *doc += &format!(" # {}\n\n", title);
1073                    for line in table.to_string().lines() {
1074                        *doc += line.trim_end();
1075                        *doc += "\n";
1076                    }
1077                }
1078            }
1079
1080            let mut doc = "".to_owned();
1081
1082            list_packages(
1083                &mut doc,
1084                "Bundled libraries",
1085                cargo_equip_mod_name,
1086                libs.iter()
1087                    .filter(|(_, (p, _, _, _))| p.has_lib())
1088                    .map(|(k, (p, _, _, _))| (Some(*k), *p)),
1089            );
1090
1091            list_packages(
1092                &mut doc,
1093                "Procedural macros",
1094                cargo_equip_mod_name,
1095                libs.iter()
1096                    .filter(|(_, (p, _, _, _))| p.has_proc_macro())
1097                    .map(|(_, (p, _, _, _))| (None, *p)),
1098            );
1099
1100            let notices = libs
1101                .iter()
1102                .filter(|(_, (p, _, _, _))| {
1103                    p.has_lib() && !metadata.workspace_members.contains(&p.id)
1104                })
1105                .map(|(_, (lib_package, _, _, _))| {
1106                    shell.status("Checking", format!("the license of `{}`", lib_package.id))?;
1107                    lib_package
1108                        .read_license_text(mine, cache_dir)
1109                        .map(|license_text| {
1110                            license_text.map(|license_text| (&lib_package.id, license_text))
1111                        })
1112                })
1113                .flat_map(Result::transpose)
1114                .collect::<Result<Vec<_>, _>>()?;
1115
1116            if !notices.is_empty() {
1117                doc += "\n # License and Copyright Notices\n";
1118                for (package_id, license_text) in notices {
1119                    doc += &format!("\n - `{}`\n\n", package_id.mask_path());
1120                    let backquotes = {
1121                        let (mut n, mut m) = (2, None);
1122                        for c in license_text.chars() {
1123                            if c == '`' {
1124                                m = Some(m.unwrap_or(0) + 1);
1125                            } else if let Some(m) = m.take() {
1126                                n = cmp::max(n, m);
1127                            }
1128                        }
1129                        "`".repeat(cmp::max(n, m.unwrap_or(0)) + 1)
1130                    };
1131                    doc += &format!("     {}text\n", backquotes);
1132                    for line in license_text.lines() {
1133                        match line {
1134                            "" => doc += "\n",
1135                            line => doc += &format!("     {}\n", line),
1136                        }
1137                    }
1138                    doc += &format!("     {}\n", backquotes);
1139                }
1140            }
1141
1142            doc
1143        };
1144
1145        code += "\n";
1146        code += &match root_crate {
1147            RootCrate::BinLike(..) => {
1148                "// The following code was expanded by `cargo-equip`.\n".to_owned()
1149            }
1150            RootCrate::Lib(..) => format!("use {}::prelude::*;\n", cargo_equip_mod_name),
1151        };
1152        code += "\n";
1153
1154        let crate_mods = libs
1155            .iter()
1156            .map(|(name, (_, content, _, _))| (*name, &**content))
1157            .collect::<Vec<_>>();
1158
1159        let macro_mods = libs
1160            .iter()
1161            .map(|(name, (_, _, content, _))| (*name, &**content))
1162            .collect::<Vec<_>>();
1163
1164        let prelude_mods = libs
1165            .iter()
1166            .map(|(name, (_, _, _, content))| (*name, &**content))
1167            .collect::<Vec<_>>();
1168
1169        let render_mods = |code: &mut String, mods: &[(&str, &str)]| -> anyhow::Result<()> {
1170            if minify == Minify::Libs {
1171                for (pseudo_extern_crate_name, mod_content) in mods {
1172                    *code += "        pub mod ";
1173                    *code += pseudo_extern_crate_name;
1174                    *code += " {";
1175                    *code += &rustminify::minify_file(&rust::parse_file(mod_content)?);
1176                    *code += "}\n";
1177                }
1178            } else {
1179                for (i, (pseudo_extern_crate_name, mod_content)) in mods.iter().enumerate() {
1180                    if i > 0 {
1181                        *code += "\n";
1182                    }
1183                    *code += "        pub mod ";
1184                    *code += pseudo_extern_crate_name;
1185                    *code += " {\n";
1186                    *code += &rust::indent_code(mod_content, 3);
1187                    *code += "    }\n";
1188                }
1189            }
1190            Ok(())
1191        };
1192
1193        for doc in doc.lines() {
1194            code += "///";
1195            if !code.is_empty() {
1196                code += " ";
1197            }
1198            code += doc;
1199            code += "\n";
1200        }
1201        if minify == Minify::Libs {
1202            code += "#[cfg_attr(any(), rustfmt::skip)]\n";
1203        }
1204        code += "#[allow(unused)]\n";
1205        code += &format!("mod {} {{\n", cargo_equip_mod_name);
1206        code += "    pub(crate) mod crates {\n";
1207        render_mods(&mut code, &crate_mods)?;
1208        code += "    }\n";
1209        code += "\n";
1210        code += "    pub(crate) mod macros {\n";
1211        render_mods(&mut code, &macro_mods)?;
1212        code += "    }\n";
1213        code += "\n";
1214        code += "    pub(crate) mod prelude {";
1215        match root_crate {
1216            RootCrate::BinLike(..) => {
1217                let prelude_for_main = {
1218                    let local_macro_uses_in_main_crate = libs_with_local_inner_macros
1219                        .values()
1220                        .flatten()
1221                        .unique()
1222                        .sorted()
1223                        .map(|name| format!("{}::*", name))
1224                        .collect::<Vec<_>>();
1225
1226                    let local_macro_uses_in_main_crate = match &*local_macro_uses_in_main_crate {
1227                        [] => None,
1228                        [part] => Some(part.clone()),
1229                        parts => Some(format!("{{{}}}", parts.iter().format(","))),
1230                    };
1231
1232                    format!(
1233                        "pub use crate::{}::{};",
1234                        cargo_equip_mod_name,
1235                        if let Some(local_macro_uses_in_main_crate) = local_macro_uses_in_main_crate
1236                        {
1237                            format!("{{crates::*,macros::{}}}", local_macro_uses_in_main_crate)
1238                        } else {
1239                            "crates::*".to_owned()
1240                        }
1241                    )
1242                };
1243                code += &if minify == Minify::Libs {
1244                    prelude_for_main
1245                } else {
1246                    format!("\n    {}\n    ", prelude_for_main)
1247                };
1248            }
1249            RootCrate::Lib(_, krate) => {
1250                code += &format!("pub use crate::{}::crates::", cargo_equip_mod_name);
1251                code += &krate.crate_name();
1252                code += ";";
1253            }
1254        }
1255        code += "}\n";
1256        code += "\n";
1257        code += "    mod preludes {\n";
1258        render_mods(&mut code, &prelude_mods)?;
1259        code += "    }\n";
1260        code += "}\n";
1261    }
1262
1263    if minify == Minify::All {
1264        code = rustminify::minify_file(&rust::parse_file(&code)?);
1265    }
1266
1267    if rustfmt {
1268        code = rustfmt::rustfmt(
1269            &metadata.workspace_root,
1270            &code,
1271            &root_crate.package().edition,
1272        )?;
1273    }
1274
1275    Ok(code)
1276}
1277
1278fn normal_non_host_dep_graph<'cm>(
1279    resolve_nodes: &HashMap<&'cm cm::PackageId, &cm::Node>,
1280    libs_to_bundle: &BTreeMap<&'cm cm::PackageId, (&cm::Target, String)>,
1281) -> (
1282    Graph<&'cm cm::PackageId, ()>,
1283    HashMap<&'cm cm::PackageId, NodeIndex>,
1284) {
1285    let mut graph = Graph::new();
1286    let mut indices = hashmap!();
1287    for pkg in libs_to_bundle.keys() {
1288        indices.insert(*pkg, graph.add_node(*pkg));
1289    }
1290    for (from_pkg, (from_crate, _)) in libs_to_bundle {
1291        if from_crate.is_lib() {
1292            for cm::NodeDep {
1293                pkg: to, dep_kinds, ..
1294            } in &resolve_nodes[from_pkg].deps
1295            {
1296                if *from_pkg != to
1297                    && dep_kinds
1298                        .iter()
1299                        .any(|cm::DepKindInfo { kind, .. }| *kind == cm::DependencyKind::Normal)
1300                    && libs_to_bundle.contains_key(to)
1301                {
1302                    graph.add_edge(indices[to], indices[*from_pkg], ());
1303                }
1304            }
1305        }
1306    }
1307    (graph, indices)
1308}
1309
1310#[derive(Clone, Copy)]
1311enum RootCrate<'cm> {
1312    BinLike(&'cm cm::Package, &'cm cm::Target),
1313    Lib(&'cm cm::Package, &'cm cm::Target),
1314}
1315
1316impl<'cm> RootCrate<'cm> {
1317    fn split(self) -> (&'cm cm::Package, &'cm cm::Target) {
1318        match self {
1319            RootCrate::BinLike(p, t) | RootCrate::Lib(p, t) => (p, t),
1320        }
1321    }
1322
1323    fn package(self) -> &'cm cm::Package {
1324        match self {
1325            RootCrate::BinLike(p, _) | RootCrate::Lib(p, _) => p,
1326        }
1327    }
1328
1329    fn bin_like(self) -> Option<(&'cm cm::Package, &'cm cm::Target)> {
1330        match self {
1331            RootCrate::BinLike(p, t) => Some((p, t)),
1332            RootCrate::Lib(..) => None,
1333        }
1334    }
1335}