diff --git a/Changelog.md b/Changelog.md index 7584264d5..6a24b37c7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,8 @@ ## Unreleased +* Refactor `ModuleWriter` to be easier to implement and use + ## [1.10.2] * Fix tagging for iOS x86_64 simulator wheels. diff --git a/src/binding_generator/cffi_binding.rs b/src/binding_generator/cffi_binding.rs index 1725a6f86..628ab9a17 100644 --- a/src/binding_generator/cffi_binding.rs +++ b/src/binding_generator/cffi_binding.rs @@ -20,6 +20,7 @@ use tracing::debug; use crate::ModuleWriter; use crate::PyProjectToml; use crate::Target; +use crate::module_writer::ModuleWriterExt; use crate::module_writer::write_python_part; use crate::project_layout::ProjectLayout; use crate::target::Os; @@ -82,8 +83,8 @@ pub fn write_cffi_module( .join(format!("{module_name}.pyi")); if type_stub.exists() { eprintln!("📖 Found type stub file at {module_name}.pyi"); - writer.add_file(module.join("__init__.pyi"), type_stub)?; - writer.add_bytes(module.join("py.typed"), None, b"")?; + writer.add_file(module.join("__init__.pyi"), type_stub, false)?; + writer.add_empty_file(module.join("py.typed"))?; } }; @@ -92,9 +93,15 @@ pub fn write_cffi_module( module.join("__init__.py"), None, cffi_init_file(&cffi_module_file_name).as_bytes(), + false, )?; - writer.add_bytes(module.join("ffi.py"), None, cffi_declarations.as_bytes())?; - writer.add_file_with_permissions(module.join(&cffi_module_file_name), artifact, 0o755)?; + writer.add_bytes( + module.join("ffi.py"), + None, + cffi_declarations.as_bytes(), + false, + )?; + writer.add_file(module.join(&cffi_module_file_name), artifact, true)?; } Ok(()) diff --git a/src/binding_generator/mod.rs b/src/binding_generator/mod.rs index 79a6bf218..dac25f7bc 100644 --- a/src/binding_generator/mod.rs +++ b/src/binding_generator/mod.rs @@ -5,6 +5,7 @@ use anyhow::Result; use crate::Metadata24; use crate::ModuleWriter; +use crate::module_writer::ModuleWriterExt; mod cffi_binding; mod pyo3_binding; @@ -31,6 +32,6 @@ pub fn write_bin( .join("scripts"); // We can't use add_file since we need to mark the file as executable - writer.add_file_with_permissions(data_dir.join(bin_name), artifact, 0o755)?; + writer.add_file(data_dir.join(bin_name), artifact, true)?; Ok(()) } diff --git a/src/binding_generator/pyo3_binding.rs b/src/binding_generator/pyo3_binding.rs index eecec5f87..c50b0a838 100644 --- a/src/binding_generator/pyo3_binding.rs +++ b/src/binding_generator/pyo3_binding.rs @@ -14,6 +14,7 @@ use crate::ModuleWriter; use crate::PyProjectToml; use crate::PythonInterpreter; use crate::Target; +use crate::module_writer::ModuleWriterExt; use crate::module_writer::write_python_part; use crate::project_layout::ProjectLayout; @@ -120,7 +121,7 @@ pub fn write_bindings_module( .rust_module .strip_prefix(python_module.parent().unwrap()) .unwrap(); - writer.add_file_with_permissions(relative.join(&so_filename), artifact, 0o755)?; + writer.add_file(relative.join(&so_filename), artifact, true)?; } } else { let module = PathBuf::from(ext_name); @@ -136,14 +137,15 @@ if hasattr({ext_name}, "__all__"): __all__ = {ext_name}.__all__"# ) .as_bytes(), + false, )?; let type_stub = project_layout.rust_module.join(format!("{ext_name}.pyi")); if type_stub.exists() { eprintln!("📖 Found type stub file at {ext_name}.pyi"); - writer.add_file(module.join("__init__.pyi"), type_stub)?; - writer.add_bytes(module.join("py.typed"), None, b"")?; + writer.add_file(module.join("__init__.pyi"), type_stub, false)?; + writer.add_empty_file(module.join("py.typed"))?; } - writer.add_file_with_permissions(module.join(so_filename), artifact, 0o755)?; + writer.add_file(module.join(so_filename), artifact, true)?; } Ok(()) diff --git a/src/binding_generator/uniffi_binding.rs b/src/binding_generator/uniffi_binding.rs index a9b6d079c..d6a7d20b8 100644 --- a/src/binding_generator/uniffi_binding.rs +++ b/src/binding_generator/uniffi_binding.rs @@ -14,6 +14,7 @@ use tracing::debug; use crate::ModuleWriter; use crate::PyProjectToml; +use crate::module_writer::ModuleWriterExt; use crate::module_writer::write_python_part; use crate::project_layout::ProjectLayout; use crate::target::Os; @@ -241,20 +242,21 @@ pub fn write_uniffi_module( .join(format!("{module_name}.pyi")); if type_stub.exists() { eprintln!("📖 Found type stub file at {module_name}.pyi"); - writer.add_file(module.join("__init__.pyi"), type_stub)?; - writer.add_bytes(module.join("py.typed"), None, b"")?; + writer.add_file(module.join("__init__.pyi"), type_stub, false)?; + writer.add_empty_file(module.join("py.typed"))?; } }; if !editable || project_layout.python_module.is_none() { - writer.add_bytes(module.join("__init__.py"), None, py_init.as_bytes())?; + writer.add_bytes(module.join("__init__.py"), None, py_init.as_bytes(), false)?; for binding in binding_names.iter() { writer.add_file( module.join(binding).with_extension("py"), binding_dir.join(binding).with_extension("py"), + false, )?; } - writer.add_file_with_permissions(module.join(cdylib), artifact, 0o755)?; + writer.add_file(module.join(cdylib), artifact, true)?; } Ok(()) diff --git a/src/binding_generator/wasm_binding.rs b/src/binding_generator/wasm_binding.rs index 1c25a1064..ecf1f6adc 100644 --- a/src/binding_generator/wasm_binding.rs +++ b/src/binding_generator/wasm_binding.rs @@ -51,10 +51,9 @@ if __name__ == '__main__': "# ); - // We can't use add_file since we want to mark the file as executable let launcher_path = Path::new(&metadata.get_distribution_escaped()) .join(bin_name.replace('-', "_")) .with_extension("py"); - writer.add_bytes_with_permissions(&launcher_path, None, entrypoint_script.as_bytes(), 0o755)?; + writer.add_bytes(&launcher_path, None, entrypoint_script.as_bytes(), true)?; Ok(()) } diff --git a/src/build_context.rs b/src/build_context.rs index 0b2067503..717dcc73c 100644 --- a/src/build_context.rs +++ b/src/build_context.rs @@ -7,14 +7,14 @@ use crate::bridge::Abi3Version; use crate::build_options::CargoOptions; use crate::compile::{CompileTarget, warn_missing_py_init}; use crate::compression::CompressionOptions; -use crate::module_writer::{WheelWriter, add_data, write_python_part}; +use crate::module_writer::{ModuleWriterExt, WheelWriter, add_data, write_python_part}; use crate::project_layout::ProjectLayout; use crate::source_distribution::source_distribution; use crate::target::validate_wheel_filename_for_pypi; use crate::target::{Arch, Os}; use crate::{ - BridgeModel, BuildArtifact, Metadata24, ModuleWriter, PyProjectToml, PythonInterpreter, Target, - compile, pyproject_toml::Format, + BridgeModel, BuildArtifact, Metadata24, PyProjectToml, PythonInterpreter, Target, compile, + pyproject_toml::Format, }; use anyhow::{Context, Result, anyhow, bail}; use cargo_metadata::CrateType; @@ -500,7 +500,7 @@ impl BuildContext { if !replacements.is_empty() { patchelf::replace_needed(path, &replacements[..])?; } - writer.add_file_with_permissions(libs_dir.join(new_soname), path, 0o755)?; + writer.add_file(libs_dir.join(new_soname), path, true)?; } eprintln!( diff --git a/src/module_writer/mod.rs b/src/module_writer/mod.rs index 6ee952a78..326cae891 100644 --- a/src/module_writer/mod.rs +++ b/src/module_writer/mod.rs @@ -1,5 +1,6 @@ use std::fmt::Write as _; -use std::io::Read as _; +use std::io; +use std::io::Read; #[cfg(unix)] use std::os::unix::fs::PermissionsExt as _; use std::path::Path; @@ -30,59 +31,49 @@ pub use wheel_writer::WheelWriter; /// Allows writing the module to a wheel or add it directly to the virtualenv pub trait ModuleWriter { - /// Adds a file with bytes as content in target relative to the module base path. + /// Adds a file with data as content in target relative to the module base path while setting + /// the appropriate unix permissions /// /// For generated files, `source` is `None`. fn add_bytes( &mut self, target: impl AsRef, source: Option<&Path>, - bytes: &[u8], - ) -> Result<()> { - debug!("Adding {}", target.as_ref().display()); - // 0o644 is the default from the zip crate - self.add_bytes_with_permissions(target, source, bytes, 0o644) - } - - /// Adds a file with bytes as content in target relative to the module base path while setting - /// the given unix permissions - /// - /// For generated files, `source` is `None`. - fn add_bytes_with_permissions( - &mut self, - target: impl AsRef, - source: Option<&Path>, - bytes: &[u8], - permissions: u32, + data: impl Read, + executable: bool, ) -> Result<()>; +} - /// Copies the source file to the target path relative to the module base path - fn add_file(&mut self, target: impl AsRef, source: impl AsRef) -> Result<()> { - self.add_file_with_permissions(target, source, 0o644) - } - +/// Extension trait with convenience methods for interacting with a [ModuleWriter] +pub trait ModuleWriterExt: ModuleWriter { /// Copies the source file the target path relative to the module base path while setting /// the given unix permissions - fn add_file_with_permissions( + fn add_file( &mut self, target: impl AsRef, source: impl AsRef, - permissions: u32, + executable: bool, ) -> Result<()> { let target = target.as_ref(); let source = source.as_ref(); debug!("Adding {} from {}", target.display(), source.display()); - let read_failed_context = format!("Failed to read {}", source.display()); - let mut file = File::open(source).context(read_failed_context.clone())?; - let mut buffer = Vec::new(); - file.read_to_end(&mut buffer).context(read_failed_context)?; - self.add_bytes_with_permissions(target, Some(source), &buffer, permissions) - .context(format!("Failed to write to {}", target.display()))?; + let file = + File::open(source).with_context(|| format!("Failed to open {}", source.display()))?; + self.add_bytes(target, Some(source), file, executable) + .with_context(|| format!("Failed to write to {}", target.display()))?; Ok(()) } + + /// Add an empty file to the target path + fn add_empty_file(&mut self, target: impl AsRef) -> Result<()> { + self.add_bytes(target, None, io::empty(), false) + } } +/// This blanket impl makes it impossible to overwrite the methods in [ModuleWriterExt] +impl ModuleWriterExt for T {} + /// Adds the python part of a mixed project to the writer, pub fn write_python_part( writer: &mut impl ModuleWriter, @@ -130,7 +121,7 @@ pub fn write_python_part( #[cfg(not(unix))] let mode = 0o644; writer - .add_file_with_permissions(relative, &absolute, mode) + .add_file(relative, &absolute, permission_is_executable(mode)) .context(format!("File to add file from {}", absolute.display()))?; } } @@ -155,7 +146,7 @@ pub fn write_python_part( let mode = source.metadata()?.permissions().mode(); #[cfg(not(unix))] let mode = 0o644; - writer.add_file_with_permissions(target, source, mode)?; + writer.add_file(target, source, permission_is_executable(mode))?; } } } @@ -210,13 +201,13 @@ pub fn add_data( // Copy the actual file contents, not the link, so that you can create a // data directory by joining different data sources let source = fs::read_link(file.path())?; - writer.add_file_with_permissions( + writer.add_file( relative, source.parent().unwrap(), - mode, + permission_is_executable(mode), )?; } else if file.path().is_file() { - writer.add_file_with_permissions(relative, file.path(), mode)?; + writer.add_file(relative, file.path(), permission_is_executable(mode))?; } else if file.path().is_dir() { // Intentionally ignored } else { @@ -244,12 +235,14 @@ pub fn write_dist_info( dist_info_dir.join("METADATA"), None, metadata24.to_file_contents()?.as_bytes(), + false, )?; writer.add_bytes( dist_info_dir.join("WHEEL"), None, wheel_file(tags)?.as_bytes(), + false, )?; let mut entry_points = String::new(); @@ -267,13 +260,18 @@ pub fn write_dist_info( dist_info_dir.join("entry_points.txt"), None, entry_points.as_bytes(), + false, )?; } if !metadata24.license_files.is_empty() { let license_files_dir = dist_info_dir.join("licenses"); for path in &metadata24.license_files { - writer.add_file(license_files_dir.join(path), pyproject_dir.join(path))?; + writer.add_file( + license_files_dir.join(path), + pyproject_dir.join(path), + false, + )?; } } @@ -328,6 +326,19 @@ fn entry_points_txt( }) } +#[inline] +fn permission_is_executable(mode: u32) -> bool { + (0o100 & mode) == 0o100 +} + +#[inline] +fn default_permission(executable: bool) -> u32 { + match executable { + true => 0o755, + false => 0o644, + } +} + #[cfg(test)] mod tests { use super::wheel_file; diff --git a/src/module_writer/path_writer.rs b/src/module_writer/path_writer.rs index 14726262f..2345781d1 100644 --- a/src/module_writer/path_writer.rs +++ b/src/module_writer/path_writer.rs @@ -1,4 +1,5 @@ -use std::io::Write as _; +use std::io; +use std::io::Read; use std::path::Path; use std::path::PathBuf; @@ -13,6 +14,7 @@ use fs_err::OpenOptions; use fs_err::os::unix::fs::OpenOptionsExt as _; use super::ModuleWriter; +use super::default_permission; use super::util::FileTracker; /// A [ModuleWriter] that adds the module somewhere in the filesystem, e.g. in a virtualenv @@ -32,12 +34,12 @@ impl PathWriter { } impl ModuleWriter for PathWriter { - fn add_bytes_with_permissions( + fn add_bytes( &mut self, target: impl AsRef, source: Option<&Path>, - bytes: &[u8], - #[cfg_attr(target_os = "windows", allow(unused_variables))] permissions: u32, + mut data: impl Read, + #[cfg_attr(target_os = "windows", allow(unused_variables))] executable: bool, ) -> Result<()> { let path = self.base_path.join(&target); @@ -59,7 +61,7 @@ impl ModuleWriter for PathWriter { .create(true) .write(true) .truncate(true) - .mode(permissions) + .mode(default_permission(executable)) .open(&path) } #[cfg(target_os = "windows")] @@ -67,10 +69,10 @@ impl ModuleWriter for PathWriter { File::create(&path) } } - .context(format!("Failed to create a file at {}", path.display()))?; + .with_context(|| format!("Failed to create a file at {}", path.display()))?; - file.write_all(bytes) - .context(format!("Failed to write to file at {}", path.display()))?; + io::copy(&mut data, &mut file) + .with_context(|| format!("Failed to write to file at {}", path.display()))?; Ok(()) } diff --git a/src/module_writer/sdist_writer.rs b/src/module_writer/sdist_writer.rs index 540232a6b..4aa4bdade 100644 --- a/src/module_writer/sdist_writer.rs +++ b/src/module_writer/sdist_writer.rs @@ -1,4 +1,5 @@ use std::io; +use std::io::Read; use std::path::Path; use std::path::PathBuf; @@ -13,6 +14,7 @@ use normpath::PathExt as _; use crate::Metadata24; use super::ModuleWriter; +use super::default_permission; use super::util::FileTracker; /// A deterministic, arbitrary, non-zero timestamp that use used as `mtime` @@ -35,12 +37,12 @@ pub struct SDistWriter { } impl ModuleWriter for SDistWriter { - fn add_bytes_with_permissions( + fn add_bytes( &mut self, target: impl AsRef, source: Option<&Path>, - bytes: &[u8], - permissions: u32, + mut data: impl Read, + executable: bool, ) -> Result<()> { if let Some(source) = source { if self.exclude(source) { @@ -58,19 +60,24 @@ impl ModuleWriter for SDistWriter { return Ok(()); } + let mut buffer = Vec::new(); + data.read_to_end(&mut buffer) + .with_context(|| format!("Failed to read data into buffer for {}", target.display()))?; + let mut header = tar::Header::new_gnu(); header.set_entry_type(tar::EntryType::Regular); - header.set_size(bytes.len() as u64); - header.set_mode(permissions); + header.set_size(buffer.len() as u64); + header.set_mode(default_permission(executable)); header.set_mtime(self.mtime); - header.set_cksum(); self.tar - .append_data(&mut header, target, bytes) - .context(format!( - "Failed to add {} bytes to sdist as {}", - bytes.len(), - target.display() - ))?; + .append_data(&mut header, target, buffer.as_slice()) + .with_context(|| { + format!( + "Failed to add {} bytes to sdist as {}", + buffer.len(), + target.display() + ) + })?; Ok(()) } } @@ -121,6 +128,7 @@ impl SDistWriter { #[cfg(test)] mod tests { + use std::io::empty; use std::path::Path; use ignore::overrides::Override; @@ -137,13 +145,12 @@ mod tests { // The mechanism is the same for wheel_writer fn sdist_writer_excludes() -> Result<(), Box> { let metadata = Metadata24::new("dummy".to_string(), Version::new([1, 0])); - let perm = 0o777; // No excludes let tmp_dir = TempDir::new()?; let mut writer = SDistWriter::new(&tmp_dir, &metadata, Override::empty(), None)?; assert!(writer.file_tracker.files.is_empty()); - writer.add_bytes_with_permissions("test", Some(Path::new("test")), &[], perm)?; + writer.add_bytes("test", Some(Path::new("test")), empty(), true)?; assert_eq!(writer.file_tracker.files.len(), 1); writer.finish()?; tmp_dir.close()?; @@ -154,12 +161,12 @@ mod tests { excludes.add("test*")?; excludes.add("!test2")?; let mut writer = SDistWriter::new(&tmp_dir, &metadata, excludes.build()?, None)?; - writer.add_bytes_with_permissions("test1", Some(Path::new("test1")), &[], perm)?; - writer.add_bytes_with_permissions("test3", Some(Path::new("test3")), &[], perm)?; + writer.add_bytes("test1", Some(Path::new("test1")), empty(), true)?; + writer.add_bytes("test3", Some(Path::new("test3")), empty(), true)?; assert!(writer.file_tracker.files.is_empty()); - writer.add_bytes_with_permissions("test2", Some(Path::new("test2")), &[], perm)?; + writer.add_bytes("test2", Some(Path::new("test2")), empty(), true)?; assert!(!writer.file_tracker.files.is_empty()); - writer.add_bytes_with_permissions("yes", Some(Path::new("yes")), &[], perm)?; + writer.add_bytes("yes", Some(Path::new("yes")), empty(), true)?; assert_eq!(writer.file_tracker.files.len(), 2); writer.finish()?; tmp_dir.close()?; diff --git a/src/module_writer/util.rs b/src/module_writer/util.rs index cd5dd7208..e13ef4c3b 100644 --- a/src/module_writer/util.rs +++ b/src/module_writer/util.rs @@ -1,10 +1,16 @@ use std::collections::HashMap; +use std::io::Error as IoError; +use std::io::Write; use std::path::Path; use std::path::PathBuf; use anyhow::Result; use anyhow::bail; +use base64::Engine as _; +use base64::engine::general_purpose::URL_SAFE_NO_PAD; use same_file::is_same_file; +use sha2::Digest as _; +use sha2::Sha256; /// Keep track of which files we added from where, so we can skip duplicate files and error when /// adding conflicting files. @@ -64,3 +70,44 @@ impl FileTracker { } } } + +pub(super) struct StreamSha256<'a, W> { + hasher: Sha256, + inner: &'a mut W, + bytes_written: usize, +} + +impl<'a, W> StreamSha256<'a, W> +where + W: Write, +{ + pub(super) fn new(inner: &'a mut W) -> Self { + Self { + hasher: Sha256::new(), + inner, + bytes_written: 0, + } + } + + pub(super) fn finalize(self) -> Result<(String, usize)> { + self.inner.flush()?; + let hash = URL_SAFE_NO_PAD.encode(self.hasher.finalize()); + Ok((hash, self.bytes_written)) + } +} + +impl<'a, W> Write for StreamSha256<'a, W> +where + W: Write, +{ + fn write(&mut self, buf: &[u8]) -> Result { + let written = self.inner.write(buf)?; + self.hasher.update(&buf[..written]); + self.bytes_written += written; + Ok(written) + } + + fn flush(&mut self) -> Result<(), IoError> { + self.inner.flush() + } +} diff --git a/src/module_writer/wheel_writer.rs b/src/module_writer/wheel_writer.rs index aa2fce74c..040baf37d 100644 --- a/src/module_writer/wheel_writer.rs +++ b/src/module_writer/wheel_writer.rs @@ -1,5 +1,6 @@ use std::env; use std::io; +use std::io::Read; use std::io::Write as _; use std::path::Path; use std::path::PathBuf; @@ -7,13 +8,9 @@ use std::path::PathBuf; use anyhow::Context as _; use anyhow::Result; use anyhow::anyhow; -use base64::Engine as _; -use base64::engine::general_purpose::URL_SAFE_NO_PAD; use fs_err::File; use ignore::overrides::Override; use normpath::PathExt as _; -use sha2::Digest as _; -use sha2::Sha256; use tracing::debug; use zip::DateTime; use zip::ZipWriter; @@ -23,7 +20,9 @@ use crate::Metadata24; use crate::project_layout::ProjectLayout; use super::ModuleWriter; +use super::default_permission; use super::util::FileTracker; +use super::util::StreamSha256; use super::write_dist_info; /// A glorified zip builder, mostly useful for writing the record file of a wheel @@ -38,12 +37,12 @@ pub struct WheelWriter { } impl ModuleWriter for WheelWriter { - fn add_bytes_with_permissions( + fn add_bytes( &mut self, target: impl AsRef, source: Option<&Path>, - bytes: &[u8], - permissions: u32, + mut data: impl Read, + executable: bool, ) -> Result<()> { let target = target.as_ref(); if self.exclude(target) { @@ -61,18 +60,20 @@ impl ModuleWriter for WheelWriter { let mut options = self .compression .get_file_options() - .unix_permissions(permissions); + .unix_permissions(default_permission(executable)); - let mtime = self.mtime().ok(); - if let Some(mtime) = mtime { + if let Ok(mtime) = self.mtime() { options = options.last_modified_time(mtime); } self.zip.start_file(target.clone(), options)?; - self.zip.write_all(bytes)?; + let mut writer = StreamSha256::new(&mut self.zip); + + io::copy(&mut data, &mut writer) + .with_context(|| format!("Failed to write to zip archive for {target}"))?; - let hash = URL_SAFE_NO_PAD.encode(Sha256::digest(bytes)); - self.record.push((target, hash, bytes.len())); + let (hash, length) = writer.finalize()?; + self.record.push((target, hash, length)); Ok(()) } @@ -136,7 +137,7 @@ impl WheelWriter { let name = metadata24.get_distribution_escaped(); let target = format!("{name}.pth"); debug!("Adding {} from {}", target, python_path); - self.add_bytes(target, None, python_path.as_bytes())?; + self.add_bytes(target, None, python_path.as_bytes(), false)?; } else { eprintln!( "⚠️ source code path contains non-Unicode sequences, editable installs may not work." diff --git a/src/source_distribution.rs b/src/source_distribution.rs index 1fd39ed55..e701d783f 100644 --- a/src/source_distribution.rs +++ b/src/source_distribution.rs @@ -1,4 +1,4 @@ -use crate::module_writer::ModuleWriter; +use crate::module_writer::{ModuleWriter, ModuleWriterExt}; use crate::pyproject_toml::SdistGenerator; use crate::{BuildContext, PyProjectToml, SDistWriter, pyproject_toml::Format}; use anyhow::{Context, Result, bail}; @@ -288,6 +288,7 @@ fn add_crate_to_source_distribution( cargo_toml_path, Some(manifest_path), document.to_string().as_bytes(), + false, )?; } else if !skip_cargo_toml { let mut document = parse_toml_file(manifest_path, "Cargo.toml")?; @@ -296,11 +297,12 @@ fn add_crate_to_source_distribution( cargo_toml_path, Some(manifest_path), document.to_string().as_bytes(), + false, )?; } for (target, source) in target_source { - writer.add_file(prefix.join(target), source)?; + writer.add_file(prefix.join(target), source, false)?; } Ok(()) @@ -430,7 +432,7 @@ fn add_git_tracked_files_to_sdist( .filter(|s| !s.is_empty()) .map(Path::new); for source in file_paths { - writer.add_file(prefix.join(source), pyproject_dir.join(source))?; + writer.add_file(prefix.join(source), pyproject_dir.join(source), false)?; } Ok(()) } @@ -518,6 +520,7 @@ fn add_cargo_package_files_to_sdist( .join(relative_main_crate_manifest_dir) .join(readme.file_name().unwrap()), &abs_readme, + false, )?; Some(abs_readme) } else { @@ -555,7 +558,7 @@ fn add_cargo_package_files_to_sdist( pyproject_root }; let relative_cargo_lock = cargo_lock_path.strip_prefix(project_root).unwrap(); - writer.add_file(root_dir.join(relative_cargo_lock), &cargo_lock_path)?; + writer.add_file(root_dir.join(relative_cargo_lock), &cargo_lock_path, false)?; if use_workspace_cargo_lock { let relative_workspace_cargo_toml = relative_cargo_lock.with_file_name("Cargo.toml"); let mut deps_to_keep = known_path_deps.clone(); @@ -585,6 +588,7 @@ fn add_cargo_package_files_to_sdist( root_dir.join(relative_workspace_cargo_toml), Some(workspace_manifest_path.as_std_path()), document.to_string().as_bytes(), + false, )?; } } else if cargo_lock_required { @@ -608,9 +612,10 @@ fn add_cargo_package_files_to_sdist( root_dir.join("pyproject.toml"), Some(pyproject_toml_path), rewritten_pyproject_toml.as_bytes(), + false, )?; } else { - writer.add_file(root_dir.join("pyproject.toml"), pyproject_toml_path)?; + writer.add_file(root_dir.join("pyproject.toml"), pyproject_toml_path, false)?; } // Add python source files @@ -645,7 +650,7 @@ fn add_cargo_package_files_to_sdist( } let target = root_dir.join(source.strip_prefix(pyproject_dir).unwrap()); if !source.is_dir() { - writer.add_file(target, &source)?; + writer.add_file(target, &source, false)?; } } } @@ -708,6 +713,7 @@ fn add_path_dep( .join(relative_path_dep_manifest_dir) .join(readme.file_name().unwrap()), &abs_readme, + false, )?; } // Handle different workspace manifest @@ -719,6 +725,7 @@ fn add_path_dep( writer.add_file( root_dir.join(relative_path_dep_workspace_manifest), &path_dep_workspace_manifest, + false, )?; } Ok(()) @@ -791,10 +798,10 @@ pub fn source_distribution( // Add readme, license if let Some(project) = pyproject.project.as_ref() { if let Some(pyproject_toml::ReadMe::RelativePath(readme)) = project.readme.as_ref() { - writer.add_file(root_dir.join(readme), pyproject_dir.join(readme))?; + writer.add_file(root_dir.join(readme), pyproject_dir.join(readme), false)?; } if let Some(pyproject_toml::License::File { file }) = project.license.as_ref() { - writer.add_file(root_dir.join(file), pyproject_dir.join(file))?; + writer.add_file(root_dir.join(file), pyproject_dir.join(file), false)?; } if let Some(license_files) = &project.license_files { // Safe on Windows and Unix as neither forward nor backwards slashes are escaped. @@ -819,6 +826,7 @@ pub fn source_distribution( writer.add_file( root_dir.join(&license_path), pyproject_dir.join(&license_path), + false, )?; } } @@ -834,7 +842,7 @@ pub fn source_distribution( { let target = root_dir.join(source.strip_prefix(pyproject_dir).unwrap()); if !source.is_dir() { - writer.add_file(target, source)?; + writer.add_file(target, source, false)?; } } Ok(()) @@ -853,6 +861,7 @@ pub fn source_distribution( root_dir.join("PKG-INFO"), None, metadata24.to_file_contents()?.as_bytes(), + false, )?; let source_distribution_path = writer.finish()?; diff --git a/tests/common/metadata.rs b/tests/common/metadata.rs index a8f1385bd..86d04676e 100644 --- a/tests/common/metadata.rs +++ b/tests/common/metadata.rs @@ -11,19 +11,20 @@ struct MockWriter { } impl ModuleWriter for MockWriter { - fn add_bytes_with_permissions( + fn add_bytes( &mut self, target: impl AsRef, _source: Option<&Path>, - bytes: &[u8], - _permissions: u32, + mut data: impl std::io::Read, + _executable: bool, ) -> Result<()> { - self.files - .push(target.as_ref().to_string_lossy().to_string()); - self.contents.insert( - target.as_ref().to_string_lossy().into(), - std::str::from_utf8(bytes).unwrap().to_string(), - ); + let target = target.as_ref().to_string_lossy().to_string(); + let mut buffer = String::new(); + data.read_to_string(&mut buffer)?; + + self.files.push(target.clone()); + self.contents.insert(target, buffer); + Ok(()) } }