cargo/util/
workspace.rs

1use crate::core::compiler::Unit;
2use crate::core::manifest::TargetSourcePath;
3use crate::core::{Target, Workspace};
4use crate::ops::CompileOptions;
5use crate::util::CargoResult;
6use anyhow::bail;
7use cargo_util::paths::normalize_path;
8use cargo_util::ProcessBuilder;
9use std::fmt::Write;
10use std::path::PathBuf;
11
12fn get_available_targets<'a>(
13    filter_fn: fn(&Target) -> bool,
14    ws: &'a Workspace<'_>,
15    options: &'a CompileOptions,
16) -> CargoResult<Vec<&'a str>> {
17    let packages = options.spec.get_packages(ws)?;
18
19    let mut targets: Vec<_> = packages
20        .into_iter()
21        .flat_map(|pkg| {
22            pkg.manifest()
23                .targets()
24                .iter()
25                .filter(|target| filter_fn(target))
26        })
27        .map(Target::name)
28        .collect();
29
30    targets.sort();
31
32    Ok(targets)
33}
34
35fn print_available_targets(
36    filter_fn: fn(&Target) -> bool,
37    ws: &Workspace<'_>,
38    options: &CompileOptions,
39    option_name: &str,
40    plural_name: &str,
41) -> CargoResult<()> {
42    let targets = get_available_targets(filter_fn, ws, options)?;
43
44    let mut output = String::new();
45    writeln!(output, "\"{}\" takes one argument.", option_name)?;
46
47    if targets.is_empty() {
48        writeln!(output, "No {} available.", plural_name)?;
49    } else {
50        writeln!(output, "Available {}:", plural_name)?;
51        for target in targets {
52            writeln!(output, "    {}", target)?;
53        }
54    }
55    bail!("{}", output)
56}
57
58pub fn print_available_packages(ws: &Workspace<'_>) -> CargoResult<()> {
59    let packages = ws
60        .members()
61        .map(|pkg| pkg.name().as_str())
62        .collect::<Vec<_>>();
63
64    let mut output = "\"--package <SPEC>\" requires a SPEC format value, \
65        which can be any package ID specifier in the dependency graph.\n\
66        Run `cargo help pkgid` for more information about SPEC format.\n\n"
67        .to_string();
68
69    if packages.is_empty() {
70        // This would never happen.
71        // Just in case something regresses we covers it here.
72        writeln!(output, "No packages available.")?;
73    } else {
74        writeln!(output, "Possible packages/workspace members:")?;
75        for package in packages {
76            writeln!(output, "    {}", package)?;
77        }
78    }
79    bail!("{}", output)
80}
81
82pub fn print_available_examples(ws: &Workspace<'_>, options: &CompileOptions) -> CargoResult<()> {
83    print_available_targets(Target::is_example, ws, options, "--example", "examples")
84}
85
86pub fn print_available_binaries(ws: &Workspace<'_>, options: &CompileOptions) -> CargoResult<()> {
87    print_available_targets(Target::is_bin, ws, options, "--bin", "binaries")
88}
89
90pub fn print_available_benches(ws: &Workspace<'_>, options: &CompileOptions) -> CargoResult<()> {
91    print_available_targets(Target::is_bench, ws, options, "--bench", "bench targets")
92}
93
94pub fn print_available_tests(ws: &Workspace<'_>, options: &CompileOptions) -> CargoResult<()> {
95    print_available_targets(Target::is_test, ws, options, "--test", "test targets")
96}
97
98/// The source path and its current dir for use in compilation.
99///
100/// The path that we pass to rustc is actually fairly important because it will
101/// show up in error messages (important for readability), debug information
102/// (important for caching), etc. As a result we need to be pretty careful how we
103/// actually invoke rustc.
104///
105/// In general users don't expect `cargo build` to cause rebuilds if you change
106/// directories. That could be if you just change directories in the package or
107/// if you literally move the whole package wholesale to a new directory. As a
108/// result we mostly don't factor in `cwd` to this calculation. Instead we try to
109/// track the workspace as much as possible and we update the current directory
110/// of rustc/rustdoc where appropriate.
111///
112/// The first returned value here is the argument to pass to rustc, and the
113/// second is the cwd that rustc should operate in.
114pub fn path_args(ws: &Workspace<'_>, unit: &Unit) -> (PathBuf, PathBuf) {
115    let src = match unit.target.src_path() {
116        TargetSourcePath::Path(path) => path.to_path_buf(),
117        TargetSourcePath::Metabuild => unit.pkg.manifest().metabuild_path(ws.target_dir()),
118    };
119    assert!(src.is_absolute());
120    if unit.pkg.package_id().source_id().is_path() {
121        // Determine which path we make this relative to: usually it's the workspace root,
122        // but this can be overwritten with a `-Z` flag.
123        let root = match &ws.gctx().cli_unstable().root_dir {
124            None => ws.root().to_owned(),
125            Some(root_dir) => normalize_path(&ws.gctx().cwd().join(root_dir)),
126        };
127        if let Ok(path) = src.strip_prefix(&root) {
128            return (path.to_path_buf(), root);
129        }
130    }
131    (src, unit.pkg.root().to_path_buf())
132}
133
134pub fn add_path_args(ws: &Workspace<'_>, unit: &Unit, cmd: &mut ProcessBuilder) {
135    let (arg, cwd) = path_args(ws, unit);
136    cmd.arg(arg);
137    cmd.cwd(cwd);
138}