From 068fbdf6360e02c5320fcf536a499125e084ad6f Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 5 Sep 2024 11:44:28 -0400 Subject: [PATCH] WIP --- constraints.txt | 1 + crates/uv/src/commands/build.rs | 28 ++++-- crates/uv/tests/build.rs | 166 ++++++++++++++++++++++++++++++++ req.txt | 1 + 4 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 constraints.txt create mode 100644 req.txt diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 000000000000..cb77b477792f --- /dev/null +++ b/constraints.txt @@ -0,0 +1 @@ +anyio==4.0.0 --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f diff --git a/crates/uv/src/commands/build.rs b/crates/uv/src/commands/build.rs index 8558c77f1bfb..00d80388f970 100644 --- a/crates/uv/src/commands/build.rs +++ b/crates/uv/src/commands/build.rs @@ -13,7 +13,7 @@ use std::path::{Path, PathBuf}; use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; -use uv_configuration::{BuildKind, BuildOutput, Concurrency, Constraints}; +use uv_configuration::{BuildKind, BuildOutput, Concurrency, Constraints, HashCheckingMode}; use uv_dispatch::BuildDispatch; use uv_fs::{Simplified, CWD}; use uv_normalize::PackageName; @@ -225,6 +225,8 @@ async fn build_impl( .await? .into_interpreter(); + + // Add all authenticated sources to the cache. for url in index_locations.urls() { store_credentials_from_url(url); @@ -234,6 +236,24 @@ async fn build_impl( let build_constraints = operations::read_constraints(build_constraints, &client_builder).await?; + // Collect the set of required hashes. + // Enforce (but never require) the build constraints, if `--require-hashes` or `--verify-hashes` + // is provided. _Requiring_ hashes would be too strict, and would break with pip. + let build_hasher = HashStrategy::from_requirements( + std::iter::empty(), + build_constraints + .iter() + .map(|entry| (&entry.requirement, entry.hashes.as_slice())), + Some(&interpreter.resolver_markers()), + HashCheckingMode::Verify, + )?; + let build_constraints = Constraints::from_requirements( + build_constraints + .iter() + .map(|constraint| constraint.requirement.clone()), + ); + + // Initialize the registry client. let client = RegistryClientBuilder::new(cache.clone()) .native_tls(native_tls) @@ -258,15 +278,11 @@ async fn build_impl( BuildIsolation::SharedPackage(&environment, no_build_isolation_package) }; - // TODO(charlie): These are all default values. We should consider whether we want to make them - // optional on the downstream APIs. - let hasher = HashStrategy::None; - // Resolve the flat indexes from `--find-links`. let flat_index = { let client = FlatIndexClient::new(&client, cache); let entries = client.fetch(index_locations.flat_index()).await?; - FlatIndex::from_entries(entries, None, &hasher, build_options) + FlatIndex::from_entries(entries, None, &build_hasher, build_options) }; // Initialize any shared state. diff --git a/crates/uv/tests/build.rs b/crates/uv/tests/build.rs index 5bba20ba2ace..9bf4884bcbbf 100644 --- a/crates/uv/tests/build.rs +++ b/crates/uv/tests/build.rs @@ -1206,3 +1206,169 @@ fn build_constraints() -> Result<()> { Ok(()) } + + +#[test] +fn sha() -> Result<()> { + let context = TestContext::new("3.8"); + let filters = context + .filters() + .into_iter() + .chain([ + (r"exit code: 1", "exit status: 1"), + (r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"), + (r"\\\.", ""), + ]) + .collect::>(); + + let project = context.temp_dir.child("project"); + + let pyproject_toml = project.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.8" + dependencies = ["anyio==3.7.0"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + project.child("src").child("__init__.py").touch()?; + project.child("README").touch()?; + + // Reject an incorrect hash. + let constraints = project.child("constraints.txt"); + constraints.write_str("setuptools==68.2.2 --hash=sha256:a248cb506794bececcddeddb1678bc722f9cfcacf02f98f7c0af6b9ed893caf2")?; + + + uv_snapshot!(&filters, context.build().arg("--build-constraint").arg("constraints.txt").current_dir(&project), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + error: Failed to install requirements from `build-system.requires` (install) + Caused by: Failed to prepare distributions + Caused by: Failed to fetch wheel: setuptools==68.2.2 + Caused by: Hash mismatch for `setuptools==68.2.2` + + Expected: + sha256:a248cb506794bececcddeddb1678bc722f9cfcacf02f98f7c0af6b9ed893caf2 + + Computed: + sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a + "###); + + project + .child("dist") + .child("project-0.1.0.tar.gz") + .assert(predicate::path::missing()); + project + .child("dist") + .child("project-0.1.0-py3-none-any.whl") + .assert(predicate::path::missing()); + + // Accept a correct hash. + let constraints = project.child("constraints.txt"); + constraints.write_str("setuptools==68.2.2 --hash=sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a")?; + + + uv_snapshot!(&filters, context.build().arg("--build-constraint").arg("constraints.txt").current_dir(&project), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution... + running egg_info + creating src/project.egg-info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + writing manifest file 'src/project.egg-info/SOURCES.txt' + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running sdist + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running check + creating project-0.1.0 + creating project-0.1.0/src + creating project-0.1.0/src/project.egg-info + copying files to project-0.1.0... + copying README -> project-0.1.0 + copying pyproject.toml -> project-0.1.0 + copying src/__init__.py -> project-0.1.0/src + copying src/project.egg-info/PKG-INFO -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/SOURCES.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/dependency_links.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/requires.txt -> project-0.1.0/src/project.egg-info + copying src/project.egg-info/top_level.txt -> project-0.1.0/src/project.egg-info + Writing project-0.1.0/setup.cfg + Creating tar archive + removing 'project-0.1.0' (and everything under it) + Building wheel from source distribution... + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + running bdist_wheel + running build + running build_py + creating build + creating build/lib + copying src/__init__.py -> build/lib + running egg_info + writing src/project.egg-info/PKG-INFO + writing dependency_links to src/project.egg-info/dependency_links.txt + writing requirements to src/project.egg-info/requires.txt + writing top-level names to src/project.egg-info/top_level.txt + reading manifest file 'src/project.egg-info/SOURCES.txt' + writing manifest file 'src/project.egg-info/SOURCES.txt' + installing to build/bdist.linux-x86_64/wheel + running install + running install_lib + creating build/bdist.linux-x86_64 + creating build/bdist.linux-x86_64/wheel + copying build/lib/__init__.py -> build/bdist.linux-x86_64/wheel + running install_egg_info + Copying src/project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.8.egg-info + running install_scripts + creating build/bdist.linux-x86_64/wheel/project-0.1.0.dist-info/WHEEL + creating '[TEMP_DIR]/project/dist/[TMP]/wheel' to it + adding '__init__.py' + adding 'project-0.1.0.dist-info/METADATA' + adding 'project-0.1.0.dist-info/WHEEL' + adding 'project-0.1.0.dist-info/top_level.txt' + adding 'project-0.1.0.dist-info/RECORD' + removing build/bdist.linux-x86_64/wheel + Successfully built dist/project-0.1.0.tar.gz and dist/project-0.1.0-py3-none-any.whl + "###); + + project + .child("dist") + .child("project-0.1.0.tar.gz") + .assert(predicate::path::is_file()); + project + .child("dist") + .child("project-0.1.0-py3-none-any.whl") + .assert(predicate::path::is_file()); + + + Ok(()) +} diff --git a/req.txt b/req.txt new file mode 100644 index 000000000000..564221b20ea0 --- /dev/null +++ b/req.txt @@ -0,0 +1 @@ +anyio==4.0.0