Use portable paths when serializing sources (#7504)

## Summary

Closes https://github.com/astral-sh/uv/issues/7493.
This commit is contained in:
Charlie Marsh 2024-09-18 14:51:14 -04:00 committed by GitHub
parent 1379b530f6
commit e36cc99b0d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 70 additions and 36 deletions

1
Cargo.lock generated
View file

@ -4863,6 +4863,7 @@ dependencies = [
"fs2",
"junction",
"path-slash",
"schemars",
"serde",
"tempfile",
"tokio",

View file

@ -88,20 +88,20 @@ impl LoweredRequirement {
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
return Err(LoweringError::ConflictingUrls);
}
git_source(&git, subdirectory, rev, tag, branch)?
git_source(&git, subdirectory.map(PathBuf::from), rev, tag, branch)?
}
Source::Url { url, subdirectory } => {
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
return Err(LoweringError::ConflictingUrls);
}
url_source(url, subdirectory)?
url_source(url, subdirectory.map(PathBuf::from))?
}
Source::Path { path, editable } => {
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
return Err(LoweringError::ConflictingUrls);
}
path_source(
path,
PathBuf::from(path),
origin,
project_dir,
workspace.install_path(),
@ -203,19 +203,25 @@ impl LoweredRequirement {
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
return Err(LoweringError::ConflictingUrls);
}
git_source(&git, subdirectory, rev, tag, branch)?
git_source(&git, subdirectory.map(PathBuf::from), rev, tag, branch)?
}
Source::Url { url, subdirectory } => {
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
return Err(LoweringError::ConflictingUrls);
}
url_source(url, subdirectory)?
url_source(url, subdirectory.map(PathBuf::from))?
}
Source::Path { path, editable } => {
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
return Err(LoweringError::ConflictingUrls);
}
path_source(path, Origin::Project, dir, dir, editable.unwrap_or(false))?
path_source(
PathBuf::from(path),
Origin::Project,
dir,
dir,
editable.unwrap_or(false),
)?
}
Source::Registry { index } => registry_source(&requirement, index)?,
Source::Workspace { .. } => {

View file

@ -22,6 +22,7 @@ encoding_rs_io = { workspace = true }
fs-err = { workspace = true }
fs2 = { workspace = true }
path-slash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
tokio = { workspace = true, optional = true}
tempfile = { workspace = true }

View file

@ -301,6 +301,17 @@ pub struct PortablePath<'a>(&'a Path);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PortablePathBuf(PathBuf);
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for PortablePathBuf {
fn schema_name() -> String {
PathBuf::schema_name()
}
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
PathBuf::json_schema(_gen)
}
}
impl AsRef<Path> for PortablePath<'_> {
fn as_ref(&self) -> &Path {
self.0

View file

@ -16,7 +16,7 @@ workspace = true
pep440_rs = { workspace = true }
pep508_rs = { workspace = true }
pypi-types = { workspace = true }
uv-fs = { workspace = true, features = ["tokio"] }
uv-fs = { workspace = true, features = ["tokio", "schemars"] }
uv-git = { workspace = true }
uv-macros = { workspace = true }
uv-normalize = { workspace = true }

View file

@ -17,7 +17,7 @@ use url::Url;
use pep440_rs::{Version, VersionSpecifiers};
use pypi_types::{RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
use uv_fs::relative_to;
use uv_fs::{relative_to, PortablePathBuf};
use uv_git::GitReference;
use uv_macros::OptionsMetadata;
use uv_normalize::{ExtraName, PackageName};
@ -413,7 +413,7 @@ pub enum Source {
/// The repository URL (without the `git+` prefix).
git: Url,
/// The path to the directory with the `pyproject.toml`, if it's not in the archive root.
subdirectory: Option<PathBuf>,
subdirectory: Option<PortablePathBuf>,
// Only one of the three may be used; we'll validate this later and emit a custom error.
rev: Option<String>,
tag: Option<String>,
@ -430,13 +430,13 @@ pub enum Source {
url: Url,
/// For source distributions, the path to the directory with the `pyproject.toml`, if it's
/// not in the archive root.
subdirectory: Option<PathBuf>,
subdirectory: Option<PortablePathBuf>,
},
/// The path to a dependency, either a wheel (a `.whl` file), source distribution (a `.zip` or
/// `.tar.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or
/// `setup.py` file in the root).
Path {
path: PathBuf,
path: PortablePathBuf,
/// `false` by default.
editable: Option<bool>,
},
@ -454,12 +454,12 @@ pub enum Source {
/// A catch-all variant used to emit precise error messages when deserializing.
CatchAll {
git: String,
subdirectory: Option<PathBuf>,
subdirectory: Option<PortablePathBuf>,
rev: Option<String>,
tag: Option<String>,
branch: Option<String>,
url: String,
path: PathBuf,
path: PortablePathBuf,
index: String,
workspace: bool,
},
@ -534,15 +534,17 @@ impl Source {
RequirementSource::Path { install_path, .. }
| RequirementSource::Directory { install_path, .. } => Source::Path {
editable,
path: relative_to(&install_path, root)
.or_else(|_| std::path::absolute(&install_path))
.map_err(SourceError::Absolute)?,
path: PortablePathBuf::from(
relative_to(&install_path, root)
.or_else(|_| std::path::absolute(&install_path))
.map_err(SourceError::Absolute)?,
),
},
RequirementSource::Url {
subdirectory, url, ..
} => Source::Url {
url: url.to_url(),
subdirectory,
subdirectory: subdirectory.map(PortablePathBuf::from),
},
RequirementSource::Git {
repository,
@ -566,7 +568,7 @@ impl Source {
tag,
branch,
git: repository,
subdirectory,
subdirectory: subdirectory.map(PortablePathBuf::from),
}
} else {
Source::Git {
@ -574,7 +576,7 @@ impl Source {
tag,
branch,
git: repository,
subdirectory,
subdirectory: subdirectory.map(PortablePathBuf::from),
}
}
}

View file

@ -5,6 +5,7 @@ use assert_cmd::assert::OutputAssertExt;
use assert_fs::prelude::*;
use indoc::indoc;
use insta::assert_snapshot;
use std::path::Path;
use crate::common::{decode_token, packse_index_url};
use common::{uv_snapshot, TestContext};
@ -2019,7 +2020,7 @@ fn add_path() -> Result<()> {
build-backend = "setuptools.build_meta"
"#})?;
let child = workspace.child("child");
let child = workspace.child("packages").child("child");
child.child("pyproject.toml").write_str(indoc! {r#"
[project]
name = "child"
@ -2032,7 +2033,7 @@ fn add_path() -> Result<()> {
build-backend = "setuptools.build_meta"
"#})?;
uv_snapshot!(context.filters(), context.add().arg("./child").current_dir(workspace.path()), @r###"
uv_snapshot!(context.filters(), context.add().arg(Path::new("packages").join("child")).current_dir(workspace.path()), @r###"
success: true
exit_code: 0
----- stdout -----
@ -2043,7 +2044,7 @@ fn add_path() -> Result<()> {
Resolved 2 packages in [TIME]
Prepared 2 packages in [TIME]
Installed 2 packages in [TIME]
+ child==0.1.0 (from file://[TEMP_DIR]/workspace/child)
+ child==0.1.0 (from file://[TEMP_DIR]/workspace/packages/child)
+ parent==0.1.0 (from file://[TEMP_DIR]/workspace)
"###);
@ -2067,7 +2068,7 @@ fn add_path() -> Result<()> {
build-backend = "setuptools.build_meta"
[tool.uv.sources]
child = { path = "child" }
child = { path = "packages/child" }
"###
);
});
@ -2089,7 +2090,7 @@ fn add_path() -> Result<()> {
[[package]]
name = "child"
version = "0.1.0"
source = { directory = "child" }
source = { directory = "packages/child" }
[[package]]
name = "parent"
@ -2100,7 +2101,7 @@ fn add_path() -> Result<()> {
]
[package.metadata]
requires-dist = [{ name = "child", directory = "child" }]
requires-dist = [{ name = "child", directory = "packages/child" }]
"###
);
});

34
uv.schema.json generated
View file

@ -1230,9 +1230,13 @@
},
"subdirectory": {
"description": "The path to the directory with the `pyproject.toml`, if it's not in the archive root.",
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/String"
},
{
"type": "null"
}
]
},
"tag": {
@ -1253,9 +1257,13 @@
"properties": {
"subdirectory": {
"description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's not in the archive root.",
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/String"
},
{
"type": "null"
}
]
},
"url": {
@ -1280,7 +1288,7 @@
]
},
"path": {
"type": "string"
"$ref": "#/definitions/String"
}
},
"additionalProperties": false
@ -1336,7 +1344,7 @@
"type": "string"
},
"path": {
"type": "string"
"$ref": "#/definitions/String"
},
"rev": {
"type": [
@ -1345,9 +1353,13 @@
]
},
"subdirectory": {
"type": [
"string",
"null"
"anyOf": [
{
"$ref": "#/definitions/String"
},
{
"type": "null"
}
]
},
"tag": {