1pub mod project_json;
19pub mod toolchain_info {
20 pub mod rustc_cfg;
21 pub mod target_data_layout;
22 pub mod target_tuple;
23 pub mod version;
24
25 use std::path::Path;
26
27 use crate::{ManifestPath, Sysroot, cargo_config_file::CargoConfigFile};
28
29 #[derive(Copy, Clone)]
30 pub enum QueryConfig<'a> {
31 Rustc(&'a Sysroot, &'a Path),
33 Cargo(&'a Sysroot, &'a ManifestPath, &'a Option<CargoConfigFile>),
36 }
37}
38
39mod build_dependencies;
40mod cargo_config_file;
41mod cargo_workspace;
42mod env;
43mod manifest_path;
44mod sysroot;
45mod workspace;
46
47#[cfg(test)]
48mod tests;
49
50use std::{
51 fmt,
52 fs::{self, ReadDir, read_dir},
53 io,
54 process::Command,
55};
56
57use anyhow::{Context, bail, format_err};
58use paths::{AbsPath, AbsPathBuf, Utf8PathBuf};
59use rustc_hash::FxHashSet;
60
61pub use crate::{
62 build_dependencies::{ProcMacroDylibPath, WorkspaceBuildScripts},
63 cargo_workspace::{
64 CargoConfig, CargoFeatures, CargoMetadataConfig, CargoWorkspace, Package, PackageData,
65 PackageDependency, RustLibSource, Target, TargetData, TargetKind,
66 },
67 manifest_path::ManifestPath,
68 project_json::{ProjectJson, ProjectJsonData},
69 sysroot::Sysroot,
70 workspace::{FileLoader, PackageRoot, ProjectWorkspace, ProjectWorkspaceKind},
71};
72pub use cargo_metadata::Metadata;
73
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct ProjectJsonFromCommand {
76 pub data: ProjectJsonData,
78 pub buildfile: AbsPathBuf,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
84pub enum ProjectManifest {
85 ProjectJson(ManifestPath),
86 CargoToml(ManifestPath),
87 CargoScript(ManifestPath),
88}
89
90impl ProjectManifest {
91 pub fn from_manifest_file(path: AbsPathBuf) -> anyhow::Result<ProjectManifest> {
92 let path = ManifestPath::try_from(path)
93 .map_err(|path| format_err!("bad manifest path: {path}"))?;
94 if path.file_name().unwrap_or_default() == "rust-project.json" {
95 return Ok(ProjectManifest::ProjectJson(path));
96 }
97 if path.file_name().unwrap_or_default() == ".rust-project.json" {
98 return Ok(ProjectManifest::ProjectJson(path));
99 }
100 if path.file_name().unwrap_or_default() == "Cargo.toml" {
101 return Ok(ProjectManifest::CargoToml(path));
102 }
103 if path.extension().unwrap_or_default() == "rs" {
104 return Ok(ProjectManifest::CargoScript(path));
105 }
106 bail!(
107 "project root must point to a Cargo.toml, rust-project.json or <script>.rs file: {path}"
108 );
109 }
110
111 pub fn discover_single(path: &AbsPath) -> anyhow::Result<ProjectManifest> {
112 let mut candidates = ProjectManifest::discover(path)?;
113 let res = match candidates.pop() {
114 None => bail!("no projects"),
115 Some(it) => it,
116 };
117
118 if !candidates.is_empty() {
119 bail!("more than one project");
120 }
121 Ok(res)
122 }
123
124 pub fn discover(path: &AbsPath) -> io::Result<Vec<ProjectManifest>> {
125 if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") {
126 return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
127 }
128 if let Some(project_json) = find_in_parent_dirs(path, ".rust-project.json") {
129 return Ok(vec![ProjectManifest::ProjectJson(project_json)]);
130 }
131 return find_cargo_toml(path)
132 .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect());
133
134 fn find_cargo_toml(path: &AbsPath) -> io::Result<Vec<ManifestPath>> {
135 match find_in_parent_dirs(path, "Cargo.toml") {
136 Some(it) => Ok(vec![it]),
137 None => Ok(find_cargo_toml_in_child_dir(read_dir(path)?)),
138 }
139 }
140
141 fn find_in_parent_dirs(path: &AbsPath, target_file_name: &str) -> Option<ManifestPath> {
142 if path.file_name().unwrap_or_default() == target_file_name
143 && let Ok(manifest) = ManifestPath::try_from(path.to_path_buf())
144 {
145 return Some(manifest);
146 }
147
148 let mut curr = Some(path);
149
150 while let Some(path) = curr {
151 let candidate = path.join(target_file_name);
152 if fs::metadata(&candidate).is_ok()
153 && let Ok(manifest) = ManifestPath::try_from(candidate)
154 {
155 return Some(manifest);
156 }
157
158 curr = path.parent();
159 }
160
161 None
162 }
163
164 fn find_cargo_toml_in_child_dir(entities: ReadDir) -> Vec<ManifestPath> {
165 entities
167 .filter_map(Result::ok)
168 .map(|it| it.path().join("Cargo.toml"))
169 .filter(|it| it.exists())
170 .map(Utf8PathBuf::from_path_buf)
171 .filter_map(Result::ok)
172 .map(AbsPathBuf::try_from)
173 .filter_map(Result::ok)
174 .filter_map(|it| it.try_into().ok())
175 .collect()
176 }
177 }
178
179 pub fn discover_all(paths: &[AbsPathBuf]) -> Vec<ProjectManifest> {
180 let mut res = paths
181 .iter()
182 .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok())
183 .flatten()
184 .collect::<FxHashSet<_>>()
185 .into_iter()
186 .collect::<Vec<_>>();
187 res.sort();
188 res
189 }
190
191 pub fn manifest_path(&self) -> &ManifestPath {
192 match self {
193 ProjectManifest::ProjectJson(it)
194 | ProjectManifest::CargoToml(it)
195 | ProjectManifest::CargoScript(it) => it,
196 }
197 }
198}
199
200impl fmt::Display for ProjectManifest {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 fmt::Display::fmt(self.manifest_path(), f)
203 }
204}
205
206fn utf8_stdout(cmd: &mut Command) -> anyhow::Result<String> {
207 let output = cmd.output().with_context(|| format!("{cmd:?} failed"))?;
208 if !output.status.success() {
209 match String::from_utf8(output.stderr) {
210 Ok(stderr) if !stderr.is_empty() => {
211 bail!("{:?} failed, {}\nstderr:\n{}", cmd, output.status, stderr)
212 }
213 _ => bail!("{:?} failed, {}", cmd, output.status),
214 }
215 }
216 let stdout = String::from_utf8(output.stdout)?;
217 Ok(stdout.trim().to_owned())
218}
219
220#[derive(Clone, Debug, Default, PartialEq, Eq)]
221pub enum InvocationStrategy {
222 Once,
223 #[default]
224 PerWorkspace,
225}
226
227#[derive(Default, Debug, Clone, Eq, PartialEq)]
229pub struct CfgOverrides {
230 pub global: cfg::CfgDiff,
232 pub selective: rustc_hash::FxHashMap<String, cfg::CfgDiff>,
234}
235
236impl CfgOverrides {
237 pub fn len(&self) -> usize {
238 self.global.len() + self.selective.values().map(|it| it.len()).sum::<usize>()
239 }
240
241 pub fn apply(&self, cfg_options: &mut cfg::CfgOptions, name: &str) {
242 if !self.global.is_empty() {
243 cfg_options.apply_diff(self.global.clone());
244 };
245 if let Some(diff) = self.selective.get(name) {
246 cfg_options.apply_diff(diff.clone());
247 };
248 }
249}
250
251fn parse_cfg(s: &str) -> Result<cfg::CfgAtom, String> {
252 let res = match s.split_once('=') {
253 Some((key, value)) => {
254 if !(value.starts_with('"') && value.ends_with('"')) {
255 return Err(format!("Invalid cfg ({s:?}), value should be in quotes"));
256 }
257 let key = intern::Symbol::intern(key);
258 let value = intern::Symbol::intern(&value[1..value.len() - 1]);
259 cfg::CfgAtom::KeyValue { key, value }
260 }
261 None => cfg::CfgAtom::Flag(intern::Symbol::intern(s)),
262 };
263 Ok(res)
264}
265
266#[derive(Clone, Debug, PartialEq, Eq)]
267pub enum RustSourceWorkspaceConfig {
268 CargoMetadata(CargoMetadataConfig),
269 Json(ProjectJson),
270}
271
272impl Default for RustSourceWorkspaceConfig {
273 fn default() -> Self {
274 RustSourceWorkspaceConfig::default_cargo()
275 }
276}
277
278impl RustSourceWorkspaceConfig {
279 pub fn default_cargo() -> Self {
280 RustSourceWorkspaceConfig::CargoMetadata(Default::default())
281 }
282}