Respect relative paths in `uv build` sources (#8237)

## Summary

Right now, `uv build` will fail if a package depends on a local source
in `build-system.requires`.
This commit is contained in:
Charlie Marsh 2024-10-15 18:46:29 -07:00 committed by GitHub
parent b4dca669b4
commit 999b3f06a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 174 additions and 3 deletions

View File

@ -245,6 +245,7 @@ impl SourceBuild {
pub async fn setup(
source: &Path,
subdirectory: Option<&Path>,
install_path: &Path,
fallback_package_name: Option<&PackageName>,
fallback_package_version: Option<&Version>,
interpreter: &Interpreter,
@ -273,6 +274,7 @@ impl SourceBuild {
// Check if we have a PEP 517 build backend.
let (pep517_backend, project) = Self::extract_pep517_backend(
&source_tree,
install_path,
fallback_package_name,
locations,
source_strategy,
@ -368,6 +370,7 @@ impl SourceBuild {
create_pep517_build_environment(
&runner,
&source_tree,
install_path,
&venv,
&pep517_backend,
build_context,
@ -436,6 +439,7 @@ impl SourceBuild {
/// Extract the PEP 517 backend from the `pyproject.toml` or `setup.py` file.
async fn extract_pep517_backend(
source_tree: &Path,
install_path: &Path,
package_name: Option<&PackageName>,
locations: &IndexLocations,
source_strategy: SourceStrategy,
@ -469,7 +473,7 @@ impl SourceBuild {
};
let requires_dist = RequiresDist::from_project_maybe_workspace(
requires_dist,
source_tree,
install_path,
locations,
source_strategy,
LowerBound::Allow,
@ -803,6 +807,7 @@ fn escape_path_for_python(path: &Path) -> String {
async fn create_pep517_build_environment(
runner: &PythonRunner,
source_tree: &Path,
install_path: &Path,
venv: &PythonEnvironment,
pep517_backend: &Pep517Backend,
build_context: &impl BuildContext,
@ -921,7 +926,7 @@ async fn create_pep517_build_environment(
};
let requires_dist = RequiresDist::from_project_maybe_workspace(
requires_dist,
source_tree,
install_path,
locations,
source_strategy,
LowerBound::Allow,

View File

@ -318,6 +318,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
&'data self,
source: &'data Path,
subdirectory: Option<&'data Path>,
install_path: &'data Path,
version_id: Option<String>,
dist: Option<&'data SourceDist>,
sources: SourceStrategy,
@ -352,6 +353,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
let builder = SourceBuild::setup(
source,
subdirectory,
install_path,
dist_name,
dist_version,
self.interpreter,

View File

@ -1712,6 +1712,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.setup_build(
source_root,
subdirectory,
source_root,
Some(source.to_string()),
source.as_dist(),
source_strategy,
@ -1756,6 +1757,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
.setup_build(
source_root,
subdirectory,
source_root,
Some(source.to_string()),
source.as_dist(),
source_strategy,

View File

@ -111,6 +111,7 @@ pub trait BuildContext {
&'a self,
source: &'a Path,
subdirectory: Option<&'a Path>,
install_path: &'a Path,
version_id: Option<String>,
dist: Option<&'a SourceDist>,
sources: SourceStrategy,

View File

@ -535,9 +535,9 @@ async fn build_package(
};
// Prepare some common arguments for the build.
let dist = None;
let subdirectory = None;
let version_id = source.path().file_name().and_then(|name| name.to_str());
let dist = None;
let build_output = match printer {
Printer::Default | Printer::NoProgress | Printer::Verbose => {
@ -563,6 +563,7 @@ async fn build_package(
.setup_build(
source.path(),
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
@ -601,6 +602,7 @@ async fn build_package(
.setup_build(
&extracted,
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
@ -623,6 +625,7 @@ async fn build_package(
.setup_build(
source.path(),
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
@ -645,6 +648,7 @@ async fn build_package(
.setup_build(
source.path(),
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
@ -666,6 +670,7 @@ async fn build_package(
.setup_build(
source.path(),
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
@ -684,6 +689,7 @@ async fn build_package(
.setup_build(
source.path(),
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,
@ -724,6 +730,7 @@ async fn build_package(
.setup_build(
&extracted,
subdirectory,
source.path(),
version_id.map(ToString::to_string),
dist,
sources,

View File

@ -2,6 +2,7 @@ use crate::common::{uv_snapshot, TestContext};
use anyhow::Result;
use assert_fs::prelude::*;
use fs_err::File;
use indoc::indoc;
use insta::assert_snapshot;
use predicates::prelude::predicate;
use zip::ZipArchive;
@ -1766,6 +1767,159 @@ fn build_no_build_logs() -> Result<()> {
Ok(())
}
#[test]
fn tool_uv_sources() -> Result<()> {
let context = TestContext::new("3.12");
let filters = context
.filters()
.into_iter()
.chain([
(r"exit code: 1", "exit status: 1"),
(r"bdist\.[^/\\\s]+-[^/\\\s]+", "bdist.linux-x86_64"),
(r"\\\.", ""),
])
.collect::<Vec<_>>();
let build = context.temp_dir.child("backend");
build.child("pyproject.toml").write_str(
r#"
[project]
name = "backend"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["typing-extensions>=3.10"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
build
.child("src")
.child("backend")
.child("__init__.py")
.write_str(indoc! { r#"
def hello() -> str:
return "Hello, world!"
"#})?;
build.child("README.md").touch()?;
let project = context.temp_dir.child("project");
project.child("pyproject.toml").write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["iniconfig>1"]
[build-system]
requires = ["setuptools>=42", "backend==0.1.0"]
build-backend = "setuptools.build_meta"
[tool.uv.sources]
backend = { path = "../backend" }
"#,
)?;
project.child("setup.py").write_str(indoc! {r"
from setuptools import setup
from backend import hello
hello()
setup()
",
})?;
uv_snapshot!(filters, context.build().current_dir(project.path()), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Building source distribution...
running egg_info
creating project.egg-info
writing project.egg-info/PKG-INFO
writing dependency_links to project.egg-info/dependency_links.txt
writing requirements to project.egg-info/requires.txt
writing top-level names to project.egg-info/top_level.txt
writing manifest file 'project.egg-info/SOURCES.txt'
reading manifest file 'project.egg-info/SOURCES.txt'
writing manifest file 'project.egg-info/SOURCES.txt'
running sdist
running egg_info
writing project.egg-info/PKG-INFO
writing dependency_links to project.egg-info/dependency_links.txt
writing requirements to project.egg-info/requires.txt
writing top-level names to project.egg-info/top_level.txt
reading manifest file 'project.egg-info/SOURCES.txt'
writing manifest file 'project.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md
running check
creating project-0.1.0
creating project-0.1.0/project.egg-info
copying files to project-0.1.0...
copying pyproject.toml -> project-0.1.0
copying setup.py -> project-0.1.0
copying project.egg-info/PKG-INFO -> project-0.1.0/project.egg-info
copying project.egg-info/SOURCES.txt -> project-0.1.0/project.egg-info
copying project.egg-info/dependency_links.txt -> project-0.1.0/project.egg-info
copying project.egg-info/requires.txt -> project-0.1.0/project.egg-info
copying project.egg-info/top_level.txt -> project-0.1.0/project.egg-info
copying project.egg-info/SOURCES.txt -> project-0.1.0/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 project.egg-info/PKG-INFO
writing dependency_links to project.egg-info/dependency_links.txt
writing requirements to project.egg-info/requires.txt
writing top-level names to project.egg-info/top_level.txt
reading manifest file 'project.egg-info/SOURCES.txt'
writing manifest file 'project.egg-info/SOURCES.txt'
running bdist_wheel
running build
installing to build/bdist.linux-x86_64/wheel
running install
running install_egg_info
running egg_info
writing project.egg-info/PKG-INFO
writing dependency_links to project.egg-info/dependency_links.txt
writing requirements to project.egg-info/requires.txt
writing top-level names to project.egg-info/top_level.txt
reading manifest file 'project.egg-info/SOURCES.txt'
writing manifest file 'project.egg-info/SOURCES.txt'
Copying project.egg-info to build/bdist.linux-x86_64/wheel/project-0.1.0-py3.12.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 '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(())
}
/// Check that we have a working git boundary for builds from source dist to wheel in `dist/`.
#[test]
fn git_boundary_in_dist_build() -> Result<()> {