Line data Source code
1 : use crate::CommandExt;
2 : use ansi_term::Style;
3 : use anyhow::{anyhow, ensure, Result};
4 : use bitflags::bitflags;
5 : use cargo_metadata::{Metadata, MetadataCommand, Package, PackageId};
6 : use is_terminal::IsTerminal;
7 : use once_cell::sync::Lazy;
8 : use std::{
9 : io::Write,
10 : path::{Path, PathBuf},
11 : process::{Command, Stdio},
12 : };
13 :
14 : #[allow(clippy::module_name_repetitions)]
15 : pub use home::cargo_home;
16 :
17 6 : static STABLE_CARGO: Lazy<PathBuf> = Lazy::new(|| {
18 6 : let mut command = Command::new("rustup");
19 6 : command.args(["+stable", "which", "cargo"]);
20 6 : let output = command.logged_output(true).unwrap();
21 6 : assert!(output.status.success());
22 6 : let stdout = String::from_utf8(output.stdout).unwrap();
23 6 : PathBuf::from(stdout.trim_end())
24 6 : });
25 :
26 : bitflags! {
27 : pub struct Quiet: u8 {
28 : const MESSAGE = 1 << 0;
29 : const STDERR = 1 << 1;
30 : }
31 : }
32 :
33 : impl From<bool> for Quiet {
34 38 : fn from(value: bool) -> Self {
35 38 : if value {
36 9 : Self::all()
37 : } else {
38 29 : Self::empty()
39 : }
40 38 : }
41 : }
42 :
43 : /// A `cargo` command builder
44 : ///
45 : /// Note that [`std::process::Command`]is itself a builder. So technically that makes this a
46 : /// "builder builder".
47 : pub struct Builder {
48 : subcommand: String,
49 : verb: String,
50 : description: String,
51 : quiet: Quiet,
52 : stable: bool,
53 : }
54 :
55 : #[must_use]
56 57 : pub fn build(description: &str) -> Builder {
57 57 : Builder::new("build", "Building", description)
58 57 : }
59 :
60 : #[must_use]
61 21 : pub fn check(description: &str) -> Builder {
62 21 : Builder::new("check", "Checking", description)
63 21 : }
64 :
65 : #[must_use]
66 9 : pub fn fetch(description: &str) -> Builder {
67 9 : Builder::new("fetch", "Fetching", description)
68 9 : }
69 :
70 : #[must_use]
71 1 : pub fn fix(description: &str) -> Builder {
72 1 : Builder::new("fix", "Fixing", description)
73 1 : }
74 :
75 : #[must_use]
76 18 : pub fn init(description: &str) -> Builder {
77 18 : Builder::new("init", "Initializing", description)
78 18 : }
79 :
80 : #[must_use]
81 0 : pub fn run(description: &str) -> Builder {
82 0 : Builder::new("run", "Running", description)
83 0 : }
84 :
85 : #[must_use]
86 3 : pub fn test(description: &str) -> Builder {
87 3 : Builder::new("test", "Testing", description)
88 3 : }
89 :
90 : #[must_use]
91 0 : pub fn update(description: &str) -> Builder {
92 0 : Builder::new("update", "Updating", description)
93 0 : }
94 :
95 : impl Builder {
96 109 : fn new(subcommand: &str, verb: &str, description: &str) -> Self {
97 109 : Self {
98 109 : subcommand: subcommand.to_owned(),
99 109 : verb: verb.to_owned(),
100 109 : description: description.to_owned(),
101 109 : quiet: Quiet::empty(),
102 109 : stable: false,
103 109 : }
104 109 : }
105 :
106 : /// Whether to allow the command to write to standard error.
107 47 : pub fn quiet(&mut self, value: impl Into<Quiet>) -> &mut Self {
108 47 : let value = value.into();
109 47 : // smoelius: `cargo check` and `cargo fix` are never silenced.
110 47 : if !value.is_empty() {
111 18 : assert!(!matches!(self.subcommand.as_str(), "check" | "fix"));
112 29 : }
113 47 : self.quiet = value;
114 47 : self
115 47 : }
116 :
117 : /// Whether to use a cached path to stable `cargo`. Using the cached path avoids repeated calls
118 : /// to `rustup`.
119 18 : pub fn stable(&mut self, value: bool) -> &mut Self {
120 18 : self.stable = value;
121 18 : self
122 18 : }
123 :
124 : /// Consumes the builder and returns a [`std::process::Command`].
125 : #[allow(clippy::needless_pass_by_ref_mut)]
126 109 : pub fn build(&mut self) -> Command {
127 109 : if !self.quiet.contains(Quiet::MESSAGE) {
128 : // smoelius: Writing directly to `stderr` prevents capture by `libtest`.
129 91 : let message = format!("{} {}", self.verb, self.description);
130 91 : std::io::stderr()
131 91 : .write_fmt(format_args!(
132 91 : "{}\n",
133 91 : if std::io::stderr().is_terminal() {
134 0 : Style::new().bold()
135 : } else {
136 91 : Style::new()
137 : }
138 91 : .paint(message)
139 91 : ))
140 91 : .expect("Could not write to stderr");
141 18 : }
142 109 : let mut command = if self.stable {
143 18 : Command::new(&*STABLE_CARGO)
144 : } else {
145 91 : Command::new("cargo")
146 : };
147 : #[cfg(windows)]
148 : {
149 : // smoelius: Work around: https://github.com/rust-lang/rustup/pull/2978
150 : let cargo_home = cargo_home().unwrap();
151 : let new_path = crate::prepend_path(Path::new(&cargo_home).join("bin")).unwrap();
152 : command.envs(vec![(crate::env::PATH, new_path)]);
153 : }
154 109 : command.args([&self.subcommand]);
155 109 : if self.quiet.contains(Quiet::STDERR) {
156 9 : command.stderr(Stdio::null());
157 100 : }
158 109 : command
159 109 : }
160 : }
161 :
162 : /// Get metadata based on the current directory.
163 11 : pub fn current_metadata() -> Result<Metadata> {
164 11 : MetadataCommand::new().no_deps().exec().map_err(Into::into)
165 11 : }
166 :
167 30 : pub fn package_with_root(metadata: &Metadata, package_root: &Path) -> Result<Package> {
168 30 : let packages = metadata
169 30 : .packages
170 30 : .iter()
171 288 : .map(|package| {
172 288 : let path = package
173 288 : .manifest_path
174 288 : .parent()
175 288 : .ok_or_else(|| anyhow!("Could not get parent directory"))?;
176 288 : Ok(if path == package_root {
177 30 : Some(package)
178 : } else {
179 258 : None
180 : })
181 288 : })
182 30 : .filter_map(Result::transpose)
183 30 : .collect::<Result<Vec<_>>>()?;
184 :
185 30 : ensure!(
186 30 : packages.len() <= 1,
187 0 : "Found multiple packages in `{}`",
188 0 : package_root.to_string_lossy()
189 : );
190 :
191 30 : packages
192 30 : .into_iter()
193 30 : .next()
194 30 : .cloned()
195 30 : .ok_or_else(|| anyhow!("Found no packages in `{}`", package_root.to_string_lossy()))
196 30 : }
197 :
198 63 : pub fn package(metadata: &Metadata, package_id: &PackageId) -> Result<Package> {
199 63 : metadata
200 63 : .packages
201 63 : .iter()
202 252 : .find(|package| package.id == *package_id)
203 63 : .cloned()
204 63 : .ok_or_else(|| anyhow!("Could not find package"))
205 63 : }
|