diff --git a/Cargo.lock b/Cargo.lock index 59f20a1fc..79154a884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2646,6 +2646,7 @@ dependencies = [ "anyhow", "distribution-types", "fs-err", + "futures", "gourgeist", "itertools 0.12.1", "pep508_rs", diff --git a/crates/puffin-build/src/lib.rs b/crates/puffin-build/src/lib.rs index dc842b43d..1e96c47ac 100644 --- a/crates/puffin-build/src/lib.rs +++ b/crates/puffin-build/src/lib.rs @@ -316,57 +316,59 @@ impl SourceBuild { let default_backend: Pep517Backend = DEFAULT_BACKEND.clone(); // Check if we have a PEP 517 build backend. - let pep517_backend = match fs::read_to_string(source_tree.join("pyproject.toml")) { - Ok(toml) => { - let pyproject_toml: PyProjectToml = - toml::from_str(&toml).map_err(Error::InvalidPyprojectToml)?; - if let Some(build_system) = pyproject_toml.build_system { - Some(Pep517Backend { - // If `build-backend` is missing, inject the legacy setuptools backend, but - // retain the `requires`, to match `pip` and `build`. Note that while PEP 517 - // says that in this case we "should revert to the legacy behaviour of running - // `setup.py` (either directly, or by implicitly invoking the - // `setuptools.build_meta:__legacy__` backend)", we found that in practice, only - // the legacy setuptools backend is allowed. See also: - // https://github.com/pypa/build/blob/de5b44b0c28c598524832dff685a98d5a5148c44/src/build/__init__.py#L114-L118 - backend: build_system - .build_backend - .unwrap_or_else(|| "setuptools.build_meta:__legacy__".to_string()), - backend_path: build_system.backend_path, - requirements: build_system.requires, - }) - } else { - // If a `pyproject.toml` is present, but `[build-system]` is missing, proceed with - // a PEP 517 build using the default backend, to match `pip` and `build`. - Some(default_backend.clone()) - } - } - Err(err) if err.kind() == io::ErrorKind::NotFound => { - // We require either a `pyproject.toml` or a `setup.py` file at the top level. - if !source_tree.join("setup.py").is_file() { - return Err(Error::InvalidSourceDist( - "The archive contains neither a `pyproject.toml` nor a `setup.py` file at the top level" - .to_string(), - )); - } - - // If no `pyproject.toml` is present, by default, proceed with a PEP 517 build using - // the default backend, to match `build`. `pip` uses `setup.py` directly in this - // case (which we allow via `SetupPyStrategy::Setuptools`), but plans to make PEP - // 517 builds the default in the future. - // See: https://github.com/pypa/pip/issues/9175. - match setup_py { - SetupPyStrategy::Pep517 => Some(default_backend.clone()), - SetupPyStrategy::Setuptools => None, - } - } - Err(err) => return Err(err.into()), - }; + let pep517_backend = Self::get_pep517_backend(setup_py, &source_tree, &default_backend) + .map_err(|err| *err)?; let venv = gourgeist::create_venv(&temp_dir.path().join(".venv"), interpreter.clone())?; // Setup the build environment. - let resolved_requirements = if let Some(pep517_backend) = pep517_backend.as_ref() { + let resolved_requirements = Self::get_resolved_requirements( + build_context, + source_build_context, + &default_backend, + pep517_backend.as_ref(), + ) + .await?; + + build_context + .install(&resolved_requirements, &venv) + .await + .map_err(|err| Error::RequirementsInstall("build-system.requires (install)", err))?; + + // If we're using the default backend configuration, skip `get_requires_for_build_*`, since + // we already installed the requirements above. + if let Some(pep517_backend) = &pep517_backend { + if pep517_backend != &default_backend { + create_pep517_build_environment( + &source_tree, + &venv, + pep517_backend, + build_context, + &package_id, + build_kind, + ) + .await?; + } + } + + Ok(Self { + temp_dir, + source_tree, + pep517_backend, + venv, + build_kind, + metadata_directory: None, + package_id, + }) + } + + async fn get_resolved_requirements( + build_context: &impl BuildContext, + source_build_context: SourceBuildContext, + default_backend: &Pep517Backend, + pep517_backend: Option<&Pep517Backend>, + ) -> Result { + Ok(if let Some(pep517_backend) = pep517_backend { if pep517_backend.requirements == default_backend.requirements { let mut resolution = source_build_context.setup_py_resolution.lock().await; if let Some(resolved_requirements) = &*resolution { @@ -402,40 +404,62 @@ impl SourceBuild { *resolution = Some(resolved_requirements.clone()); resolved_requirements } - }; - - build_context - .install(&resolved_requirements, &venv) - .await - .map_err(|err| Error::RequirementsInstall("build-system.requires (install)", err))?; - - // If we're using the default backend configuration, skip `get_requires_for_build_*`, since - // we already installed the requirements above. - if let Some(pep517_backend) = &pep517_backend { - if pep517_backend != &default_backend { - create_pep517_build_environment( - &source_tree, - &venv, - pep517_backend, - build_context, - &package_id, - build_kind, - ) - .await?; - } - } - - Ok(Self { - temp_dir, - source_tree, - pep517_backend, - venv, - build_kind, - metadata_directory: None, - package_id, }) } + fn get_pep517_backend( + setup_py: SetupPyStrategy, + source_tree: &Path, + default_backend: &Pep517Backend, + ) -> Result, Box> { + match fs::read_to_string(source_tree.join("pyproject.toml")) { + Ok(toml) => { + let pyproject_toml: PyProjectToml = + toml::from_str(&toml).map_err(Error::InvalidPyprojectToml)?; + if let Some(build_system) = pyproject_toml.build_system { + Ok(Some(Pep517Backend { + // If `build-backend` is missing, inject the legacy setuptools backend, but + // retain the `requires`, to match `pip` and `build`. Note that while PEP 517 + // says that in this case we "should revert to the legacy behaviour of running + // `setup.py` (either directly, or by implicitly invoking the + // `setuptools.build_meta:__legacy__` backend)", we found that in practice, only + // the legacy setuptools backend is allowed. See also: + // https://github.com/pypa/build/blob/de5b44b0c28c598524832dff685a98d5a5148c44/src/build/__init__.py#L114-L118 + backend: build_system + .build_backend + .unwrap_or_else(|| "setuptools.build_meta:__legacy__".to_string()), + backend_path: build_system.backend_path, + requirements: build_system.requires, + })) + } else { + // If a `pyproject.toml` is present, but `[build-system]` is missing, proceed with + // a PEP 517 build using the default backend, to match `pip` and `build`. + Ok(Some(default_backend.clone())) + } + } + Err(err) if err.kind() == io::ErrorKind::NotFound => { + // We require either a `pyproject.toml` or a `setup.py` file at the top level. + if !source_tree.join("setup.py").is_file() { + return Err(Box::new(Error::InvalidSourceDist( + "The archive contains neither a `pyproject.toml` nor a `setup.py` file at the top level" + .to_string(), + ))); + } + + // If no `pyproject.toml` is present, by default, proceed with a PEP 517 build using + // the default backend, to match `build`. `pip` uses `setup.py` directly in this + // case (which we allow via `SetupPyStrategy::Setuptools`), but plans to make PEP + // 517 builds the default in the future. + // See: https://github.com/pypa/pip/issues/9175. + match setup_py { + SetupPyStrategy::Pep517 => Ok(Some(default_backend.clone())), + SetupPyStrategy::Setuptools => Ok(None), + } + } + Err(err) => Err(Box::new(err.into())), + } + } + /// Try calling `prepare_metadata_for_build_wheel` to get the metadata without executing the /// actual build. pub async fn get_metadata_without_build(&mut self) -> Result, Error> { diff --git a/crates/puffin-dispatch/Cargo.toml b/crates/puffin-dispatch/Cargo.toml index 7ea34e331..bb94597de 100644 --- a/crates/puffin-dispatch/Cargo.toml +++ b/crates/puffin-dispatch/Cargo.toml @@ -31,6 +31,7 @@ pypi-types = { path = "../pypi-types" } anyhow = { workspace = true } fs-err = { workspace = true } +futures = { workspace = true } itertools = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true } diff --git a/crates/puffin-dispatch/src/lib.rs b/crates/puffin-dispatch/src/lib.rs index 3a74d2c0e..69486c632 100644 --- a/crates/puffin-dispatch/src/lib.rs +++ b/crates/puffin-dispatch/src/lib.rs @@ -10,6 +10,7 @@ use itertools::Itertools; use tracing::{debug, instrument}; use distribution_types::{IndexLocations, Name, Resolution}; +use futures::FutureExt; use pep508_rs::Requirement; use puffin_build::{SourceBuild, SourceBuildContext}; use puffin_cache::Cache; @@ -240,30 +241,29 @@ impl<'a> BuildContext for BuildDispatch<'a> { #[allow(clippy::manual_async_fn)] // TODO(konstin): rustc 1.75 gets into a type inference cycle with async fn #[instrument(skip_all, fields(package_id = package_id, subdirectory = ?subdirectory))] - fn setup_build<'data>( + async fn setup_build<'data>( &'data self, source: &'data Path, subdirectory: Option<&'data Path>, package_id: &'data str, build_kind: BuildKind, - ) -> impl Future> + Send + 'data { - async move { - if self.no_build { - bail!("Building source distributions is disabled"); - } - - let builder = SourceBuild::setup( - source, - subdirectory, - self.interpreter, - self, - self.source_build_context.clone(), - package_id.to_string(), - self.setup_py, - build_kind, - ) - .await?; - Ok(builder) + ) -> Result { + if self.no_build { + bail!("Building source distributions is disabled"); } + + let builder = SourceBuild::setup( + source, + subdirectory, + self.interpreter, + self, + self.source_build_context.clone(), + package_id.to_string(), + self.setup_py, + build_kind, + ) + .boxed() + .await?; + Ok(builder) } } diff --git a/crates/puffin-distribution/src/source/mod.rs b/crates/puffin-distribution/src/source/mod.rs index 7e3464ae9..b7013d857 100644 --- a/crates/puffin-distribution/src/source/mod.rs +++ b/crates/puffin-distribution/src/source/mod.rs @@ -119,7 +119,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> { path: path.clone(), editable: false, }; - return self.path(source_dist, &path_source_dist).await; + return self.path(source_dist, &path_source_dist).boxed().await; } }; @@ -141,8 +141,12 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> { .boxed() .await? } - SourceDist::Git(git_source_dist) => self.git(source_dist, git_source_dist).await?, - SourceDist::Path(path_source_dist) => self.path(source_dist, path_source_dist).await?, + SourceDist::Git(git_source_dist) => { + self.git(source_dist, git_source_dist).boxed().await? + } + SourceDist::Path(path_source_dist) => { + self.path(source_dist, path_source_dist).boxed().await? + } }; Ok(built_wheel_metadata) @@ -268,6 +272,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> { Ok(manifest) } + .boxed() .instrument(info_span!("download", source_dist = %source_dist)) }; let req = self.cached_client.uncached().get(url.clone()).build()?; @@ -361,6 +366,7 @@ impl<'a, T: BuildContext> SourceDistCachedBuilder<'a, T> { Ok(manifest) } + .boxed() .instrument(info_span!("download", source_dist = %source_dist)) }; let req = self.cached_client.uncached().get(url.clone()).build()?; diff --git a/crates/puffin-interpreter/src/interpreter.rs b/crates/puffin-interpreter/src/interpreter.rs index 5e2ab71c3..a16e19e0a 100644 --- a/crates/puffin-interpreter/src/interpreter.rs +++ b/crates/puffin-interpreter/src/interpreter.rs @@ -24,7 +24,7 @@ use crate::{Error, PythonVersion}; #[derive(Debug, Clone)] pub struct Interpreter { pub(crate) platform: PythonPlatform, - pub(crate) markers: MarkerEnvironment, + pub(crate) markers: Box, pub(crate) base_exec_prefix: PathBuf, pub(crate) base_prefix: PathBuf, pub(crate) stdlib: PathBuf, @@ -50,7 +50,7 @@ impl Interpreter { Ok(Self { platform: PythonPlatform(platform.to_owned()), - markers: info.markers, + markers: Box::new(info.markers), base_exec_prefix: info.base_exec_prefix, base_prefix: info.base_prefix, stdlib: info.stdlib, @@ -70,7 +70,7 @@ impl Interpreter { ) -> Self { Self { platform: PythonPlatform(platform), - markers, + markers: Box::new(markers), base_exec_prefix, base_prefix, stdlib, diff --git a/crates/puffin/tests/pip_install.rs b/crates/puffin/tests/pip_install.rs index 13e6b887e..80f530c5f 100644 --- a/crates/puffin/tests/pip_install.rs +++ b/crates/puffin/tests/pip_install.rs @@ -662,6 +662,7 @@ fn install_no_index_version() { /// Install a package without using pre-built wheels. #[test] +#[cfg(not(all(windows, debug_assertions)))] // Stack overflow on debug on windows -.- fn install_no_binary() { let context = TestContext::new("3.12"); @@ -725,6 +726,7 @@ fn install_no_binary_subset() { /// Install a package without using pre-built wheels. #[test] +#[cfg(not(all(windows, debug_assertions)))] // Stack overflow on debug on windows -.- fn reinstall_no_binary() { let context = TestContext::new("3.12"); diff --git a/crates/puffin/tests/pip_sync.rs b/crates/puffin/tests/pip_sync.rs index 7d67bc106..be0664998 100644 --- a/crates/puffin/tests/pip_sync.rs +++ b/crates/puffin/tests/pip_sync.rs @@ -805,6 +805,7 @@ fn install_numpy_py38() -> Result<()> { /// Install a package without using pre-built wheels. #[test] +#[cfg(not(all(windows, debug_assertions)))] // Stack overflow on debug on windows -.- fn install_no_binary() -> Result<()> { let context = TestContext::new("3.12");