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