mirror of
https://github.com/astral-sh/uv
synced 2026-06-21 13:47:25 +00:00
Use locked ty versions in uv check
This commit is contained in:
@@ -5384,7 +5384,9 @@ pub struct CheckArgs {
|
||||
/// Accepts either a version (e.g., `0.0.1`) which will be treated as an exact pin,
|
||||
/// a version specifier (e.g., `>=0.0.1`), or `latest` to use the latest available version.
|
||||
///
|
||||
/// By default, a constrained version range of ty will be used (e.g., `>=0.0,<0.1`).
|
||||
/// By default, the exact version resolved in `uv.lock` will be used when the project has an
|
||||
/// active `ty` development dependency. Otherwise, a constrained version range of ty will be
|
||||
/// used (e.g., `>=0.0,<0.1`).
|
||||
#[arg(long, value_hint = ValueHint::Other)]
|
||||
pub ty_version: Option<String>,
|
||||
|
||||
|
||||
@@ -775,6 +775,64 @@ impl Lock {
|
||||
&self.manifest.dependency_groups
|
||||
}
|
||||
|
||||
/// Returns the package selected by a direct dependency in a dependency group.
|
||||
///
|
||||
/// If `project_name` is provided, the dependency group attached to that package is used.
|
||||
/// Otherwise, the dependency group attached directly to the lock manifest is used.
|
||||
pub fn find_dependency_group_package(
|
||||
&self,
|
||||
project_name: Option<&PackageName>,
|
||||
group: &GroupName,
|
||||
dependency_name: &PackageName,
|
||||
marker_environment: &MarkerEnvironment,
|
||||
) -> Result<Option<&Package>, String> {
|
||||
let Some(project_name) = project_name else {
|
||||
let Some(requirements) = self.manifest.dependency_groups.get(group) else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !requirements.iter().any(|requirement| {
|
||||
&requirement.name == dependency_name
|
||||
&& requirement.marker.evaluate(marker_environment, &[])
|
||||
}) {
|
||||
return Ok(None);
|
||||
}
|
||||
return self.find_by_markers(dependency_name, marker_environment);
|
||||
};
|
||||
|
||||
let Some(project) = self.find_by_name(project_name)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(dependencies) = project.resolved_dependency_groups().get(group) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut selected = None;
|
||||
for dependency in dependencies {
|
||||
if &dependency.package_id.name != dependency_name
|
||||
|| !dependency.complexified_marker.evaluate(
|
||||
marker_environment,
|
||||
std::iter::empty::<&PackageName>(),
|
||||
dependency
|
||||
.extra
|
||||
.iter()
|
||||
.map(|extra| (&dependency.package_id.name, extra)),
|
||||
std::iter::once((project_name, group)),
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let package = self.find_by_id(&dependency.package_id);
|
||||
if selected.is_some_and(|selected: &Package| selected.id != package.id) {
|
||||
return Err(format!(
|
||||
"found multiple packages matching `{dependency_name}` in dependency group `{group}` for `{project_name}`"
|
||||
));
|
||||
}
|
||||
selected = Some(package);
|
||||
}
|
||||
Ok(selected)
|
||||
}
|
||||
|
||||
/// Returns the build constraints that were used to generate this lock.
|
||||
pub fn build_constraints(&self, root: &Path) -> Constraints {
|
||||
Constraints::from_requirements(
|
||||
|
||||
@@ -204,6 +204,7 @@ pub(crate) async fn check(
|
||||
|
||||
// Select an environment and, if we found a project, sync it before running checks.
|
||||
let mut workspace_metadata = None;
|
||||
let mut locked_ty_version = None;
|
||||
let venv_path = if let Some(project) = &project {
|
||||
let extras = extras.with_defaults(DefaultExtras::default());
|
||||
|
||||
@@ -229,18 +230,34 @@ pub(crate) async fn check(
|
||||
.into_environment()?
|
||||
};
|
||||
|
||||
let ty_declaration = if ty_path.is_none() && ty_version.is_none() {
|
||||
ty::active_declaration(project, venv.interpreter(), &settings.resolver.sources)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let state = UniversalState::default();
|
||||
let _environment_lock;
|
||||
let lock = if no_sync {
|
||||
debug!("Skipping environment synchronization due to `--no-sync`");
|
||||
|
||||
match LockTarget::Workspace(project.workspace()).read().await {
|
||||
let lock = match LockTarget::Workspace(project.workspace()).read().await {
|
||||
Ok(lock) => lock,
|
||||
Err(err) if ty_declaration.is_some() => return Err(err.into()),
|
||||
Err(err) => {
|
||||
debug!("Failed to read lockfile; skipping workspace metadata: {err}");
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(declaration) = ty_declaration.as_ref() {
|
||||
let Some(lock) = lock.as_ref() else {
|
||||
anyhow::bail!(
|
||||
"The active `ty` development dependency requires an existing lockfile when `--no-sync` is used; update `uv.lock`, remove `--no-sync`, or use `--ty-version` or the `TY` environment variable"
|
||||
);
|
||||
};
|
||||
locked_ty_version = Some(ty::version_from_lock(declaration, project, lock, &venv)?);
|
||||
}
|
||||
lock
|
||||
} else {
|
||||
// Keep the environment locked through synchronization and metadata collection.
|
||||
_environment_lock = venv
|
||||
@@ -291,6 +308,15 @@ pub(crate) async fn check(
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
if let Some(declaration) = ty_declaration.as_ref() {
|
||||
locked_ty_version = Some(ty::version_from_lock(
|
||||
declaration,
|
||||
project,
|
||||
result.lock(),
|
||||
&venv,
|
||||
)?);
|
||||
}
|
||||
|
||||
let target = match project {
|
||||
VirtualProject::Project(project) => InstallTarget::Project {
|
||||
workspace: project.workspace(),
|
||||
@@ -381,7 +407,7 @@ pub(crate) async fn check(
|
||||
.map(|value| value.timestamp());
|
||||
|
||||
ty::run(
|
||||
ty_version,
|
||||
ty_version.or(locked_ty_version),
|
||||
ty_path,
|
||||
&target_dir,
|
||||
venv_path.as_deref(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::process::Stdio;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::process::{ChildStdin, Command};
|
||||
use tracing::debug;
|
||||
@@ -11,6 +11,13 @@ use tracing::debug;
|
||||
use uv_bin_install::{BinVersion, Binary, ResolvedVersion, bin_install, find_matching_version};
|
||||
use uv_cache::Cache;
|
||||
use uv_client::BaseClientBuilder;
|
||||
use uv_configuration::NoSources;
|
||||
use uv_normalize::{DEV_DEPENDENCIES, PackageName};
|
||||
use uv_python::{Interpreter, PythonEnvironment};
|
||||
use uv_resolver::Lock;
|
||||
use uv_workspace::VirtualProject;
|
||||
use uv_workspace::dependency_groups::FlatDependencyGroups;
|
||||
use uv_workspace::pyproject::{Source, ToolUvSources};
|
||||
|
||||
use crate::child::run_to_completion;
|
||||
use crate::commands::ExitStatus;
|
||||
@@ -20,6 +27,129 @@ use crate::printer::Printer;
|
||||
/// Limit how long uv can block if a version of ty does not consume metadata from stdin.
|
||||
const WORKSPACE_METADATA_WRITE_TIMEOUT: Duration = Duration::from_mins(1);
|
||||
|
||||
/// An active `ty` declaration in the current project's development dependencies.
|
||||
pub(super) struct ActiveDeclaration {
|
||||
package_name: PackageName,
|
||||
}
|
||||
|
||||
/// Find and validate an active `ty` declaration for the selected interpreter.
|
||||
pub(super) fn active_declaration(
|
||||
project: &VirtualProject,
|
||||
interpreter: &Interpreter,
|
||||
no_sources: &NoSources,
|
||||
) -> Result<Option<ActiveDeclaration>> {
|
||||
let package_name = PackageName::from_str("ty")?;
|
||||
let dependency_groups =
|
||||
FlatDependencyGroups::from_pyproject_toml(project.root(), project.pyproject_toml())?;
|
||||
let Some(dev_dependencies) = dependency_groups.get(&DEV_DEPENDENCIES) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let active_requirements = dev_dependencies
|
||||
.requirements
|
||||
.iter()
|
||||
.filter(|requirement| {
|
||||
requirement.name == package_name
|
||||
&& requirement.evaluate_markers(interpreter.markers(), &[])
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if active_requirements.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let source = if no_sources.for_package(&package_name) {
|
||||
None
|
||||
} else {
|
||||
project
|
||||
.pyproject_toml()
|
||||
.tool
|
||||
.as_ref()
|
||||
.and_then(|tool| tool.uv.as_ref())
|
||||
.and_then(|uv| uv.sources.as_ref())
|
||||
.map(ToolUvSources::inner)
|
||||
.and_then(|sources| sources.get(&package_name))
|
||||
.or_else(|| project.workspace().sources().get(&package_name))
|
||||
.and_then(|sources| {
|
||||
sources.iter().find(|source| {
|
||||
source.extra().is_none()
|
||||
&& source
|
||||
.group()
|
||||
.is_none_or(|group| group == &*DEV_DEPENDENCIES)
|
||||
&& source.marker().evaluate(interpreter.markers(), &[])
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
match source {
|
||||
Some(Source::Registry { .. }) => {}
|
||||
Some(source) => {
|
||||
bail!(
|
||||
"The active `ty` development dependency uses the non-registry source `{}`, but `uv check` can only install standalone `ty` releases by version; use a registry source, `--ty-version`, or the `TY` environment variable",
|
||||
source_reference(source)
|
||||
);
|
||||
}
|
||||
None if let Some(url) = active_requirements.iter().find_map(|requirement| {
|
||||
let Some(uv_pep508::VersionOrUrl::Url(url)) = requirement.version_or_url.as_ref()
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
Some(url)
|
||||
}) =>
|
||||
{
|
||||
bail!(
|
||||
"The active `ty` development dependency uses the direct URL `{url}`, but `uv check` can only install standalone `ty` releases by version; use a registry requirement, `--ty-version`, or the `TY` environment variable"
|
||||
);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
Ok(Some(ActiveDeclaration { package_name }))
|
||||
}
|
||||
|
||||
fn source_reference(source: &Source) -> String {
|
||||
match source {
|
||||
Source::Git { git, .. } => git.to_string(),
|
||||
Source::Url { url, .. } => url.to_string(),
|
||||
Source::Path { path, .. } => path.to_string(),
|
||||
Source::Registry { index, .. } => index.to_string(),
|
||||
Source::Workspace { workspace, .. } => format!("workspace = {workspace}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Select the exact registry version of `ty` reachable from the current project in the lockfile.
|
||||
pub(super) fn version_from_lock(
|
||||
declaration: &ActiveDeclaration,
|
||||
project: &VirtualProject,
|
||||
lock: &Lock,
|
||||
environment: &PythonEnvironment,
|
||||
) -> Result<String> {
|
||||
let marker_environment = environment.interpreter().resolver_marker_environment();
|
||||
let package = lock
|
||||
.find_dependency_group_package(
|
||||
project.project_name(),
|
||||
&DEV_DEPENDENCIES,
|
||||
&declaration.package_name,
|
||||
marker_environment.markers(),
|
||||
)
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let Some(package) = package else {
|
||||
bail!(
|
||||
"The active `ty` development dependency is not present in the lockfile for the selected Python environment; update `uv.lock`, or use `--ty-version` or the `TY` environment variable"
|
||||
);
|
||||
};
|
||||
if package.index(project.workspace().install_path())?.is_none() {
|
||||
bail!(
|
||||
"The locked `ty` package uses a non-registry source, but `uv check` can only install standalone `ty` releases by version; use a registry source, `--ty-version`, or the `TY` environment variable"
|
||||
);
|
||||
}
|
||||
let Some(version) = package.version() else {
|
||||
bail!(
|
||||
"The locked `ty` package has no version, but `uv check` can only install standalone `ty` releases by version; use a registry source, `--ty-version`, or the `TY` environment variable"
|
||||
);
|
||||
};
|
||||
Ok(version.to_string())
|
||||
}
|
||||
|
||||
async fn write_workspace_metadata(mut stdin: ChildStdin, workspace_metadata: String) -> Result<()> {
|
||||
match tokio::time::timeout(
|
||||
WORKSPACE_METADATA_WRITE_TIMEOUT,
|
||||
|
||||
@@ -38,6 +38,536 @@ fn check_project() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn check_uses_exact_ty_version_from_lock() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
typing = ["ty>=0.0.1"]
|
||||
dev = [{ include-group = "typing" }]
|
||||
|
||||
[tool.uv]
|
||||
constraint-dependencies = ["ty==0.0.17"]
|
||||
|
||||
[tool.uv.sources]
|
||||
ty = { path = "does-not-exist" }
|
||||
"#})?;
|
||||
context
|
||||
.lock()
|
||||
.arg("--no-sources")
|
||||
.env(EnvVars::UV_EXCLUDE_NEWER, "2026-02-15T00:00:00Z")
|
||||
.assert()
|
||||
.success();
|
||||
context.temp_dir.child("main.py").write_str("x = 1")?;
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context
|
||||
.check()
|
||||
.arg("--no-sources")
|
||||
.arg("--no-sync")
|
||||
.arg("--no-dev")
|
||||
.arg("--no-build-package")
|
||||
.arg("ty")
|
||||
.arg("--no-binary-package")
|
||||
.arg("ty")
|
||||
.arg("--verbose")
|
||||
.env(EnvVars::RUST_LOG, "uv::commands::project::check::ty=debug"),
|
||||
@"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
DEBUG `--exclude-newer` is ignored for pinned version `0.0.17`
|
||||
DEBUG Using `ty==0.0.17`
|
||||
DEBUG Passing workspace metadata to `ty check` via stdin
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn check_uses_exact_ty_version_from_legacy_dev_dependencies() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty<0.0.18"]
|
||||
|
||||
[tool.uv]
|
||||
dev-dependencies = ["ty>=0.0.17"]
|
||||
"#})?;
|
||||
context.temp_dir.child("main.py").write_str("x = 1")?;
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context
|
||||
.check()
|
||||
.arg("--no-dev")
|
||||
.arg("--exclude-newer")
|
||||
.arg("2026-02-15T00:00:00Z")
|
||||
.arg("--verbose")
|
||||
.env(EnvVars::RUST_LOG, "uv::commands::project::check::ty=debug"),
|
||||
@"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: The `tool.uv.dev-dependencies` field (used in `pyproject.toml`) is deprecated and will be removed in a future release; use `dependency-groups.dev` instead
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
DEBUG `--exclude-newer` is ignored for pinned version `0.0.17`
|
||||
DEBUG Using `ty==0.0.17`
|
||||
DEBUG Passing workspace metadata to `ty check` via stdin
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn check_uses_applicable_ty_version_from_forked_lock() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ty==0.0.16 ; python_version < '3.12'",
|
||||
"ty==0.0.17 ; python_version >= '3.12'",
|
||||
]
|
||||
"#})?;
|
||||
context.temp_dir.child("main.py").write_str("x = 1")?;
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context
|
||||
.check()
|
||||
.arg("--no-dev")
|
||||
.arg("--verbose")
|
||||
.env(EnvVars::RUST_LOG, "uv::commands::project::check::ty=debug"),
|
||||
@"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
DEBUG `--exclude-newer` is ignored for pinned version `0.0.17`
|
||||
DEBUG Using `ty==0.0.17`
|
||||
DEBUG Passing workspace metadata to `ty check` via stdin
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_ignores_inactive_ty_declaration() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty>=999 ; python_version < '3.12'"]
|
||||
"#})?;
|
||||
context.temp_dir.child("main.py").write_str("x = 1")?;
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context
|
||||
.check()
|
||||
.arg("--verbose")
|
||||
.env(EnvVars::RUST_LOG, "uv::commands::project::check::ty=debug"),
|
||||
@"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
DEBUG Resolved `ty@>=0.0, <0.1` to `ty==0.0.17`
|
||||
DEBUG Passing workspace metadata to `ty check` via stdin
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_rejects_non_registry_ty_source() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty"]
|
||||
|
||||
[tool.uv.sources]
|
||||
ty = { path = "does-not-exist" }
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.check(), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
error: The active `ty` development dependency uses the non-registry source `does-not-exist`, but `uv check` can only install standalone `ty` releases by version; use a registry source, `--ty-version`, or the `TY` environment variable
|
||||
");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty @ https://example.com/ty.whl"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.check(), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
error: The active `ty` development dependency uses the direct URL `https://example.com/ty.whl`, but `uv check` can only install standalone `ty` releases by version; use a registry requirement, `--ty-version`, or the `TY` environment variable
|
||||
");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty @ https://example.com/ty.whl"]
|
||||
|
||||
[tool.uv.sources]
|
||||
ty = { index = "test" }
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "test"
|
||||
url = "https://example.com/simple"
|
||||
explicit = true
|
||||
"#})?;
|
||||
|
||||
// The registry override makes the inline URL registry-backed, so selection proceeds to the
|
||||
// missing-lock error instead of rejecting the declaration as a direct URL.
|
||||
uv_snapshot!(context.filters(), context.check().arg("--no-sync"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
error: The active `ty` development dependency requires an existing lockfile when `--no-sync` is used; update `uv.lock`, remove `--no-sync`, or use `--ty-version` or the `TY` environment variable
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_active_ty_requires_lock_with_no_sync() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty==0.0.17"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.check().arg("--no-sync"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
error: The active `ty` development dependency requires an existing lockfile when `--no-sync` is used; update `uv.lock`, remove `--no-sync`, or use `--ty-version` or the `TY` environment variable
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_active_ty_rejects_stale_frozen_lock() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#})?;
|
||||
context.lock().assert().success();
|
||||
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty==0.0.17"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.check().arg("--frozen"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
error: The active `ty` development dependency is not present in the lockfile for the selected Python environment; update `uv.lock`, or use `--ty-version` or the `TY` environment variable
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn check_virtual_root_does_not_use_member_ty() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[tool.uv.workspace]
|
||||
members = ["member"]
|
||||
"#})?;
|
||||
let member = context.temp_dir.child("member");
|
||||
member.create_dir_all()?;
|
||||
member.child("pyproject.toml").write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "member"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty==0.0.17"]
|
||||
"#})?;
|
||||
context
|
||||
.lock()
|
||||
.arg("--exclude-newer")
|
||||
.arg("2026-02-15T00:00:00Z")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[dependency-groups]
|
||||
dev = ["ty==0.0.17"]
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["member"]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.check().arg("--frozen"), @"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
error: The active `ty` development dependency is not present in the lockfile for the selected Python environment; update `uv.lock`, or use `--ty-version` or the `TY` environment variable
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn check_virtual_root_uses_exact_ty_version_from_lock() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[dependency-groups]
|
||||
dev = ["ty==0.0.17"]
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["member"]
|
||||
"#})?;
|
||||
let member = context.temp_dir.child("member");
|
||||
member.create_dir_all()?;
|
||||
member.child("pyproject.toml").write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "member"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
"#})?;
|
||||
context.temp_dir.child("main.py").write_str("x = 1")?;
|
||||
context
|
||||
.lock()
|
||||
.arg("--exclude-newer")
|
||||
.arg("2026-02-15T00:00:00Z")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context
|
||||
.check()
|
||||
.arg("--no-sync")
|
||||
.arg("--verbose")
|
||||
.env(EnvVars::RUST_LOG, "uv::commands::project::check::ty=debug"),
|
||||
@r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
DEBUG `--exclude-newer` is ignored for pinned version `0.0.17`
|
||||
DEBUG Using `ty==0.0.17`
|
||||
DEBUG Passing workspace metadata to `ty check` via stdin
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn check_member_ty_ignores_virtual_root_dev_dependencies() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[dependency-groups]
|
||||
dev = ["iniconfig==2.0.0"]
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["member"]
|
||||
"#})?;
|
||||
let member = context.temp_dir.child("member");
|
||||
member.create_dir_all()?;
|
||||
member.child("pyproject.toml").write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "member"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty==0.0.17"]
|
||||
"#})?;
|
||||
member.child("main.py").write_str("x = 1")?;
|
||||
context
|
||||
.lock()
|
||||
.arg("--exclude-newer")
|
||||
.arg("2026-02-15T00:00:00Z")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
// These mutually exclusive build settings would make the workspace root's development
|
||||
// dependency unusable if it leaked into the member-only version extraction.
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context
|
||||
.check()
|
||||
.current_dir(member.path())
|
||||
.arg("--no-sync")
|
||||
.arg("--no-build-package")
|
||||
.arg("iniconfig")
|
||||
.arg("--no-binary-package")
|
||||
.arg("iniconfig")
|
||||
.arg("--verbose")
|
||||
.env(EnvVars::RUST_LOG, "uv::commands::project::check::ty=debug"),
|
||||
@"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
All checks passed!
|
||||
|
||||
----- stderr -----
|
||||
warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning.
|
||||
DEBUG `--exclude-newer` is ignored for pinned version `0.0.17`
|
||||
DEBUG Using `ty==0.0.17`
|
||||
DEBUG Passing workspace metadata to `ty check` via stdin
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "test-pypi")]
|
||||
fn check_uses_ty_from_environment() -> Result<()> {
|
||||
@@ -56,13 +586,27 @@ fn check_uses_ty_from_environment() -> Result<()> {
|
||||
.success();
|
||||
|
||||
let ty = bin_dir.child(format!("ty{}", std::env::consts::EXE_SUFFIX));
|
||||
// `TY` takes precedence over both an explicit version and an unsupported project source.
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty @ https://example.com/ty.whl"]
|
||||
"#})?;
|
||||
context.temp_dir.child("main.py").write_str("x = 1")?;
|
||||
|
||||
uv_snapshot!(
|
||||
context.filters(),
|
||||
context
|
||||
.check()
|
||||
.arg("--no-project")
|
||||
.arg("--no-sync")
|
||||
.arg("--ty-version")
|
||||
.arg(">=999.0.0")
|
||||
.arg("--verbose")
|
||||
@@ -130,6 +674,7 @@ fn check_passes_workspace_metadata_to_ty() -> Result<()> {
|
||||
fn check_no_sync_ignores_invalid_lockfile() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
// An explicit version bypasses both project-source validation and lockfile parsing.
|
||||
context
|
||||
.temp_dir
|
||||
.child("pyproject.toml")
|
||||
@@ -139,6 +684,9 @@ fn check_no_sync_ignores_invalid_lockfile() -> Result<()> {
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = []
|
||||
|
||||
[dependency-groups]
|
||||
dev = ["ty @ https://example.com/ty.whl"]
|
||||
"#})?;
|
||||
context.temp_dir.child("uv.lock").write_str("invalid")?;
|
||||
context.temp_dir.child("main.py").write_str(indoc! {r"
|
||||
|
||||
Reference in New Issue
Block a user