Refactor PythonVersionFile global loading (#14107)

I was looking into `uv tool` not supporting version files, and noticed
this implementation was confusing and skipped handling like a tracing
log if `--no-config` excludes selection a file. I've refactored it in
preparation for the next change.
This commit is contained in:
Zanie Blue 2025-06-20 15:31:32 -05:00 committed by GitHub
parent 563e9495ba
commit 1dbe750452
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 47 deletions

View file

@ -37,11 +37,14 @@ pub enum FilePreference {
pub struct DiscoveryOptions<'a> { pub struct DiscoveryOptions<'a> {
/// The path to stop discovery at. /// The path to stop discovery at.
stop_discovery_at: Option<&'a Path>, stop_discovery_at: Option<&'a Path>,
/// When `no_config` is set, Python version files will be ignored. /// Ignore Python version files.
/// ///
/// Discovery will still run in order to display a log about the ignored file. /// Discovery will still run in order to display a log about the ignored file.
no_config: bool, no_config: bool,
/// Whether `.python-version` or `.python-versions` should be preferred.
preference: FilePreference, preference: FilePreference,
/// Whether to ignore local version files, and only search for a global one.
no_local: bool,
} }
impl<'a> DiscoveryOptions<'a> { impl<'a> DiscoveryOptions<'a> {
@ -62,6 +65,11 @@ impl<'a> DiscoveryOptions<'a> {
..self ..self
} }
} }
#[must_use]
pub fn with_no_local(self, no_local: bool) -> Self {
Self { no_local, ..self }
}
} }
impl PythonVersionFile { impl PythonVersionFile {
@ -70,33 +78,38 @@ impl PythonVersionFile {
working_directory: impl AsRef<Path>, working_directory: impl AsRef<Path>,
options: &DiscoveryOptions<'_>, options: &DiscoveryOptions<'_>,
) -> Result<Option<Self>, std::io::Error> { ) -> Result<Option<Self>, std::io::Error> {
let Some(path) = Self::find_nearest(&working_directory, options) else { let allow_local = !options.no_local;
if let Some(stop_discovery_at) = options.stop_discovery_at { let Some(path) = allow_local.then(|| {
if stop_discovery_at == working_directory.as_ref() { // First, try to find a local version file.
debug!( let local = Self::find_nearest(&working_directory, options);
"No Python version file found in workspace: {}", if local.is_none() {
working_directory.as_ref().display() // Log where we searched for the file, if not found
); if let Some(stop_discovery_at) = options.stop_discovery_at {
if stop_discovery_at == working_directory.as_ref() {
debug!(
"No Python version file found in workspace: {}",
working_directory.as_ref().display()
);
} else {
debug!(
"No Python version file found between working directory `{}` and workspace root `{}`",
working_directory.as_ref().display(),
stop_discovery_at.display()
);
}
} else { } else {
debug!( debug!(
"No Python version file found between working directory `{}` and workspace root `{}`", "No Python version file found in ancestors of working directory: {}",
working_directory.as_ref().display(), working_directory.as_ref().display()
stop_discovery_at.display()
); );
} }
} else {
debug!(
"No Python version file found in ancestors of working directory: {}",
working_directory.as_ref().display()
);
} }
// Not found in directory or its ancestors. Looking in user-level config. local
return Ok(match user_uv_config_dir() { }).flatten().or_else(|| {
Some(user_dir) => Self::discover_user_config(user_dir, options) // Search for a global config
.await? Self::find_global(options)
.or(None), }) else {
None => None, return Ok(None);
});
}; };
if options.no_config { if options.no_config {
@ -111,20 +124,9 @@ impl PythonVersionFile {
Self::try_from_path(path).await Self::try_from_path(path).await
} }
pub async fn discover_user_config( fn find_global(options: &DiscoveryOptions<'_>) -> Option<PathBuf> {
user_config_working_directory: impl AsRef<Path>, let user_config_dir = user_uv_config_dir()?;
options: &DiscoveryOptions<'_>, Self::find_in_directory(&user_config_dir, options)
) -> Result<Option<Self>, std::io::Error> {
if !options.no_config {
if let Some(path) =
Self::find_in_directory(user_config_working_directory.as_ref(), options)
.into_iter()
.find(|path| path.is_file())
{
return Self::try_from_path(path).await;
}
}
Ok(None)
} }
fn find_nearest(path: impl AsRef<Path>, options: &DiscoveryOptions<'_>) -> Option<PathBuf> { fn find_nearest(path: impl AsRef<Path>, options: &DiscoveryOptions<'_>) -> Option<PathBuf> {

View file

@ -57,16 +57,13 @@ pub(crate) async fn pin(
} }
}; };
let version_file = if global { // Search for an existing file, we won't necessarily write to this, we'll construct a target
if let Some(path) = user_uv_config_dir() { // path if there's a request later on.
PythonVersionFile::discover_user_config(path, &VersionFileDiscoveryOptions::default()) let version_file = PythonVersionFile::discover(
.await project_dir,
} else { &VersionFileDiscoveryOptions::default().with_no_local(global),
Ok(None) )
} .await;
} else {
PythonVersionFile::discover(project_dir, &VersionFileDiscoveryOptions::default()).await
};
if rm { if rm {
let Some(file) = version_file? else { let Some(file) = version_file? else {