mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 13:58:29 +00:00
Build backend: Normalize glob paths (#13465)
Unlike OS APIs, glob inclusion checks don't work when there are relative path elements such as `./`. We normalize the path before using it for the glob. Fixes #13407
This commit is contained in:
parent
7a83f51de2
commit
18a1f0d9db
3 changed files with 94 additions and 21 deletions
|
@ -85,8 +85,9 @@ pub enum Error {
|
||||||
module_name: Identifier,
|
module_name: Identifier,
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
},
|
},
|
||||||
#[error("Absolute module root is not allowed: `{}`", _0.display())]
|
/// Either an absolute path or a parent path through `..`.
|
||||||
AbsoluteModuleRoot(PathBuf),
|
#[error("Module root must be inside the project: `{}`", _0.user_display())]
|
||||||
|
InvalidModuleRoot(PathBuf),
|
||||||
#[error("Inconsistent metadata between prepare and build step: `{0}`")]
|
#[error("Inconsistent metadata between prepare and build step: `{0}`")]
|
||||||
InconsistentSteps(&'static str),
|
InconsistentSteps(&'static str),
|
||||||
#[error("Failed to write to {}", _0.user_display())]
|
#[error("Failed to write to {}", _0.user_display())]
|
||||||
|
@ -203,12 +204,10 @@ fn find_roots(
|
||||||
relative_module_root: &Path,
|
relative_module_root: &Path,
|
||||||
module_name: Option<&Identifier>,
|
module_name: Option<&Identifier>,
|
||||||
) -> Result<(PathBuf, PathBuf), Error> {
|
) -> Result<(PathBuf, PathBuf), Error> {
|
||||||
if relative_module_root.is_absolute() {
|
let src_root = source_tree.join(uv_fs::normalize_path(relative_module_root));
|
||||||
return Err(Error::AbsoluteModuleRoot(
|
if !src_root.starts_with(source_tree) {
|
||||||
relative_module_root.to_path_buf(),
|
return Err(Error::InvalidModuleRoot(relative_module_root.to_path_buf()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
let src_root = source_tree.join(relative_module_root);
|
|
||||||
|
|
||||||
let module_name = if let Some(module_name) = module_name {
|
let module_name = if let Some(module_name) = module_name {
|
||||||
module_name.clone()
|
module_name.clone()
|
||||||
|
@ -288,6 +287,7 @@ mod tests {
|
||||||
/// source tree -> wheel
|
/// source tree -> wheel
|
||||||
/// and a build with
|
/// and a build with
|
||||||
/// source tree -> source dist -> wheel.
|
/// source tree -> source dist -> wheel.
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
struct BuildResults {
|
struct BuildResults {
|
||||||
source_dist_list_files: FileList,
|
source_dist_list_files: FileList,
|
||||||
source_dist_filename: SourceDistFilename,
|
source_dist_filename: SourceDistFilename,
|
||||||
|
@ -694,4 +694,73 @@ mod tests {
|
||||||
Version: 1.0.0
|
Version: 1.0.0
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that non-normalized paths for `module-root` work with the glob inclusions.
|
||||||
|
#[test]
|
||||||
|
fn test_glob_path_normalization() {
|
||||||
|
let src = TempDir::new().unwrap();
|
||||||
|
fs_err::write(
|
||||||
|
src.path().join("pyproject.toml"),
|
||||||
|
indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "two-step-build"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.5.15,<0.6"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
|
||||||
|
[tool.uv.build-backend]
|
||||||
|
module-root = "./"
|
||||||
|
"#
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
fs_err::create_dir_all(src.path().join("two_step_build")).unwrap();
|
||||||
|
File::create(src.path().join("two_step_build").join("__init__.py")).unwrap();
|
||||||
|
|
||||||
|
let dist = TempDir::new().unwrap();
|
||||||
|
let build1 = build(src.path(), dist.path());
|
||||||
|
|
||||||
|
assert_snapshot!(build1.source_dist_contents.join("\n"), @r"
|
||||||
|
two_step_build-1.0.0/
|
||||||
|
two_step_build-1.0.0/PKG-INFO
|
||||||
|
two_step_build-1.0.0/pyproject.toml
|
||||||
|
two_step_build-1.0.0/two_step_build
|
||||||
|
two_step_build-1.0.0/two_step_build/__init__.py
|
||||||
|
");
|
||||||
|
|
||||||
|
assert_snapshot!(build1.wheel_contents.join("\n"), @r"
|
||||||
|
two_step_build-1.0.0.dist-info/
|
||||||
|
two_step_build-1.0.0.dist-info/METADATA
|
||||||
|
two_step_build-1.0.0.dist-info/RECORD
|
||||||
|
two_step_build-1.0.0.dist-info/WHEEL
|
||||||
|
two_step_build/
|
||||||
|
two_step_build/__init__.py
|
||||||
|
");
|
||||||
|
|
||||||
|
// A path with a parent reference.
|
||||||
|
fs_err::write(
|
||||||
|
src.path().join("pyproject.toml"),
|
||||||
|
indoc! {r#"
|
||||||
|
[project]
|
||||||
|
name = "two-step-build"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["uv_build>=0.5.15,<0.6"]
|
||||||
|
build-backend = "uv_build"
|
||||||
|
|
||||||
|
[tool.uv.build-backend]
|
||||||
|
module-root = "two_step_build/.././"
|
||||||
|
"#
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let dist = TempDir::new().unwrap();
|
||||||
|
let build2 = build(src.path(), dist.path());
|
||||||
|
assert_eq!(build1, build2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,12 +79,10 @@ fn source_dist_matcher(
|
||||||
|
|
||||||
// The wheel must not include any files included by the source distribution (at least until we
|
// The wheel must not include any files included by the source distribution (at least until we
|
||||||
// have files generated in the source dist -> wheel build step).
|
// have files generated in the source dist -> wheel build step).
|
||||||
let import_path = &settings
|
let import_path = uv_fs::normalize_path(&settings.module_root.join(module_name.as_ref()))
|
||||||
.module_root
|
|
||||||
.join(module_name.as_ref())
|
|
||||||
.portable_display()
|
.portable_display()
|
||||||
.to_string();
|
.to_string();
|
||||||
includes.push(format!("{}/**", globset::escape(import_path)));
|
includes.push(format!("{}/**", globset::escape(&import_path)));
|
||||||
for include in includes {
|
for include in includes {
|
||||||
let glob = PortableGlobParser::Uv
|
let glob = PortableGlobParser::Uv
|
||||||
.parse(&include)
|
.parse(&include)
|
||||||
|
@ -92,7 +90,7 @@ fn source_dist_matcher(
|
||||||
field: "tool.uv.build-backend.source-include".to_string(),
|
field: "tool.uv.build-backend.source-include".to_string(),
|
||||||
source: err,
|
source: err,
|
||||||
})?;
|
})?;
|
||||||
include_globs.push(glob.clone());
|
include_globs.push(glob);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the Readme
|
// Include the Readme
|
||||||
|
@ -101,11 +99,11 @@ fn source_dist_matcher(
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|readme| readme.path())
|
.and_then(|readme| readme.path())
|
||||||
{
|
{
|
||||||
|
let readme = uv_fs::normalize_path(readme);
|
||||||
trace!("Including readme at: `{}`", readme.user_display());
|
trace!("Including readme at: `{}`", readme.user_display());
|
||||||
include_globs.push(
|
let readme = readme.portable_display().to_string();
|
||||||
Glob::new(&globset::escape(&readme.portable_display().to_string()))
|
let glob = Glob::new(&globset::escape(&readme)).expect("escaped globset is parseable");
|
||||||
.expect("escaped globset is parseable"),
|
include_globs.push(glob);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include the license files
|
// Include the license files
|
||||||
|
@ -122,13 +120,19 @@ fn source_dist_matcher(
|
||||||
|
|
||||||
// Include the data files
|
// Include the data files
|
||||||
for (name, directory) in settings.data.iter() {
|
for (name, directory) in settings.data.iter() {
|
||||||
|
let directory = uv_fs::normalize_path(Path::new(directory));
|
||||||
|
trace!(
|
||||||
|
"Including data ({}) at: `{}`",
|
||||||
|
name,
|
||||||
|
directory.user_display()
|
||||||
|
);
|
||||||
|
let directory = directory.portable_display().to_string();
|
||||||
let glob = PortableGlobParser::Uv
|
let glob = PortableGlobParser::Uv
|
||||||
.parse(&format!("{}/**", globset::escape(directory)))
|
.parse(&format!("{}/**", globset::escape(&directory)))
|
||||||
.map_err(|err| Error::PortableGlob {
|
.map_err(|err| Error::PortableGlob {
|
||||||
field: format!("tool.uv.build-backend.data.{name}"),
|
field: format!("tool.uv.build-backend.data.{name}"),
|
||||||
source: err,
|
source: err,
|
||||||
})?;
|
})?;
|
||||||
trace!("Including data ({name}) at: `{directory}`");
|
|
||||||
include_globs.push(glob);
|
include_globs.push(glob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -268,10 +268,10 @@ pub fn build_editable(
|
||||||
let mut wheel_writer = ZipDirectoryWriter::new_wheel(File::create(&wheel_path)?);
|
let mut wheel_writer = ZipDirectoryWriter::new_wheel(File::create(&wheel_path)?);
|
||||||
|
|
||||||
debug!("Adding pth file to {}", wheel_path.user_display());
|
debug!("Adding pth file to {}", wheel_path.user_display());
|
||||||
if settings.module_root.is_absolute() {
|
let src_root = source_tree.join(&settings.module_root);
|
||||||
return Err(Error::AbsoluteModuleRoot(settings.module_root.clone()));
|
if !src_root.starts_with(source_tree) {
|
||||||
|
return Err(Error::InvalidModuleRoot(settings.module_root.clone()));
|
||||||
}
|
}
|
||||||
let src_root = source_tree.join(settings.module_root);
|
|
||||||
|
|
||||||
let module_name = if let Some(module_name) = settings.module_name {
|
let module_name = if let Some(module_name) = settings.module_name {
|
||||||
module_name
|
module_name
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue