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