use std::path::Path;
use anyhow::Result;
use tracing::debug;
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
use uv_configuration::{
Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification,
InstallOptions,
};
use uv_normalize::DefaultExtras;
use uv_preview::{Preview, PreviewFeature};
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
};
use uv_settings::{MalwareCheckSettings, PythonInstallMirrors};
use uv_warnings::warn_user;
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceCache, WorkspaceError};
use crate::commands::pip::loggers::{SummaryInstallLogger, SummaryResolveLogger};
use crate::commands::pip::operations::Modifications;
use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::{
ProjectEnvironment, ProjectError, UniversalState, WorkspacePython, default_dependency_groups,
validate_project_requires_python,
};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::{ExitStatus, diagnostics, project};
use crate::printer::Printer;
use crate::settings::{FrozenSource, LockCheck, ResolverInstallerSettings};
mod ty;
#[expect(clippy::fn_params_excessive_bools)]
pub(crate) async fn check(
project_dir: &Path,
lock_check: LockCheck,
frozen: Option<FrozenSource>,
no_sync: bool,
isolated: bool,
extras: ExtrasSpecification,
groups: DependencyGroups,
python: Option<String>,
install_mirrors: PythonInstallMirrors,
settings: ResolverInstallerSettings,
ty_version: Option<String>,
client_builder: BaseClientBuilder<'_>,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
installer_metadata: bool,
concurrency: Concurrency,
cache: &Cache,
workspace_cache: &WorkspaceCache,
printer: Printer,
preview: Preview,
no_project: bool,
no_config: bool,
malware_settings: MalwareCheckSettings,
) -> Result<ExitStatus> {
if !preview.is_enabled(PreviewFeature::Check) {
warn_user!(
"`uv check` is experimental and may change without warning. Pass `--preview-features {}` to disable this warning.",
PreviewFeature::Check
);
}
let project = if no_project {
None
} else {
match VirtualProject::discover(project_dir, &DiscoveryOptions::default(), workspace_cache)
.await
{
Ok(project) => Some(project),
Err(
WorkspaceError::MissingPyprojectToml
| WorkspaceError::MissingProject(_)
| WorkspaceError::NonWorkspace(_),
) => None,
Err(err) => return Err(err.into()),
}
};
if no_project {
for flag in extras.history().as_flags_pretty() {
warn_user!("`{flag}` has no effect when used alongside `--no-project`");
}
for flag in groups.history().as_flags_pretty() {
warn_user!("`{flag}` has no effect when used alongside `--no-project`");
}
if let LockCheck::Enabled(lock_check) = lock_check {
warn_user!("`{lock_check}` has no effect when used alongside `--no-project`");
}
if frozen.is_some() {
warn_user!("`--frozen` has no effect when used alongside `--no-project`");
}
if no_sync {
warn_user!("`--no-sync` has no effect when used alongside `--no-project`");
}
} else if project.is_none() {
for flag in extras.history().as_flags_pretty() {
warn_user!("`{flag}` has no effect when used outside of a project");
}
for flag in groups.history().as_flags_pretty() {
warn_user!("`{flag}` has no effect when used outside of a project");
}
if let LockCheck::Enabled(lock_check) = lock_check {
warn_user!("`{lock_check}` has no effect when used outside of a project");
}
if frozen.is_some() {
warn_user!("`--frozen` has no effect when used outside of a project");
}
if no_sync {
warn_user!("`--no-sync` has no effect when used outside of a project");
}
}
let target_dir = project
.as_ref()
.map(|p| p.root().to_owned())
.unwrap_or_else(|| project_dir.to_owned());
let groups = if let Some(project) = &project {
groups.with_defaults(default_dependency_groups(project.pyproject_toml())?)
} else {
DependencyGroupsWithDefaults::none()
};
let temp_dir;
let isolated_venv = if isolated {
debug!("Creating isolated virtual environment");
let workspace = project.as_ref().map(VirtualProject::workspace);
let WorkspacePython {
source,
python_request,
requires_python,
} = WorkspacePython::from_request(
python.as_deref().map(PythonRequest::parse),
workspace,
&groups,
project_dir,
no_config,
)
.await?;
let reporter = PythonDownloadReporter::single(printer);
let interpreter = PythonInstallation::find_or_download(
python_request.as_ref(),
EnvironmentPreference::Any,
python_preference,
python_downloads,
&client_builder,
cache,
Some(&reporter),
install_mirrors.python_install_mirror.as_deref(),
install_mirrors.pypy_install_mirror.as_deref(),
install_mirrors.python_downloads_json_url.as_deref(),
)
.await?
.into_interpreter();
if let Some(requires_python) = requires_python.as_ref() {
validate_project_requires_python(
&interpreter,
workspace,
&groups,
requires_python,
&source,
)?;
}
temp_dir = cache.venv_dir()?;
Some(uv_virtualenv::create_venv(
temp_dir.path(),
interpreter,
uv_virtualenv::Prompt::None,
false,
uv_virtualenv::OnExisting::Remove(uv_virtualenv::RemovalReason::TemporaryEnvironment),
false,
false,
false,
)?)
} else {
None
};
let venv_path = if let Some(project) = &project {
let extras = extras.with_defaults(DefaultExtras::default());
let venv = if let Some(venv) = isolated_venv {
venv
} else {
ProjectEnvironment::get_or_init(
project.workspace(),
&groups,
python.as_deref().map(PythonRequest::parse),
&install_mirrors,
&client_builder,
python_preference,
python_downloads,
no_sync,
no_config,
None,
cache,
DryRun::Disabled,
printer,
)
.await?
.into_environment()?
};
if no_sync {
debug!("Skipping environment synchronization due to `--no-sync`");
} else {
let _lock = venv
.lock()
.await
.inspect_err(|err| {
tracing::warn!("Failed to acquire environment lock: {err}");
})
.ok();
let lock_state = UniversalState::default();
let sync_state = lock_state.fork();
let mode = if let Some(frozen_source) = frozen {
LockMode::Frozen(frozen_source.into())
} else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(venv.interpreter(), lock_check)
} else if isolated {
LockMode::DryRun(venv.interpreter())
} else {
LockMode::Write(venv.interpreter())
};
let result = match Box::pin(
project::lock::LockOperation::new(
mode,
&settings.resolver,
&client_builder,
&lock_state,
Box::new(SummaryResolveLogger),
&concurrency,
cache,
workspace_cache,
printer,
preview,
)
.execute(project.workspace().into()),
)
.await
{
Ok(result) => result,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::with_system_certs(
client_builder.system_certs(),
)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
Err(err) => return Err(err.into()),
};
let target = match project {
VirtualProject::Project(project) => InstallTarget::Project {
workspace: project.workspace(),
name: project.project_name(),
lock: result.lock(),
},
VirtualProject::NonProject(workspace) => InstallTarget::NonProjectWorkspace {
workspace,
lock: result.lock(),
},
};
target.validate_extras(&extras)?;
target.validate_groups(&groups)?;
match project::sync::do_sync(
target,
&venv,
&extras,
&groups,
None,
InstallOptions::default(),
Modifications::Sufficient,
None,
(&settings).into(),
&client_builder,
&sync_state,
Box::new(SummaryInstallLogger),
installer_metadata,
&concurrency,
cache,
workspace_cache,
DryRun::Disabled,
printer,
preview,
&malware_settings,
)
.await
{
Ok(_) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::with_system_certs(
client_builder.system_certs(),
)
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
}
Err(err) => return Err(err.into()),
}
}
Some(venv.root().to_owned())
} else {
isolated_venv.map(|venv| venv.root().to_owned())
};
let exclude_newer = settings
.resolver
.exclude_newer
.global
.map(|value| value.timestamp());
ty::run(
ty_version,
&target_dir,
venv_path.as_deref(),
exclude_newer,
&client_builder,
cache,
printer,
)
.await
}