Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions crates/uv-build-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ pub use settings::{BuildBackendSettings, WheelDataIncludes};
pub use source_dist::{build_source_dist, list_source_dist};
pub use wheel::{build_editable, build_wheel, list_wheel, metadata};

use std::fs::FileType;
use std::io;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use thiserror::Error;
use tracing::debug;
use walkdir::DirEntry;

use uv_fs::Simplified;
use uv_globfilter::PortableGlobError;
Expand Down Expand Up @@ -54,8 +54,6 @@ pub enum Error {
#[source]
err: walkdir::Error,
},
#[error("Unsupported file type {:?}: `{}`", _1, _0.user_display())]
UnsupportedFileType(PathBuf, FileType),
#[error("Failed to write wheel zip archive")]
Zip(#[from] zip::result::ZipError),
#[error("Failed to write RECORD file")]
Expand Down Expand Up @@ -86,6 +84,16 @@ trait DirectoryWriter {
/// Files added through the method are considered generated when listing included files.
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error>;

/// Add the file or directory to the path.
fn write_dir_entry(&mut self, entry: &DirEntry, target_path: &str) -> Result<(), Error> {
if entry.file_type().is_dir() {
self.write_directory(target_path)?;
} else {
self.write_file(target_path, entry.path())?;
}
Ok(())
}

/// Add a local file.
fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error>;

Expand Down
30 changes: 7 additions & 23 deletions crates/uv-build-backend/src/source_dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,32 +250,16 @@ fn write_source_dist(
.expect("walkdir starts with root");

if !include_matcher.match_path(relative) || exclude_matcher.is_match(relative) {
trace!("Excluding: `{}`", relative.user_display());
trace!("Excluding from sdist: `{}`", relative.user_display());
continue;
}

debug!("Including {}", relative.user_display());
if entry.file_type().is_dir() {
writer.write_directory(
&Path::new(&top_level)
.join(relative)
.portable_display()
.to_string(),
)?;
} else if entry.file_type().is_file() {
writer.write_file(
&Path::new(&top_level)
.join(relative)
.portable_display()
.to_string(),
entry.path(),
)?;
} else {
return Err(Error::UnsupportedFileType(
relative.to_path_buf(),
entry.file_type(),
));
}
let entry_path = Path::new(&top_level)
.join(relative)
.portable_display()
.to_string();
debug!("Adding to sdist: {}", relative.user_display());
writer.write_dir_entry(&entry, &entry_path)?;
}
debug!("Visited {files_visited} files for source dist build");

Expand Down
36 changes: 7 additions & 29 deletions crates/uv-build-backend/src/wheel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,29 +164,18 @@ fn write_wheel(
.path()
.strip_prefix(source_tree)
.expect("walkdir starts with root");
let wheel_path = entry
let entry_path = entry
.path()
.strip_prefix(&src_root)
.expect("walkdir starts with root");
if exclude_matcher.is_match(match_path) {
trace!("Excluding from module: `{}`", match_path.user_display());
continue;
}
let wheel_path = wheel_path.portable_display().to_string();

debug!("Adding to wheel: `{wheel_path}`");

if entry.file_type().is_dir() {
wheel_writer.write_directory(&wheel_path)?;
} else if entry.file_type().is_file() {
wheel_writer.write_file(&wheel_path, entry.path())?;
} else {
// TODO(konsti): We may want to support symlinks, there is support for installing them.
return Err(Error::UnsupportedFileType(
entry.path().to_path_buf(),
entry.file_type(),
));
}
let entry_path = entry_path.portable_display().to_string();
debug!("Adding to wheel: {entry_path}");
wheel_writer.write_dir_entry(&entry, &entry_path)?;
}
debug!("Visited {files_visited} files for wheel build");

Expand Down Expand Up @@ -519,23 +508,12 @@ fn wheel_subdir_from_globs(
continue;
}

let relative_licenses = Path::new(target)
let license_path = Path::new(target)
.join(relative)
.portable_display()
.to_string();

if entry.file_type().is_dir() {
wheel_writer.write_directory(&relative_licenses)?;
} else if entry.file_type().is_file() {
debug!("Adding {} file: `{}`", globs_field, relative.user_display());
wheel_writer.write_file(&relative_licenses, entry.path())?;
} else {
// TODO(konsti): We may want to support symlinks, there is support for installing them.
return Err(Error::UnsupportedFileType(
entry.path().to_path_buf(),
entry.file_type(),
));
}
debug!("Adding for {}: `{}`", globs_field, relative.user_display());
wheel_writer.write_dir_entry(&entry, &license_path)?;
}
Ok(())
}
Expand Down
90 changes: 90 additions & 0 deletions crates/uv/tests/it/build_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -768,3 +768,93 @@ fn complex_namespace_packages() -> Result<()> {
);
Ok(())
}

/// Test that a symlinked file (here: license) gets included.
#[test]
#[cfg(unix)]
fn symlinked_file() -> Result<()> {
let context = TestContext::new("3.12");

let project = context.temp_dir.child("project");
context
.init()
.arg("--preview")
.arg("--build-backend")
.arg("uv")
.arg(project.path())
.assert()
.success();

project.child("pyproject.toml").write_str(indoc! {r#"
[project]
name = "project"
version = "1.0.0"
license-files = ["LICENSE"]

[build-system]
requires = ["uv_build>=0.5.15,<10000"]
build-backend = "uv_build"
"#
})?;

let license_file = context.temp_dir.child("LICENSE");
let license_symlink = project.child("LICENSE");

let license_text = "Project license";
license_file.write_str(license_text)?;
fs_err::os::unix::fs::symlink(license_file.path(), license_symlink.path())?;

uv_snapshot!(context
.build_backend()
.arg("build-sdist")
.arg(context.temp_dir.path())
.current_dir(project.path()), @r"
success: true
exit_code: 0
----- stdout -----
project-1.0.0.tar.gz

----- stderr -----
");

uv_snapshot!(context
.build_backend()
.arg("build-wheel")
.arg(context.temp_dir.path())
.current_dir(project.path()), @r"
success: true
exit_code: 0
----- stdout -----
project-1.0.0-py3-none-any.whl

----- stderr -----
");

uv_snapshot!(context.filters(), context.pip_install().arg("project-1.0.0-py3-none-any.whl"), @r"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 1 package in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ project==1.0.0 (from file://[TEMP_DIR]/project-1.0.0-py3-none-any.whl)
");

// Check that we included the actual license text and not a broken symlink.
let installed_license = context
.site_packages()
.join("project-1.0.0.dist-info")
.join("licenses")
.join("LICENSE");
assert!(
fs_err::symlink_metadata(&installed_license)?
.file_type()
.is_file()
);
let license = fs_err::read_to_string(&installed_license)?;
assert_eq!(license, license_text);

Ok(())
}
Loading