This commit is contained in:
Aria Desires 2025-07-06 00:13:16 +02:00 committed by GitHub
commit 5e7874953a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 483 additions and 6 deletions

View file

@ -119,10 +119,9 @@ impl FilesystemOptions {
.ok()
.and_then(|content| toml::from_str::<PyProjectToml>(&content).ok())
{
if pyproject.tool.is_some_and(|tool| tool.uv.is_some()) {
warn_user!(
"Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The `[tool.uv]` section will be ignored in favor of the `uv.toml` file."
);
if let Some(options) = pyproject.tool.as_ref().and_then(|tool| tool.uv.as_ref())
{
warn_uv_toml_masked_fields(options);
}
}
@ -225,6 +224,253 @@ fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> {
Ok(())
}
/// Validate that an [`Options`] contains no fields that `uv.toml` would mask
///
/// This is essentially the inverse of [`validated_uv_toml`][].
fn warn_uv_toml_masked_fields(options: &Options) {
let Options {
globals:
GlobalOptions {
required_version,
native_tls,
offline,
no_cache,
cache_dir,
preview,
python_preference,
python_downloads,
concurrent_downloads,
concurrent_builds,
concurrent_installs,
allow_insecure_host,
},
top_level:
ResolverInstallerOptions {
index,
index_url,
extra_index_url,
no_index,
find_links,
index_strategy,
keyring_provider,
resolution,
prerelease,
fork_strategy,
dependency_metadata,
config_settings,
no_build_isolation,
no_build_isolation_package,
exclude_newer,
link_mode,
compile_bytecode,
no_sources,
upgrade,
upgrade_package,
reinstall,
reinstall_package,
no_build,
no_build_package,
no_binary,
no_binary_package,
},
install_mirrors:
PythonInstallMirrors {
python_install_mirror,
pypy_install_mirror,
python_downloads_json_url,
},
publish:
PublishOptions {
publish_url,
trusted_publishing,
check_url,
},
add: AddOptions { add_bounds },
pip,
cache_keys,
override_dependencies,
constraint_dependencies,
build_constraint_dependencies,
environments,
required_environments,
conflicts: _,
workspace: _,
sources: _,
dev_dependencies: _,
default_groups: _,
dependency_groups: _,
managed: _,
package: _,
build_backend: _,
} = options;
let mut masked_fields = vec![];
if required_version.is_some() {
masked_fields.push("required-version");
}
if native_tls.is_some() {
masked_fields.push("native-tls");
}
if offline.is_some() {
masked_fields.push("offline");
}
if no_cache.is_some() {
masked_fields.push("no-cache");
}
if cache_dir.is_some() {
masked_fields.push("cache-dir");
}
if preview.is_some() {
masked_fields.push("preview");
}
if python_preference.is_some() {
masked_fields.push("python-preference");
}
if python_downloads.is_some() {
masked_fields.push("python-downloads");
}
if concurrent_downloads.is_some() {
masked_fields.push("concurrent-downloads");
}
if concurrent_builds.is_some() {
masked_fields.push("concurrent-builds");
}
if concurrent_installs.is_some() {
masked_fields.push("concurrent-installs");
}
if allow_insecure_host.is_some() {
masked_fields.push("allow-insecure-host");
}
if index.is_some() {
masked_fields.push("index");
}
if index_url.is_some() {
masked_fields.push("index-url");
}
if extra_index_url.is_some() {
masked_fields.push("extra-index-url");
}
if no_index.is_some() {
masked_fields.push("no-index");
}
if find_links.is_some() {
masked_fields.push("find-links");
}
if index_strategy.is_some() {
masked_fields.push("index-strategy");
}
if keyring_provider.is_some() {
masked_fields.push("keyring-provider");
}
if resolution.is_some() {
masked_fields.push("resolution");
}
if prerelease.is_some() {
masked_fields.push("prerelease");
}
if fork_strategy.is_some() {
masked_fields.push("fork-strategy");
}
if dependency_metadata.is_some() {
masked_fields.push("dependency-metadata");
}
if config_settings.is_some() {
masked_fields.push("config-settings");
}
if no_build_isolation.is_some() {
masked_fields.push("no-build-isolation");
}
if no_build_isolation_package.is_some() {
masked_fields.push("no-build-isolation-package");
}
if exclude_newer.is_some() {
masked_fields.push("exclude-newer");
}
if link_mode.is_some() {
masked_fields.push("link-mode");
}
if compile_bytecode.is_some() {
masked_fields.push("compile-bytecode");
}
if no_sources.is_some() {
masked_fields.push("no-sources");
}
if upgrade.is_some() {
masked_fields.push("upgrade");
}
if upgrade_package.is_some() {
masked_fields.push("upgrade-package");
}
if reinstall.is_some() {
masked_fields.push("reinstall");
}
if reinstall_package.is_some() {
masked_fields.push("reinstall-package");
}
if no_build.is_some() {
masked_fields.push("no-build");
}
if no_build_package.is_some() {
masked_fields.push("no-build-package");
}
if no_binary.is_some() {
masked_fields.push("no-binary");
}
if no_binary_package.is_some() {
masked_fields.push("no-binary-package");
}
if python_install_mirror.is_some() {
masked_fields.push("python-install-mirror");
}
if pypy_install_mirror.is_some() {
masked_fields.push("pypy-install-mirror");
}
if python_downloads_json_url.is_some() {
masked_fields.push("python-downloads-json-url");
}
if publish_url.is_some() {
masked_fields.push("publish-url");
}
if trusted_publishing.is_some() {
masked_fields.push("trusted-publishing");
}
if check_url.is_some() {
masked_fields.push("check-url");
}
if add_bounds.is_some() {
masked_fields.push("add-bounds");
}
if pip.is_some() {
masked_fields.push("pip");
}
if cache_keys.is_some() {
masked_fields.push("cache_keys");
}
if override_dependencies.is_some() {
masked_fields.push("override-dependencies");
}
if constraint_dependencies.is_some() {
masked_fields.push("constraint-dependencies");
}
if build_constraint_dependencies.is_some() {
masked_fields.push("build-constraint-dependencies");
}
if environments.is_some() {
masked_fields.push("environments");
}
if required_environments.is_some() {
masked_fields.push("required-environments");
}
if !masked_fields.is_empty() {
let field_listing = masked_fields.join("\n- ");
warn_user!(
"Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file:\n- {}",
field_listing,
);
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]

View file

@ -103,7 +103,7 @@ pub struct Options {
cache-keys = [{ file = "pyproject.toml" }, { file = "requirements.txt" }, { git = { commit = true } }]
"#
)]
cache_keys: Option<Vec<CacheKey>>,
pub cache_keys: Option<Vec<CacheKey>>,
// NOTE(charlie): These fields are shared with `ToolUv` in
// `crates/uv-workspace/src/pyproject.rs`. The documentation lives on that struct.

View file

@ -3441,6 +3441,8 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
}
/// Read from both a `uv.toml` and `pyproject.toml` file in the current directory.
///
/// Some fields in `[tool.uv]` are masked by `uv.toml` being defined, and should be warned about.
#[test]
#[cfg_attr(
windows,
@ -3465,6 +3467,10 @@ fn resolve_both() -> anyhow::Result<()> {
name = "example"
version = "0.0.0"
[tool.uv]
offline = true
dev-dependencies = ["pytest"]
[tool.uv.pip]
resolution = "highest"
extra-index-url = ["https://test.pypi.org/simple"]
@ -3650,7 +3656,232 @@ fn resolve_both() -> anyhow::Result<()> {
}
----- stderr -----
warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The `[tool.uv]` section will be ignored in favor of the `uv.toml` file.
warning: Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file:
- offline
- pip
"#
);
Ok(())
}
/// Read from both a `uv.toml` and `pyproject.toml` file in the current directory.
///
/// But the fields `[tool.uv]` defines aren't allowed in `uv.toml` so there's no warning.
#[test]
#[cfg_attr(
windows,
ignore = "Configuration tests are not yet supported on Windows"
)]
fn resolve_both_special_fields() -> anyhow::Result<()> {
let context = TestContext::new("3.12");
// Write a `uv.toml` file to the directory.
let config = context.temp_dir.child("uv.toml");
config.write_str(indoc::indoc! {r#"
[pip]
resolution = "lowest-direct"
generate-hashes = true
index-url = "https://pypi.org/simple"
"#})?;
// Write a `pyproject.toml` file to the directory
let config = context.temp_dir.child("pyproject.toml");
config.write_str(indoc::indoc! {r#"
[project]
name = "example"
version = "0.0.0"
[dependency-groups]
mygroup = ["iniconfig"]
[tool.uv]
dev-dependencies = ["pytest"]
[tool.uv.dependency-groups]
mygroup = {requires-python = ">=3.12"}
"#})?;
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio>3.0.0")?;
// Resolution should succeed, but warn that the `pip` section in `pyproject.toml` is ignored.
uv_snapshot!(context.filters(), add_shared_args(context.pip_compile(), context.temp_dir.path())
.arg("--show-settings")
.arg("requirements.in"), @r#"
success: true
exit_code: 0
----- stdout -----
GlobalSettings {
required_version: None,
quiet: 0,
verbose: 0,
color: Auto,
network_settings: NetworkSettings {
connectivity: Online,
native_tls: false,
allow_insecure_host: [],
},
concurrency: Concurrency {
downloads: 50,
builds: 16,
installs: 8,
},
show_settings: true,
preview: Disabled,
python_preference: Managed,
python_downloads: Automatic,
no_progress: false,
installer_metadata: true,
}
CacheSettings {
no_cache: false,
cache_dir: Some(
"[CACHE_DIR]/",
),
}
PipCompileSettings {
format: None,
src_file: [
"requirements.in",
],
constraints: [],
overrides: [],
build_constraints: [],
constraints_from_workspace: [],
overrides_from_workspace: [],
build_constraints_from_workspace: [],
environments: SupportedEnvironments(
[],
),
refresh: None(
Timestamp(
SystemTime {
tv_sec: [TIME],
tv_nsec: [TIME],
},
),
),
settings: PipSettings {
index_locations: IndexLocations {
indexes: [
Index {
name: None,
url: Pypi(
VerbatimUrl {
url: DisplaySafeUrl {
scheme: "https",
cannot_be_a_base: false,
username: "",
password: None,
host: Some(
Domain(
"pypi.org",
),
),
port: None,
path: "/simple",
query: None,
fragment: None,
},
given: Some(
"https://pypi.org/simple",
),
},
),
explicit: false,
default: true,
origin: None,
format: Simple,
publish_url: None,
authenticate: Auto,
ignore_error_codes: None,
},
],
flat_index: [],
no_index: false,
},
python: None,
install_mirrors: PythonInstallMirrors {
python_install_mirror: None,
pypy_install_mirror: None,
python_downloads_json_url: None,
},
system: false,
extras: ExtrasSpecification(
ExtrasSpecificationInner {
include: Some(
[],
),
exclude: [],
only_extras: false,
history: ExtrasSpecificationHistory {
extra: [],
only_extra: [],
no_extra: [],
all_extras: false,
no_default_extras: false,
defaults: List(
[],
),
},
},
),
groups: [],
break_system_packages: false,
target: None,
prefix: None,
index_strategy: FirstIndex,
keyring_provider: Disabled,
torch_backend: None,
no_build_isolation: false,
no_build_isolation_package: [],
build_options: BuildOptions {
no_binary: None,
no_build: None,
},
allow_empty_requirements: false,
strict: false,
dependency_mode: Transitive,
resolution: LowestDirect,
prerelease: IfNecessaryOrExplicit,
fork_strategy: RequiresPython,
dependency_metadata: DependencyMetadata(
{},
),
output_file: None,
no_strip_extras: false,
no_strip_markers: false,
no_annotate: false,
no_header: false,
custom_compile_command: None,
generate_hashes: true,
config_setting: ConfigSettings(
{},
),
python_version: None,
python_platform: None,
universal: false,
exclude_newer: None,
no_emit_package: [],
emit_index_url: false,
emit_find_links: false,
emit_build_options: false,
emit_marker_expression: false,
emit_index_annotation: false,
annotation_style: Split,
link_mode: Clone,
compile_bytecode: false,
sources: Enabled,
hash_checking: Some(
Verify,
),
upgrade: None,
reinstall: None,
},
}
----- stderr -----
"#
);