mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00
Build backend: Build editable (#9230)
Support for editable installs. This is a simple PEP 660 implementation.
This commit is contained in:
parent
0913382aa5
commit
8e0389e2fd
3 changed files with 161 additions and 14 deletions
|
@ -357,8 +357,6 @@ pub fn build_wheel(
|
|||
entry.file_type(),
|
||||
));
|
||||
}
|
||||
|
||||
entry.path();
|
||||
}
|
||||
|
||||
// Add the license files
|
||||
|
@ -416,6 +414,63 @@ pub fn build_wheel(
|
|||
Ok(filename)
|
||||
}
|
||||
|
||||
/// Build a wheel from the source tree and place it in the output directory.
|
||||
pub fn build_editable(
|
||||
source_tree: &Path,
|
||||
wheel_dir: &Path,
|
||||
metadata_directory: Option<&Path>,
|
||||
uv_version: &str,
|
||||
) -> Result<WheelFilename, Error> {
|
||||
let contents = fs_err::read_to_string(source_tree.join("pyproject.toml"))?;
|
||||
let pyproject_toml = PyProjectToml::parse(&contents)?;
|
||||
pyproject_toml.check_build_system("1.0.0+test");
|
||||
|
||||
check_metadata_directory(source_tree, metadata_directory, &pyproject_toml)?;
|
||||
|
||||
let filename = WheelFilename {
|
||||
name: pyproject_toml.name().clone(),
|
||||
version: pyproject_toml.version().clone(),
|
||||
build_tag: None,
|
||||
python_tag: vec!["py3".to_string()],
|
||||
abi_tag: vec!["none".to_string()],
|
||||
platform_tag: vec!["any".to_string()],
|
||||
};
|
||||
|
||||
let wheel_path = wheel_dir.join(filename.to_string());
|
||||
debug!("Writing wheel at {}", wheel_path.user_display());
|
||||
let mut wheel_writer = ZipDirectoryWriter::new_wheel(File::create(&wheel_path)?);
|
||||
|
||||
debug!("Adding pth file to {}", wheel_path.user_display());
|
||||
let module_root = pyproject_toml
|
||||
.wheel_settings()
|
||||
.and_then(|wheel_settings| wheel_settings.module_root.as_deref())
|
||||
.unwrap_or_else(|| Path::new("src"));
|
||||
if module_root.is_absolute() {
|
||||
return Err(Error::AbsoluteModuleRoot(module_root.to_path_buf()));
|
||||
}
|
||||
let src_root = source_tree.join(module_root);
|
||||
let module_root = src_root.join(pyproject_toml.name().as_dist_info_name().as_ref());
|
||||
if !module_root.join("__init__.py").is_file() {
|
||||
return Err(Error::MissingModule(module_root));
|
||||
}
|
||||
wheel_writer.write_bytes(
|
||||
&format!("{}.pth", pyproject_toml.name().as_dist_info_name()),
|
||||
src_root.as_os_str().as_encoded_bytes(),
|
||||
)?;
|
||||
|
||||
debug!("Adding metadata files to: `{}`", wheel_path.user_display());
|
||||
let dist_info_dir = write_dist_info(
|
||||
&mut wheel_writer,
|
||||
&pyproject_toml,
|
||||
&filename,
|
||||
source_tree,
|
||||
uv_version,
|
||||
)?;
|
||||
wheel_writer.close(&dist_info_dir)?;
|
||||
|
||||
Ok(filename)
|
||||
}
|
||||
|
||||
/// Add the files and directories matching from the source tree matching any of the globs in the
|
||||
/// wheel subdirectory.
|
||||
fn wheel_subdir_from_globs(
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
#![allow(clippy::print_stdout)]
|
||||
|
||||
use crate::commands::ExitStatus;
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use uv_build_backend::SourceDistSettings;
|
||||
|
||||
/// PEP 517 hook to build a source distribution.
|
||||
pub(crate) fn build_sdist(sdist_directory: &Path) -> Result<ExitStatus> {
|
||||
let filename = uv_build_backend::build_source_dist(
|
||||
&env::current_dir()?,
|
||||
|
@ -13,9 +15,12 @@ pub(crate) fn build_sdist(sdist_directory: &Path) -> Result<ExitStatus> {
|
|||
SourceDistSettings::default(),
|
||||
uv_version::version(),
|
||||
)?;
|
||||
println!("{filename}");
|
||||
// Tell the build frontend about the name of the artifact we built
|
||||
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// PEP 517 hook to build a wheel.
|
||||
pub(crate) fn build_wheel(
|
||||
wheel_directory: &Path,
|
||||
metadata_directory: Option<&Path>,
|
||||
|
@ -26,38 +31,62 @@ pub(crate) fn build_wheel(
|
|||
metadata_directory,
|
||||
uv_version::version(),
|
||||
)?;
|
||||
println!("{filename}");
|
||||
// Tell the build frontend about the name of the artifact we built
|
||||
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// PEP 660 hook to build a wheel.
|
||||
pub(crate) fn build_editable(
|
||||
_wheel_directory: &Path,
|
||||
_metadata_directory: Option<&Path>,
|
||||
wheel_directory: &Path,
|
||||
metadata_directory: Option<&Path>,
|
||||
) -> Result<ExitStatus> {
|
||||
todo!()
|
||||
let filename = uv_build_backend::build_editable(
|
||||
&env::current_dir()?,
|
||||
wheel_directory,
|
||||
metadata_directory,
|
||||
uv_version::version(),
|
||||
)?;
|
||||
// Tell the build frontend about the name of the artifact we built
|
||||
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// Not used from Python code, exists for symmetry with PEP 517.
|
||||
pub(crate) fn get_requires_for_build_sdist() -> Result<ExitStatus> {
|
||||
todo!()
|
||||
unimplemented!("uv does not support extra requires")
|
||||
}
|
||||
|
||||
/// Not used from Python code, exists for symmetry with PEP 517.
|
||||
pub(crate) fn get_requires_for_build_wheel() -> Result<ExitStatus> {
|
||||
todo!()
|
||||
unimplemented!("uv does not support extra requires")
|
||||
}
|
||||
|
||||
/// PEP 517 hook to just emit metadata through `.dist-info`.
|
||||
pub(crate) fn prepare_metadata_for_build_wheel(metadata_directory: &Path) -> Result<ExitStatus> {
|
||||
let filename = uv_build_backend::metadata(
|
||||
&env::current_dir()?,
|
||||
metadata_directory,
|
||||
uv_version::version(),
|
||||
)?;
|
||||
println!("{filename}");
|
||||
// Tell the build frontend about the name of the artifact we built
|
||||
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
||||
/// Not used from Python code, exists for symmetry with PEP 660.
|
||||
pub(crate) fn get_requires_for_build_editable() -> Result<ExitStatus> {
|
||||
todo!()
|
||||
unimplemented!("uv does not support extra requires")
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_metadata_for_build_editable(_wheel_directory: &Path) -> Result<ExitStatus> {
|
||||
todo!()
|
||||
/// PEP 660 hook to just emit metadata through `.dist-info`.
|
||||
pub(crate) fn prepare_metadata_for_build_editable(metadata_directory: &Path) -> Result<ExitStatus> {
|
||||
let filename = uv_build_backend::metadata(
|
||||
&env::current_dir()?,
|
||||
metadata_directory,
|
||||
uv_version::version(),
|
||||
)?;
|
||||
// Tell the build frontend about the name of the artifact we built
|
||||
writeln!(&mut std::io::stdout(), "{filename}").context("stdout is closed")?;
|
||||
Ok(ExitStatus::Success)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ use indoc::indoc;
|
|||
use std::env;
|
||||
use std::io::BufReader;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use tempfile::TempDir;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
|
@ -142,3 +143,65 @@ fn built_by_uv_direct() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that editables work.
|
||||
///
|
||||
/// We can't test end-to-end here including the PEP 517 bridge code since we don't have a uv wheel,
|
||||
/// so we call the build backend directly.
|
||||
#[test]
|
||||
fn built_by_uv_editable() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
let built_by_uv = Path::new("../../scripts/packages/built-by-uv");
|
||||
|
||||
// Without the editable, pytest fails.
|
||||
context.pip_install().arg("pytest").assert().success();
|
||||
Command::new(context.interpreter())
|
||||
.arg("-m")
|
||||
.arg("pytest")
|
||||
.current_dir(built_by_uv)
|
||||
.assert()
|
||||
.failure();
|
||||
|
||||
// Build and install the editable. Normally, this should be one step with the editable never
|
||||
// been seen, but we have to split it for the test.
|
||||
let wheel_dir = TempDir::new()?;
|
||||
uv_snapshot!(context
|
||||
.build_backend()
|
||||
.arg("build-wheel")
|
||||
.arg(wheel_dir.path())
|
||||
.current_dir(built_by_uv), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
built_by_uv-0.1.0-py3-none-any.whl
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
context
|
||||
.pip_install()
|
||||
.arg(wheel_dir.path().join("built_by_uv-0.1.0-py3-none-any.whl"))
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
drop(wheel_dir);
|
||||
|
||||
// Now, pytest passes.
|
||||
uv_snapshot!(Command::new(context.interpreter())
|
||||
.arg("-m")
|
||||
.arg("pytest")
|
||||
// Avoid showing absolute paths
|
||||
.arg("--no-header")
|
||||
// Otherwise, the header has a different length on windows
|
||||
.arg("--quiet")
|
||||
.current_dir(built_by_uv), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
.. [100%]
|
||||
2 passed in [TIME]
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue