cargo/ops/
cargo_run.rs

1use std::ffi::OsString;
2use std::fmt::Write as _;
3use std::iter;
4use std::path::Path;
5
6use crate::core::compiler::UnitOutput;
7use crate::core::{TargetKind, Workspace};
8use crate::ops;
9use crate::util::CargoResult;
10
11pub fn run(
12    ws: &Workspace<'_>,
13    options: &ops::CompileOptions,
14    args: &[OsString],
15) -> CargoResult<()> {
16    let gctx = ws.gctx();
17
18    if options.filter.contains_glob_patterns() {
19        anyhow::bail!("`cargo run` does not support glob patterns on target selection")
20    }
21
22    // We compute the `bins` here *just for diagnosis*. The actual set of
23    // packages to be run is determined by the `ops::compile` call below.
24    let packages = options.spec.get_packages(ws)?;
25    let bins: Vec<_> = packages
26        .into_iter()
27        .flat_map(|pkg| {
28            iter::repeat(pkg).zip(pkg.manifest().targets().iter().filter(|target| {
29                !target.is_lib()
30                    && !target.is_custom_build()
31                    && if !options.filter.is_specific() {
32                        target.is_bin()
33                    } else {
34                        options.filter.target_run(target)
35                    }
36            }))
37        })
38        .collect();
39
40    if bins.is_empty() {
41        if !options.filter.is_specific() {
42            anyhow::bail!("a bin target must be available for `cargo run`")
43        } else {
44            // This will be verified in `cargo_compile`.
45        }
46    }
47
48    if bins.len() == 1 {
49        let target = bins[0].1;
50        if let TargetKind::ExampleLib(..) = target.kind() {
51            anyhow::bail!(
52                "example target `{}` is a library and cannot be executed",
53                target.name()
54            )
55        }
56    }
57
58    if bins.len() > 1 {
59        if !options.filter.is_specific() {
60            let mut names: Vec<&str> = bins
61                .into_iter()
62                .map(|(_pkg, target)| target.name())
63                .collect();
64            names.sort();
65            anyhow::bail!(
66                "`cargo run` could not determine which binary to run. \
67                 Use the `--bin` option to specify a binary, \
68                 or the `default-run` manifest key.\n\
69                 available binaries: {}",
70                names.join(", ")
71            )
72        } else {
73            let mut message = "`cargo run` can run at most one executable, but \
74                 multiple were specified"
75                .to_owned();
76            write!(&mut message, "\nhelp: available targets:")?;
77            for (pkg, bin) in &bins {
78                write!(
79                    &mut message,
80                    "\n    {} `{}` in package `{}`",
81                    bin.kind().description(),
82                    bin.name(),
83                    pkg.name()
84                )?;
85            }
86            anyhow::bail!(message)
87        }
88    }
89
90    // `cargo run` is only compatible with one `--target` flag at most
91    options.build_config.single_requested_kind()?;
92
93    let compile = ops::compile(ws, options)?;
94    assert_eq!(compile.binaries.len(), 1);
95    let UnitOutput {
96        unit,
97        path,
98        script_meta,
99    } = &compile.binaries[0];
100    let exe = match path.strip_prefix(gctx.cwd()) {
101        Ok(path) if path.file_name() == Some(path.as_os_str()) => Path::new(".").join(path),
102        Ok(path) => path.to_path_buf(),
103        Err(_) => path.to_path_buf(),
104    };
105    let pkg = bins[0].0;
106    let mut process = compile.target_process(exe, unit.kind, pkg, *script_meta)?;
107
108    // Sets the working directory of the child process to the current working
109    // directory of the parent process.
110    // Overrides the default working directory of the `ProcessBuilder` returned
111    // by `compile.target_process` (the package's root directory)
112    process.args(args).cwd(gctx.cwd());
113
114    if gctx.extra_verbose() {
115        process.display_env_vars();
116    }
117
118    gctx.shell().status("Running", process.to_string())?;
119
120    process.exec_replace()
121}