1use crate::args::Args;
2use crate::artifact::{Artifact, ArtifactType};
3use crate::error::{Error, Result};
4use crate::manifest::Manifest;
5use crate::profile::Profile;
6use crate::{utils, CrateType, LocalizedConfig};
7use std::collections::HashMap;
8use std::ffi::OsStr;
9use std::path::{Path, PathBuf};
10
11#[derive(Debug)]
12pub struct Subcommand {
13 args: Args,
14 package: String,
15 workspace_manifest: Option<PathBuf>,
16 manifest: PathBuf,
17 target_dir: PathBuf,
18 host_triple: String,
19 profile: Profile,
20 lib_artifact: Option<Artifact>,
21 bin_artifacts: Vec<Artifact>,
22 example_artifacts: Vec<Artifact>,
23 config: Option<LocalizedConfig>,
24}
25
26impl Subcommand {
27 pub fn new(args: Args) -> Result<Self> {
28 assert!(
30 args.package.len() < 2,
31 "Multiple packages are not supported yet by `cargo-subcommand`"
32 );
33 let package = args.package.get(0).map(|s| s.as_str());
34 assert!(
35 !args.workspace,
36 "`--workspace` is not supported yet by `cargo-subcommand`"
37 );
38 assert!(
39 args.exclude.is_empty(),
40 "`--exclude` is not supported yet by `cargo-subcommand`"
41 );
42
43 let manifest_path = args
44 .manifest_path
45 .clone()
46 .map(|path| {
47 if path.file_name() != Some(OsStr::new("Cargo.toml")) || !path.is_file() {
48 Err(Error::ManifestPathNotFound)
49 } else {
50 Ok(path)
51 }
52 })
53 .transpose()?;
54
55 let search_path = manifest_path.map_or_else(
56 || std::env::current_dir().map_err(|e| Error::Io(PathBuf::new(), e)),
57 |manifest_path| utils::canonicalize(manifest_path.parent().unwrap()),
58 )?;
59
60 let potential_manifest = utils::find_manifest(&search_path)?;
62 let workspace_manifest = utils::find_workspace(&search_path)?;
64
65 let (manifest_path, manifest) = {
66 if let Some(workspace_manifest) = &workspace_manifest {
67 utils::find_package_manifest_in_workspace(
68 workspace_manifest,
69 potential_manifest,
70 package,
71 )?
72 } else {
73 let (manifest_path, manifest) = potential_manifest;
74 manifest.map_nonvirtual_package(manifest_path, package)?
75 }
76 };
77
78 let package = &manifest.package.as_ref().unwrap().name;
80
81 let root_dir = manifest_path.parent().unwrap();
82
83 let config = LocalizedConfig::find_cargo_config_for_workspace(root_dir)?;
86 if let Some(config) = &config {
87 config.set_env_vars().unwrap();
88 }
89
90 let parsed_manifest = Manifest::parse_from_toml(&manifest_path)?;
91
92 let target_dir = args
93 .target_dir
94 .clone()
95 .or_else(|| {
96 std::env::var_os("CARGO_BUILD_TARGET_DIR")
97 .or_else(|| std::env::var_os("CARGO_TARGET_DIR"))
98 .map(|os_str| os_str.into())
99 })
100 .map(|target_dir| {
101 if target_dir.is_relative() {
102 std::env::current_dir().unwrap().join(target_dir)
103 } else {
104 target_dir
105 }
106 });
107
108 let target_dir = target_dir.unwrap_or_else(|| {
109 workspace_manifest
110 .as_ref()
111 .map(|(path, _)| path)
112 .unwrap_or_else(|| &manifest_path)
113 .parent()
114 .unwrap()
115 .join(utils::get_target_dir_name(config.as_deref()).unwrap())
116 });
117
118 let main_bin_path = Path::new("src/main.rs");
121 let main_lib_path = Path::new("src/lib.rs");
122
123 let mut bin_artifacts = HashMap::new();
124 let mut example_artifacts = HashMap::new();
125
126 fn find_main_file(dir: &Path, name: &str) -> Option<PathBuf> {
127 let alt_path = dir.join(format!("{}.rs", name));
128 alt_path.is_file().then_some(alt_path).or_else(|| {
129 let alt_path = dir.join(name).join("main.rs");
130 alt_path.is_file().then_some(alt_path)
131 })
132 }
133
134 for bin in &parsed_manifest.bins {
136 let path = bin
137 .path
138 .clone()
139 .or_else(|| find_main_file(&root_dir.join("src/bin"), &bin.name))
140 .ok_or_else(|| Error::BinNotFound(bin.name.clone()))?;
141
142 let prev = bin_artifacts.insert(
143 bin.name.clone(),
144 Artifact {
145 name: bin.name.clone(),
146 path,
147 r#type: ArtifactType::Bin,
148 },
149 );
150 if prev.is_some() {
151 return Err(Error::DuplicateBin(bin.name.clone()));
152 }
153 }
154
155 for example in &parsed_manifest.examples {
157 let path = example
158 .path
159 .clone()
160 .or_else(|| find_main_file(&root_dir.join("examples"), &example.name))
161 .ok_or_else(|| Error::ExampleNotFound(example.name.clone()))?;
162
163 let prev = example_artifacts.insert(
164 example.name.clone(),
165 Artifact {
166 name: example.name.clone(),
167 path,
168 r#type: ArtifactType::Example,
169 },
170 );
171 if prev.is_some() {
172 return Err(Error::DuplicateExample(example.name.clone()));
173 }
174 }
175
176 fn insert_if_unconfigured(
178 name: Option<String>,
179 path: &Path,
180 r#type: ArtifactType,
181 artifacts: &mut HashMap<String, Artifact>,
182 ) {
183 if artifacts.values().any(|bin| bin.path == path) {
185 println!("Already configuring {path:?}");
186 return;
187 }
188
189 let name =
190 name.unwrap_or_else(|| path.file_stem().unwrap().to_str().unwrap().to_owned());
191
192 artifacts.entry(name.clone()).or_insert(Artifact {
194 name,
195 path: path.to_owned(),
196 r#type,
197 });
198 }
199
200 if parsed_manifest
202 .package
203 .as_ref()
204 .map_or(true, |p| p.autobins)
205 {
206 if root_dir.join(main_bin_path).is_file() {
208 insert_if_unconfigured(
209 Some(package.clone()),
210 main_bin_path,
211 ArtifactType::Bin,
212 &mut bin_artifacts,
213 );
214 }
215
216 for file in utils::list_rust_files(&root_dir.join("src").join("bin"))? {
217 let file = file.strip_prefix(root_dir).unwrap();
218
219 insert_if_unconfigured(None, file, ArtifactType::Bin, &mut bin_artifacts);
220 }
221 }
222
223 if parsed_manifest
225 .package
226 .as_ref()
227 .map_or(true, |p| p.autoexamples)
228 {
229 for file in utils::list_rust_files(&root_dir.join("examples"))? {
230 let file = file.strip_prefix(root_dir).unwrap();
231
232 insert_if_unconfigured(None, file, ArtifactType::Example, &mut example_artifacts);
233 }
234 }
235
236 let mut lib_artifact = parsed_manifest
237 .lib
238 .as_ref()
239 .map(|lib| Artifact {
240 name: lib.name.as_ref().unwrap_or(package).clone(),
242 path: lib.path.as_deref().unwrap_or(main_lib_path).to_owned(),
243 r#type: ArtifactType::Lib,
244 })
245 .or_else(|| {
246 root_dir.join(main_lib_path).is_file().then(|| Artifact {
248 name: package.clone(),
249 path: main_lib_path.to_owned(),
250 r#type: ArtifactType::Lib,
251 })
252 });
253
254 let specific_target_selected = args.specific_target_selected();
258
259 if specific_target_selected {
260 if !args.lib {
261 lib_artifact = None;
262 }
263
264 if !args.bins {
265 bin_artifacts.retain(|a, _| args.bin.contains(a));
266 }
267
268 if !args.examples {
269 example_artifacts.retain(|a, _| args.example.contains(a));
270 }
271 }
272
273 let host_triple = current_platform::CURRENT_PLATFORM.to_owned();
274 let profile = args.profile();
275 Ok(Self {
276 args,
277 package: package.clone(),
278 workspace_manifest: workspace_manifest.map(|(path, _)| path),
279 manifest: manifest_path,
280 target_dir,
281 host_triple,
282 profile,
283 lib_artifact,
284 bin_artifacts: bin_artifacts.into_values().collect(),
285 example_artifacts: example_artifacts.into_values().collect(),
286 config,
287 })
288 }
289
290 pub fn args(&self) -> &Args {
291 &self.args
292 }
293
294 pub fn package(&self) -> &str {
295 &self.package
296 }
297
298 pub fn workspace_manifest(&self) -> Option<&Path> {
299 self.workspace_manifest.as_deref()
300 }
301
302 pub fn manifest(&self) -> &Path {
303 &self.manifest
304 }
305
306 pub fn target(&self) -> Option<&str> {
307 self.args.target.as_deref()
308 }
309
310 pub fn profile(&self) -> &Profile {
311 &self.profile
312 }
313
314 pub fn artifacts(&self) -> impl Iterator<Item = &Artifact> {
315 self.lib_artifact
316 .iter()
317 .chain(&self.bin_artifacts)
318 .chain(&self.example_artifacts)
319 }
320
321 pub fn target_dir(&self) -> &Path {
322 &self.target_dir
323 }
324
325 pub fn host_triple(&self) -> &str {
326 &self.host_triple
327 }
328
329 pub fn quiet(&self) -> bool {
330 self.args.quiet
331 }
332
333 pub fn config(&self) -> Option<&LocalizedConfig> {
334 self.config.as_ref()
335 }
336
337 pub fn build_dir(&self, target: Option<&str>) -> PathBuf {
338 let target_dir = dunce::simplified(self.target_dir());
339 let arch_dir = if let Some(target) = target {
340 target_dir.join(target)
341 } else {
342 target_dir.to_path_buf()
343 };
344 arch_dir.join(self.profile())
345 }
346
347 pub fn artifact(
348 &self,
349 artifact: &Artifact,
350 target: Option<&str>,
351 crate_type: CrateType,
352 ) -> PathBuf {
353 let triple = target.unwrap_or_else(|| self.host_triple());
354 let file_name = artifact.file_name(crate_type, triple);
355 self.build_dir(target)
356 .join(artifact.build_dir())
357 .join(file_name)
358 }
359}