mirror of https://github.com/astral-sh/uv
Implement editable installs in dev command (#566)
First step, sufficient to run ```shell cargo run --bin puffin-dev -- build --editable -w target/editables/ scripts/editable-installs/poetry_editable/ ``` and check the wheel to confirm its working. Tests will be added with the pip-sync integration.
This commit is contained in:
parent
c25d5240f1
commit
7c1dd71f66
|
|
@ -51,6 +51,8 @@ pub enum Error {
|
||||||
InvalidSourceDist(String),
|
InvalidSourceDist(String),
|
||||||
#[error("Invalid pyproject.toml")]
|
#[error("Invalid pyproject.toml")]
|
||||||
InvalidPyprojectToml(#[from] toml::de::Error),
|
InvalidPyprojectToml(#[from] toml::de::Error),
|
||||||
|
#[error("Editable installs with setup.py legacy builds are unsupported, please specify a build backend in pyproject.toml")]
|
||||||
|
EditableSetupPy,
|
||||||
#[error("Failed to install requirements from {0}")]
|
#[error("Failed to install requirements from {0}")]
|
||||||
RequirementsInstall(&'static str, #[source] anyhow::Error),
|
RequirementsInstall(&'static str, #[source] anyhow::Error),
|
||||||
#[error("Failed to create temporary virtual environment")]
|
#[error("Failed to create temporary virtual environment")]
|
||||||
|
|
@ -181,6 +183,24 @@ impl Pep517Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum BuildKind {
|
||||||
|
/// A regular PEP 517 wheel build
|
||||||
|
#[default]
|
||||||
|
Wheel,
|
||||||
|
/// A PEP 660 editable installation wheel build
|
||||||
|
Editable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BuildKind {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
BuildKind::Wheel => f.write_str("wheel"),
|
||||||
|
BuildKind::Editable => f.write_str("editable"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Uses an [`Arc`] internally, clone freely
|
/// Uses an [`Arc`] internally, clone freely
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct SourceBuildContext {
|
pub struct SourceBuildContext {
|
||||||
|
|
@ -212,6 +232,8 @@ pub struct SourceBuild {
|
||||||
metadata_directory: Option<PathBuf>,
|
metadata_directory: Option<PathBuf>,
|
||||||
/// Package id such as `foo-1.2.3`, for error reporting
|
/// Package id such as `foo-1.2.3`, for error reporting
|
||||||
package_id: String,
|
package_id: String,
|
||||||
|
/// Whether we do a regular PEP 517 build or an PEP 660 editable build
|
||||||
|
build_kind: BuildKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SourceBuild {
|
impl SourceBuild {
|
||||||
|
|
@ -226,6 +248,7 @@ impl SourceBuild {
|
||||||
build_context: &impl BuildContext,
|
build_context: &impl BuildContext,
|
||||||
source_build_context: SourceBuildContext,
|
source_build_context: SourceBuildContext,
|
||||||
source_dist: &str,
|
source_dist: &str,
|
||||||
|
build_kind: BuildKind,
|
||||||
) -> Result<SourceBuild, Error> {
|
) -> Result<SourceBuild, Error> {
|
||||||
let temp_dir = tempdir()?;
|
let temp_dir = tempdir()?;
|
||||||
|
|
||||||
|
|
@ -324,6 +347,7 @@ impl SourceBuild {
|
||||||
pep517_backend,
|
pep517_backend,
|
||||||
build_context,
|
build_context,
|
||||||
source_dist,
|
source_dist,
|
||||||
|
build_kind,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else if !source_tree.join("setup.py").is_file() {
|
} else if !source_tree.join("setup.py").is_file() {
|
||||||
|
|
@ -338,6 +362,7 @@ impl SourceBuild {
|
||||||
source_tree,
|
source_tree,
|
||||||
pep517_backend,
|
pep517_backend,
|
||||||
venv,
|
venv,
|
||||||
|
build_kind,
|
||||||
metadata_directory: None,
|
metadata_directory: None,
|
||||||
package_id: source_dist.to_string(),
|
package_id: source_dist.to_string(),
|
||||||
})
|
})
|
||||||
|
|
@ -423,16 +448,16 @@ impl SourceBuild {
|
||||||
if let Some(pep517_backend) = &self.pep517_backend {
|
if let Some(pep517_backend) = &self.pep517_backend {
|
||||||
// Prevent clashes from two puffin processes building wheels in parallel.
|
// Prevent clashes from two puffin processes building wheels in parallel.
|
||||||
let tmp_dir = tempdir_in(&wheel_dir)?;
|
let tmp_dir = tempdir_in(&wheel_dir)?;
|
||||||
let filename = self
|
let filename = self.pep517_build(tmp_dir.path(), pep517_backend).await?;
|
||||||
.pep517_build_wheel(tmp_dir.path(), pep517_backend)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let from = tmp_dir.path().join(&filename);
|
let from = tmp_dir.path().join(&filename);
|
||||||
let to = wheel_dir.join(&filename);
|
let to = wheel_dir.join(&filename);
|
||||||
fs_err::rename(from, to)?;
|
fs_err::rename(from, to)?;
|
||||||
|
|
||||||
Ok(filename)
|
Ok(filename)
|
||||||
} else {
|
} else {
|
||||||
|
if self.build_kind != BuildKind::Wheel {
|
||||||
|
return Err(Error::EditableSetupPy);
|
||||||
|
}
|
||||||
// We checked earlier that setup.py exists.
|
// We checked earlier that setup.py exists.
|
||||||
let python_interpreter = self.venv.python_executable();
|
let python_interpreter = self.venv.python_executable();
|
||||||
let output = Command::new(&python_interpreter)
|
let output = Command::new(&python_interpreter)
|
||||||
|
|
@ -468,7 +493,7 @@ impl SourceBuild {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn pep517_build_wheel(
|
async fn pep517_build(
|
||||||
&self,
|
&self,
|
||||||
wheel_dir: &Path,
|
wheel_dir: &Path,
|
||||||
pep517_backend: &Pep517Backend,
|
pep517_backend: &Pep517Backend,
|
||||||
|
|
@ -480,18 +505,18 @@ impl SourceBuild {
|
||||||
format!(r#""{}""#, escape_path_for_python(path))
|
format!(r#""{}""#, escape_path_for_python(path))
|
||||||
});
|
});
|
||||||
debug!(
|
debug!(
|
||||||
"Calling `{}.build_wheel(metadata_directory={})`",
|
"Calling `{}.build_{}(metadata_directory={})`",
|
||||||
pep517_backend.backend, metadata_directory
|
pep517_backend.backend, self.build_kind, metadata_directory
|
||||||
);
|
);
|
||||||
let escaped_wheel_dir = escape_path_for_python(wheel_dir);
|
let escaped_wheel_dir = escape_path_for_python(wheel_dir);
|
||||||
let script = formatdoc! {
|
let script = formatdoc! {
|
||||||
r#"{}
|
r#"{}
|
||||||
print(backend.build_wheel("{}", metadata_directory={}))
|
print(backend.build_{}("{}", metadata_directory={}))
|
||||||
"#, pep517_backend.backend_import(), escaped_wheel_dir, metadata_directory
|
"#, pep517_backend.backend_import(), self.build_kind, escaped_wheel_dir, metadata_directory
|
||||||
};
|
};
|
||||||
let span = info_span!(
|
let span = info_span!(
|
||||||
"run_python_script",
|
"run_python_script",
|
||||||
name="build_wheel",
|
name=format!("build_{}", self.build_kind),
|
||||||
python_version = %self.venv.interpreter().version()
|
python_version = %self.venv.interpreter().version()
|
||||||
);
|
);
|
||||||
let output =
|
let output =
|
||||||
|
|
@ -499,7 +524,10 @@ impl SourceBuild {
|
||||||
drop(span);
|
drop(span);
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(Error::from_command_output(
|
return Err(Error::from_command_output(
|
||||||
"Build backend failed to build wheel through `build_wheel()`".to_string(),
|
format!(
|
||||||
|
"Build backend failed to build wheel through `build_{}()`",
|
||||||
|
self.build_kind
|
||||||
|
),
|
||||||
&output,
|
&output,
|
||||||
&self.package_id,
|
&self.package_id,
|
||||||
));
|
));
|
||||||
|
|
@ -510,8 +538,10 @@ impl SourceBuild {
|
||||||
distribution_filename.filter(|wheel| wheel_dir.join(wheel).is_file())
|
distribution_filename.filter(|wheel| wheel_dir.join(wheel).is_file())
|
||||||
else {
|
else {
|
||||||
return Err(Error::from_command_output(
|
return Err(Error::from_command_output(
|
||||||
"Build backend did not return the wheel filename through `build_wheel()`"
|
format!(
|
||||||
.to_string(),
|
"Build backend failed to build wheel through `build_{}()`",
|
||||||
|
self.build_kind
|
||||||
|
),
|
||||||
&output,
|
&output,
|
||||||
&self.package_id,
|
&self.package_id,
|
||||||
));
|
));
|
||||||
|
|
@ -533,23 +563,24 @@ async fn create_pep517_build_environment(
|
||||||
pep517_backend: &Pep517Backend,
|
pep517_backend: &Pep517Backend,
|
||||||
build_context: &impl BuildContext,
|
build_context: &impl BuildContext,
|
||||||
package_id: &str,
|
package_id: &str,
|
||||||
|
build_kind: BuildKind,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
debug!(
|
debug!(
|
||||||
"Calling `{}.get_requires_for_build_wheel()`",
|
"Calling `{}.get_requires_for_build_{}()`",
|
||||||
pep517_backend.backend
|
pep517_backend.backend, build_kind
|
||||||
);
|
);
|
||||||
let script = formatdoc! {
|
let script = formatdoc! {
|
||||||
r#"
|
r#"
|
||||||
{}
|
{}
|
||||||
import json
|
import json
|
||||||
|
|
||||||
get_requires_for_build_wheel = getattr(backend, "get_requires_for_build_wheel", None)
|
get_requires_for_build = getattr(backend, "get_requires_for_build_{}", None)
|
||||||
if get_requires_for_build_wheel:
|
if get_requires_for_build:
|
||||||
requires = get_requires_for_build_wheel()
|
requires = get_requires_for_build()
|
||||||
else:
|
else:
|
||||||
requires = []
|
requires = []
|
||||||
print(json.dumps(requires))
|
print(json.dumps(requires))
|
||||||
"#, pep517_backend.backend_import()
|
"#, pep517_backend.backend_import(), build_kind
|
||||||
};
|
};
|
||||||
let span = info_span!(
|
let span = info_span!(
|
||||||
"get_requires_for_build_wheel",
|
"get_requires_for_build_wheel",
|
||||||
|
|
@ -560,8 +591,7 @@ async fn create_pep517_build_environment(
|
||||||
drop(span);
|
drop(span);
|
||||||
if !output.status.success() {
|
if !output.status.success() {
|
||||||
return Err(Error::from_command_output(
|
return Err(Error::from_command_output(
|
||||||
"Build backend failed to determine extra requires with `get_requires_for_build_wheel`"
|
format!("Build backend failed to determine extra requires with `build_{build_kind}()`",),
|
||||||
.to_string(),
|
|
||||||
&output,
|
&output,
|
||||||
package_id,
|
package_id,
|
||||||
));
|
));
|
||||||
|
|
@ -577,7 +607,7 @@ async fn create_pep517_build_environment(
|
||||||
let extra_requires: Vec<Requirement> = extra_requires.map_err(|err| {
|
let extra_requires: Vec<Requirement> = extra_requires.map_err(|err| {
|
||||||
Error::from_command_output(
|
Error::from_command_output(
|
||||||
format!(
|
format!(
|
||||||
"Build backend failed to return extra requires with `get_requires_for_build_wheel`: {err}"
|
"Build backend failed to return extra requires with `get_requires_for_build_{build_kind}`: {err}"
|
||||||
),
|
),
|
||||||
&output,
|
&output,
|
||||||
package_id,
|
package_id,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use clap::Parser;
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
|
|
||||||
use platform_host::Platform;
|
use platform_host::Platform;
|
||||||
|
use puffin_build::{BuildKind, SourceBuild, SourceBuildContext};
|
||||||
use puffin_cache::{Cache, CacheArgs};
|
use puffin_cache::{Cache, CacheArgs};
|
||||||
use puffin_client::RegistryClientBuilder;
|
use puffin_client::RegistryClientBuilder;
|
||||||
use puffin_dispatch::BuildDispatch;
|
use puffin_dispatch::BuildDispatch;
|
||||||
|
|
@ -26,6 +27,10 @@ pub(crate) struct BuildArgs {
|
||||||
sdist: PathBuf,
|
sdist: PathBuf,
|
||||||
/// The subdirectory to build within the source distribution.
|
/// The subdirectory to build within the source distribution.
|
||||||
subdirectory: Option<PathBuf>,
|
subdirectory: Option<PathBuf>,
|
||||||
|
/// You can edit the python sources of an editable install and the changes will be used without
|
||||||
|
/// the need to reinstall it.
|
||||||
|
#[clap(short, long)]
|
||||||
|
editable: bool,
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
cache_args: CacheArgs,
|
cache_args: CacheArgs,
|
||||||
}
|
}
|
||||||
|
|
@ -38,6 +43,11 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
|
||||||
} else {
|
} else {
|
||||||
env::current_dir()?
|
env::current_dir()?
|
||||||
};
|
};
|
||||||
|
let build_kind = if args.editable {
|
||||||
|
BuildKind::Editable
|
||||||
|
} else {
|
||||||
|
BuildKind::Wheel
|
||||||
|
};
|
||||||
|
|
||||||
let cache = Cache::try_from(args.cache_args)?;
|
let cache = Cache::try_from(args.cache_args)?;
|
||||||
|
|
||||||
|
|
@ -52,14 +62,16 @@ pub(crate) async fn build(args: BuildArgs) -> Result<PathBuf> {
|
||||||
false,
|
false,
|
||||||
IndexUrls::default(),
|
IndexUrls::default(),
|
||||||
);
|
);
|
||||||
let wheel = build_dispatch
|
let builder = SourceBuild::setup(
|
||||||
.build_source(
|
|
||||||
&args.sdist,
|
&args.sdist,
|
||||||
args.subdirectory.as_deref(),
|
args.subdirectory.as_deref(),
|
||||||
&wheel_dir,
|
build_dispatch.interpreter(),
|
||||||
|
&build_dispatch,
|
||||||
|
SourceBuildContext::default(),
|
||||||
// Good enough for the dev command
|
// Good enough for the dev command
|
||||||
&args.sdist.display().to_string(),
|
&args.sdist.display().to_string(),
|
||||||
|
build_kind,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(wheel_dir.join(wheel))
|
Ok(wheel_dir.join(builder.build(&wheel_dir).await?))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use tracing::{debug, instrument};
|
||||||
use distribution_types::{CachedDist, Metadata};
|
use distribution_types::{CachedDist, Metadata};
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use puffin_build::{SourceBuild, SourceBuildContext};
|
use puffin_build::{BuildKind, SourceBuild, SourceBuildContext};
|
||||||
use puffin_cache::Cache;
|
use puffin_cache::Cache;
|
||||||
use puffin_client::RegistryClient;
|
use puffin_client::RegistryClient;
|
||||||
use puffin_installer::{Downloader, InstallPlan, Installer, Reinstall};
|
use puffin_installer::{Downloader, InstallPlan, Installer, Reinstall};
|
||||||
|
|
@ -230,6 +230,7 @@ impl BuildContext for BuildDispatch {
|
||||||
self,
|
self,
|
||||||
self.source_build_context.clone(),
|
self.source_build_context.clone(),
|
||||||
source_dist,
|
source_dist,
|
||||||
|
BuildKind::Wheel,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(builder.build(wheel_dir).await?)
|
Ok(builder.build(wheel_dir).await?)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue