mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-14 20:39:37 +00:00
Use portable paths when serializing sources (#7504)
## Summary Closes https://github.com/astral-sh/uv/issues/7493.
This commit is contained in:
parent
1379b530f6
commit
e36cc99b0d
8 changed files with 70 additions and 36 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4863,6 +4863,7 @@ dependencies = [
|
||||||
"fs2",
|
"fs2",
|
||||||
"junction",
|
"junction",
|
||||||
"path-slash",
|
"path-slash",
|
||||||
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
@ -88,20 +88,20 @@ impl LoweredRequirement {
|
||||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||||
return Err(LoweringError::ConflictingUrls);
|
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 } => {
|
Source::Url { url, subdirectory } => {
|
||||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||||
return Err(LoweringError::ConflictingUrls);
|
return Err(LoweringError::ConflictingUrls);
|
||||||
}
|
}
|
||||||
url_source(url, subdirectory)?
|
url_source(url, subdirectory.map(PathBuf::from))?
|
||||||
}
|
}
|
||||||
Source::Path { path, editable } => {
|
Source::Path { path, editable } => {
|
||||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||||
return Err(LoweringError::ConflictingUrls);
|
return Err(LoweringError::ConflictingUrls);
|
||||||
}
|
}
|
||||||
path_source(
|
path_source(
|
||||||
path,
|
PathBuf::from(path),
|
||||||
origin,
|
origin,
|
||||||
project_dir,
|
project_dir,
|
||||||
workspace.install_path(),
|
workspace.install_path(),
|
||||||
|
@ -203,19 +203,25 @@ impl LoweredRequirement {
|
||||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||||
return Err(LoweringError::ConflictingUrls);
|
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 } => {
|
Source::Url { url, subdirectory } => {
|
||||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||||
return Err(LoweringError::ConflictingUrls);
|
return Err(LoweringError::ConflictingUrls);
|
||||||
}
|
}
|
||||||
url_source(url, subdirectory)?
|
url_source(url, subdirectory.map(PathBuf::from))?
|
||||||
}
|
}
|
||||||
Source::Path { path, editable } => {
|
Source::Path { path, editable } => {
|
||||||
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
if matches!(requirement.version_or_url, Some(VersionOrUrl::Url(_))) {
|
||||||
return Err(LoweringError::ConflictingUrls);
|
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::Registry { index } => registry_source(&requirement, index)?,
|
||||||
Source::Workspace { .. } => {
|
Source::Workspace { .. } => {
|
||||||
|
|
|
@ -22,6 +22,7 @@ encoding_rs_io = { workspace = true }
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
fs2 = { workspace = true }
|
fs2 = { workspace = true }
|
||||||
path-slash = { workspace = true }
|
path-slash = { workspace = true }
|
||||||
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true, optional = true }
|
serde = { workspace = true, optional = true }
|
||||||
tokio = { workspace = true, optional = true}
|
tokio = { workspace = true, optional = true}
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
|
@ -301,6 +301,17 @@ pub struct PortablePath<'a>(&'a Path);
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct PortablePathBuf(PathBuf);
|
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<'_> {
|
impl AsRef<Path> for PortablePath<'_> {
|
||||||
fn as_ref(&self) -> &Path {
|
fn as_ref(&self) -> &Path {
|
||||||
self.0
|
self.0
|
||||||
|
|
|
@ -16,7 +16,7 @@ workspace = true
|
||||||
pep440_rs = { workspace = true }
|
pep440_rs = { workspace = true }
|
||||||
pep508_rs = { workspace = true }
|
pep508_rs = { workspace = true }
|
||||||
pypi-types = { workspace = true }
|
pypi-types = { workspace = true }
|
||||||
uv-fs = { workspace = true, features = ["tokio"] }
|
uv-fs = { workspace = true, features = ["tokio", "schemars"] }
|
||||||
uv-git = { workspace = true }
|
uv-git = { workspace = true }
|
||||||
uv-macros = { workspace = true }
|
uv-macros = { workspace = true }
|
||||||
uv-normalize = { workspace = true }
|
uv-normalize = { workspace = true }
|
||||||
|
|
|
@ -17,7 +17,7 @@ use url::Url;
|
||||||
|
|
||||||
use pep440_rs::{Version, VersionSpecifiers};
|
use pep440_rs::{Version, VersionSpecifiers};
|
||||||
use pypi_types::{RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
|
use pypi_types::{RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
|
||||||
use uv_fs::relative_to;
|
use uv_fs::{relative_to, PortablePathBuf};
|
||||||
use uv_git::GitReference;
|
use uv_git::GitReference;
|
||||||
use uv_macros::OptionsMetadata;
|
use uv_macros::OptionsMetadata;
|
||||||
use uv_normalize::{ExtraName, PackageName};
|
use uv_normalize::{ExtraName, PackageName};
|
||||||
|
@ -413,7 +413,7 @@ pub enum Source {
|
||||||
/// The repository URL (without the `git+` prefix).
|
/// The repository URL (without the `git+` prefix).
|
||||||
git: Url,
|
git: Url,
|
||||||
/// The path to the directory with the `pyproject.toml`, if it's not in the archive root.
|
/// 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.
|
// Only one of the three may be used; we'll validate this later and emit a custom error.
|
||||||
rev: Option<String>,
|
rev: Option<String>,
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
|
@ -430,13 +430,13 @@ pub enum Source {
|
||||||
url: Url,
|
url: Url,
|
||||||
/// For source distributions, the path to the directory with the `pyproject.toml`, if it's
|
/// For source distributions, the path to the directory with the `pyproject.toml`, if it's
|
||||||
/// not in the archive root.
|
/// 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
|
/// 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
|
/// `.tar.gz` file), or source tree (i.e., a directory containing a `pyproject.toml` or
|
||||||
/// `setup.py` file in the root).
|
/// `setup.py` file in the root).
|
||||||
Path {
|
Path {
|
||||||
path: PathBuf,
|
path: PortablePathBuf,
|
||||||
/// `false` by default.
|
/// `false` by default.
|
||||||
editable: Option<bool>,
|
editable: Option<bool>,
|
||||||
},
|
},
|
||||||
|
@ -454,12 +454,12 @@ pub enum Source {
|
||||||
/// A catch-all variant used to emit precise error messages when deserializing.
|
/// A catch-all variant used to emit precise error messages when deserializing.
|
||||||
CatchAll {
|
CatchAll {
|
||||||
git: String,
|
git: String,
|
||||||
subdirectory: Option<PathBuf>,
|
subdirectory: Option<PortablePathBuf>,
|
||||||
rev: Option<String>,
|
rev: Option<String>,
|
||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
branch: Option<String>,
|
branch: Option<String>,
|
||||||
url: String,
|
url: String,
|
||||||
path: PathBuf,
|
path: PortablePathBuf,
|
||||||
index: String,
|
index: String,
|
||||||
workspace: bool,
|
workspace: bool,
|
||||||
},
|
},
|
||||||
|
@ -534,15 +534,17 @@ impl Source {
|
||||||
RequirementSource::Path { install_path, .. }
|
RequirementSource::Path { install_path, .. }
|
||||||
| RequirementSource::Directory { install_path, .. } => Source::Path {
|
| RequirementSource::Directory { install_path, .. } => Source::Path {
|
||||||
editable,
|
editable,
|
||||||
path: relative_to(&install_path, root)
|
path: PortablePathBuf::from(
|
||||||
.or_else(|_| std::path::absolute(&install_path))
|
relative_to(&install_path, root)
|
||||||
.map_err(SourceError::Absolute)?,
|
.or_else(|_| std::path::absolute(&install_path))
|
||||||
|
.map_err(SourceError::Absolute)?,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
RequirementSource::Url {
|
RequirementSource::Url {
|
||||||
subdirectory, url, ..
|
subdirectory, url, ..
|
||||||
} => Source::Url {
|
} => Source::Url {
|
||||||
url: url.to_url(),
|
url: url.to_url(),
|
||||||
subdirectory,
|
subdirectory: subdirectory.map(PortablePathBuf::from),
|
||||||
},
|
},
|
||||||
RequirementSource::Git {
|
RequirementSource::Git {
|
||||||
repository,
|
repository,
|
||||||
|
@ -566,7 +568,7 @@ impl Source {
|
||||||
tag,
|
tag,
|
||||||
branch,
|
branch,
|
||||||
git: repository,
|
git: repository,
|
||||||
subdirectory,
|
subdirectory: subdirectory.map(PortablePathBuf::from),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Source::Git {
|
Source::Git {
|
||||||
|
@ -574,7 +576,7 @@ impl Source {
|
||||||
tag,
|
tag,
|
||||||
branch,
|
branch,
|
||||||
git: repository,
|
git: repository,
|
||||||
subdirectory,
|
subdirectory: subdirectory.map(PortablePathBuf::from),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use assert_cmd::assert::OutputAssertExt;
|
||||||
use assert_fs::prelude::*;
|
use assert_fs::prelude::*;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use insta::assert_snapshot;
|
use insta::assert_snapshot;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use crate::common::{decode_token, packse_index_url};
|
use crate::common::{decode_token, packse_index_url};
|
||||||
use common::{uv_snapshot, TestContext};
|
use common::{uv_snapshot, TestContext};
|
||||||
|
@ -2019,7 +2020,7 @@ fn add_path() -> Result<()> {
|
||||||
build-backend = "setuptools.build_meta"
|
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#"
|
child.child("pyproject.toml").write_str(indoc! {r#"
|
||||||
[project]
|
[project]
|
||||||
name = "child"
|
name = "child"
|
||||||
|
@ -2032,7 +2033,7 @@ fn add_path() -> Result<()> {
|
||||||
build-backend = "setuptools.build_meta"
|
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
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
@ -2043,7 +2044,7 @@ fn add_path() -> Result<()> {
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Prepared 2 packages in [TIME]
|
Prepared 2 packages in [TIME]
|
||||||
Installed 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)
|
+ parent==0.1.0 (from file://[TEMP_DIR]/workspace)
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
@ -2067,7 +2068,7 @@ fn add_path() -> Result<()> {
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
child = { path = "child" }
|
child = { path = "packages/child" }
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -2089,7 +2090,7 @@ fn add_path() -> Result<()> {
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "child"
|
name = "child"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { directory = "child" }
|
source = { directory = "packages/child" }
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parent"
|
name = "parent"
|
||||||
|
@ -2100,7 +2101,7 @@ fn add_path() -> Result<()> {
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [{ name = "child", directory = "child" }]
|
requires-dist = [{ name = "child", directory = "packages/child" }]
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
34
uv.schema.json
generated
34
uv.schema.json
generated
|
@ -1230,9 +1230,13 @@
|
||||||
},
|
},
|
||||||
"subdirectory": {
|
"subdirectory": {
|
||||||
"description": "The path to the directory with the `pyproject.toml`, if it's not in the archive root.",
|
"description": "The path to the directory with the `pyproject.toml`, if it's not in the archive root.",
|
||||||
"type": [
|
"anyOf": [
|
||||||
"string",
|
{
|
||||||
"null"
|
"$ref": "#/definitions/String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tag": {
|
"tag": {
|
||||||
|
@ -1253,9 +1257,13 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"subdirectory": {
|
"subdirectory": {
|
||||||
"description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's not in the archive root.",
|
"description": "For source distributions, the path to the directory with the `pyproject.toml`, if it's not in the archive root.",
|
||||||
"type": [
|
"anyOf": [
|
||||||
"string",
|
{
|
||||||
"null"
|
"$ref": "#/definitions/String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
|
@ -1280,7 +1288,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/String"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
@ -1336,7 +1344,7 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"path": {
|
"path": {
|
||||||
"type": "string"
|
"$ref": "#/definitions/String"
|
||||||
},
|
},
|
||||||
"rev": {
|
"rev": {
|
||||||
"type": [
|
"type": [
|
||||||
|
@ -1345,9 +1353,13 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"subdirectory": {
|
"subdirectory": {
|
||||||
"type": [
|
"anyOf": [
|
||||||
"string",
|
{
|
||||||
"null"
|
"$ref": "#/definitions/String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"tag": {
|
"tag": {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue