Skip to content
Open
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
235 changes: 224 additions & 11 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ sha2 = { version = "0.11.0" }
smallvec = { version = "1.13.2" }
spdx = { version = "0.13.0" }
syn = { version = "3.0.0" }
tar-codec = { version = "0.0.9" }
target-lexicon = { version = "0.13.0" }
tempfile = { version = "3.14.0" }
terminal_size = { version = "0.4.2" }
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ uv-workspace = { workspace = true }

anyhow = { workspace = true }
async_zip = { workspace = true }
astral-tokio-tar = { workspace = true }
clap = { workspace = true }
criterion = { version = "5.0.1", default-features = false, package = "codspeed-criterion-compat", features = ["async_tokio"] }
flate2 = { workspace = true }
fs-err = { workspace = true }
futures = { workspace = true }
jiff = { workspace = true }
sha2 = { workspace = true }
tar-codec = { workspace = true }
tempfile = { workspace = true }
tokio = { workspace = true }
tokio-util = { workspace = true }
Expand Down
46 changes: 20 additions & 26 deletions crates/uv-bench/benches/uv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ extern crate uv_performance_memory_allocator;

use std::fmt::Write;
use std::hint::black_box;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use std::str::FromStr;

Expand All @@ -15,13 +14,14 @@ use flate2::write::GzEncoder;
use futures::executor::block_on;
use futures::io::AllowStdIo;
use sha2::{Digest, Sha256};
use tokio_util::compat::{FuturesAsyncReadCompatExt, FuturesAsyncWriteCompatExt};
use tar_codec::{ArchiveBuilder as _, EntryMetadata, TarEncoder};
use tokio_util::compat::FuturesAsyncWriteCompatExt;
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, RegistryClientBuilder};
use uv_distribution_filename::{SourceDistExtension, WheelFilename};
use uv_distribution_types::Requirement;
use uv_install_wheel::{InstallState, Layout, LinkMode};
use uv_preview::Preview;
use uv_preview::{MaybePreviewFeature, Preview, PreviewFeature};
use uv_pypi_types::Scheme;
use uv_python::PythonEnvironment;
use uv_resolver::Manifest;
Expand Down Expand Up @@ -77,9 +77,8 @@ fn create_many_files_wheel() -> tempfile::NamedTempFile {

fn create_many_files_sdist() -> tempfile::NamedTempFile {
let archive = tempfile::NamedTempFile::new().expect("Failed to create temporary archive");
let encoder = GzEncoder::new(archive.as_file(), flate2::Compression::default());
let mut writer =
tokio_tar::Builder::new_non_terminated(AllowStdIo::new(encoder).compat_write());
let mut encoder = GzEncoder::new(archive.as_file(), flate2::Compression::default());
let mut writer = TarEncoder::new(AllowStdIo::new(&mut encoder).compat_write()).builder();
for index in 0..MANY_FILES_SDIST_FILE_COUNT {
write_tar_entry(
&mut writer,
Expand All @@ -97,12 +96,8 @@ fn create_many_files_sdist() -> tempfile::NamedTempFile {
&format!("{MANY_FILES_SDIST_TOP_LEVEL}/pyproject.toml"),
b"[project]\nname = \"manyfiles\"\nversion = \"0.0.0\"\n",
);
let writer = block_on(writer.into_inner()).expect("Failed to finish tar archive");
writer
.into_inner()
.into_inner()
.finish()
.expect("Failed to finish gzip archive");
block_on(writer.finish()).expect("Failed to finish tar archive");
encoder.finish().expect("Failed to finish gzip archive");
archive
}

Expand All @@ -122,6 +117,11 @@ fn unpack_sdist_many_files(c: &mut Criterion<WallTime>) {
.build()
.expect("Failed to create Tokio runtime");

uv_preview::set(Preview::from_feature_names(&[MaybePreviewFeature::Known(
PreviewFeature::TarCodec,
)]))
.expect("Failed to configure tar backend preview features");

c.bench_function("unpack_sdist_many_files", |b| {
b.iter_batched(
|| {
Expand All @@ -147,6 +147,10 @@ fn unpack_sdist_many_files(c: &mut Criterion<WallTime>) {
BatchSize::PerIteration,
);
});

uv_preview::set(Preview::default())
.expect("Failed to restore default preview features after tar benchmark");
uv_preview::finalize().expect("Failed to finalize preview features");
}

fn unzip_wheel_many_files(c: &mut Criterion<WallTime>) {
Expand Down Expand Up @@ -255,22 +259,13 @@ fn write_zip_entry(writer: &mut ZipFileWriter<Vec<u8>>, path: &str, contents: &[
block_on(writer.write_entry_whole(entry, contents)).expect("Failed to write ZIP entry");
}

fn write_tar_entry<W: tokio::io::AsyncWrite + Unpin + Send>(
writer: &mut tokio_tar::Builder<W>,
fn write_tar_entry<W: tokio::io::AsyncWrite + Unpin>(
writer: &mut tar_codec::Builder<TarEncoder<W>>,
path: &str,
contents: &[u8],
) {
let mut header = tokio_tar::Header::new_gnu();
header.set_size(contents.len() as u64);
header.set_mode(0o644);
header.set_entry_type(tokio_tar::EntryType::Regular);
header.set_cksum();
block_on(writer.append_data(
&mut header,
path,
AllowStdIo::new(Cursor::new(contents)).compat(),
))
.expect("Failed to write tar entry");
block_on(writer.add_file(path, contents, EntryMetadata::default()))
.expect("Failed to write tar entry");
}

fn layout(root: &Path) -> Layout {
Expand Down Expand Up @@ -331,7 +326,6 @@ fn resolve_warm_airflow(c: &mut Criterion<WallTime>) {
fn criterion_with_preview() -> Criterion<WallTime> {
uv_preview::set(Preview::default())
.expect("Global preview features should not have been initialized already");
uv_preview::finalize().expect("Failed to finalize preview features");

Criterion::default()
}
Expand Down
1 change: 1 addition & 0 deletions crates/uv-build-backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
sha2 = { workspace = true }
spdx = { workspace = true }
tar-codec = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
Expand Down
65 changes: 41 additions & 24 deletions crates/uv-build-backend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,15 @@ pub enum Error {
VenvInSourceTree(PathBuf),
#[error("Inconsistent metadata between prepare and build step: {0}")]
InconsistentSteps(&'static str),
#[error("Failed to write to {}", _0.user_display())]
#[error("Failed to write tar archive to {}", _0.user_display())]
TarWrite(PathBuf, #[source] io::Error),
#[error("Failed to write tar archive to {}", _0.user_display())]
TarCodecWrite(
PathBuf,
#[source] tar_codec::BuildError<tar_codec::EncodeError>,
),
#[error("Failed to finish gzip stream for {}", _0.user_display())]
GzipWrite(PathBuf, #[source] io::Error),
}

impl uv_errors::Hint for Error {
Expand Down Expand Up @@ -475,15 +482,15 @@ mod tests {
use async_zip::base::read::mem::ZipFileReader;
use flate2::bufread::GzDecoder;
use fs_err::File;
use futures_lite::{StreamExt, future::block_on};
use futures_lite::future::block_on;
use indoc::indoc;
use insta::assert_snapshot;
use itertools::Itertools;
use regex::regex;
use sha2::Digest;
use std::io::BufReader;
use std::iter;
use std::pin::Pin;
use tar_codec::{Archive as _, TarArchive, extract::ExtractPolicy};
use tempfile::TempDir;
use uv_distribution_filename::{SourceDistFilename, WheelFilename};
use uv_errors::{ErrorWithHints, Hint};
Expand Down Expand Up @@ -603,22 +610,12 @@ mod tests {

fn sdist_contents(source_dist_path: &Path) -> Vec<String> {
let sdist_reader = BufReader::new(File::open(source_dist_path).unwrap());
let mut source_dist =
tokio_tar::Archive::new(SyncReader::new(GzDecoder::new(sdist_reader)));
let source_dist = TarArchive::new(SyncReader::new(GzDecoder::new(sdist_reader)));
let mut source_dist_contents = block_on(async {
let mut entries = source_dist.entries().unwrap();
let mut entries = Pin::new(&mut entries);
let mut members = source_dist.members();
let mut contents = Vec::new();
while let Some(entry) = entries.next().await {
contents.push(
entry
.unwrap()
.path()
.unwrap()
.to_str()
.unwrap()
.replace('\\', "/"),
);
while let Some(member) = members.next().await.unwrap() {
contents.push(member.metadata().path.replace('\\', "/"));
}
contents
});
Expand All @@ -628,12 +625,12 @@ mod tests {

fn unpack_sdist(source_dist_path: &Path, target: &Path) -> Result<(), Error> {
let sdist_reader = BufReader::new(File::open(source_dist_path)?);
let mut source_dist =
tokio_tar::Archive::new(SyncReader::new(GzDecoder::new(sdist_reader)));
let source_dist = TarArchive::new(SyncReader::new(GzDecoder::new(sdist_reader)));
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(source_dist.unpack(target))?;
.block_on(source_dist.extract_in(target, ExtractPolicy::default()))
.map_err(io::Error::other)?;
Ok(())
}

Expand Down Expand Up @@ -716,7 +713,25 @@ mod tests {
/// platform-independent deterministic builds.
#[test]
fn built_by_uv_building() {
let _preview = uv_preview::test::with_features(&[]);
built_by_uv_building_with_backend(
&[],
"1d9ce1ce63195fbee07314c0b595ba9e063670da8d10c252c351b21e94e3f508",
);
}

#[test]
fn built_by_uv_building_tar_codec() {
built_by_uv_building_with_backend(
&[PreviewFeature::TarCodec],
"88540014e8884fff1d6479c0c7315fcf497ba2a46f6db5c9f6e04f64a8620dcc",
);
}

fn built_by_uv_building_with_backend(
preview_features: &[PreviewFeature],
expected_source_dist_hash: &str,
) {
let _preview = uv_preview::test::with_features(preview_features);
let built_by_uv = Path::new("../../test/packages/built-by-uv");
let src = TempDir::new().unwrap();
for dir in [
Expand Down Expand Up @@ -786,9 +801,11 @@ mod tests {
"built_by_uv-0.1.0.tar.gz"
);
// Check that the source dist is reproducible across platforms.
assert_snapshot!(
hex::encode(sha2::Sha256::digest(fs_err::read(&source_dist_path).unwrap())),
@"1d9ce1ce63195fbee07314c0b595ba9e063670da8d10c252c351b21e94e3f508"
assert_eq!(
hex::encode(sha2::Sha256::digest(
fs_err::read(&source_dist_path).unwrap()
)),
expected_source_dist_hash
);
// Check both the files we report and the actual files
assert_snapshot!(format_file_list(build.source_dist_list_files, src.path()), @"
Expand Down
Loading
Loading