diff --git a/src/archive_source.rs b/src/archive_source.rs index 3dacd97a8..ab212bba6 100644 --- a/src/archive_source.rs +++ b/src/archive_source.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; #[derive(Debug, Clone)] pub(crate) enum ArchiveSource { @@ -13,11 +13,19 @@ impl ArchiveSource { Self::File(source) => source.executable, } } + + pub(crate) fn path(&self) -> Option<&Path> { + match self { + Self::Generated(source) => source.path.as_deref(), + Self::File(source) => Some(&source.path), + } + } } #[derive(Debug, Clone)] pub(crate) struct GeneratedSourceData { pub(crate) data: Vec, + pub(crate) path: Option, pub(crate) executable: bool, } diff --git a/src/binding_generator/bin_binding.rs b/src/binding_generator/bin_binding.rs index a5eda0ae9..b782b288e 100644 --- a/src/binding_generator/bin_binding.rs +++ b/src/binding_generator/bin_binding.rs @@ -60,6 +60,7 @@ impl<'m> BindingGenerator for BinBindingGenerator<'m> { .with_extension("py"), ArchiveSource::Generated(GeneratedSourceData { data: generate_wasm_launcher(&bin_name).into(), + path: None, executable: false, }), ); diff --git a/src/binding_generator/cffi_binding.rs b/src/binding_generator/cffi_binding.rs index 572247457..caa2e3a0c 100644 --- a/src/binding_generator/cffi_binding.rs +++ b/src/binding_generator/cffi_binding.rs @@ -57,13 +57,13 @@ impl BindingGenerator for CffiBindingGenerator { let artifact_target = base_path.join(&cffi_module_file_name); let mut additional_files = HashMap::new(); - let source = GeneratedSourceData { - data: cffi_init_file(&cffi_module_file_name).into(), - executable: false, - }; additional_files.insert( base_path.join("__init__.py"), - ArchiveSource::Generated(source), + ArchiveSource::Generated(GeneratedSourceData { + data: cffi_init_file(&cffi_module_file_name).into(), + path: None, + executable: false, + }), ); let declarations = generate_cffi_declarations( @@ -77,11 +77,14 @@ impl BindingGenerator for CffiBindingGenerator { })? .executable, )?; - let source = GeneratedSourceData { - data: declarations.into(), - executable: false, - }; - additional_files.insert(base_path.join("ffi.py"), ArchiveSource::Generated(source)); + additional_files.insert( + base_path.join("ffi.py"), + ArchiveSource::Generated(GeneratedSourceData { + data: declarations.into(), + path: None, + executable: false, + }), + ); Ok(GeneratorOutput { artifact_target, diff --git a/src/binding_generator/mod.rs b/src/binding_generator/mod.rs index 409323a7f..670c026ac 100644 --- a/src/binding_generator/mod.rs +++ b/src/binding_generator/mod.rs @@ -20,7 +20,7 @@ use crate::BuildContext; use crate::ModuleWriter; use crate::PythonInterpreter; use crate::archive_source::ArchiveSource; -use crate::module_writer::ModuleWriterExt; +use crate::module_writer::ModuleWriterInternal; #[cfg(unix)] use crate::module_writer::default_permission; use crate::module_writer::write_python_part; @@ -85,7 +85,7 @@ pub(crate) struct GeneratorOutput { /// /// Note: Writing the pth to the archive is handled by [BuildContext], not here pub fn generate_binding( - writer: &mut impl ModuleWriter, + writer: &mut impl ModuleWriterInternal, generator: &mut impl BindingGenerator, context: &BuildContext, interpreter: Option<&PythonInterpreter>, @@ -187,19 +187,7 @@ where if let Some(additional_files) = additional_files { for (target, source) in additional_files { debug!("Generating archive entry {}", target.display()); - match source { - ArchiveSource::Generated(source) => { - writer.add_bytes( - target, - None, - source.data.as_slice(), - source.executable, - )?; - } - ArchiveSource::File(source) => { - writer.add_file(target, source.path, source.executable)?; - } - } + writer.add_entry(target, source)?; } } } diff --git a/src/binding_generator/pyo3_binding.rs b/src/binding_generator/pyo3_binding.rs index 4cc3a1b06..73847d20e 100644 --- a/src/binding_generator/pyo3_binding.rs +++ b/src/binding_generator/pyo3_binding.rs @@ -91,6 +91,7 @@ if hasattr({ext_name}, "__all__"): __all__ = {ext_name}.__all__"# ) .into(), + path: None, executable: false, }; files.insert(module.join("__init__.py"), ArchiveSource::Generated(source)); diff --git a/src/binding_generator/uniffi_binding.rs b/src/binding_generator/uniffi_binding.rs index 5e9d19247..8cc10cd23 100644 --- a/src/binding_generator/uniffi_binding.rs +++ b/src/binding_generator/uniffi_binding.rs @@ -60,13 +60,13 @@ impl BindingGenerator for UniFfiBindingGenerator { .map(|name| format!("from .{name} import * # NOQA\n")) .collect::>() .join(""); - let source = GeneratedSourceData { - data: py_init.into(), - executable: false, - }; additional_files.insert( base_path.join("__init__.py"), - ArchiveSource::Generated(source), + ArchiveSource::Generated(GeneratedSourceData { + data: py_init.into(), + path: None, + executable: false, + }), ); for binding in binding_names { diff --git a/src/build_context.rs b/src/build_context.rs index 10af19a24..7f88bc8ba 100644 --- a/src/build_context.rs +++ b/src/build_context.rs @@ -8,14 +8,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::{ModuleWriterExt, WheelWriter, add_data}; +use crate::module_writer::{WheelWriter, add_data}; 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, PyProjectToml, PythonInterpreter, Target, compile, - pyproject_toml::Format, + BridgeModel, BuildArtifact, Metadata24, ModuleWriter, PyProjectToml, PythonInterpreter, Target, + compile, pyproject_toml::Format, }; use anyhow::{Context, Result, anyhow, bail}; use cargo_metadata::CrateType; diff --git a/tests/common/metadata.rs b/src/module_writer/mock_writer.rs similarity index 52% rename from tests/common/metadata.rs rename to src/module_writer/mock_writer.rs index 86d04676e..c0a08de3d 100644 --- a/tests/common/metadata.rs +++ b/src/module_writer/mock_writer.rs @@ -1,30 +1,48 @@ +use std::io::Read as _; +use std::path::Path; +use std::path::PathBuf; +use std::str; + use anyhow::Result; +use anyhow::bail; +use fs_err::File; +use indexmap::IndexMap; +use indexmap::map::Entry; use insta::assert_snapshot; -use maturin::{BuildOptions, CargoOptions, ModuleWriter, write_dist_info}; -use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use itertools::Itertools as _; + +use crate::BuildOptions; +use crate::CargoOptions; +use crate::archive_source::ArchiveSource; +use crate::write_dist_info; + +use super::ModuleWriterInternal; #[derive(Default)] struct MockWriter { - files: Vec, - contents: HashMap, + files: IndexMap>, } -impl ModuleWriter for MockWriter { - fn add_bytes( - &mut self, - target: impl AsRef, - _source: Option<&Path>, - mut data: impl std::io::Read, - _executable: bool, - ) -> Result<()> { - let target = target.as_ref().to_string_lossy().to_string(); - let mut buffer = String::new(); - data.read_to_string(&mut buffer)?; +impl super::private::Sealed for MockWriter {} - self.files.push(target.clone()); - self.contents.insert(target, buffer); +impl ModuleWriterInternal for MockWriter { + fn add_entry(&mut self, target: impl AsRef, source: ArchiveSource) -> Result<()> { + let target = target.as_ref().to_path_buf(); + let Entry::Vacant(entry) = self.files.entry(target.clone()) else { + bail!("Duplicate file {target:?} written to mock writer"); + }; + let buffer = match source { + ArchiveSource::Generated(source) => source.data, + ArchiveSource::File(source) => { + let mut file = File::options().read(true).open(source.path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + buffer + } + }; + + entry.insert(buffer); Ok(()) } } @@ -53,19 +71,16 @@ fn metadata_hello_world_pep639() { ) .unwrap(); - assert_snapshot!(writer.files.join("\n").replace("\\", "/"), @r" + assert_snapshot!(writer.files.keys().map(|p| p.to_string_lossy()).collect_vec().join("\n").replace("\\", "/"), @r" hello_world-0.1.0.dist-info/METADATA hello_world-0.1.0.dist-info/WHEEL hello_world-0.1.0.dist-info/licenses/LICENSE hello_world-0.1.0.dist-info/licenses/licenses/AUTHORS.txt "); - let metadata_path = Path::new("hello_world-0.1.0.dist-info") - .join("METADATA") - .to_str() - .unwrap() - .to_string(); + let metadata_path = Path::new("hello_world-0.1.0.dist-info").join("METADATA"); // Remove the README in the body of the email - let metadata = writer.contents[&metadata_path] + let metadata = str::from_utf8(&writer.files[&metadata_path]) + .unwrap() .split_once("\n\n") .unwrap() .0; diff --git a/src/module_writer/mod.rs b/src/module_writer/mod.rs index c3b612785..9c408cf00 100644 --- a/src/module_writer/mod.rs +++ b/src/module_writer/mod.rs @@ -1,6 +1,4 @@ use std::fmt::Write as _; -use std::io; -use std::io::Read; #[cfg(unix)] use std::os::unix::fs::PermissionsExt as _; use std::path::Path; @@ -9,7 +7,6 @@ use anyhow::Context as _; use anyhow::Result; use anyhow::bail; use fs_err as fs; -use fs_err::File; use ignore::WalkBuilder; use indexmap::IndexMap; use itertools::Itertools as _; @@ -17,9 +14,14 @@ use tracing::debug; use crate::Metadata24; use crate::PyProjectToml; +use crate::archive_source::ArchiveSource; +use crate::archive_source::FileSourceData; +use crate::archive_source::GeneratedSourceData; use crate::project_layout::ProjectLayout; use crate::pyproject_toml::Format; +#[cfg(test)] +mod mock_writer; mod path_writer; mod sdist_writer; mod util; @@ -29,8 +31,20 @@ pub use path_writer::PathWriter; pub use sdist_writer::SDistWriter; pub use wheel_writer::WheelWriter; +mod private { + pub trait Sealed {} +} + +const EMPTY: Vec = vec![]; + /// Allows writing the module to a wheel or add it directly to the virtualenv -pub trait ModuleWriter { +pub(crate) trait ModuleWriterInternal: private::Sealed { + /// Adds an entry into the archive + fn add_entry(&mut self, target: impl AsRef, source: ArchiveSource) -> Result<()>; +} + +/// Extension trait with convenience methods for interacting with a [ModuleWriterInternal] +pub trait ModuleWriter: private::Sealed { /// Adds a file with data as content in target relative to the module base path while setting /// the appropriate unix permissions /// @@ -39,15 +53,42 @@ pub trait ModuleWriter { &mut self, target: impl AsRef, source: Option<&Path>, - data: impl Read, + data: impl Into>, executable: bool, ) -> Result<()>; -} -/// 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( + &mut self, + target: impl AsRef, + source: impl AsRef, + executable: bool, + ) -> Result<()>; + + /// Add an empty file to the target path + fn add_empty_file(&mut self, target: impl AsRef) -> Result<()>; +} + +/// This blanket impl makes it impossible to overwrite the methods in [ModuleWriter] +impl ModuleWriter for T { + fn add_bytes( + &mut self, + target: impl AsRef, + source: Option<&Path>, + data: impl Into>, + executable: bool, + ) -> Result<()> { + self.add_entry( + target, + ArchiveSource::Generated(GeneratedSourceData { + data: data.into(), + path: source.map(ToOwned::to_owned), + executable, + }), + ) + } + fn add_file( &mut self, target: impl AsRef, @@ -58,22 +99,21 @@ pub trait ModuleWriterExt: ModuleWriter { let source = source.as_ref(); debug!("Adding {} from {}", target.display(), source.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(()) + self.add_entry( + target, + ArchiveSource::File(FileSourceData { + path: source.to_path_buf(), + executable, + }), + ) } - /// Add an empty file to the target path + #[inline] fn add_empty_file(&mut self, target: impl AsRef) -> Result<()> { - self.add_bytes(target, None, io::empty(), false) + self.add_bytes(target, None, 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, diff --git a/src/module_writer/path_writer.rs b/src/module_writer/path_writer.rs index b50609121..913002b94 100644 --- a/src/module_writer/path_writer.rs +++ b/src/module_writer/path_writer.rs @@ -1,19 +1,19 @@ use std::io; -use std::io::Read; use std::path::Path; use std::path::PathBuf; use anyhow::Context as _; use anyhow::Result; use fs_err as fs; -#[cfg(target_os = "windows")] use fs_err::File; #[cfg(unix)] use fs_err::OpenOptions; #[cfg(unix)] use fs_err::os::unix::fs::OpenOptionsExt as _; -use super::ModuleWriter; +use crate::archive_source::ArchiveSource; + +use super::ModuleWriterInternal; #[cfg(target_family = "unix")] use super::default_permission; use super::util::FileTracker; @@ -24,34 +24,19 @@ pub struct PathWriter { file_tracker: FileTracker, } -impl PathWriter { - /// Writes the module to the given path - pub fn from_path(path: impl AsRef) -> Self { - Self { - base_path: path.as_ref().to_path_buf(), - file_tracker: FileTracker::default(), - } - } -} - -impl ModuleWriter for PathWriter { - fn add_bytes( - &mut self, - target: impl AsRef, - source: Option<&Path>, - mut data: impl Read, - #[cfg_attr(target_os = "windows", allow(unused_variables))] executable: bool, - ) -> Result<()> { - let path = self.base_path.join(&target); +impl super::private::Sealed for PathWriter {} - if !self.file_tracker.add_file(target.as_ref(), source)? { +impl ModuleWriterInternal for PathWriter { + fn add_entry(&mut self, target: impl AsRef, source: ArchiveSource) -> Result<()> { + let target = self.base_path.join(target); + if !self.file_tracker.add_file(target.as_ref(), source.path())? { // Ignore duplicate files. return Ok(()); } - if let Some(parent_dir) = path.parent() { + if let Some(parent_dir) = target.parent() { fs::create_dir_all(parent_dir) - .with_context(|| format!("Failed to create directory {}", parent_dir.display()))?; + .with_context(|| format!("Failed to create directory {parent_dir:?}"))?; } // We only need to set the executable bit on unix @@ -62,19 +47,42 @@ impl ModuleWriter for PathWriter { .create(true) .write(true) .truncate(true) - .mode(default_permission(executable)) - .open(&path) + .mode(default_permission(source.executable())) + .open(&target) } #[cfg(target_os = "windows")] { - File::create(&path) + File::create(&target) } } - .with_context(|| format!("Failed to create a file at {}", path.display()))?; + .with_context(|| format!("Failed to create a file at {target:?}"))?; + + match source { + ArchiveSource::Generated(source) => io::copy(&mut source.data.as_slice(), &mut file), + ArchiveSource::File(source) => { + let mut source_file = + File::options() + .read(true) + .open(&source.path) + .with_context(|| { + format!("Failed to open file at {:?} for reading", source.path) + })?; - io::copy(&mut data, &mut file) - .with_context(|| format!("Failed to write to file at {}", path.display()))?; + io::copy(&mut source_file, &mut file) + } + } + .context("Failed to copy entry to target")?; Ok(()) } } + +impl PathWriter { + /// Writes the module to the given path + pub fn from_path(path: impl AsRef) -> Self { + Self { + base_path: path.as_ref().to_path_buf(), + file_tracker: FileTracker::default(), + } + } +} diff --git a/src/module_writer/sdist_writer.rs b/src/module_writer/sdist_writer.rs index b8245194b..501e87f26 100644 --- a/src/module_writer/sdist_writer.rs +++ b/src/module_writer/sdist_writer.rs @@ -8,13 +8,15 @@ use anyhow::Result; use flate2::Compression; use flate2::write::GzEncoder; use fs_err as fs; +use fs_err::File; use ignore::overrides::Override; use normpath::PathExt as _; use tracing::debug; use crate::Metadata24; +use crate::archive_source::ArchiveSource; -use super::ModuleWriter; +use super::ModuleWriterInternal; use super::default_permission; use super::util::FileTracker; @@ -38,15 +40,12 @@ pub struct SDistWriter { target_exclusion_warning_emitted: bool, } -impl ModuleWriter for SDistWriter { - fn add_bytes( - &mut self, - target: impl AsRef, - source: Option<&Path>, - mut data: impl Read, - executable: bool, - ) -> Result<()> { - if let Some(source) = source { +impl super::private::Sealed for SDistWriter {} + +impl ModuleWriterInternal for SDistWriter { + fn add_entry(&mut self, target: impl AsRef, source: ArchiveSource) -> Result<()> { + let source_path = source.path(); + if let Some(source) = source_path { if self.exclude(source) { return Ok(()); } @@ -66,26 +65,41 @@ impl ModuleWriter for SDistWriter { return Ok(()); } - if !self.file_tracker.add_file(target, source)? { + if !self.file_tracker.add_file(target, source_path)? { // Ignore duplicate files. 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(buffer.len() as u64); - header.set_mode(default_permission(executable)); + header.set_mode(default_permission(source.executable())); header.set_mtime(self.mtime); + + let data = match source { + ArchiveSource::Generated(source) => source.data, + ArchiveSource::File(source) => { + let mut file = + File::options() + .read(true) + .open(&source.path) + .with_context(|| { + format!("Failed to open file {:?} for reading", source.path) + })?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer) + .context("Failed to read file into buffer")?; + buffer + } + }; + + header.set_size(data.len() as u64); + self.tar - .append_data(&mut header, target, buffer.as_slice()) + .append_data(&mut header, target, data.as_slice()) .with_context(|| { format!( "Failed to add {} bytes to sdist as {}", - buffer.len(), + data.len(), target.display() ) })?; @@ -140,7 +154,6 @@ impl SDistWriter { #[cfg(test)] mod tests { - use std::io::empty; use std::path::Path; use ignore::overrides::Override; @@ -150,6 +163,7 @@ mod tests { use crate::Metadata24; use crate::ModuleWriter; + use crate::module_writer::EMPTY; use super::SDistWriter; @@ -162,7 +176,7 @@ mod tests { 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("test", Some(Path::new("test")), empty(), true)?; + writer.add_empty_file("test")?; assert_eq!(writer.file_tracker.files.len(), 1); writer.finish()?; tmp_dir.close()?; @@ -173,12 +187,12 @@ mod tests { excludes.add("test*")?; excludes.add("!test2")?; let mut writer = SDistWriter::new(&tmp_dir, &metadata, excludes.build()?, None)?; - writer.add_bytes("test1", Some(Path::new("test1")), empty(), true)?; - writer.add_bytes("test3", Some(Path::new("test3")), empty(), true)?; + 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("test2", Some(Path::new("test2")), empty(), true)?; + writer.add_bytes("test2", Some(Path::new("test2")), EMPTY, true)?; assert!(!writer.file_tracker.files.is_empty()); - writer.add_bytes("yes", Some(Path::new("yes")), empty(), true)?; + 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/wheel_writer.rs b/src/module_writer/wheel_writer.rs index 6ebca0824..b4e5e5f4f 100644 --- a/src/module_writer/wheel_writer.rs +++ b/src/module_writer/wheel_writer.rs @@ -1,6 +1,5 @@ use std::collections::BTreeMap; use std::io; -use std::io::Read; use std::io::Write as _; use std::path::Path; use std::path::PathBuf; @@ -15,9 +14,11 @@ use zip::ZipWriter; use zip::write::SimpleFileOptions; use crate::Metadata24; +use crate::archive_source::ArchiveSource; use crate::project_layout::ProjectLayout; -use super::ModuleWriter; +use super::ModuleWriter as _; +use super::ModuleWriterInternal; use super::default_permission; use super::util::FileTracker; use super::util::StreamSha256; @@ -33,15 +34,12 @@ pub struct WheelWriter { target_exclusion_warning_emitted: bool, } -impl ModuleWriter for WheelWriter { - fn add_bytes( - &mut self, - target: impl AsRef, - source: Option<&Path>, - mut data: impl Read, - executable: bool, - ) -> Result<()> { - if let Some(source) = source { +impl super::private::Sealed for WheelWriter {} + +impl ModuleWriterInternal for WheelWriter { + fn add_entry(&mut self, target: impl AsRef, source: ArchiveSource) -> Result<()> { + let source_path = source.path(); + if let Some(source) = source_path { if self.exclude(source) { return Ok(()); } @@ -61,19 +59,29 @@ impl ModuleWriter for WheelWriter { return Ok(()); } - if !self.file_tracker.add_file(target, source)? { + if !self.file_tracker.add_file(target, source_path)? { // Ignore duplicate files. return Ok(()); } let options = self .file_options - .unix_permissions(default_permission(executable)); + .unix_permissions(default_permission(source.executable())); self.zip.start_file_from_path(target, options)?; 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:?}"))?; + match source { + ArchiveSource::Generated(source) => io::copy(&mut source.data.as_slice(), &mut writer), + ArchiveSource::File(source) => { + let mut file = File::options() + .read(true) + .open(&source.path) + .with_context(|| format!("Failed to open file {:?}", source.path))?; + + io::copy(&mut file, &mut writer) + } + } + .with_context(|| format!("Failed to write to zip archive for {target:?}"))?; let (hash, length) = writer.finalize()?; self.record.insert(target.to_path_buf(), (hash, length)); diff --git a/src/source_distribution.rs b/src/source_distribution.rs index 58bb4554b..52367fe72 100644 --- a/src/source_distribution.rs +++ b/src/source_distribution.rs @@ -1,6 +1,5 @@ -use crate::module_writer::{ModuleWriter, ModuleWriterExt}; use crate::pyproject_toml::SdistGenerator; -use crate::{BuildContext, PyProjectToml, SDistWriter, pyproject_toml::Format}; +use crate::{BuildContext, ModuleWriter, PyProjectToml, SDistWriter, pyproject_toml::Format}; use anyhow::{Context, Result, bail}; use cargo_metadata::camino::Utf8Path; use cargo_metadata::{Metadata, MetadataCommand, PackageId}; diff --git a/tests/common/mod.rs b/tests/common/mod.rs index d9aa1a47c..a8a275919 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -10,7 +10,6 @@ use std::{env, io, str}; pub mod develop; pub mod errors; pub mod integration; -pub mod metadata; pub mod other; pub mod pep517;