mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 02:48:17 +00:00
Search beyond workspace root when discovering configuration (#5931)
## Summary Previously, we wouldn't respect configuration files in directories _above_ a workspace root. But this is somewhat problematic, because any `pyproject.toml` will define a workspace root... Instead, I think we should _start_ the search at the workspace root, but go above it if necessary. Closes: #5929. See: https://github.com/astral-sh/uv/pull/4295.
This commit is contained in:
parent
cbc3274848
commit
88ece8b791
4 changed files with 284 additions and 10 deletions
|
@ -40,9 +40,12 @@ impl FilesystemOptions {
|
|||
let root = dir.join("uv");
|
||||
let file = root.join("uv.toml");
|
||||
|
||||
debug!("Loading user configuration from: `{}`", file.display());
|
||||
debug!("Searching for user configuration in: `{}`", file.display());
|
||||
match read_file(&file) {
|
||||
Ok(options) => Ok(Some(Self(options))),
|
||||
Ok(options) => {
|
||||
debug!("Found user configuration in: `{}`", file.display());
|
||||
Ok(Some(Self(options)))
|
||||
}
|
||||
Err(Error::Io(err)) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
||||
Err(_) if !dir.is_dir() => {
|
||||
// Ex) `XDG_CONFIG_HOME=/dev/null`
|
||||
|
|
|
@ -103,12 +103,10 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
|
||||
// Load configuration from the filesystem, prioritizing (in order):
|
||||
// 1. The configuration file specified on the command-line.
|
||||
// 2. The configuration file in the current workspace (i.e., the `pyproject.toml` or `uv.toml`
|
||||
// file in the workspace root directory). If found, this file is combined with the user
|
||||
// configuration file.
|
||||
// 3. The nearest `uv.toml` file in the directory tree, starting from the current directory. If
|
||||
// found, this file is combined with the user configuration file. In this case, we don't
|
||||
// search for `pyproject.toml` files, since we're not in a workspace.
|
||||
// 2. The nearest configuration file (`uv.toml` or `pyproject.toml`) above the workspace root.
|
||||
// If found, this file is combined with the user configuration file.
|
||||
// 3. The nearest configuration file (`uv.toml` or `pyproject.toml`) in the directory tree,
|
||||
// starting from the current directory.
|
||||
let filesystem = if let Some(config_file) = cli.config_file.as_ref() {
|
||||
if config_file
|
||||
.file_name()
|
||||
|
@ -122,8 +120,8 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
|
|||
} else if matches!(&*cli.command, Commands::Tool(_)) {
|
||||
// For commands that operate at the user-level, ignore local configuration.
|
||||
FilesystemOptions::user()?
|
||||
} else if let Ok(project) = Workspace::discover(&CWD, &DiscoveryOptions::default()).await {
|
||||
let project = FilesystemOptions::from_directory(project.install_path())?;
|
||||
} else if let Ok(workspace) = Workspace::discover(&CWD, &DiscoveryOptions::default()).await {
|
||||
let project = FilesystemOptions::find(workspace.install_path())?;
|
||||
let user = FilesystemOptions::user()?;
|
||||
project.combine(user)
|
||||
} else {
|
||||
|
|
|
@ -3035,3 +3035,272 @@ fn resolve_config_file() -> anyhow::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ignore empty `pyproject.toml` files when discovering configuration.
|
||||
#[test]
|
||||
#[cfg_attr(
|
||||
windows,
|
||||
ignore = "Configuration tests are not yet supported on Windows"
|
||||
)]
|
||||
fn resolve_skip_empty() -> anyhow::Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
// Set `lowest-direct` in a `uv.toml`.
|
||||
let config = context.temp_dir.child("uv.toml");
|
||||
config.write_str(indoc::indoc! {r#"
|
||||
[pip]
|
||||
resolution = "lowest-direct"
|
||||
"#})?;
|
||||
|
||||
let child = context.temp_dir.child("child");
|
||||
fs_err::create_dir(&child)?;
|
||||
|
||||
// Create an empty in a `pyproject.toml`.
|
||||
let pyproject = child.child("pyproject.toml");
|
||||
pyproject.write_str(indoc::indoc! {r#"
|
||||
[project]
|
||||
name = "child"
|
||||
dependencies = [
|
||||
"httpx",
|
||||
]
|
||||
"#})?;
|
||||
|
||||
// Resolution in `child` should use lowest-direct, skipping the `pyproject.toml`, which lacks a
|
||||
// `tool.uv`.
|
||||
uv_snapshot!(context.filters(), add_shared_args(context.pip_compile())
|
||||
.arg("--show-settings")
|
||||
.arg("requirements.in")
|
||||
.current_dir(&child), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
GlobalSettings {
|
||||
quiet: false,
|
||||
verbose: 0,
|
||||
color: Auto,
|
||||
native_tls: false,
|
||||
connectivity: Online,
|
||||
show_settings: true,
|
||||
preview: Disabled,
|
||||
python_preference: OnlySystem,
|
||||
python_fetch: Automatic,
|
||||
no_progress: false,
|
||||
}
|
||||
CacheSettings {
|
||||
no_cache: false,
|
||||
cache_dir: Some(
|
||||
"[CACHE_DIR]/",
|
||||
),
|
||||
}
|
||||
PipCompileSettings {
|
||||
src_file: [
|
||||
"requirements.in",
|
||||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
build_constraint: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
SystemTime {
|
||||
tv_sec: [TIME],
|
||||
tv_nsec: [TIME],
|
||||
},
|
||||
),
|
||||
),
|
||||
settings: PipSettings {
|
||||
index_locations: IndexLocations {
|
||||
index: None,
|
||||
extra_index: [],
|
||||
flat_index: [],
|
||||
no_index: false,
|
||||
},
|
||||
python: None,
|
||||
system: false,
|
||||
extras: None,
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
index_strategy: FirstIndex,
|
||||
keyring_provider: Disabled,
|
||||
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,
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
no_annotate: false,
|
||||
no_header: false,
|
||||
custom_compile_command: None,
|
||||
generate_hashes: false,
|
||||
setup_py: Pep517,
|
||||
config_setting: ConfigSettings(
|
||||
{},
|
||||
),
|
||||
python_version: None,
|
||||
python_platform: None,
|
||||
universal: false,
|
||||
exclude_newer: Some(
|
||||
ExcludeNewer(
|
||||
2024-03-25T00:00:00Z,
|
||||
),
|
||||
),
|
||||
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: None,
|
||||
upgrade: None,
|
||||
reinstall: None,
|
||||
concurrency: Concurrency {
|
||||
downloads: 50,
|
||||
builds: 16,
|
||||
installs: 8,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
|
||||
// Adding a `tool.uv` section should cause us to ignore the `uv.toml`.
|
||||
pyproject.write_str(indoc::indoc! {r#"
|
||||
[project]
|
||||
name = "child"
|
||||
dependencies = [
|
||||
"httpx",
|
||||
]
|
||||
|
||||
[tool.uv]
|
||||
"#})?;
|
||||
|
||||
uv_snapshot!(context.filters(), add_shared_args(context.pip_compile())
|
||||
.arg("--show-settings")
|
||||
.arg("requirements.in")
|
||||
.current_dir(&child), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
GlobalSettings {
|
||||
quiet: false,
|
||||
verbose: 0,
|
||||
color: Auto,
|
||||
native_tls: false,
|
||||
connectivity: Online,
|
||||
show_settings: true,
|
||||
preview: Disabled,
|
||||
python_preference: OnlySystem,
|
||||
python_fetch: Automatic,
|
||||
no_progress: false,
|
||||
}
|
||||
CacheSettings {
|
||||
no_cache: false,
|
||||
cache_dir: Some(
|
||||
"[CACHE_DIR]/",
|
||||
),
|
||||
}
|
||||
PipCompileSettings {
|
||||
src_file: [
|
||||
"requirements.in",
|
||||
],
|
||||
constraint: [],
|
||||
override: [],
|
||||
constraints_from_workspace: [],
|
||||
overrides_from_workspace: [],
|
||||
build_constraint: [],
|
||||
refresh: None(
|
||||
Timestamp(
|
||||
SystemTime {
|
||||
tv_sec: [TIME],
|
||||
tv_nsec: [TIME],
|
||||
},
|
||||
),
|
||||
),
|
||||
settings: PipSettings {
|
||||
index_locations: IndexLocations {
|
||||
index: None,
|
||||
extra_index: [],
|
||||
flat_index: [],
|
||||
no_index: false,
|
||||
},
|
||||
python: None,
|
||||
system: false,
|
||||
extras: None,
|
||||
break_system_packages: false,
|
||||
target: None,
|
||||
prefix: None,
|
||||
index_strategy: FirstIndex,
|
||||
keyring_provider: Disabled,
|
||||
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: Highest,
|
||||
prerelease: IfNecessaryOrExplicit,
|
||||
output_file: None,
|
||||
no_strip_extras: false,
|
||||
no_strip_markers: false,
|
||||
no_annotate: false,
|
||||
no_header: false,
|
||||
custom_compile_command: None,
|
||||
generate_hashes: false,
|
||||
setup_py: Pep517,
|
||||
config_setting: ConfigSettings(
|
||||
{},
|
||||
),
|
||||
python_version: None,
|
||||
python_platform: None,
|
||||
universal: false,
|
||||
exclude_newer: Some(
|
||||
ExcludeNewer(
|
||||
2024-03-25T00:00:00Z,
|
||||
),
|
||||
),
|
||||
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: None,
|
||||
upgrade: None,
|
||||
reinstall: None,
|
||||
concurrency: Concurrency {
|
||||
downloads: 50,
|
||||
builds: 16,
|
||||
installs: 8,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
----- stderr -----
|
||||
"###
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@ in the nearest parent directory.
|
|||
files will be ignored. Instead, uv will exclusively read from user-level configuration
|
||||
(e.g., `~/.config/uv/uv.toml`).
|
||||
|
||||
In workspaces, uv will begin its search at the workspace root, ignoring any configuration defined in
|
||||
workspace members. Since the workspace is locked as a single unit, configuration is shared across
|
||||
all members.
|
||||
|
||||
If a `pyproject.toml` file is found, uv will read configuration from the `[tool.uv.pip]` table. For
|
||||
example, to set a persistent index URL, add the following to a `pyproject.toml`:
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue