mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-17 22:07:47 +00:00
Minimal wheel settings (#9085)
A small refactoring and minimal wheel settings. source tree -> source dist -> wheel is working, which we'll add tests for in the next PR.
This commit is contained in:
parent
4ac78f673b
commit
02a7bb43d9
3 changed files with 97 additions and 44 deletions
|
@ -11,13 +11,13 @@ use std::fs::FileType;
|
||||||
use std::io::{BufReader, Cursor, Read, Write};
|
use std::io::{BufReader, Cursor, Read, Write};
|
||||||
use std::path::{Path, PathBuf, StripPrefixError};
|
use std::path::{Path, PathBuf, StripPrefixError};
|
||||||
use std::{io, mem};
|
use std::{io, mem};
|
||||||
use tar::{EntryType, Header};
|
use tar::{Builder, EntryType, Header};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, trace};
|
use tracing::{debug, trace};
|
||||||
use uv_distribution_filename::{SourceDistExtension, SourceDistFilename, WheelFilename};
|
use uv_distribution_filename::{SourceDistExtension, SourceDistFilename, WheelFilename};
|
||||||
use uv_fs::Simplified;
|
use uv_fs::Simplified;
|
||||||
use uv_globfilter::{parse_portable_glob, GlobDirFilter, PortableGlobError};
|
use uv_globfilter::{parse_portable_glob, GlobDirFilter, PortableGlobError};
|
||||||
use walkdir::WalkDir;
|
use walkdir::{DirEntry, WalkDir};
|
||||||
use zip::{CompressionMethod, ZipWriter};
|
use zip::{CompressionMethod, ZipWriter};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -66,6 +66,8 @@ pub enum Error {
|
||||||
Csv(#[from] csv::Error),
|
Csv(#[from] csv::Error),
|
||||||
#[error("Expected a Python module with an `__init__.py` at: `{}`", _0.user_display())]
|
#[error("Expected a Python module with an `__init__.py` at: `{}`", _0.user_display())]
|
||||||
MissingModule(PathBuf),
|
MissingModule(PathBuf),
|
||||||
|
#[error("Absolute module root is not allowed: `{}`", _0.display())]
|
||||||
|
AbsoluteModuleRoot(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())]
|
||||||
|
@ -292,11 +294,29 @@ fn write_hashed(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// TODO(konsti): Wire this up with actual settings and remove this struct.
|
||||||
|
///
|
||||||
|
/// Which files to include in the wheel
|
||||||
|
pub struct WheelSettings {
|
||||||
|
/// The directory that contains the module directory, usually `src`, or an empty path when
|
||||||
|
/// using the flat layout over the src layout.
|
||||||
|
module_root: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for WheelSettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
module_root: PathBuf::from("src"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a wheel from the source tree and place it in the output directory.
|
/// Build a wheel from the source tree and place it in the output directory.
|
||||||
pub fn build_wheel(
|
pub fn build_wheel(
|
||||||
source_tree: &Path,
|
source_tree: &Path,
|
||||||
wheel_dir: &Path,
|
wheel_dir: &Path,
|
||||||
metadata_directory: Option<&Path>,
|
metadata_directory: Option<&Path>,
|
||||||
|
wheel_settings: WheelSettings,
|
||||||
uv_version: &str,
|
uv_version: &str,
|
||||||
) -> Result<WheelFilename, Error> {
|
) -> Result<WheelFilename, Error> {
|
||||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||||
|
@ -319,7 +339,10 @@ pub fn build_wheel(
|
||||||
let mut wheel_writer = ZipDirectoryWriter::new_wheel(File::create(&wheel_path)?);
|
let mut wheel_writer = ZipDirectoryWriter::new_wheel(File::create(&wheel_path)?);
|
||||||
|
|
||||||
debug!("Adding content files to {}", wheel_path.user_display());
|
debug!("Adding content files to {}", wheel_path.user_display());
|
||||||
let strip_root = source_tree.join("src");
|
if wheel_settings.module_root.is_absolute() {
|
||||||
|
return Err(Error::AbsoluteModuleRoot(wheel_settings.module_root));
|
||||||
|
}
|
||||||
|
let strip_root = source_tree.join(wheel_settings.module_root);
|
||||||
let module_root = strip_root.join(pyproject_toml.name().as_dist_info_name().as_ref());
|
let module_root = strip_root.join(pyproject_toml.name().as_dist_info_name().as_ref());
|
||||||
if !module_root.join("__init__.py").is_file() {
|
if !module_root.join("__init__.py").is_file() {
|
||||||
return Err(Error::MissingModule(module_root));
|
return Err(Error::MissingModule(module_root));
|
||||||
|
@ -337,6 +360,9 @@ pub fn build_wheel(
|
||||||
let relative_path_str = relative_path
|
let relative_path_str = relative_path
|
||||||
.to_str()
|
.to_str()
|
||||||
.ok_or_else(|| Error::NotUtf8Path(relative_path.to_path_buf()))?;
|
.ok_or_else(|| Error::NotUtf8Path(relative_path.to_path_buf()))?;
|
||||||
|
|
||||||
|
debug!("Adding to wheel: `{relative_path_str}`");
|
||||||
|
|
||||||
if entry.file_type().is_dir() {
|
if entry.file_type().is_dir() {
|
||||||
wheel_writer.write_directory(relative_path_str)?;
|
wheel_writer.write_directory(relative_path_str)?;
|
||||||
} else if entry.file_type().is_file() {
|
} else if entry.file_type().is_file() {
|
||||||
|
@ -514,44 +540,7 @@ pub fn build_source_dist(
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Including {}", relative.user_display());
|
add_source_dist_entry(&mut tar, &entry, &top_level, &source_dist_path, &relative)?;
|
||||||
|
|
||||||
let metadata = fs_err::metadata(entry.path())?;
|
|
||||||
let mut header = Header::new_gnu();
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
header.set_mode(std::os::unix::fs::MetadataExt::mode(&metadata));
|
|
||||||
}
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
{
|
|
||||||
header.set_mode(0o644);
|
|
||||||
}
|
|
||||||
|
|
||||||
if entry.file_type().is_dir() {
|
|
||||||
header.set_entry_type(EntryType::Directory);
|
|
||||||
header
|
|
||||||
.set_path(Path::new(&top_level).join(relative))
|
|
||||||
.map_err(|err| Error::TarWrite(source_dist_path.clone(), err))?;
|
|
||||||
header.set_size(0);
|
|
||||||
header.set_cksum();
|
|
||||||
tar.append(&header, io::empty())
|
|
||||||
.map_err(|err| Error::TarWrite(source_dist_path.clone(), err))?;
|
|
||||||
continue;
|
|
||||||
} else if entry.file_type().is_file() {
|
|
||||||
header.set_size(metadata.len());
|
|
||||||
header.set_cksum();
|
|
||||||
tar.append_data(
|
|
||||||
&mut header,
|
|
||||||
Path::new(&top_level).join(relative),
|
|
||||||
BufReader::new(File::open(entry.path())?),
|
|
||||||
)
|
|
||||||
.map_err(|err| Error::TarWrite(source_dist_path.clone(), err))?;
|
|
||||||
} else {
|
|
||||||
return Err(Error::UnsupportedFileType(
|
|
||||||
relative.clone(),
|
|
||||||
entry.file_type(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tar.finish()
|
tar.finish()
|
||||||
|
@ -560,6 +549,55 @@ pub fn build_source_dist(
|
||||||
Ok(filename)
|
Ok(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add a file or a directory to a source distribution.
|
||||||
|
fn add_source_dist_entry(
|
||||||
|
tar: &mut Builder<GzEncoder<File>>,
|
||||||
|
entry: &DirEntry,
|
||||||
|
top_level: &str,
|
||||||
|
source_dist_path: &Path,
|
||||||
|
relative: &Path,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
debug!("Including {}", relative.user_display());
|
||||||
|
|
||||||
|
let metadata = fs_err::metadata(entry.path())?;
|
||||||
|
let mut header = Header::new_gnu();
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
header.set_mode(std::os::unix::fs::MetadataExt::mode(&metadata));
|
||||||
|
}
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
header.set_mode(0o644);
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.file_type().is_dir() {
|
||||||
|
header.set_entry_type(EntryType::Directory);
|
||||||
|
header
|
||||||
|
.set_path(Path::new(&top_level).join(relative))
|
||||||
|
.map_err(|err| Error::TarWrite(source_dist_path.to_path_buf(), err))?;
|
||||||
|
header.set_size(0);
|
||||||
|
header.set_cksum();
|
||||||
|
tar.append(&header, io::empty())
|
||||||
|
.map_err(|err| Error::TarWrite(source_dist_path.to_path_buf(), err))?;
|
||||||
|
Ok(())
|
||||||
|
} else if entry.file_type().is_file() {
|
||||||
|
header.set_size(metadata.len());
|
||||||
|
header.set_cksum();
|
||||||
|
tar.append_data(
|
||||||
|
&mut header,
|
||||||
|
Path::new(&top_level).join(relative),
|
||||||
|
BufReader::new(File::open(entry.path())?),
|
||||||
|
)
|
||||||
|
.map_err(|err| Error::TarWrite(source_dist_path.to_path_buf(), err))?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::UnsupportedFileType(
|
||||||
|
relative.to_path_buf(),
|
||||||
|
entry.file_type(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Write the dist-info directory to the output directory without building the wheel.
|
/// Write the dist-info directory to the output directory without building the wheel.
|
||||||
pub fn metadata(
|
pub fn metadata(
|
||||||
source_tree: &Path,
|
source_tree: &Path,
|
||||||
|
|
|
@ -46,7 +46,14 @@ fn test_record() {
|
||||||
fn test_determinism() {
|
fn test_determinism() {
|
||||||
let temp1 = TempDir::new().unwrap();
|
let temp1 = TempDir::new().unwrap();
|
||||||
let uv_backend = Path::new("../../scripts/packages/uv_backend");
|
let uv_backend = Path::new("../../scripts/packages/uv_backend");
|
||||||
build_wheel(uv_backend, temp1.path(), None, "1.0.0+test").unwrap();
|
build_wheel(
|
||||||
|
uv_backend,
|
||||||
|
temp1.path(),
|
||||||
|
None,
|
||||||
|
WheelSettings::default(),
|
||||||
|
"1.0.0+test",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Touch the file to check that we don't serialize the last modified date.
|
// Touch the file to check that we don't serialize the last modified date.
|
||||||
fs_err::write(
|
fs_err::write(
|
||||||
|
@ -56,7 +63,14 @@ fn test_determinism() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let temp2 = TempDir::new().unwrap();
|
let temp2 = TempDir::new().unwrap();
|
||||||
build_wheel(uv_backend, temp2.path(), None, "1.0.0+test").unwrap();
|
build_wheel(
|
||||||
|
uv_backend,
|
||||||
|
temp2.path(),
|
||||||
|
None,
|
||||||
|
WheelSettings::default(),
|
||||||
|
"1.0.0+test",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let wheel_filename = "uv_backend-0.1.0-py3-none-any.whl";
|
let wheel_filename = "uv_backend-0.1.0-py3-none-any.whl";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::commands::ExitStatus;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uv_build_backend::SourceDistSettings;
|
use uv_build_backend::{SourceDistSettings, WheelSettings};
|
||||||
|
|
||||||
pub(crate) fn build_sdist(sdist_directory: &Path) -> Result<ExitStatus> {
|
pub(crate) fn build_sdist(sdist_directory: &Path) -> Result<ExitStatus> {
|
||||||
let filename = uv_build_backend::build_source_dist(
|
let filename = uv_build_backend::build_source_dist(
|
||||||
|
@ -24,6 +24,7 @@ pub(crate) fn build_wheel(
|
||||||
&env::current_dir()?,
|
&env::current_dir()?,
|
||||||
wheel_directory,
|
wheel_directory,
|
||||||
metadata_directory,
|
metadata_directory,
|
||||||
|
WheelSettings::default(),
|
||||||
uv_version::version(),
|
uv_version::version(),
|
||||||
)?;
|
)?;
|
||||||
println!("{filename}");
|
println!("{filename}");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue