mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
[ty] Defer uv workspace discovery until after project configuration (#28525)
## Summary Today, we call uv metadata before we discover the ty project. This is fine for `uv check` where we always want to call uv metadata, but it's problematic for `ty check` because it prevents us from adding a configuration option to skip the metadata discovery (an opt out). This PR refactors the discovery to: 1. Find the ty project 2. Use the project to decide whether to call uv (for now, this is skipped because this PR doesn't add the setting) 3. Call uv metadata and if the uv discovered project is different from ty's, make that project the root instead ## Test Plan Testing: Project, CLI uv workspace, and language server tests pass; Clippy and hooks pass.
This commit is contained in:
1 parent
b6a74d26cb
commit
a81291e9db
10 files changed
+398
-375
No files matched your search
@@ -5,7 +5,7 @@ use divan::{Bencher, bench};
|
||||
use rayon::ThreadPoolBuilder;
|
||||
use ruff_db::system::{OsSystem, System, SystemPath, TestSystem};
|
||||
use ty_project::{
|
||||
Db, ProjectDatabase, ProjectMetadata, ScriptEnvironmentAvailability, uv_test_env_vars,
|
||||
Db, ProjectDatabase, ProjectMetadata, ScriptEnvironmentAvailability, UseUv, uv_test_env_vars,
|
||||
};
|
||||
use ty_static::EnvVars;
|
||||
|
||||
@@ -13,10 +13,11 @@ fn setup_iteration(root: &SystemPath, uv: &SystemPath) -> ProjectDatabase {
|
||||
let system = TestSystem::new(OsSystem::new(root));
|
||||
system.clear_env_vars();
|
||||
system.set_env_vars(uv_test_env_vars());
|
||||
system.set_env_var(EnvVars::TY_UV, "scripts");
|
||||
system.set_env_var(EnvVars::UV, uv.as_str());
|
||||
|
||||
let metadata = ProjectMetadata::discover(root, &system).unwrap();
|
||||
let metadata = ProjectMetadata::discover(root, &system)
|
||||
.unwrap()
|
||||
.with_use_uv(UseUv::Scripts);
|
||||
ProjectDatabase::fallible(metadata, system).unwrap()
|
||||
}
|
||||
|
||||
|
||||
+18
-17
@@ -31,7 +31,7 @@ use ty_project::metadata::settings::TerminalSettings;
|
||||
use ty_project::watch::ProjectWatcher;
|
||||
use ty_project::{
|
||||
ChangeResult, CollectReporter, Db, Project, ScriptEnvironmentAvailability, UseUv,
|
||||
UvSyncProgress, watch,
|
||||
UvSyncProgress, UvWorkspace, watch,
|
||||
};
|
||||
use ty_project::{ProjectDatabase, ProjectMetadata, ProjectReloadResult};
|
||||
use ty_python_semantic::{fix_all_diagnostics, suppress_all_diagnostics};
|
||||
@@ -148,8 +148,17 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
.map(|path| SystemPath::absolute(path, &cwd));
|
||||
let force_exclude = args.force_exclude();
|
||||
|
||||
let use_uv = UseUv::from_system(&system);
|
||||
let use_uv = if use_uv == UseUv::On
|
||||
let mut project_metadata = match &config_file {
|
||||
Some(config_file) => {
|
||||
ProjectMetadata::from_config_file(config_file.clone(), &project_path, &system)?
|
||||
}
|
||||
None => ProjectMetadata::discover(&project_path, &system)?,
|
||||
};
|
||||
|
||||
project_metadata.apply_configuration_files(&system)?;
|
||||
|
||||
project_metadata.set_override_options(args.into_options());
|
||||
if project_metadata.use_uv() == UseUv::On
|
||||
&& let [path] = check_paths.as_slice()
|
||||
&& system.is_file(path)
|
||||
&& system
|
||||
@@ -158,21 +167,13 @@ fn run_check(args: CheckCommand) -> anyhow::Result<ExitStatus> {
|
||||
{
|
||||
// A single PEP 723 script uses its own uv environment, even with an explicit ty
|
||||
// configuration file. The other explicit paths still discover workspace metadata.
|
||||
UseUv::Scripts
|
||||
} else {
|
||||
use_uv
|
||||
};
|
||||
project_metadata = project_metadata.with_use_uv(UseUv::Scripts);
|
||||
}
|
||||
|
||||
let mut project_metadata = match &config_file {
|
||||
Some(config_file) => {
|
||||
ProjectMetadata::from_config_file(config_file.clone(), &project_path, &system, use_uv)?
|
||||
}
|
||||
None => ProjectMetadata::discover_with_uv(&project_path, &system, use_uv)?,
|
||||
};
|
||||
|
||||
project_metadata.apply_configuration_files(&system)?;
|
||||
|
||||
project_metadata.set_override_options(args.into_options());
|
||||
if project_metadata.use_uv().workspace_discovery_enabled() {
|
||||
let workspace = UvWorkspace::discover(&project_path, &system);
|
||||
project_metadata.apply_uv_workspace(&system, workspace)?;
|
||||
}
|
||||
|
||||
let mut db = ProjectDatabase::fallible(project_metadata, system)?;
|
||||
let project = db.project();
|
||||
|
||||
@@ -27,7 +27,7 @@ use ty_project::metadata::value::{RelativeGlobPattern, RelativePathBuf};
|
||||
#[cfg(unix)]
|
||||
use ty_project::watch::watch_paths;
|
||||
use ty_project::watch::{ChangeEvent, ProjectWatcher, directory_watcher};
|
||||
use ty_project::{ChangeResult, Db, ProjectDatabase, ProjectMetadata, UseUv};
|
||||
use ty_project::{ChangeResult, Db, ProjectDatabase, ProjectMetadata, UvWorkspace};
|
||||
use ty_python_core::platform::PythonPlatform;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
@@ -552,12 +552,7 @@ where
|
||||
}
|
||||
|
||||
let mut project = if let Some(config_file_override) = config_file_override {
|
||||
ProjectMetadata::from_config_file(
|
||||
config_file_override,
|
||||
&project_path,
|
||||
&system,
|
||||
UseUv::from_system(&system),
|
||||
)?
|
||||
ProjectMetadata::from_config_file(config_file_override, &project_path, &system)?
|
||||
} else {
|
||||
ProjectMetadata::discover(&project_path, &system)?
|
||||
};
|
||||
@@ -568,6 +563,10 @@ where
|
||||
if let Some(override_options) = override_options {
|
||||
project.set_override_options(override_options);
|
||||
}
|
||||
if project.use_uv().workspace_discovery_enabled() {
|
||||
let workspace = UvWorkspace::discover(&project_path, &system);
|
||||
project.apply_uv_workspace(&system, workspace)?;
|
||||
}
|
||||
|
||||
// We need a chance to create the directories here.
|
||||
let merged_options = project.to_merged_options();
|
||||
|
||||
@@ -363,9 +363,9 @@ impl ProjectDatabase {
|
||||
if metadata.use_uv().workspace_discovery_enabled() {
|
||||
result.project_sync_path = Some(path.to_path_buf());
|
||||
} else {
|
||||
// We're not refreshing uv metadata, so use the existing environment.
|
||||
let environment = metadata.environment().clone();
|
||||
match project.rediscover(self, path, environment) {
|
||||
// We're not refreshing uv metadata, so use the existing workspace.
|
||||
let workspace = metadata.uv_workspace().clone();
|
||||
match project.rediscover(self, path, workspace) {
|
||||
Ok(ProjectReloadResult::Unchanged) => {}
|
||||
Ok(ProjectReloadResult::Changed { files_changed }) => {
|
||||
result.project_changed = true;
|
||||
@@ -642,7 +642,7 @@ fn affects_uv_metadata(db: &dyn Db, change: &ChangeEvent) -> bool {
|
||||
let project = db.project();
|
||||
let workspace_root = project
|
||||
.metadata(db)
|
||||
.uv_workspace()
|
||||
.uv_workspace_metadata()
|
||||
.map_or(project.root(db), |workspace| workspace.workspace_root());
|
||||
let is_included_workspace_directory = path.starts_with(workspace_root)
|
||||
&& project
|
||||
|
||||
@@ -36,7 +36,8 @@ use ty_python_semantic::dependency::{DependencyMetadata, DependencyProjectKind};
|
||||
use ty_python_semantic::lint::RuleSelection;
|
||||
use uv::DependencyMetadataError;
|
||||
pub use uv::{
|
||||
ScriptEnvironmentAvailability, UseUv, UvEnvironments, UvSyncChanges, uv_test_env_vars,
|
||||
ScriptEnvironmentAvailability, UseUv, UvEnvironments, UvSyncChanges, UvWorkspace,
|
||||
uv_test_env_vars,
|
||||
};
|
||||
|
||||
mod db;
|
||||
@@ -285,7 +286,7 @@ impl Project {
|
||||
db: &dyn Db,
|
||||
) -> Result<Option<Box<DependencyMetadata>>, DependencyMetadataError> {
|
||||
let metadata = self.metadata(db);
|
||||
let Some(workspace) = metadata.uv_workspace() else {
|
||||
let Some(workspace) = metadata.uv_workspace_metadata() else {
|
||||
tracing::debug!(
|
||||
"Skipping dependency checks for '{}': no uv workspace metadata is available",
|
||||
metadata.root(),
|
||||
@@ -375,11 +376,9 @@ impl Project {
|
||||
self,
|
||||
db: &mut dyn Db,
|
||||
path: &SystemPath,
|
||||
environment: uv::ProjectEnvironment,
|
||||
workspace: uv::UvWorkspace,
|
||||
) -> Result<ProjectReloadResult, ProjectMetadataError> {
|
||||
let mut metadata = self
|
||||
.metadata(db)
|
||||
.rediscover(db.system(), path, environment)?;
|
||||
let mut metadata = self.metadata(db).rediscover(db.system(), path, workspace)?;
|
||||
if let Err(error) = metadata.apply_configuration_files(db.system()) {
|
||||
let error = anyhow::Error::new(error);
|
||||
tracing::error!(
|
||||
@@ -802,7 +801,7 @@ impl Project {
|
||||
pub fn check_settings(&self, db: &dyn Db) -> Vec<Diagnostic> {
|
||||
let metadata = self.metadata(db);
|
||||
let uv_diagnostic = metadata.uv_diagnostic(db).or_else(|| {
|
||||
let workspace = metadata.uv_workspace()?;
|
||||
let workspace = metadata.uv_workspace_metadata()?;
|
||||
let error = self.dependency_metadata(db).as_ref().err()?;
|
||||
let mut diagnostic = error.to_diagnostic(DependencyProjectKind::Project);
|
||||
if let Ok(file) =
|
||||
|
||||
+299
-310
File diff suppressed because it is too large.
Load diff
@@ -5,8 +5,8 @@ use ty_combine::Combine;
|
||||
use ty_static::EnvVars;
|
||||
|
||||
pub(crate) use command::{MetadataTarget, Uv, uv_executable_error};
|
||||
pub(crate) use environments::{ProjectEnvironment, ScriptEnvironmentCacheKey, script_environment};
|
||||
pub use environments::{ScriptEnvironmentAvailability, UvEnvironments, UvSyncChanges};
|
||||
pub use environments::{ScriptEnvironmentAvailability, UvEnvironments, UvSyncChanges, UvWorkspace};
|
||||
pub(crate) use environments::{ScriptEnvironmentCacheKey, script_environment};
|
||||
pub(crate) use metadata::{DependencyMetadataError, UvMetadata, UvMetadataError};
|
||||
pub(crate) use service::{
|
||||
ScriptSyncRequest, ScriptSyncTask, UvMetadataResult, UvMetadataService, UvSyncTask,
|
||||
@@ -54,7 +54,8 @@ impl UseUv {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) const fn workspace_discovery_enabled(self) -> bool {
|
||||
/// Whether uv should be used for workspace discovery.
|
||||
pub const fn workspace_discovery_enabled(self) -> bool {
|
||||
matches!(self, Self::On)
|
||||
}
|
||||
|
||||
|
||||
@@ -77,13 +77,13 @@ use ruff_db::FxDashMap;
|
||||
use ruff_db::cancellation::CancellationTokenSource;
|
||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_db::system::{SystemPath, SystemPathBuf};
|
||||
use ruff_db::system::{System, SystemPath, SystemPathBuf};
|
||||
use salsa::Setter;
|
||||
|
||||
use crate::script::script_tag;
|
||||
use crate::uv::{
|
||||
ScriptSyncRequest, ScriptSyncTask, Uv, UvMetadata, UvMetadataError, UvMetadataResult,
|
||||
UvMetadataService, UvSyncTask,
|
||||
MetadataTarget, ScriptSyncRequest, ScriptSyncTask, Uv, UvMetadata, UvMetadataError,
|
||||
UvMetadataResult, UvMetadataService, UvSyncTask, uv_executable_error,
|
||||
};
|
||||
use crate::{Db, ProjectReloadResult, ProjectSyncProgressFactory, UseUv, UvSyncProgress};
|
||||
|
||||
@@ -356,19 +356,17 @@ impl UvEnvironments {
|
||||
};
|
||||
drop(project_sync);
|
||||
let project = db.project();
|
||||
let environment = match Uv::parse_metadata_output(db.system(), output) {
|
||||
Ok(metadata) => ProjectEnvironment {
|
||||
metadata: Some(metadata),
|
||||
error: None,
|
||||
},
|
||||
let mut workspace = UvWorkspace::from_metadata_result(
|
||||
Uv::parse_metadata_output(db.system(), output),
|
||||
);
|
||||
if workspace.error.is_some() {
|
||||
// Keep the last working uv metadata so a failed refresh does not change
|
||||
// the environment used for checking. Report the new error instead.
|
||||
Err(error) => ProjectEnvironment {
|
||||
error: Some(error.to_diagnostic(Severity::Warning)),
|
||||
..project.metadata(db).environment().clone()
|
||||
},
|
||||
};
|
||||
changes.project = Some(match project.rediscover(db, &path, environment) {
|
||||
workspace
|
||||
.metadata
|
||||
.clone_from(&project.metadata(db).uv_workspace().metadata);
|
||||
}
|
||||
changes.project = Some(match project.rediscover(db, &path, workspace) {
|
||||
Ok(result) => result,
|
||||
Err(error) => {
|
||||
let error = anyhow::Error::new(error);
|
||||
@@ -527,11 +525,38 @@ impl UvSyncChanges {
|
||||
/// Applied workspace metadata and the error from its latest request.
|
||||
/// Both fields are absent when no workspace metadata has been requested.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq, get_size2::GetSize)]
|
||||
pub(crate) struct ProjectEnvironment {
|
||||
pub struct UvWorkspace {
|
||||
pub(crate) metadata: Option<UvMetadata>,
|
||||
pub(crate) error: Option<Diagnostic>,
|
||||
}
|
||||
|
||||
impl UvWorkspace {
|
||||
/// Obtains uv workspace metadata for `path`.
|
||||
///
|
||||
/// Failures are retained as diagnostics for the project to report.
|
||||
pub fn discover(path: &SystemPath, system: &dyn System) -> Self {
|
||||
let metadata = Uv::new(system)
|
||||
.map_err(uv_executable_error)
|
||||
.map_err(UvMetadataError::Invocation)
|
||||
.and_then(|uv| uv.metadata(system, &MetadataTarget::Workspace(path)));
|
||||
|
||||
Self::from_metadata_result(metadata)
|
||||
}
|
||||
|
||||
fn from_metadata_result(metadata: Result<UvMetadata, UvMetadataError>) -> Self {
|
||||
match metadata {
|
||||
Ok(metadata) => Self {
|
||||
metadata: Some(metadata),
|
||||
error: None,
|
||||
},
|
||||
Err(error) => Self {
|
||||
metadata: None,
|
||||
error: Some(error.to_diagnostic(Severity::Warning)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a script environment is suitable for operations that depend on its dependencies.
|
||||
///
|
||||
/// When opening a script, the initial environment lacks its declared dependencies and can produce
|
||||
@@ -992,10 +1017,10 @@ mod tests {
|
||||
assert!(changes.project.is_some());
|
||||
assert!(!environments.has_pending_synchronizations());
|
||||
|
||||
let environment = case.db.project().metadata(&case.db).environment();
|
||||
assert_eq!(environment.error, None);
|
||||
let workspace = case.db.project().metadata(&case.db).uv_workspace();
|
||||
assert_eq!(workspace.error, None);
|
||||
assert_eq!(
|
||||
environment
|
||||
workspace
|
||||
.metadata
|
||||
.as_ref()
|
||||
.context("missing uv metadata")?
|
||||
|
||||
@@ -248,7 +248,7 @@ pub fn watch_paths(db: &dyn Db, project: Project) -> WatchPaths {
|
||||
let project_path = project.root(db);
|
||||
let workspace_root = project
|
||||
.metadata(db)
|
||||
.uv_workspace()
|
||||
.uv_workspace_metadata()
|
||||
.map(UvMetadata::workspace_root);
|
||||
let virtual_environment = project
|
||||
.program_settings(db)
|
||||
|
||||
@@ -26,7 +26,7 @@ use ty_project::metadata::Options;
|
||||
use ty_project::watch::{ChangeEvent, DeletedKind};
|
||||
use ty_project::{
|
||||
ChangeResult, Db as _, ProjectDatabase, ProjectMetadata, ProjectReloadResult,
|
||||
ScriptEnvironmentAvailability, UseUv, UvSyncChanges,
|
||||
ScriptEnvironmentAvailability, UseUv, UvSyncChanges, UvWorkspace,
|
||||
};
|
||||
|
||||
use index::DocumentError;
|
||||
@@ -776,11 +776,11 @@ impl Session {
|
||||
configuration_file.clone(),
|
||||
workspace_directory,
|
||||
&system,
|
||||
self.use_uv,
|
||||
)
|
||||
} else {
|
||||
ProjectMetadata::discover_with_uv(workspace_directory, &system, self.use_uv)
|
||||
};
|
||||
ProjectMetadata::discover(workspace_directory, &system)
|
||||
}
|
||||
.map(|metadata| metadata.with_use_uv(self.use_uv));
|
||||
|
||||
let (mut metadata, discovery_result) =
|
||||
match metadata.context("Failed to discover project configuration") {
|
||||
@@ -811,7 +811,15 @@ impl Session {
|
||||
.apply_configuration_files(&system)
|
||||
.context("Failed to apply configuration files"),
|
||||
)
|
||||
.and_then(|()| ProjectDatabase::fallible(metadata.clone(), system.clone()));
|
||||
.and_then(|()| {
|
||||
if metadata.use_uv().workspace_discovery_enabled() {
|
||||
let workspace = UvWorkspace::discover(workspace_directory, &system);
|
||||
metadata
|
||||
.apply_uv_workspace(&system, workspace)
|
||||
.context("Failed to discover uv workspace")?;
|
||||
}
|
||||
ProjectDatabase::fallible(metadata.clone(), system.clone())
|
||||
});
|
||||
|
||||
let mut db = project.unwrap_or_else(|err| {
|
||||
tracing::error!(
|
||||
|
||||
Reference in new issue
Block a user