1pub mod parser;
3
4use crate::parser::extract_crate_files;
5pub use cargo_metadata::Edition;
6use std::cmp::Ordering;
7use std::collections::{BTreeSet, HashSet};
8use std::hash::{Hash, Hasher};
9use std::io::{self};
10use std::path::{Path, PathBuf};
11use cargo_metadata::TargetKind;
12use thiserror::Error;
13
14#[derive(Debug, Error)]
15pub enum Error {
16 #[error("the manifest-path must be a path to a Cargo.toml file")]
17 ManifestNotCargoToml,
18 #[error("no targets were found")]
19 NoTargets,
20 #[error("there was an error reading Cargo.toml: {0}")]
21 ManifestError(io::Error),
22 #[error("there was an error reading {0}: {1}")]
23 FileError(PathBuf, io::Error),
24 #[error("there was an error parsing a source file: {0}")]
25 ParseError(#[from] syn::Error),
26 #[error("could not find module")]
27 ModuleNotFound,
28 #[error("source file must have parent")]
29 NoParent,
30 #[error("source file must have a stem")]
31 NoStem,
32}
33
34pub fn get_target_files(target: &Target) -> Result<HashSet<PathBuf>, Error> {
36 let mut acc = HashSet::new();
37 extract_crate_files(&target.path, &target.path, &mut acc)?;
38 Ok(acc)
39}
40
41pub fn get_targets(manifest_path: Option<&Path>) -> Result<BTreeSet<Target>, Error> {
43 if let Some(specified_manifest_path) = manifest_path {
44 if !specified_manifest_path.ends_with("Cargo.toml") {
45 return Err(Error::ManifestNotCargoToml);
46 }
47 _get_targets(Some(specified_manifest_path))
48 } else {
49 _get_targets(None)
50 }
51}
52
53#[derive(Debug)]
55pub struct Target {
56 pub path: PathBuf,
58 pub kind: TargetKind,
60 pub edition: Edition,
62}
63
64impl Target {
65 pub fn from_target(target: &cargo_metadata::Target) -> Self {
66 let path = PathBuf::from(&target.src_path);
67 let canonicalized = dunce::canonicalize(&path).unwrap_or(path);
68
69 Target {
70 path: canonicalized,
71 kind: target.kind[0].clone(),
72 edition: target.edition,
73 }
74 }
75}
76
77impl PartialEq for Target {
78 fn eq(&self, other: &Target) -> bool {
79 self.path == other.path
80 }
81}
82
83impl PartialOrd for Target {
84 fn partial_cmp(&self, other: &Target) -> Option<Ordering> {
85 Some(self.cmp(other))
86 }
87}
88
89impl Ord for Target {
90 fn cmp(&self, other: &Target) -> Ordering {
91 self.path.cmp(&other.path)
92 }
93}
94
95impl Eq for Target {}
96
97impl Hash for Target {
98 fn hash<H: Hasher>(&self, state: &mut H) {
99 self.path.hash(state);
100 }
101}
102
103fn _get_targets(manifest_path: Option<&Path>) -> Result<BTreeSet<Target>, Error> {
105 let mut targets = BTreeSet::new();
106 get_targets_recursive(manifest_path, &mut targets, &mut BTreeSet::new())?;
107
108 if targets.is_empty() {
109 Err(Error::NoTargets)
110 } else {
111 Ok(targets)
112 }
113}
114
115fn get_targets_recursive(
116 manifest_path: Option<&Path>,
117 targets: &mut BTreeSet<Target>,
118 visited: &mut BTreeSet<String>,
119) -> Result<(), Error> {
120 let metadata = get_cargo_metadata(manifest_path).map_err(Error::ManifestError)?;
121
122 for package in &metadata.packages {
123 add_targets(&package.targets, targets);
124
125 for dependency in &package.dependencies {
127 if dependency.path.is_none() || visited.contains(&dependency.name) {
128 continue;
129 }
130
131 let manifest_path = PathBuf::from(dependency.path.as_ref().unwrap()).join("Cargo.toml");
132 if manifest_path.exists()
133 && !metadata
134 .packages
135 .iter()
136 .any(|p| p.manifest_path.eq(&manifest_path))
137 {
138 visited.insert(dependency.name.to_owned());
139 get_targets_recursive(Some(&manifest_path), targets, visited)?;
140 }
141 }
142 }
143
144 Ok(())
145}
146
147fn add_targets(target_paths: &[cargo_metadata::Target], targets: &mut BTreeSet<Target>) {
148 for target in target_paths {
149 targets.insert(Target::from_target(target));
150 }
151}
152
153fn get_cargo_metadata(manifest_path: Option<&Path>) -> Result<cargo_metadata::Metadata, io::Error> {
154 let mut cmd = cargo_metadata::MetadataCommand::new();
155 cmd.no_deps();
156 if let Some(manifest_path) = manifest_path {
157 cmd.manifest_path(manifest_path);
158 }
159 cmd.other_options(vec![String::from("--offline")]);
160
161 match cmd.exec() {
162 Ok(metadata) => Ok(metadata),
163 Err(_) => {
164 cmd.other_options(vec![]);
165 match cmd.exec() {
166 Ok(metadata) => Ok(metadata),
167 Err(error) => Err(io::Error::new(io::ErrorKind::Other, error.to_string())),
168 }
169 }
170 }
171}