mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-29 19:17:26 +00:00
Error early for parent path in build backend (#15733)
Paths referencing above the directory of the `pyproject.toml`, such as `module-root = ".."`, are not supported by the build backend. The check that should catch was not working properly, so the source distribution built successfully and only the wheel build failed. We now error early. The same fix is applied to data includes. Fix #15702
This commit is contained in:
parent
5f8c7181b9
commit
39fe2d9eac
6 changed files with 151 additions and 20 deletions
|
|
@ -66,6 +66,9 @@ pub enum Error {
|
|||
/// Either an absolute path or a parent path through `..`.
|
||||
#[error("Module root must be inside the project: `{}`", _0.user_display())]
|
||||
InvalidModuleRoot(PathBuf),
|
||||
/// Either an absolute path or a parent path through `..`.
|
||||
#[error("The path for the data directory {} must be inside the project: `{}`", name, path.user_display())]
|
||||
InvalidDataRoot { name: String, path: PathBuf },
|
||||
#[error("Inconsistent metadata between prepare and build step: `{0}`")]
|
||||
InconsistentSteps(&'static str),
|
||||
#[error("Failed to write to {}", _0.user_display())]
|
||||
|
|
@ -209,8 +212,10 @@ fn find_roots(
|
|||
namespace: bool,
|
||||
) -> Result<(PathBuf, Vec<PathBuf>), Error> {
|
||||
let relative_module_root = uv_fs::normalize_path(relative_module_root);
|
||||
let src_root = source_tree.join(&relative_module_root);
|
||||
if !src_root.starts_with(source_tree) {
|
||||
// Check that even if a path contains `..`, we only include files below the module root.
|
||||
if !uv_fs::normalize_path(&source_tree.join(&relative_module_root))
|
||||
.starts_with(uv_fs::normalize_path(source_tree))
|
||||
{
|
||||
return Err(Error::InvalidModuleRoot(relative_module_root.to_path_buf()));
|
||||
}
|
||||
let src_root = source_tree.join(&relative_module_root);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use uv_macros::OptionsMetadata;
|
||||
|
||||
/// Settings for the uv build backend (`uv_build`).
|
||||
|
|
@ -204,16 +204,16 @@ pub enum ModuleName {
|
|||
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct WheelDataIncludes {
|
||||
purelib: Option<String>,
|
||||
platlib: Option<String>,
|
||||
headers: Option<String>,
|
||||
scripts: Option<String>,
|
||||
data: Option<String>,
|
||||
purelib: Option<PathBuf>,
|
||||
platlib: Option<PathBuf>,
|
||||
headers: Option<PathBuf>,
|
||||
scripts: Option<PathBuf>,
|
||||
data: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl WheelDataIncludes {
|
||||
/// Yield all data directories name and corresponding paths.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&'static str, &str)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&'static str, &Path)> {
|
||||
[
|
||||
("purelib", self.purelib.as_deref()),
|
||||
("platlib", self.platlib.as_deref()),
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use fs_err::File;
|
|||
use globset::{Glob, GlobSet};
|
||||
use std::io;
|
||||
use std::io::{BufReader, Cursor};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use tar::{EntryType, Header};
|
||||
use tracing::{debug, trace};
|
||||
use uv_distribution_filename::{SourceDistExtension, SourceDistFilename};
|
||||
|
|
@ -123,12 +123,22 @@ fn source_dist_matcher(
|
|||
|
||||
// Include the data files
|
||||
for (name, directory) in settings.data.iter() {
|
||||
let directory = uv_fs::normalize_path(Path::new(directory));
|
||||
let directory = uv_fs::normalize_path(directory);
|
||||
trace!(
|
||||
"Including data ({}) at: `{}`",
|
||||
name,
|
||||
directory.user_display()
|
||||
);
|
||||
if directory
|
||||
.components()
|
||||
.next()
|
||||
.is_some_and(|component| !matches!(component, Component::CurDir | Component::Normal(_)))
|
||||
{
|
||||
return Err(Error::InvalidDataRoot {
|
||||
name: name.to_string(),
|
||||
path: directory.to_path_buf(),
|
||||
});
|
||||
}
|
||||
let directory = directory.portable_display().to_string();
|
||||
let glob = PortableGlobParser::Uv
|
||||
.parse(&format!("{}/**", globset::escape(&directory)))
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use itertools::Itertools;
|
|||
use rustc_hash::FxHashSet;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::{BufReader, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::{io, mem};
|
||||
use tracing::{debug, trace};
|
||||
use walkdir::WalkDir;
|
||||
|
|
@ -207,7 +207,20 @@ fn write_wheel(
|
|||
|
||||
// Add the data files
|
||||
for (name, directory) in settings.data.iter() {
|
||||
debug!("Adding {name} data files from: `{directory}`");
|
||||
debug!(
|
||||
"Adding {name} data files from: `{}`",
|
||||
directory.user_display()
|
||||
);
|
||||
if directory
|
||||
.components()
|
||||
.next()
|
||||
.is_some_and(|component| !matches!(component, Component::CurDir | Component::Normal(_)))
|
||||
{
|
||||
return Err(Error::InvalidDataRoot {
|
||||
name: name.to_string(),
|
||||
path: directory.to_path_buf(),
|
||||
});
|
||||
}
|
||||
let data_dir = format!(
|
||||
"{}-{}.data/{}/",
|
||||
pyproject_toml.name().as_dist_info_name(),
|
||||
|
|
|
|||
|
|
@ -1442,7 +1442,7 @@ fn build_fast_path() -> Result<()> {
|
|||
uv_snapshot!(context.build()
|
||||
.arg(&built_by_uv)
|
||||
.arg("--out-dir")
|
||||
.arg(context.temp_dir.join("output1")), @r###"
|
||||
.arg(context.temp_dir.join("output1")), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
@ -1452,7 +1452,7 @@ fn build_fast_path() -> Result<()> {
|
|||
Building wheel from source distribution (uv build backend)...
|
||||
Successfully built output1/built_by_uv-0.1.0.tar.gz
|
||||
Successfully built output1/built_by_uv-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
");
|
||||
context
|
||||
.temp_dir
|
||||
.child("output1")
|
||||
|
|
@ -1487,7 +1487,7 @@ fn build_fast_path() -> Result<()> {
|
|||
.arg(&built_by_uv)
|
||||
.arg("--out-dir")
|
||||
.arg(context.temp_dir.join("output3"))
|
||||
.arg("--wheel"), @r###"
|
||||
.arg("--wheel"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
@ -1495,7 +1495,7 @@ fn build_fast_path() -> Result<()> {
|
|||
----- stderr -----
|
||||
Building wheel (uv build backend)...
|
||||
Successfully built output3/built_by_uv-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
");
|
||||
context
|
||||
.temp_dir
|
||||
.child("output3")
|
||||
|
|
@ -1507,7 +1507,7 @@ fn build_fast_path() -> Result<()> {
|
|||
.arg("--out-dir")
|
||||
.arg(context.temp_dir.join("output4"))
|
||||
.arg("--sdist")
|
||||
.arg("--wheel"), @r###"
|
||||
.arg("--wheel"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
|
@ -1517,7 +1517,7 @@ fn build_fast_path() -> Result<()> {
|
|||
Building wheel (uv build backend)...
|
||||
Successfully built output4/built_by_uv-0.1.0.tar.gz
|
||||
Successfully built output4/built_by_uv-0.1.0-py3-none-any.whl
|
||||
"###);
|
||||
");
|
||||
context
|
||||
.temp_dir
|
||||
.child("output4")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::common::{TestContext, uv_snapshot, venv_bin_path};
|
||||
use anyhow::Result;
|
||||
use assert_cmd::assert::OutputAssertExt;
|
||||
use assert_fs::fixture::{FileWriteStr, PathChild, PathCreateDir};
|
||||
use assert_fs::fixture::{FileTouch, FileWriteStr, PathChild, PathCreateDir};
|
||||
use flate2::bufread::GzDecoder;
|
||||
use fs_err::File;
|
||||
use indoc::{formatdoc, indoc};
|
||||
|
|
@ -884,3 +884,106 @@ fn invalid_build_backend_settings_are_ignored() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Error when there is a relative module root outside the project root, such as
|
||||
/// `tool.uv.build-backend.module-root = ".."`.
|
||||
#[test]
|
||||
fn error_on_relative_module_root_outside_project_root() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[tool.uv.build-backend]
|
||||
module-root = ".."
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.7,<10000"]
|
||||
build-backend = "uv_build"
|
||||
"#})?;
|
||||
|
||||
context.temp_dir.child("__init__.py").touch()?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.build(), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Building source distribution (uv build backend)...
|
||||
× Failed to build `[TEMP_DIR]/`
|
||||
╰─▶ Module root must be inside the project: `..`
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.build().arg("--wheel"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Building wheel (uv build backend)...
|
||||
× Failed to build `[TEMP_DIR]/`
|
||||
╰─▶ Module root must be inside the project: `..`
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Error when there is a relative data directory outside the project root, such as
|
||||
/// `tool.uv.build-backend.data.headers = "../headers"`.
|
||||
#[test]
|
||||
fn error_on_relative_data_dir_outside_project_root() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let project = context.temp_dir.child("project");
|
||||
project.create_dir_all()?;
|
||||
|
||||
let pyproject_toml = project.child("pyproject.toml");
|
||||
pyproject_toml.write_str(indoc! {r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[tool.uv.build-backend.data]
|
||||
headers = "../header"
|
||||
|
||||
[build-system]
|
||||
requires = ["uv_build>=0.7,<10000"]
|
||||
build-backend = "uv_build"
|
||||
"#})?;
|
||||
|
||||
let project_module = project.child("src/project");
|
||||
project_module.create_dir_all()?;
|
||||
project_module.child("__init__.py").touch()?;
|
||||
|
||||
context.temp_dir.child("headers").create_dir_all()?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.build().arg("project"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Building source distribution (uv build backend)...
|
||||
× Failed to build `[TEMP_DIR]/project`
|
||||
╰─▶ The path for the data directory headers must be inside the project: `../header`
|
||||
");
|
||||
|
||||
uv_snapshot!(context.filters(), context.build().arg("project").arg("--wheel"), @r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Building wheel (uv build backend)...
|
||||
× Failed to build `[TEMP_DIR]/project`
|
||||
╰─▶ The path for the data directory headers must be inside the project: `../header`
|
||||
");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue