mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 13:25:00 +00:00
Make warnings about masked [tool.uv]
fields more precise
This commit is contained in:
parent
4eef79e5e8
commit
3048922f28
3 changed files with 483 additions and 6 deletions
|
@ -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)]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 -----
|
||||
"#
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue