-
Notifications
You must be signed in to change notification settings - Fork 940
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
`lib.rs` has grown to large
- Loading branch information
Showing
4 changed files
with
1,025 additions
and
978 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
//! Dispatcher between writing to a directory, writing to a zip, writing to a `.tar.gz` and | ||
//! listing files. | ||
use crate::wheel::{write_hashed, RecordEntry}; | ||
use crate::Error; | ||
use flate2::write::GzEncoder; | ||
use flate2::Compression; | ||
use fs_err::File; | ||
use sha2::{Digest, Sha256}; | ||
use std::io::{BufReader, Cursor, Write}; | ||
use std::path::{Path, PathBuf}; | ||
use std::{io, mem}; | ||
use tar::{EntryType, Header}; | ||
use tracing::trace; | ||
use uv_fs::Simplified; | ||
use zip::{CompressionMethod, ZipWriter}; | ||
|
||
/// Dispatcher between writing to a directory, writing to a zip, writing to a `.tar.gz` and | ||
/// listing files. | ||
/// | ||
/// All paths are string types instead of path types since wheels are portable between platforms. | ||
/// | ||
/// Contract: You must call close before dropping to obtain a valid output (dropping is fine in the | ||
/// error case). | ||
pub(crate) trait FsWriteDispatcher { | ||
/// Add a file with the given content. | ||
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error>; | ||
|
||
/// Add a local file. | ||
fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error>; | ||
|
||
/// Create a directory. | ||
fn write_directory(&mut self, directory: &str) -> Result<(), Error>; | ||
|
||
/// Write the `RECORD` file and if applicable, the central directory. | ||
fn close(self, dist_info_dir: &str) -> Result<(), Error>; | ||
} | ||
|
||
/// Zip archive (wheel) writer. | ||
pub(crate) struct ZipDirectoryWriter { | ||
writer: ZipWriter<File>, | ||
compression: CompressionMethod, | ||
/// The entries in the `RECORD` file. | ||
record: Vec<RecordEntry>, | ||
} | ||
|
||
impl ZipDirectoryWriter { | ||
/// A wheel writer with deflate compression. | ||
pub(crate) fn new_wheel(file: File) -> Self { | ||
Self { | ||
writer: ZipWriter::new(file), | ||
compression: CompressionMethod::Deflated, | ||
record: Vec::new(), | ||
} | ||
} | ||
|
||
/// A wheel writer with no (stored) compression. | ||
/// | ||
/// Since editables are temporary, we save time be skipping compression and decompression. | ||
#[expect(dead_code)] | ||
fn new_editable(file: File) -> Self { | ||
Self { | ||
writer: ZipWriter::new(file), | ||
compression: CompressionMethod::Stored, | ||
record: Vec::new(), | ||
} | ||
} | ||
|
||
/// Add a file with the given name and return a writer for it. | ||
fn new_writer<'slf>(&'slf mut self, path: &str) -> Result<Box<dyn Write + 'slf>, Error> { | ||
// TODO(konsti): We need to preserve permissions, at least the executable bit. | ||
self.writer.start_file( | ||
path, | ||
zip::write::FileOptions::default().compression_method(self.compression), | ||
)?; | ||
Ok(Box::new(&mut self.writer)) | ||
} | ||
} | ||
|
||
impl FsWriteDispatcher for ZipDirectoryWriter { | ||
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> { | ||
trace!("Adding {}", path); | ||
let options = zip::write::FileOptions::default().compression_method(self.compression); | ||
self.writer.start_file(path, options)?; | ||
self.writer.write_all(bytes)?; | ||
|
||
let hash = format!("{:x}", Sha256::new().chain_update(bytes).finalize()); | ||
self.record.push(RecordEntry { | ||
path: path.to_string(), | ||
hash, | ||
size: bytes.len(), | ||
}); | ||
|
||
Ok(()) | ||
} | ||
|
||
fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> { | ||
trace!("Adding {} from {}", path, file.user_display()); | ||
let mut reader = BufReader::new(File::open(file)?); | ||
let mut writer = self.new_writer(path)?; | ||
let record = write_hashed(path, &mut reader, &mut writer)?; | ||
drop(writer); | ||
self.record.push(record); | ||
Ok(()) | ||
} | ||
|
||
fn write_directory(&mut self, directory: &str) -> Result<(), Error> { | ||
trace!("Adding directory {}", directory); | ||
let options = zip::write::FileOptions::default().compression_method(self.compression); | ||
Ok(self.writer.add_directory(directory, options)?) | ||
} | ||
|
||
/// Write the `RECORD` file and the central directory. | ||
fn close(mut self, dist_info_dir: &str) -> Result<(), Error> { | ||
let record_path = format!("{dist_info_dir}/RECORD"); | ||
trace!("Adding {record_path}"); | ||
let record = mem::take(&mut self.record); | ||
crate::wheel::write_record(&mut self.new_writer(&record_path)?, dist_info_dir, record)?; | ||
|
||
trace!("Adding central directory"); | ||
self.writer.finish()?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
pub(crate) struct FilesystemWriter { | ||
/// The virtualenv or metadata directory that add file paths are relative to. | ||
root: PathBuf, | ||
/// The entries in the `RECORD` file. | ||
record: Vec<RecordEntry>, | ||
} | ||
|
||
impl FilesystemWriter { | ||
pub(crate) fn new(root: &Path) -> Self { | ||
Self { | ||
root: root.to_owned(), | ||
record: Vec::new(), | ||
} | ||
} | ||
|
||
/// Add a file with the given name and return a writer for it. | ||
fn new_writer<'slf>(&'slf mut self, path: &str) -> Result<Box<dyn Write + 'slf>, Error> { | ||
trace!("Adding {}", path); | ||
Ok(Box::new(File::create(self.root.join(path))?)) | ||
} | ||
} | ||
|
||
/// File system writer. | ||
impl FsWriteDispatcher for FilesystemWriter { | ||
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> { | ||
trace!("Adding {}", path); | ||
let hash = format!("{:x}", Sha256::new().chain_update(bytes).finalize()); | ||
self.record.push(RecordEntry { | ||
path: path.to_string(), | ||
hash, | ||
size: bytes.len(), | ||
}); | ||
|
||
Ok(fs_err::write(self.root.join(path), bytes)?) | ||
} | ||
fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> { | ||
trace!("Adding {} from {}", path, file.user_display()); | ||
let mut reader = BufReader::new(File::open(file)?); | ||
let mut writer = self.new_writer(path)?; | ||
let record = write_hashed(path, &mut reader, &mut writer)?; | ||
drop(writer); | ||
self.record.push(record); | ||
Ok(()) | ||
} | ||
|
||
fn write_directory(&mut self, directory: &str) -> Result<(), Error> { | ||
trace!("Adding directory {}", directory); | ||
Ok(fs_err::create_dir(self.root.join(directory))?) | ||
} | ||
|
||
/// Write the `RECORD` file. | ||
fn close(mut self, dist_info_dir: &str) -> Result<(), Error> { | ||
let record = mem::take(&mut self.record); | ||
crate::wheel::write_record( | ||
&mut self.new_writer(&format!("{dist_info_dir}/RECORD"))?, | ||
dist_info_dir, | ||
record, | ||
)?; | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
pub(crate) struct TarGzWriter { | ||
path: PathBuf, | ||
tar: tar::Builder<GzEncoder<File>>, | ||
} | ||
|
||
impl TarGzWriter { | ||
pub(crate) fn new(path: impl Into<PathBuf>) -> Result<Self, Error> { | ||
let path = path.into(); | ||
let file = File::create(&path)?; | ||
let enc = GzEncoder::new(file, Compression::default()); | ||
let tar = tar::Builder::new(enc); | ||
Ok(Self { path, tar }) | ||
} | ||
} | ||
|
||
impl FsWriteDispatcher for TarGzWriter { | ||
fn write_bytes(&mut self, path: &str, bytes: &[u8]) -> Result<(), Error> { | ||
let mut header = Header::new_gnu(); | ||
header.set_size(bytes.len() as u64); | ||
// Reasonable default to avoid 0o000 permissions, the user's umask will be applied on | ||
// unpacking. | ||
header.set_mode(0o644); | ||
header.set_cksum(); | ||
self.tar | ||
.append_data(&mut header, path, Cursor::new(bytes)) | ||
.map_err(|err| Error::TarWrite(self.path.clone(), err))?; | ||
Ok(()) | ||
} | ||
|
||
fn write_file(&mut self, path: &str, file: &Path) -> Result<(), Error> { | ||
let metadata = fs_err::metadata(file)?; | ||
let mut header = Header::new_gnu(); | ||
#[cfg(unix)] | ||
{ | ||
// Preserve for example an executable bit. | ||
header.set_mode(std::os::unix::fs::MetadataExt::mode(&metadata)); | ||
} | ||
#[cfg(not(unix))] | ||
{ | ||
// Reasonable default to avoid 0o000 permissions, the user's umask will be applied on | ||
// unpacking. | ||
header.set_mode(0o644); | ||
} | ||
header.set_size(metadata.len()); | ||
header.set_cksum(); | ||
let reader = BufReader::new(File::open(file)?); | ||
self.tar | ||
.append_data(&mut header, path, reader) | ||
.map_err(|err| Error::TarWrite(self.path.clone(), err))?; | ||
Ok(()) | ||
} | ||
|
||
fn write_directory(&mut self, directory: &str) -> Result<(), Error> { | ||
let mut header = Header::new_gnu(); | ||
// Directories are always executable, which means they can be listed. | ||
header.set_mode(0o755); | ||
header.set_entry_type(EntryType::Directory); | ||
header | ||
.set_path(directory) | ||
.map_err(|err| Error::TarWrite(self.path.clone(), err))?; | ||
header.set_size(0); | ||
header.set_cksum(); | ||
self.tar | ||
.append(&header, io::empty()) | ||
.map_err(|err| Error::TarWrite(self.path.clone(), err))?; | ||
Ok(()) | ||
} | ||
|
||
fn close(mut self, _dist_info_dir: &str) -> Result<(), Error> { | ||
self.tar | ||
.finish() | ||
.map_err(|err| Error::TarWrite(self.path.clone(), err))?; | ||
Ok(()) | ||
} | ||
} |
Oops, something went wrong.