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