Skip to content

Commit 26cb8f4

Browse files
committed
Rebase pylock paths for nested exports
1 parent c6b5815 commit 26cb8f4

3 files changed

Lines changed: 124 additions & 26 deletions

File tree

crates/uv-resolver/src/lock/export/pylock_toml.rs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::borrow::Cow;
22
use std::ffi::OsStr;
3-
use std::path::{Path, PathBuf};
3+
use std::path::Path;
44
use std::str::FromStr;
55
use std::sync::Arc;
66

@@ -688,6 +688,7 @@ impl<'lock> PylockToml {
688688
annotate: bool,
689689
editable: Option<&EditableMode>,
690690
install_options: &'lock InstallOptions,
691+
output_path: &Path,
691692
) -> Result<Self, PylockTomlErrorKind> {
692693
// Extract the packages from the lock file.
693694
let ExportableRequirements(mut nodes) = ExportableRequirements::from_lock(
@@ -797,12 +798,13 @@ impl<'lock> PylockToml {
797798
let directory = match &sdist {
798799
Some(SourceDist::Directory(sdist)) => Some(PylockTomlDirectory {
799800
path: PortablePathBuf::from(
800-
sdist
801-
.url
802-
.given()
803-
.map(PathBuf::from)
804-
.unwrap_or_else(|| sdist.install_path.to_path_buf())
805-
.into_boxed_path(),
801+
try_relative_to_if(
802+
&sdist.install_path,
803+
output_path,
804+
!sdist.url.was_given_absolute(),
805+
)
806+
.map(Box::<Path>::from)
807+
.unwrap_or_else(|_| sdist.install_path.clone()),
806808
),
807809
editable: match editable
808810
.and_then(|editable| editable.for_package(&package.id.name))
@@ -845,12 +847,13 @@ impl<'lock> PylockToml {
845847
Some(SourceDist::Path(sdist)) => Some(PylockTomlArchive {
846848
url: None,
847849
path: Some(PortablePathBuf::from(
848-
sdist
849-
.url
850-
.given()
851-
.map(PathBuf::from)
852-
.unwrap_or_else(|| sdist.install_path.to_path_buf())
853-
.into_boxed_path(),
850+
try_relative_to_if(
851+
&sdist.install_path,
852+
output_path,
853+
!sdist.url.was_given_absolute(),
854+
)
855+
.map(Box::<Path>::from)
856+
.unwrap_or_else(|_| sdist.install_path.clone()),
854857
)),
855858
size,
856859
upload_time: None,
@@ -859,13 +862,20 @@ impl<'lock> PylockToml {
859862
}),
860863
_ => match &package.id.source {
861864
Source::Registry(..) => None,
862-
Source::Path(source) => package.wheels.first().map(|wheel| PylockTomlArchive {
863-
url: None,
864-
path: Some(PortablePathBuf::from(source.clone())),
865-
size: wheel.size,
866-
upload_time: None,
867-
subdirectory: None,
868-
hashes: wheel.hash.clone().map(Hashes::from).unwrap_or_default(),
865+
Source::Path(source) => package.wheels.first().map(|wheel| {
866+
let install_path = target.install_path().join(source);
867+
let path =
868+
try_relative_to_if(&install_path, output_path, source.is_relative())
869+
.map(Box::<Path>::from)
870+
.unwrap_or_else(|_| install_path.into_boxed_path());
871+
PylockTomlArchive {
872+
url: None,
873+
path: Some(PortablePathBuf::from(path)),
874+
size: wheel.size,
875+
upload_time: None,
876+
subdirectory: None,
877+
hashes: wheel.hash.clone().map(Hashes::from).unwrap_or_default(),
878+
}
869879
}),
870880
Source::Git(..) => None,
871881
Source::Direct(source, ..) => {

crates/uv/src/commands/project/export.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
1919
use uv_preview::Preview;
2020
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
2121
use uv_requirements::is_pylock_toml;
22-
use uv_resolver::{PylockToml, RequirementsTxtExport, cyclonedx_json};
22+
use uv_resolver::{Installable, PylockToml, RequirementsTxtExport, cyclonedx_json};
2323
use uv_scripts::Pep723Script;
2424
use uv_settings::PythonInstallMirrors;
2525
use uv_warnings::warn_user;
@@ -436,6 +436,11 @@ pub(crate) async fn export(
436436
write!(writer, "{export}")?;
437437
}
438438
ExportFormat::PylockToml => {
439+
let output_path = output_file
440+
.as_deref()
441+
.and_then(Path::parent)
442+
.map(std::path::absolute)
443+
.transpose()?;
439444
let export = PylockToml::from_lock(
440445
&target,
441446
&prune,
@@ -444,6 +449,7 @@ pub(crate) async fn export(
444449
include_annotations,
445450
editable.as_ref(),
446451
&install_options,
452+
output_path.as_deref().unwrap_or(target.install_path()),
447453
)?;
448454

449455
if include_header {

crates/uv/tests/project/export.rs

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5188,26 +5188,54 @@ async fn pep_751_https_credentials() -> Result<()> {
51885188
#[test]
51895189
fn pep_751_relative_and_absolute_paths() -> Result<()> {
51905190
let context = uv_test::test_context!("3.12");
5191+
let links = context.workspace_root.join("test/links");
5192+
fs_err::copy(
5193+
links.join("ok-1.0.0-py3-none-any.whl"),
5194+
context.temp_dir.child("ok-1.0.0-py3-none-any.whl"),
5195+
)?;
5196+
5197+
let sdist = context.temp_dir.child("basic-package-src");
5198+
sdist.child("pyproject.toml").write_str(indoc! {r#"
5199+
[project]
5200+
name = "basic-package"
5201+
version = "0.1.0"
5202+
requires-python = ">=3.12"
5203+
5204+
[build-system]
5205+
requires = ["uv_build>=0.7,<10000"]
5206+
build-backend = "uv_build"
5207+
"#})?;
5208+
sdist.child("src/basic_package/__init__.py").touch()?;
5209+
context
5210+
.build()
5211+
.arg("--sdist")
5212+
.arg("--out-dir")
5213+
.arg(context.temp_dir.path())
5214+
.arg(sdist.path())
5215+
.assert()
5216+
.success();
51915217

51925218
let pyproject_toml = context.temp_dir.child("pyproject.toml");
51935219
pyproject_toml.write_str(&formatdoc! {r#"
51945220
[project]
51955221
name = "a"
51965222
version = "0.1.0"
51975223
requires-python = ">=3.12"
5198-
dependencies = ["b", "c"]
5224+
dependencies = ["b", "c", "ok", "basic-package"]
51995225
52005226
[tool.uv.sources]
52015227
b = {{ path = "b" }}
52025228
c = {{ path = '{}' }}
5229+
ok = {{ path = "ok-1.0.0-py3-none-any.whl" }}
5230+
basic-package = {{ path = "basic_package-0.1.0.tar.gz" }}
52035231
52045232
[build-system]
52055233
requires = ["uv_build>=0.7,<10000"]
52065234
build-backend = "uv_build"
52075235
"#,
52085236
context.temp_dir.join("c").display()
52095237
})?;
5210-
context.temp_dir.child("a/__init__.py").touch()?;
5238+
context.temp_dir.child("src/a/__init__.py").touch()?;
52115239
context
52125240
.temp_dir
52135241
.child("b/pyproject.toml")
@@ -5223,7 +5251,7 @@ fn pep_751_relative_and_absolute_paths() -> Result<()> {
52235251
requires = ["uv_build>=0.7,<10000"]
52245252
build-backend = "uv_build"
52255253
"#})?;
5226-
context.temp_dir.child("b/b/__init__.py").touch()?;
5254+
context.temp_dir.child("b/src/b/__init__.py").touch()?;
52275255
context
52285256
.temp_dir
52295257
.child("c/pyproject.toml")
@@ -5239,7 +5267,7 @@ fn pep_751_relative_and_absolute_paths() -> Result<()> {
52395267
requires = ["uv_build>=0.7,<10000"]
52405268
build-backend = "uv_build"
52415269
"#})?;
5242-
context.temp_dir.child("c/c/__init__.py").touch()?;
5270+
context.temp_dir.child("c/src/c/__init__.py").touch()?;
52435271

52445272
context.lock().assert().success();
52455273

@@ -5261,14 +5289,68 @@ fn pep_751_relative_and_absolute_paths() -> Result<()> {
52615289
name = "b"
52625290
directory = { path = "b", editable = false }
52635291
5292+
[[packages]]
5293+
name = "basic-package"
5294+
version = "0.1.0"
5295+
archive = { path = "basic_package-0.1.0.tar.gz", hashes = { sha256 = "0692ad37b19e73fd2f411fbc1590ed9c1f39a1d9f31e132ba07e4e3f945160af" } }
5296+
52645297
[[packages]]
52655298
name = "c"
52665299
directory = { path = "[TEMP_DIR]/c", editable = false }
52675300
5301+
[[packages]]
5302+
name = "ok"
5303+
version = "1.0.0"
5304+
archive = { path = "ok-1.0.0-py3-none-any.whl", hashes = { sha256 = "79f0b33e6ce1e09eaa1784c8eee275dfe84d215d9c65c652f07c18e85fdaac5f" } }
5305+
52685306
----- stderr -----
5269-
Resolved 3 packages in [TIME]
5307+
Resolved 5 packages in [TIME]
52705308
"#);
52715309

5310+
uv_snapshot!(context.filters(), context.export().arg("-o").arg("locks/pylock.toml"), @r#"
5311+
success: true
5312+
exit_code: 0
5313+
----- stdout -----
5314+
# This file was autogenerated by uv via the following command:
5315+
# uv export --cache-dir [CACHE_DIR] -o locks/pylock.toml
5316+
lock-version = "1.0"
5317+
created-by = "uv"
5318+
requires-python = ">=3.12"
5319+
5320+
[[packages]]
5321+
name = "a"
5322+
directory = { path = "../", editable = true }
5323+
5324+
[[packages]]
5325+
name = "b"
5326+
directory = { path = "../b", editable = false }
5327+
5328+
[[packages]]
5329+
name = "basic-package"
5330+
version = "0.1.0"
5331+
archive = { path = "../basic_package-0.1.0.tar.gz", hashes = { sha256 = "0692ad37b19e73fd2f411fbc1590ed9c1f39a1d9f31e132ba07e4e3f945160af" } }
5332+
5333+
[[packages]]
5334+
name = "c"
5335+
directory = { path = "[TEMP_DIR]/c", editable = false }
5336+
5337+
[[packages]]
5338+
name = "ok"
5339+
version = "1.0.0"
5340+
archive = { path = "../ok-1.0.0-py3-none-any.whl", hashes = { sha256 = "79f0b33e6ce1e09eaa1784c8eee275dfe84d215d9c65c652f07c18e85fdaac5f" } }
5341+
5342+
----- stderr -----
5343+
Resolved 5 packages in [TIME]
5344+
"#);
5345+
5346+
context
5347+
.pip_install()
5348+
.arg("--preview")
5349+
.arg("-r")
5350+
.arg("locks/pylock.toml")
5351+
.assert()
5352+
.success();
5353+
52725354
Ok(())
52735355
}
52745356

0 commit comments

Comments
 (0)