1 //! In rust-analyzer, we maintain a strict separation between pure abstract
2 //! semantic project model and a concrete model of a particular build system.
4 //! Pure model is represented by the [`base_db::CrateGraph`] from another crate.
6 //! In this crate, we are concerned with "real world" project models.
8 //! Specifically, here we have a representation for a Cargo project
9 //! ([`CargoWorkspace`]) and for manually specified layout ([`ProjectJson`]).
11 //! Roughly, the things we do here are:
13 //! * Project discovery (where's the relevant Cargo.toml for the current dir).
14 //! * Custom build steps (`build.rs` code generation and compilation of
15 //! procedural macros).
16 //! * Lowering of concrete model to a [`base_db::CrateGraph`]
18 #![warn(rust_2018_idioms, unused_lifetimes, semicolon_in_expressions_from_macros)]
28 pub mod target_data_layout
;
35 fs
::{self, read_dir, ReadDir}
,
40 use anyhow
::{bail, format_err, Context}
;
41 use paths
::{AbsPath, AbsPathBuf}
;
42 use rustc_hash
::FxHashSet
;
45 build_scripts
::WorkspaceBuildScripts
,
47 CargoConfig
, CargoFeatures
, CargoWorkspace
, Package
, PackageData
, PackageDependency
,
48 RustLibSource
, Target
, TargetData
, TargetKind
,
50 manifest_path
::ManifestPath
,
51 project_json
::{ProjectJson, ProjectJsonData}
,
53 workspace
::{CfgOverrides, PackageRoot, ProjectWorkspace}
,
56 #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
57 pub enum ProjectManifest
{
58 ProjectJson(ManifestPath
),
59 CargoToml(ManifestPath
),
62 impl ProjectManifest
{
63 pub fn from_manifest_file(path
: AbsPathBuf
) -> anyhow
::Result
<ProjectManifest
> {
64 let path
= ManifestPath
::try_from(path
)
65 .map_err(|path
| format_err
!("bad manifest path: {path}"))?
;
66 if path
.file_name().unwrap_or_default() == "rust-project.json" {
67 return Ok(ProjectManifest
::ProjectJson(path
));
69 if path
.file_name().unwrap_or_default() == "Cargo.toml" {
70 return Ok(ProjectManifest
::CargoToml(path
));
72 bail
!("project root must point to Cargo.toml or rust-project.json: {path}");
75 pub fn discover_single(path
: &AbsPath
) -> anyhow
::Result
<ProjectManifest
> {
76 let mut candidates
= ProjectManifest
::discover(path
)?
;
77 let res
= match candidates
.pop() {
78 None
=> bail
!("no projects"),
82 if !candidates
.is_empty() {
83 bail
!("more than one project");
88 pub fn discover(path
: &AbsPath
) -> io
::Result
<Vec
<ProjectManifest
>> {
89 if let Some(project_json
) = find_in_parent_dirs(path
, "rust-project.json") {
90 return Ok(vec
![ProjectManifest
::ProjectJson(project_json
)]);
92 return find_cargo_toml(path
)
93 .map(|paths
| paths
.into_iter().map(ProjectManifest
::CargoToml
).collect());
95 fn find_cargo_toml(path
: &AbsPath
) -> io
::Result
<Vec
<ManifestPath
>> {
96 match find_in_parent_dirs(path
, "Cargo.toml") {
97 Some(it
) => Ok(vec
![it
]),
98 None
=> Ok(find_cargo_toml_in_child_dir(read_dir(path
)?
)),
102 fn find_in_parent_dirs(path
: &AbsPath
, target_file_name
: &str) -> Option
<ManifestPath
> {
103 if path
.file_name().unwrap_or_default() == target_file_name
{
104 if let Ok(manifest
) = ManifestPath
::try_from(path
.to_path_buf()) {
105 return Some(manifest
);
109 let mut curr
= Some(path
);
111 while let Some(path
) = curr
{
112 let candidate
= path
.join(target_file_name
);
113 if fs
::metadata(&candidate
).is_ok() {
114 if let Ok(manifest
) = ManifestPath
::try_from(candidate
) {
115 return Some(manifest
);
118 curr
= path
.parent();
124 fn find_cargo_toml_in_child_dir(entities
: ReadDir
) -> Vec
<ManifestPath
> {
125 // Only one level down to avoid cycles the easy way and stop a runaway scan with large projects
127 .filter_map(Result
::ok
)
128 .map(|it
| it
.path().join("Cargo.toml"))
129 .filter(|it
| it
.exists())
130 .map(AbsPathBuf
::assert
)
131 .filter_map(|it
| it
.try_into().ok())
136 pub fn discover_all(paths
: &[AbsPathBuf
]) -> Vec
<ProjectManifest
> {
139 .filter_map(|it
| ProjectManifest
::discover(it
.as_ref()).ok())
141 .collect
::<FxHashSet
<_
>>()
143 .collect
::<Vec
<_
>>();
149 impl fmt
::Display
for ProjectManifest
{
150 fn fmt(&self, f
: &mut fmt
::Formatter
<'_
>) -> fmt
::Result
{
152 ProjectManifest
::ProjectJson(it
) | ProjectManifest
::CargoToml(it
) => {
153 fmt
::Display
::fmt(&it
, f
)
159 fn utf8_stdout(mut cmd
: Command
) -> anyhow
::Result
<String
> {
160 let output
= cmd
.output().with_context(|| format
!("{cmd:?} failed"))?
;
161 if !output
.status
.success() {
162 match String
::from_utf8(output
.stderr
) {
163 Ok(stderr
) if !stderr
.is_empty() => {
164 bail
!("{:?} failed, {}\nstderr:\n{}", cmd
, output
.status
, stderr
)
166 _
=> bail
!("{:?} failed, {}", cmd
, output
.status
),
169 let stdout
= String
::from_utf8(output
.stdout
)?
;
170 Ok(stdout
.trim().to_string())
173 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
174 pub enum InvocationStrategy
{
180 #[derive(Clone, Debug, Default, PartialEq, Eq)]
181 pub enum InvocationLocation
{