mirror of https://github.com/astral-sh/ruff
Split out `discover_in` from `ProjectMetadata::discover`
This commit is contained in:
parent
efb23b01af
commit
1e77da4d17
|
|
@ -1,7 +1,9 @@
|
||||||
use configuration_file::{ConfigurationFile, ConfigurationFileError};
|
use configuration_file::{ConfigurationFile, ConfigurationFileError};
|
||||||
|
use ruff_db::system::walk_directory::WalkState;
|
||||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||||
use ruff_db::vendored::VendoredFileSystem;
|
use ruff_db::vendored::VendoredFileSystem;
|
||||||
use ruff_python_ast::name::Name;
|
use ruff_python_ast::name::Name;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use ty_combine::Combine;
|
use ty_combine::Combine;
|
||||||
|
|
@ -141,90 +143,25 @@ impl ProjectMetadata {
|
||||||
let mut closest_project: Option<ProjectMetadata> = None;
|
let mut closest_project: Option<ProjectMetadata> = None;
|
||||||
|
|
||||||
for project_root in path.ancestors() {
|
for project_root in path.ancestors() {
|
||||||
let pyproject_path = project_root.join("pyproject.toml");
|
let Some(discovered) = Self::discover_in(project_root, system)? else {
|
||||||
|
continue;
|
||||||
let pyproject = if let Ok(pyproject_str) = system.read_to_string(&pyproject_path) {
|
|
||||||
match PyProject::from_toml_str(
|
|
||||||
&pyproject_str,
|
|
||||||
ValueSource::File(Arc::new(pyproject_path.clone())),
|
|
||||||
) {
|
|
||||||
Ok(pyproject) => Some(pyproject),
|
|
||||||
Err(error) => {
|
|
||||||
return Err(ProjectMetadataError::InvalidPyProject {
|
|
||||||
path: pyproject_path,
|
|
||||||
source: Box::new(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// A `ty.toml` takes precedence over a `pyproject.toml`.
|
match discovered {
|
||||||
let ty_toml_path = project_root.join("ty.toml");
|
DiscoveredProject::PyProject {
|
||||||
if let Ok(ty_str) = system.read_to_string(&ty_toml_path) {
|
has_ty_section: true,
|
||||||
let options = match Options::from_toml_str(
|
metadata,
|
||||||
&ty_str,
|
|
||||||
ValueSource::File(Arc::new(ty_toml_path.clone())),
|
|
||||||
) {
|
|
||||||
Ok(options) => options,
|
|
||||||
Err(error) => {
|
|
||||||
return Err(ProjectMetadataError::InvalidTyToml {
|
|
||||||
path: ty_toml_path,
|
|
||||||
source: Box::new(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if pyproject
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|project| project.ty().is_some())
|
|
||||||
{
|
|
||||||
// TODO: Consider using a diagnostic here
|
|
||||||
tracing::warn!(
|
|
||||||
"Ignoring the `tool.ty` section in `{pyproject_path}` because `{ty_toml_path}` takes precedence."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
| DiscoveredProject::Ty { metadata } => {
|
||||||
tracing::debug!("Found project at '{}'", project_root);
|
|
||||||
|
|
||||||
let metadata = ProjectMetadata::from_options(
|
|
||||||
options,
|
|
||||||
project_root.to_path_buf(),
|
|
||||||
pyproject
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|pyproject| pyproject.project.as_ref()),
|
|
||||||
)
|
|
||||||
.map_err(|err| {
|
|
||||||
ProjectMetadataError::InvalidRequiresPythonConstraint {
|
|
||||||
source: err,
|
|
||||||
path: pyproject_path,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
return Ok(metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pyproject) = pyproject {
|
|
||||||
let has_ty_section = pyproject.ty().is_some();
|
|
||||||
let metadata =
|
|
||||||
ProjectMetadata::from_pyproject(pyproject, project_root.to_path_buf())
|
|
||||||
.map_err(
|
|
||||||
|err| ProjectMetadataError::InvalidRequiresPythonConstraint {
|
|
||||||
source: err,
|
|
||||||
path: pyproject_path,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
if has_ty_section {
|
|
||||||
tracing::debug!("Found project at '{}'", project_root);
|
tracing::debug!("Found project at '{}'", project_root);
|
||||||
|
|
||||||
return Ok(metadata);
|
return Ok(metadata);
|
||||||
}
|
}
|
||||||
|
DiscoveredProject::PyProject { metadata, .. } => {
|
||||||
// Not a project itself, keep looking for an enclosing project.
|
// Not a project itself, keep looking for an enclosing project.
|
||||||
if closest_project.is_none() {
|
if closest_project.is_none() {
|
||||||
closest_project = Some(metadata);
|
closest_project = Some(metadata);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -252,6 +189,101 @@ impl ProjectMetadata {
|
||||||
Ok(metadata)
|
Ok(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn discover_in(
|
||||||
|
project_root: &SystemPath,
|
||||||
|
system: &dyn System,
|
||||||
|
) -> Result<Option<DiscoveredProject>, ProjectMetadataError> {
|
||||||
|
tracing::debug!("Searching for a project in '{project_root}'");
|
||||||
|
|
||||||
|
if !system.is_directory(project_root) {
|
||||||
|
return Err(ProjectMetadataError::NotADirectory(
|
||||||
|
project_root.to_path_buf(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let pyproject_path = project_root.join("pyproject.toml");
|
||||||
|
|
||||||
|
let pyproject = if let Ok(pyproject_str) = system.read_to_string(&pyproject_path) {
|
||||||
|
match PyProject::from_toml_str(
|
||||||
|
&pyproject_str,
|
||||||
|
ValueSource::File(Arc::new(pyproject_path.clone())),
|
||||||
|
) {
|
||||||
|
Ok(pyproject) => Some(pyproject),
|
||||||
|
Err(error) => {
|
||||||
|
return Err(ProjectMetadataError::InvalidPyProject {
|
||||||
|
path: pyproject_path,
|
||||||
|
source: Box::new(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// A `ty.toml` takes precedence over a `pyproject.toml`.
|
||||||
|
let ty_toml_path = project_root.join("ty.toml");
|
||||||
|
if let Ok(ty_str) = system.read_to_string(&ty_toml_path) {
|
||||||
|
let options = match Options::from_toml_str(
|
||||||
|
&ty_str,
|
||||||
|
ValueSource::File(Arc::new(ty_toml_path.clone())),
|
||||||
|
) {
|
||||||
|
Ok(options) => options,
|
||||||
|
Err(error) => {
|
||||||
|
return Err(ProjectMetadataError::InvalidTyToml {
|
||||||
|
path: ty_toml_path,
|
||||||
|
source: Box::new(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if pyproject
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|project| project.ty().is_some())
|
||||||
|
{
|
||||||
|
// TODO: Consider using a diagnostic here
|
||||||
|
tracing::warn!(
|
||||||
|
"Ignoring the `tool.ty` section in `{pyproject_path}` because `{ty_toml_path}` takes precedence."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Found project at '{}'", project_root);
|
||||||
|
|
||||||
|
let metadata = ProjectMetadata::from_options(
|
||||||
|
options,
|
||||||
|
project_root.to_path_buf(),
|
||||||
|
pyproject
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|pyproject| pyproject.project.as_ref()),
|
||||||
|
)
|
||||||
|
.map_err(
|
||||||
|
|err| ProjectMetadataError::InvalidRequiresPythonConstraint {
|
||||||
|
source: err,
|
||||||
|
path: pyproject_path,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
return Ok(Some(DiscoveredProject::Ty { metadata }));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pyproject) = pyproject {
|
||||||
|
let has_ty_section = pyproject.ty().is_some();
|
||||||
|
let metadata = ProjectMetadata::from_pyproject(pyproject, project_root.to_path_buf())
|
||||||
|
.map_err(|err| {
|
||||||
|
ProjectMetadataError::InvalidRequiresPythonConstraint {
|
||||||
|
source: err,
|
||||||
|
path: pyproject_path,
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
return Ok(Some(DiscoveredProject::PyProject {
|
||||||
|
has_ty_section,
|
||||||
|
metadata,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn root(&self) -> &SystemPath {
|
pub fn root(&self) -> &SystemPath {
|
||||||
&self.root
|
&self.root
|
||||||
}
|
}
|
||||||
|
|
@ -344,6 +376,36 @@ pub enum ProjectMetadataError {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DiscoveredProject {
|
||||||
|
PyProject {
|
||||||
|
has_ty_section: bool,
|
||||||
|
metadata: ProjectMetadata,
|
||||||
|
},
|
||||||
|
Ty {
|
||||||
|
metadata: ProjectMetadata,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiscoveredProject {
|
||||||
|
fn is_ty_or_project_with_ty_section(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
DiscoveredProject::PyProject {
|
||||||
|
has_ty_section: tool_ty,
|
||||||
|
..
|
||||||
|
} => *tool_ty,
|
||||||
|
DiscoveredProject::Ty { .. } => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_metadata(self) -> ProjectMetadata {
|
||||||
|
match self {
|
||||||
|
DiscoveredProject::PyProject { metadata, .. } => metadata,
|
||||||
|
DiscoveredProject::Ty { metadata } => metadata,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
//! Integration tests for project discovery
|
//! Integration tests for project discovery
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,9 @@ impl Session {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: Discover all projects within this workspace, starting from root.
|
||||||
|
//
|
||||||
|
|
||||||
// For now, create one project database per workspace.
|
// For now, create one project database per workspace.
|
||||||
// In the future, index the workspace directories to find all projects
|
// In the future, index the workspace directories to find all projects
|
||||||
// and create a project database for each.
|
// and create a project database for each.
|
||||||
|
|
@ -1175,8 +1178,7 @@ impl Workspaces {
|
||||||
) -> Option<(SystemPathBuf, &mut Workspace)> {
|
) -> Option<(SystemPathBuf, &mut Workspace)> {
|
||||||
let path = url.to_file_path().ok()?;
|
let path = url.to_file_path().ok()?;
|
||||||
|
|
||||||
// Realistically I don't think this can fail because we got the path from a Url
|
let system_path = SystemPathBuf::from_path_buf(path).expect("URL to be valid UTF-8");
|
||||||
let system_path = SystemPathBuf::from_path_buf(path).ok()?;
|
|
||||||
|
|
||||||
if let Some(workspace) = self.workspaces.get_mut(&system_path) {
|
if let Some(workspace) = self.workspaces.get_mut(&system_path) {
|
||||||
workspace.settings = Arc::new(settings);
|
workspace.settings = Arc::new(settings);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue