mirror of https://github.com/astral-sh/uv
Add editable to pip-install
This commit is contained in:
parent
77c6e6fa6c
commit
754c99ea75
|
|
@ -8,7 +8,7 @@ use requirements_txt::EditableRequirement;
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LocalEditable {
|
pub struct LocalEditable {
|
||||||
pub requirement: EditableRequirement,
|
pub requirement: EditableRequirement,
|
||||||
/// Either the path to the editable or its checkout
|
/// Either the path to the editable or its checkout.
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ use pep440_rs::Version;
|
||||||
use pep508_rs::VerbatimUrl;
|
use pep508_rs::VerbatimUrl;
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
use pypi_types::{File, IndexUrl};
|
use pypi_types::{File, IndexUrl};
|
||||||
|
use requirements_txt::EditableRequirement;
|
||||||
|
|
||||||
pub use crate::any::*;
|
pub use crate::any::*;
|
||||||
pub use crate::cached::*;
|
pub use crate::cached::*;
|
||||||
|
|
@ -243,6 +244,28 @@ impl Dist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a [`Dist`] for a local editable distribution.
|
||||||
|
pub fn from_editable(name: PackageName, editable: LocalEditable) -> Result<Self, Error> {
|
||||||
|
match editable.requirement {
|
||||||
|
EditableRequirement::Path { url, path } => {
|
||||||
|
Ok(Self::Source(SourceDist::Path(PathSourceDist {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
path,
|
||||||
|
editable: true,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
EditableRequirement::Url(url) => Ok(Self::Source(SourceDist::Path(PathSourceDist {
|
||||||
|
name,
|
||||||
|
path: url
|
||||||
|
.to_file_path()
|
||||||
|
.map_err(|()| Error::UrlFilename(url.to_url()))?,
|
||||||
|
url,
|
||||||
|
editable: true,
|
||||||
|
}))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`File`] instance, if this dist is from a registry with simple json api support
|
/// Returns the [`File`] instance, if this dist is from a registry with simple json api support
|
||||||
pub fn file(&self) -> Option<&File> {
|
pub fn file(&self) -> Option<&File> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
|
use requirements_txt::EditableRequirement;
|
||||||
|
|
||||||
use crate::{BuiltDist, Dist, SourceDist};
|
use crate::{BuiltDist, Dist, PathSourceDist, SourceDist};
|
||||||
|
|
||||||
/// A set of packages pinned at specific versions.
|
/// A set of packages pinned at specific versions.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
|
|
@ -45,17 +46,45 @@ impl Resolution {
|
||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the set of [`Requirement`]s that this resolution represents.
|
/// Return the set of [`Requirement`]s that this resolution represents, exclusive of any
|
||||||
|
/// editable requirements.
|
||||||
pub fn requirements(&self) -> Vec<Requirement> {
|
pub fn requirements(&self) -> Vec<Requirement> {
|
||||||
let mut requirements = self
|
let mut requirements = self
|
||||||
.0
|
.0
|
||||||
.values()
|
.values()
|
||||||
.cloned()
|
.filter_map(|dist| match dist {
|
||||||
.map(Requirement::from)
|
Dist::Source(SourceDist::Path(PathSourceDist { editable: true, .. })) => None,
|
||||||
|
dist => Some(Requirement::from(dist.clone())),
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
requirements.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
requirements.sort_unstable_by(|a, b| a.name.cmp(&b.name));
|
||||||
requirements
|
requirements
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the set of [`EditableRequirement`]s that this resolution represents.
|
||||||
|
pub fn editable_requirements(&self) -> Vec<EditableRequirement> {
|
||||||
|
let mut requirements = self
|
||||||
|
.0
|
||||||
|
.values()
|
||||||
|
.filter_map(|dist| {
|
||||||
|
let Dist::Source(SourceDist::Path(PathSourceDist {
|
||||||
|
url,
|
||||||
|
path,
|
||||||
|
editable: true,
|
||||||
|
..
|
||||||
|
})) = dist
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(EditableRequirement::Path {
|
||||||
|
path: path.clone(),
|
||||||
|
url: url.clone(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
requirements.sort_unstable_by(|a, b| a.url().cmp(b.url()));
|
||||||
|
requirements
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Dist> for Requirement {
|
impl From<Dist> for Requirement {
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,25 @@
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use tempfile::tempdir_in;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use distribution_types::{AnyDist, Metadata, Resolution};
|
use distribution_types::{AnyDist, LocalEditable, Metadata, Resolution};
|
||||||
use install_wheel_rs::linker::LinkMode;
|
use install_wheel_rs::linker::LinkMode;
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::{MarkerEnvironment, Requirement};
|
||||||
use platform_host::Platform;
|
use platform_host::Platform;
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
use puffin_cache::Cache;
|
use puffin_cache::Cache;
|
||||||
use puffin_client::RegistryClientBuilder;
|
use puffin_client::{RegistryClient, RegistryClientBuilder};
|
||||||
use puffin_dispatch::BuildDispatch;
|
use puffin_dispatch::BuildDispatch;
|
||||||
use puffin_installer::{Downloader, InstallPlan, Reinstall, SitePackages};
|
use puffin_installer::{
|
||||||
|
BuiltEditable, Downloader, EditableMode, InstallPlan, Reinstall, SitePackages,
|
||||||
|
};
|
||||||
use puffin_interpreter::Virtualenv;
|
use puffin_interpreter::Virtualenv;
|
||||||
use puffin_resolver::{
|
use puffin_resolver::{
|
||||||
Manifest, PreReleaseMode, ResolutionGraph, ResolutionMode, ResolutionOptions, Resolver,
|
Manifest, PreReleaseMode, ResolutionGraph, ResolutionMode, ResolutionOptions, Resolver,
|
||||||
|
|
@ -73,17 +77,14 @@ pub(crate) async fn pip_install(
|
||||||
// enough to let us remove this check. But right now, for large environments, it's an order of
|
// enough to let us remove this check. But right now, for large environments, it's an order of
|
||||||
// magnitude faster to validate the environment than to resolve the requirements.
|
// magnitude faster to validate the environment than to resolve the requirements.
|
||||||
if reinstall.is_none() && satisfied(&spec, &venv)? {
|
if reinstall.is_none() && satisfied(&spec, &venv)? {
|
||||||
let s = if spec.requirements.len() == 1 {
|
let num_requirements = spec.requirements.len() + spec.editables.len();
|
||||||
""
|
let s = if num_requirements == 1 { "" } else { "s" };
|
||||||
} else {
|
|
||||||
"s"
|
|
||||||
};
|
|
||||||
writeln!(
|
writeln!(
|
||||||
printer,
|
printer,
|
||||||
"{}",
|
"{}",
|
||||||
format!(
|
format!(
|
||||||
"Audited {} in {}",
|
"Audited {} in {}",
|
||||||
format!("{} package{}", spec.requirements.len(), s).bold(),
|
format!("{num_requirements} package{s}").bold(),
|
||||||
elapsed(start.elapsed())
|
elapsed(start.elapsed())
|
||||||
)
|
)
|
||||||
.dimmed()
|
.dimmed()
|
||||||
|
|
@ -91,18 +92,61 @@ pub(crate) async fn pip_install(
|
||||||
return Ok(ExitStatus::Success);
|
return Ok(ExitStatus::Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
let editable_requirements = spec.editables.clone();
|
// Determine the compatible platform tags.
|
||||||
|
let tags = Tags::from_interpreter(venv.interpreter())?;
|
||||||
|
|
||||||
|
// Determine the interpreter to use for resolution.
|
||||||
|
let interpreter = venv.interpreter().clone();
|
||||||
|
|
||||||
|
// Determine the markers to use for resolution.
|
||||||
|
let markers = venv.interpreter().markers();
|
||||||
|
|
||||||
|
// Instantiate a client.
|
||||||
|
let client = RegistryClientBuilder::new(cache.clone())
|
||||||
|
.index_urls(index_urls.clone())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let options = ResolutionOptions::new(resolution_mode, prerelease_mode, exclude_newer);
|
||||||
|
let build_dispatch = BuildDispatch::new(
|
||||||
|
client.clone(),
|
||||||
|
cache.clone(),
|
||||||
|
interpreter,
|
||||||
|
fs::canonicalize(venv.python_executable())?,
|
||||||
|
no_build,
|
||||||
|
index_urls.clone(),
|
||||||
|
)
|
||||||
|
.with_options(options);
|
||||||
|
|
||||||
|
// Build all editable distributions. The editables are shared between resolution and
|
||||||
|
// installation, and should live for the duration of the command. If an editable is already
|
||||||
|
// installed in the environment, we'll still re-build it here.
|
||||||
|
let editable_wheel_dir;
|
||||||
|
let editables = if spec.editables.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
editable_wheel_dir = tempdir_in(venv.root())?;
|
||||||
|
build_editables(
|
||||||
|
&spec.editables,
|
||||||
|
editable_wheel_dir.path(),
|
||||||
|
&cache,
|
||||||
|
&tags,
|
||||||
|
&client,
|
||||||
|
&build_dispatch,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
// Resolve the requirements.
|
// Resolve the requirements.
|
||||||
let resolution = match resolve(
|
let resolution = match resolve(
|
||||||
spec,
|
spec,
|
||||||
|
&editables,
|
||||||
reinstall,
|
reinstall,
|
||||||
resolution_mode,
|
&tags,
|
||||||
prerelease_mode,
|
markers,
|
||||||
&index_urls,
|
&client,
|
||||||
no_build,
|
&build_dispatch,
|
||||||
exclude_newer,
|
options,
|
||||||
&cache,
|
|
||||||
&venv,
|
&venv,
|
||||||
printer,
|
printer,
|
||||||
)
|
)
|
||||||
|
|
@ -124,11 +168,13 @@ pub(crate) async fn pip_install(
|
||||||
// Sync the environment.
|
// Sync the environment.
|
||||||
install(
|
install(
|
||||||
&resolution,
|
&resolution,
|
||||||
|
&editables,
|
||||||
reinstall,
|
reinstall,
|
||||||
&editable_requirements,
|
|
||||||
link_mode,
|
link_mode,
|
||||||
index_urls,
|
index_urls,
|
||||||
no_build,
|
&tags,
|
||||||
|
&client,
|
||||||
|
&build_dispatch,
|
||||||
&cache,
|
&cache,
|
||||||
&venv,
|
&venv,
|
||||||
printer,
|
printer,
|
||||||
|
|
@ -184,20 +230,74 @@ fn specification(
|
||||||
|
|
||||||
/// Returns `true` if the requirements are already satisfied.
|
/// Returns `true` if the requirements are already satisfied.
|
||||||
fn satisfied(spec: &RequirementsSpecification, venv: &Virtualenv) -> Result<bool, Error> {
|
fn satisfied(spec: &RequirementsSpecification, venv: &Virtualenv) -> Result<bool, Error> {
|
||||||
Ok(SitePackages::from_executable(venv)?.satisfies(&spec.requirements, &spec.constraints)?)
|
Ok(SitePackages::from_executable(venv)?.satisfies(
|
||||||
|
&spec.requirements,
|
||||||
|
&spec.editables,
|
||||||
|
&spec.constraints,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a set of editable distributions.
|
||||||
|
async fn build_editables(
|
||||||
|
editables: &[EditableRequirement],
|
||||||
|
editable_wheel_dir: &Path,
|
||||||
|
cache: &Cache,
|
||||||
|
tags: &Tags,
|
||||||
|
client: &RegistryClient,
|
||||||
|
build_dispatch: &BuildDispatch,
|
||||||
|
mut printer: Printer,
|
||||||
|
) -> Result<Vec<BuiltEditable>, Error> {
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
|
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
||||||
|
.with_reporter(DownloadReporter::from(printer).with_length(editables.len() as u64));
|
||||||
|
|
||||||
|
let editables: Vec<LocalEditable> = editables
|
||||||
|
.iter()
|
||||||
|
.map(|editable| match editable {
|
||||||
|
EditableRequirement::Path { path, .. } => Ok(LocalEditable {
|
||||||
|
requirement: editable.clone(),
|
||||||
|
path: path.clone(),
|
||||||
|
}),
|
||||||
|
EditableRequirement::Url(_) => {
|
||||||
|
bail!("Editable installs for URLs are not yet supported");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
|
let editables: Vec<_> = downloader
|
||||||
|
.build_editables(editables, editable_wheel_dir)
|
||||||
|
.await
|
||||||
|
.context("Failed to build editables")?
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let s = if editables.len() == 1 { "" } else { "s" };
|
||||||
|
writeln!(
|
||||||
|
printer,
|
||||||
|
"{}",
|
||||||
|
format!(
|
||||||
|
"Built {} in {}",
|
||||||
|
format!("{} editable{}", editables.len(), s).bold(),
|
||||||
|
elapsed(start.elapsed())
|
||||||
|
)
|
||||||
|
.dimmed()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(editables)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve a set of requirements, similar to running `pip-compile`.
|
/// Resolve a set of requirements, similar to running `pip-compile`.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn resolve(
|
async fn resolve(
|
||||||
spec: RequirementsSpecification,
|
spec: RequirementsSpecification,
|
||||||
|
editables: &[BuiltEditable],
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
resolution_mode: ResolutionMode,
|
tags: &Tags,
|
||||||
prerelease_mode: PreReleaseMode,
|
markers: &MarkerEnvironment,
|
||||||
index_urls: &IndexUrls,
|
client: &RegistryClient,
|
||||||
no_build: bool,
|
build_dispatch: &BuildDispatch,
|
||||||
exclude_newer: Option<DateTime<Utc>>,
|
options: ResolutionOptions,
|
||||||
cache: &Cache,
|
|
||||||
venv: &Virtualenv,
|
venv: &Virtualenv,
|
||||||
mut printer: Printer,
|
mut printer: Printer,
|
||||||
) -> Result<ResolutionGraph, Error> {
|
) -> Result<ResolutionGraph, Error> {
|
||||||
|
|
@ -225,43 +325,28 @@ async fn resolve(
|
||||||
.collect(),
|
.collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(charlie): Support editable installs.
|
// Map the editables to their metadata.
|
||||||
|
let editables = editables
|
||||||
|
.iter()
|
||||||
|
.map(|built_editable| {
|
||||||
|
(
|
||||||
|
built_editable.editable.clone(),
|
||||||
|
built_editable.metadata.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let manifest = Manifest::new(
|
let manifest = Manifest::new(
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
overrides,
|
overrides,
|
||||||
preferences,
|
preferences,
|
||||||
project,
|
project,
|
||||||
Vec::new(),
|
editables,
|
||||||
);
|
);
|
||||||
let options = ResolutionOptions::new(resolution_mode, prerelease_mode, exclude_newer);
|
|
||||||
|
|
||||||
// Determine the compatible platform tags.
|
|
||||||
let tags = Tags::from_interpreter(venv.interpreter())?;
|
|
||||||
|
|
||||||
// Determine the interpreter to use for resolution.
|
|
||||||
let interpreter = venv.interpreter().clone();
|
|
||||||
|
|
||||||
// Determine the markers to use for resolution.
|
|
||||||
let markers = venv.interpreter().markers();
|
|
||||||
|
|
||||||
// Instantiate a client.
|
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
|
||||||
.index_urls(index_urls.clone())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let build_dispatch = BuildDispatch::new(
|
|
||||||
client.clone(),
|
|
||||||
cache.clone(),
|
|
||||||
interpreter,
|
|
||||||
fs::canonicalize(venv.python_executable())?,
|
|
||||||
no_build,
|
|
||||||
index_urls.clone(),
|
|
||||||
)
|
|
||||||
.with_options(options);
|
|
||||||
|
|
||||||
// Resolve the dependencies.
|
// Resolve the dependencies.
|
||||||
let resolver = Resolver::new(manifest, options, markers, &tags, &client, &build_dispatch)
|
let resolver = Resolver::new(manifest, options, markers, tags, client, build_dispatch)
|
||||||
.with_reporter(ResolverReporter::from(printer));
|
.with_reporter(ResolverReporter::from(printer));
|
||||||
let resolution = resolver.resolve().await?;
|
let resolution = resolver.resolve().await?;
|
||||||
|
|
||||||
|
|
@ -284,41 +369,41 @@ async fn resolve(
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn install(
|
async fn install(
|
||||||
resolution: &Resolution,
|
resolution: &Resolution,
|
||||||
|
built_editables: &[BuiltEditable],
|
||||||
reinstall: &Reinstall,
|
reinstall: &Reinstall,
|
||||||
editables: &[EditableRequirement],
|
|
||||||
link_mode: LinkMode,
|
link_mode: LinkMode,
|
||||||
index_urls: IndexUrls,
|
index_urls: IndexUrls,
|
||||||
no_build: bool,
|
tags: &Tags,
|
||||||
|
client: &RegistryClient,
|
||||||
|
build_dispatch: &BuildDispatch,
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
venv: &Virtualenv,
|
venv: &Virtualenv,
|
||||||
mut printer: Printer,
|
mut printer: Printer,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
// Determine the current environment markers.
|
|
||||||
let tags = Tags::from_interpreter(venv.interpreter())?;
|
|
||||||
|
|
||||||
// Partition into those that should be linked from the cache (`local`), those that need to be
|
// Partition into those that should be linked from the cache (`local`), those that need to be
|
||||||
// downloaded (`remote`), and those that should be removed (`extraneous`).
|
// downloaded (`remote`), and those that should be removed (`extraneous`).
|
||||||
let InstallPlan {
|
let InstallPlan {
|
||||||
local,
|
local,
|
||||||
remote,
|
remote,
|
||||||
reinstalls,
|
reinstalls,
|
||||||
editables: _,
|
editables,
|
||||||
extraneous: _,
|
extraneous: _,
|
||||||
} = InstallPlan::from_requirements(
|
} = InstallPlan::from_requirements(
|
||||||
&resolution.requirements(),
|
&resolution.requirements(),
|
||||||
editables,
|
&resolution.editable_requirements(),
|
||||||
reinstall,
|
reinstall,
|
||||||
&index_urls,
|
&index_urls,
|
||||||
cache,
|
cache,
|
||||||
venv,
|
venv,
|
||||||
&tags,
|
tags,
|
||||||
|
EditableMode::Mutable,
|
||||||
)
|
)
|
||||||
.context("Failed to determine installation plan")?;
|
.context("Failed to determine installation plan")?;
|
||||||
|
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
if remote.is_empty() && local.is_empty() && reinstalls.is_empty() {
|
if remote.is_empty() && local.is_empty() && reinstalls.is_empty() && editables.is_empty() {
|
||||||
let s = if resolution.len() == 1 { "" } else { "s" };
|
let s = if resolution.len() == 1 { "" } else { "s" };
|
||||||
writeln!(
|
writeln!(
|
||||||
printer,
|
printer,
|
||||||
|
|
@ -334,12 +419,7 @@ async fn install(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate a client.
|
// Map any registry-based requirements back to those returned by the resolver.
|
||||||
let client = RegistryClientBuilder::new(cache.clone())
|
|
||||||
.index_urls(index_urls.clone())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Resolve any registry-based requirements.
|
|
||||||
let remote = remote
|
let remote = remote
|
||||||
.iter()
|
.iter()
|
||||||
.map(|dist| {
|
.map(|dist| {
|
||||||
|
|
@ -350,22 +430,25 @@ async fn install(
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Map any local editable requirements back to those that were built ahead of time.
|
||||||
|
let built_editables = editables
|
||||||
|
.iter()
|
||||||
|
.map(|editable| {
|
||||||
|
let built_editable = built_editables
|
||||||
|
.iter()
|
||||||
|
.find(|built_editable| built_editable.editable.requirement == *editable)
|
||||||
|
.expect("Editable should be built");
|
||||||
|
built_editable.wheel.clone()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Download, build, and unzip any missing distributions.
|
// Download, build, and unzip any missing distributions.
|
||||||
let wheels = if remote.is_empty() {
|
let wheels = if remote.is_empty() {
|
||||||
vec![]
|
vec![]
|
||||||
} else {
|
} else {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
|
|
||||||
let build_dispatch = BuildDispatch::new(
|
let downloader = Downloader::new(cache, tags, client, build_dispatch)
|
||||||
client.clone(),
|
|
||||||
cache.clone(),
|
|
||||||
venv.interpreter().clone(),
|
|
||||||
fs::canonicalize(venv.python_executable())?,
|
|
||||||
no_build,
|
|
||||||
index_urls.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let downloader = Downloader::new(cache, &tags, &client, &build_dispatch)
|
|
||||||
.with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64));
|
.with_reporter(DownloadReporter::from(printer).with_length(remote.len() as u64));
|
||||||
|
|
||||||
let wheels = downloader
|
let wheels = downloader
|
||||||
|
|
@ -404,7 +487,11 @@ async fn install(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Install the resolved distributions.
|
// Install the resolved distributions.
|
||||||
let wheels = wheels.into_iter().chain(local).collect::<Vec<_>>();
|
let wheels = wheels
|
||||||
|
.into_iter()
|
||||||
|
.chain(local)
|
||||||
|
.chain(built_editables)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
if !wheels.is_empty() {
|
if !wheels.is_empty() {
|
||||||
let start = std::time::Instant::now();
|
let start = std::time::Instant::now();
|
||||||
puffin_installer::Installer::new(venv)
|
puffin_installer::Installer::new(venv)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ use platform_tags::Tags;
|
||||||
use puffin_cache::Cache;
|
use puffin_cache::Cache;
|
||||||
use puffin_client::RegistryClientBuilder;
|
use puffin_client::RegistryClientBuilder;
|
||||||
use puffin_dispatch::BuildDispatch;
|
use puffin_dispatch::BuildDispatch;
|
||||||
use puffin_installer::{Downloader, InstallPlan, Reinstall, SitePackages};
|
use puffin_installer::{Downloader, EditableMode, InstallPlan, Reinstall, SitePackages};
|
||||||
use puffin_interpreter::Virtualenv;
|
use puffin_interpreter::Virtualenv;
|
||||||
use puffin_traits::OnceMap;
|
use puffin_traits::OnceMap;
|
||||||
use pypi_types::{IndexUrls, Yanked};
|
use pypi_types::{IndexUrls, Yanked};
|
||||||
|
|
@ -98,6 +98,7 @@ pub(crate) async fn sync_requirements(
|
||||||
cache,
|
cache,
|
||||||
&venv,
|
&venv,
|
||||||
&tags,
|
&tags,
|
||||||
|
EditableMode::Immutable,
|
||||||
)
|
)
|
||||||
.context("Failed to determine installation plan")?;
|
.context("Failed to determine installation plan")?;
|
||||||
|
|
||||||
|
|
@ -193,7 +194,7 @@ pub(crate) async fn sync_requirements(
|
||||||
DownloadReporter::from(printer).with_length((editables.len() + remote.len()) as u64),
|
DownloadReporter::from(printer).with_length((editables.len() + remote.len()) as u64),
|
||||||
);
|
);
|
||||||
|
|
||||||
// We must not cache editable wheels, so we put them in a temp dir.
|
// Build any editable requirements.
|
||||||
let editable_wheel_dir = tempdir_in(venv.root())?;
|
let editable_wheel_dir = tempdir_in(venv.root())?;
|
||||||
let built_editables = if editables.is_empty() {
|
let built_editables = if editables.is_empty() {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,10 @@ struct PipInstallArgs {
|
||||||
#[clap(short, long, group = "sources")]
|
#[clap(short, long, group = "sources")]
|
||||||
requirement: Vec<PathBuf>,
|
requirement: Vec<PathBuf>,
|
||||||
|
|
||||||
|
/// Install the editable package based on the provided local file path.
|
||||||
|
#[clap(short, long, group = "sources")]
|
||||||
|
editable: Vec<String>,
|
||||||
|
|
||||||
/// Constrain versions using the given requirements files.
|
/// Constrain versions using the given requirements files.
|
||||||
///
|
///
|
||||||
/// Constraints files are `requirements.txt`-like files that only control the _version_ of a
|
/// Constraints files are `requirements.txt`-like files that only control the _version_ of a
|
||||||
|
|
@ -480,6 +484,7 @@ async fn inner() -> Result<ExitStatus> {
|
||||||
.package
|
.package
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(RequirementsSource::Package)
|
.map(RequirementsSource::Package)
|
||||||
|
.chain(args.editable.into_iter().map(RequirementsSource::Editable))
|
||||||
.chain(args.requirement.into_iter().map(RequirementsSource::from))
|
.chain(args.requirement.into_iter().map(RequirementsSource::from))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let constraints = args
|
let constraints = args
|
||||||
|
|
|
||||||
|
|
@ -2560,7 +2560,7 @@ fn compile_editable() -> Result<()> {
|
||||||
requirements_in.write_str(indoc! {r"
|
requirements_in.write_str(indoc! {r"
|
||||||
-e ../../scripts/editable-installs/poetry_editable
|
-e ../../scripts/editable-installs/poetry_editable
|
||||||
-e ../../scripts/editable-installs/maturin_editable
|
-e ../../scripts/editable-installs/maturin_editable
|
||||||
boltons # normal depedency for comparison
|
boltons # normal dependency for comparison
|
||||||
"
|
"
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -425,3 +425,128 @@ fn allow_incompatibilities() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn install_editable() -> Result<()> {
|
||||||
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
|
let cache_dir = assert_fs::TempDir::new()?;
|
||||||
|
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||||
|
|
||||||
|
// Install the editable package.
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec()
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.arg("pip-install")
|
||||||
|
.arg("-e")
|
||||||
|
.arg("../../scripts/editable-installs/poetry_editable")
|
||||||
|
.arg("--cache-dir")
|
||||||
|
.arg(cache_dir.path())
|
||||||
|
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||||
|
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Built 1 editable in [TIME]
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Downloaded 1 package in [TIME]
|
||||||
|
Installed 2 packages in [TIME]
|
||||||
|
+ numpy==1.26.2
|
||||||
|
+ poetry-editable @ ../../scripts/editable-installs/poetry_editable
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Install it again (no-op).
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec()
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.arg("pip-install")
|
||||||
|
.arg("-e")
|
||||||
|
.arg("../../scripts/editable-installs/poetry_editable")
|
||||||
|
.arg("--cache-dir")
|
||||||
|
.arg(cache_dir.path())
|
||||||
|
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||||
|
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add another, non-editable dependency.
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec()
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.arg("pip-install")
|
||||||
|
.arg("-e")
|
||||||
|
.arg("../../scripts/editable-installs/poetry_editable")
|
||||||
|
.arg("black")
|
||||||
|
.arg("--cache-dir")
|
||||||
|
.arg(cache_dir.path())
|
||||||
|
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||||
|
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Built 1 editable in [TIME]
|
||||||
|
Resolved 15 packages in [TIME]
|
||||||
|
Downloaded 13 packages in [TIME]
|
||||||
|
Installed 14 packages in [TIME]
|
||||||
|
+ aiohttp==3.9.1
|
||||||
|
+ aiosignal==1.3.1
|
||||||
|
+ attrs==23.1.0
|
||||||
|
+ black==23.12.0
|
||||||
|
+ click==8.1.7
|
||||||
|
+ frozenlist==1.4.1
|
||||||
|
+ idna==3.6
|
||||||
|
+ multidict==6.0.4
|
||||||
|
+ mypy-extensions==1.0.0
|
||||||
|
+ packaging==23.2
|
||||||
|
+ pathspec==0.12.1
|
||||||
|
+ platformdirs==4.1.0
|
||||||
|
- poetry-editable==0.1.0
|
||||||
|
+ poetry-editable @ ../../scripts/editable-installs/poetry_editable
|
||||||
|
+ yarl==1.9.4
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add another, editable dependency.
|
||||||
|
insta::with_settings!({
|
||||||
|
filters => INSTA_FILTERS.to_vec()
|
||||||
|
}, {
|
||||||
|
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||||
|
.arg("pip-install")
|
||||||
|
.arg("-e")
|
||||||
|
.arg("../../scripts/editable-installs/poetry_editable")
|
||||||
|
.arg("black")
|
||||||
|
.arg("-e")
|
||||||
|
.arg("../../scripts/editable-installs/maturin_editable")
|
||||||
|
.arg("--cache-dir")
|
||||||
|
.arg(cache_dir.path())
|
||||||
|
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||||
|
.env("CARGO_TARGET_DIR", "../../../target/target_install_editable"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Built 2 editables in [TIME]
|
||||||
|
Resolved 16 packages in [TIME]
|
||||||
|
Installed 2 packages in [TIME]
|
||||||
|
+ maturin-editable @ ../../scripts/editable-installs/maturin_editable
|
||||||
|
- poetry-editable==0.1.0
|
||||||
|
+ poetry-editable @ ../../scripts/editable-installs/poetry_editable
|
||||||
|
"###);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2090,7 +2090,7 @@ fn reinstall_package() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn install_editable() -> Result<()> {
|
fn sync_editable() -> Result<()> {
|
||||||
let temp_dir = assert_fs::TempDir::new()?;
|
let temp_dir = assert_fs::TempDir::new()?;
|
||||||
let cache_dir = assert_fs::TempDir::new()?;
|
let cache_dir = assert_fs::TempDir::new()?;
|
||||||
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ use platform_tags::Tags;
|
||||||
use puffin_build::{SourceBuild, SourceBuildContext};
|
use puffin_build::{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, EditableMode, InstallPlan, Installer, Reinstall};
|
||||||
use puffin_interpreter::{Interpreter, Virtualenv};
|
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||||
use puffin_resolver::{Manifest, ResolutionOptions, Resolver};
|
use puffin_resolver::{Manifest, ResolutionOptions, Resolver};
|
||||||
use puffin_traits::{BuildContext, BuildKind, OnceMap};
|
use puffin_traits::{BuildContext, BuildKind, OnceMap};
|
||||||
|
|
@ -147,6 +147,7 @@ impl BuildContext for BuildDispatch {
|
||||||
self.cache(),
|
self.cache(),
|
||||||
venv,
|
venv,
|
||||||
&tags,
|
&tags,
|
||||||
|
EditableMode::default(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Resolve any registry-based requirements.
|
// Resolve any registry-based requirements.
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,8 @@ use puffin_cache::Cache;
|
||||||
use puffin_client::RegistryClient;
|
use puffin_client::RegistryClient;
|
||||||
use puffin_distribution::{DistributionDatabase, DistributionDatabaseError, LocalWheel, Unzip};
|
use puffin_distribution::{DistributionDatabase, DistributionDatabaseError, LocalWheel, Unzip};
|
||||||
use puffin_traits::{BuildContext, OnceMap};
|
use puffin_traits::{BuildContext, OnceMap};
|
||||||
use pypi_types::Metadata21;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
use crate::editable::BuiltEditable;
|
||||||
pub struct BuiltEditable {
|
|
||||||
pub editable: LocalEditable,
|
|
||||||
pub wheel: CachedDist,
|
|
||||||
pub metadata: Metadata21,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
use distribution_types::{CachedDist, LocalEditable};
|
||||||
|
use pypi_types::Metadata21;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BuiltEditable {
|
||||||
|
pub editable: LocalEditable,
|
||||||
|
pub wheel: CachedDist,
|
||||||
|
pub metadata: Metadata21,
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
pub use downloader::{Downloader, Reporter as DownloadReporter};
|
pub use downloader::{Downloader, Reporter as DownloadReporter};
|
||||||
|
pub use editable::BuiltEditable;
|
||||||
pub use installer::{Installer, Reporter as InstallReporter};
|
pub use installer::{Installer, Reporter as InstallReporter};
|
||||||
pub use plan::{InstallPlan, Reinstall};
|
pub use plan::{EditableMode, InstallPlan, Reinstall};
|
||||||
pub use site_packages::SitePackages;
|
pub use site_packages::SitePackages;
|
||||||
pub use uninstall::uninstall;
|
pub use uninstall::uninstall;
|
||||||
|
|
||||||
mod downloader;
|
mod downloader;
|
||||||
|
mod editable;
|
||||||
mod installer;
|
mod installer;
|
||||||
mod plan;
|
mod plan;
|
||||||
mod site_packages;
|
mod site_packages;
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ pub struct InstallPlan {
|
||||||
impl InstallPlan {
|
impl InstallPlan {
|
||||||
/// Partition a set of requirements into those that should be linked from the cache, those that
|
/// Partition a set of requirements into those that should be linked from the cache, those that
|
||||||
/// need to be downloaded, and those that should be removed.
|
/// need to be downloaded, and those that should be removed.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn from_requirements(
|
pub fn from_requirements(
|
||||||
requirements: &[Requirement],
|
requirements: &[Requirement],
|
||||||
editable_requirements: &[EditableRequirement],
|
editable_requirements: &[EditableRequirement],
|
||||||
|
|
@ -54,6 +55,7 @@ impl InstallPlan {
|
||||||
cache: &Cache,
|
cache: &Cache,
|
||||||
venv: &Virtualenv,
|
venv: &Virtualenv,
|
||||||
tags: &Tags,
|
tags: &Tags,
|
||||||
|
editable_mode: EditableMode,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
// Index all the already-installed packages in site-packages.
|
// Index all the already-installed packages in site-packages.
|
||||||
let mut site_packages =
|
let mut site_packages =
|
||||||
|
|
@ -90,8 +92,17 @@ impl InstallPlan {
|
||||||
}
|
}
|
||||||
editables.push(editable.clone());
|
editables.push(editable.clone());
|
||||||
} else {
|
} else {
|
||||||
if site_packages.remove_editable(editable.raw()).is_some() {
|
if let Some(dist) = site_packages.remove_editable(editable.raw()) {
|
||||||
debug!("Treating editable requirement as immutable: {editable}");
|
match editable_mode {
|
||||||
|
EditableMode::Immutable => {
|
||||||
|
debug!("Treating editable requirement as immutable: {editable}");
|
||||||
|
}
|
||||||
|
EditableMode::Mutable => {
|
||||||
|
debug!("Treating editable requirement as mutable: {editable}");
|
||||||
|
reinstalls.push(dist);
|
||||||
|
editables.push(editable.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
editables.push(editable.clone());
|
editables.push(editable.clone());
|
||||||
}
|
}
|
||||||
|
|
@ -351,3 +362,13 @@ impl Reinstall {
|
||||||
matches!(self, Self::None)
|
matches!(self, Self::None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Copy, Clone)]
|
||||||
|
pub enum EditableMode {
|
||||||
|
/// Assume that editables are immutable, such that they're left untouched if already present
|
||||||
|
/// in the environment.
|
||||||
|
#[default]
|
||||||
|
Immutable,
|
||||||
|
/// Assume that editables are mutable, such that they're always reinstalled.
|
||||||
|
Mutable,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use pep440_rs::{Version, VersionSpecifiers};
|
||||||
use pep508_rs::Requirement;
|
use pep508_rs::Requirement;
|
||||||
use puffin_interpreter::Virtualenv;
|
use puffin_interpreter::Virtualenv;
|
||||||
use puffin_normalize::PackageName;
|
use puffin_normalize::PackageName;
|
||||||
|
use requirements_txt::EditableRequirement;
|
||||||
|
|
||||||
/// An index over the packages installed in an environment.
|
/// An index over the packages installed in an environment.
|
||||||
///
|
///
|
||||||
|
|
@ -221,12 +222,29 @@ impl<'a> SitePackages<'a> {
|
||||||
pub fn satisfies(
|
pub fn satisfies(
|
||||||
&self,
|
&self,
|
||||||
requirements: &[Requirement],
|
requirements: &[Requirement],
|
||||||
|
editables: &[EditableRequirement],
|
||||||
constraints: &[Requirement],
|
constraints: &[Requirement],
|
||||||
) -> Result<bool> {
|
) -> Result<bool> {
|
||||||
let mut requirements = requirements.to_vec();
|
let mut requirements = requirements.to_vec();
|
||||||
let mut seen =
|
let mut seen =
|
||||||
FxHashSet::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default());
|
FxHashSet::with_capacity_and_hasher(requirements.len(), BuildHasherDefault::default());
|
||||||
|
|
||||||
|
// Verify that all editable requirements are met.
|
||||||
|
for requirement in editables {
|
||||||
|
let Some(distribution) = self
|
||||||
|
.by_url
|
||||||
|
.get(requirement.raw())
|
||||||
|
.map(|idx| &self.distributions[*idx])
|
||||||
|
else {
|
||||||
|
// The package isn't installed.
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recurse into the dependencies.
|
||||||
|
requirements.extend(distribution.metadata()?.requires_dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that all non-editable requirements are met.
|
||||||
while let Some(requirement) = requirements.pop() {
|
while let Some(requirement) = requirements.pop() {
|
||||||
if !requirement.evaluate_markers(self.venv.interpreter().markers(), &[]) {
|
if !requirement.evaluate_markers(self.venv.interpreter().markers(), &[]) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use pubgrub::type_aliases::SelectedDependencies;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use distribution_types::{Dist, DistributionId, Identifier, LocalEditable, Metadata, PackageId};
|
use distribution_types::{Dist, LocalEditable, Metadata, PackageId};
|
||||||
use pep440_rs::Version;
|
use pep440_rs::Version;
|
||||||
use pep508_rs::{Requirement, VerbatimUrl};
|
use pep508_rs::{Requirement, VerbatimUrl};
|
||||||
use puffin_normalize::{ExtraName, PackageName};
|
use puffin_normalize::{ExtraName, PackageName};
|
||||||
|
|
@ -28,7 +28,7 @@ pub struct ResolutionGraph {
|
||||||
/// The underlying graph.
|
/// The underlying graph.
|
||||||
petgraph: petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed>,
|
petgraph: petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed>,
|
||||||
/// The set of editable requirements in this resolution.
|
/// The set of editable requirements in this resolution.
|
||||||
editables: FxHashMap<DistributionId, (LocalEditable, Metadata21)>,
|
editables: FxHashMap<PackageName, (LocalEditable, Metadata21)>,
|
||||||
/// Any diagnostics that were encountered while building the graph.
|
/// Any diagnostics that were encountered while building the graph.
|
||||||
diagnostics: Vec<Diagnostic>,
|
diagnostics: Vec<Diagnostic>,
|
||||||
}
|
}
|
||||||
|
|
@ -41,7 +41,7 @@ impl ResolutionGraph {
|
||||||
distributions: &OnceMap<PackageId, Metadata21>,
|
distributions: &OnceMap<PackageId, Metadata21>,
|
||||||
redirects: &OnceMap<Url, Url>,
|
redirects: &OnceMap<Url, Url>,
|
||||||
state: &State<PubGrubPackage, Range<PubGrubVersion>, PubGrubPriority>,
|
state: &State<PubGrubPackage, Range<PubGrubVersion>, PubGrubPriority>,
|
||||||
editables: FxHashMap<DistributionId, (LocalEditable, Metadata21)>,
|
editables: FxHashMap<PackageName, (LocalEditable, Metadata21)>,
|
||||||
) -> Result<Self, ResolveError> {
|
) -> Result<Self, ResolveError> {
|
||||||
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
|
// TODO(charlie): petgraph is a really heavy and unnecessary dependency here. We should
|
||||||
// write our own graph, given that our requirements are so simple.
|
// write our own graph, given that our requirements are so simple.
|
||||||
|
|
@ -66,11 +66,15 @@ impl ResolutionGraph {
|
||||||
inverse.insert(package_name, index);
|
inverse.insert(package_name, index);
|
||||||
}
|
}
|
||||||
PubGrubPackage::Package(package_name, None, Some(url)) => {
|
PubGrubPackage::Package(package_name, None, Some(url)) => {
|
||||||
let url = redirects.get(url).map_or_else(
|
let pinned_package = if let Some((editable, _)) = editables.get(package_name) {
|
||||||
|| url.clone(),
|
Dist::from_editable(package_name.clone(), editable.clone())?
|
||||||
|url| VerbatimUrl::unknown(url.value().clone()),
|
} else {
|
||||||
);
|
let url = redirects.get(url).map_or_else(
|
||||||
let pinned_package = Dist::from_url(package_name.clone(), url)?;
|
|| url.clone(),
|
||||||
|
|url| VerbatimUrl::unknown(url.value().clone()),
|
||||||
|
);
|
||||||
|
Dist::from_url(package_name.clone(), url)?
|
||||||
|
};
|
||||||
|
|
||||||
let index = petgraph.add_node(pinned_package);
|
let index = petgraph.add_node(pinned_package);
|
||||||
inverse.insert(package_name, index);
|
inverse.insert(package_name, index);
|
||||||
|
|
@ -173,13 +177,6 @@ impl ResolutionGraph {
|
||||||
|
|
||||||
/// Return the set of [`Requirement`]s that this graph represents.
|
/// Return the set of [`Requirement`]s that this graph represents.
|
||||||
pub fn requirements(&self) -> Vec<Requirement> {
|
pub fn requirements(&self) -> Vec<Requirement> {
|
||||||
// Collect and sort all packages.
|
|
||||||
let mut nodes = self
|
|
||||||
.petgraph
|
|
||||||
.node_indices()
|
|
||||||
.map(|node| (node, &self.petgraph[node]))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
nodes.sort_unstable_by_key(|(_, package)| package.name());
|
|
||||||
self.petgraph
|
self.petgraph
|
||||||
.node_indices()
|
.node_indices()
|
||||||
.map(|node| &self.petgraph[node])
|
.map(|node| &self.petgraph[node])
|
||||||
|
|
@ -199,14 +196,6 @@ impl ResolutionGraph {
|
||||||
) -> &petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed> {
|
) -> &petgraph::graph::Graph<Dist, Range<PubGrubVersion>, petgraph::Directed> {
|
||||||
&self.petgraph
|
&self.petgraph
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the set of editable requirements in this resolution.
|
|
||||||
///
|
|
||||||
/// The editable requirements themselves are unchanged, but their dependencies were added to the general
|
|
||||||
/// list of dependencies.
|
|
||||||
pub fn editables(&self) -> &FxHashMap<DistributionId, (LocalEditable, Metadata21)> {
|
|
||||||
&self.editables
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
|
/// Write the graph in the `{name}=={version}` format of requirements.txt that pip uses.
|
||||||
|
|
@ -222,9 +211,8 @@ impl std::fmt::Display for ResolutionGraph {
|
||||||
|
|
||||||
// Print out the dependency graph.
|
// Print out the dependency graph.
|
||||||
for (index, package) in nodes {
|
for (index, package) in nodes {
|
||||||
if let Some((editable_requirement, _)) = self.editables.get(&package.distribution_id())
|
if let Some((editable, _)) = self.editables.get(package.name()) {
|
||||||
{
|
writeln!(f, "-e {editable}")?;
|
||||||
writeln!(f, "-e {editable_requirement}")?;
|
|
||||||
} else {
|
} else {
|
||||||
writeln!(f, "{package}")?;
|
writeln!(f, "{package}")?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,7 @@ use url::Url;
|
||||||
|
|
||||||
use distribution_filename::WheelFilename;
|
use distribution_filename::WheelFilename;
|
||||||
use distribution_types::{
|
use distribution_types::{
|
||||||
BuiltDist, Dist, DistributionId, Identifier, LocalEditable, Metadata, PackageId, SourceDist,
|
BuiltDist, Dist, LocalEditable, Metadata, PackageId, SourceDist, VersionOrUrl,
|
||||||
VersionOrUrl,
|
|
||||||
};
|
};
|
||||||
use pep508_rs::{MarkerEnvironment, Requirement};
|
use pep508_rs::{MarkerEnvironment, Requirement};
|
||||||
use platform_tags::Tags;
|
use platform_tags::Tags;
|
||||||
|
|
@ -157,7 +156,7 @@ pub struct Resolver<'a, Provider: ResolverProvider> {
|
||||||
markers: &'a MarkerEnvironment,
|
markers: &'a MarkerEnvironment,
|
||||||
selector: CandidateSelector,
|
selector: CandidateSelector,
|
||||||
index: Arc<Index>,
|
index: Arc<Index>,
|
||||||
editables: FxHashMap<DistributionId, (LocalEditable, Metadata21)>,
|
editables: FxHashMap<PackageName, (LocalEditable, Metadata21)>,
|
||||||
reporter: Option<Arc<dyn Reporter>>,
|
reporter: Option<Arc<dyn Reporter>>,
|
||||||
provider: Provider,
|
provider: Provider,
|
||||||
}
|
}
|
||||||
|
|
@ -204,15 +203,17 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
||||||
// Determine all the editable requirements.
|
// Determine all the editable requirements.
|
||||||
let mut editables = FxHashMap::default();
|
let mut editables = FxHashMap::default();
|
||||||
for (editable_requirement, metadata) in &manifest.editables {
|
for (editable_requirement, metadata) in &manifest.editables {
|
||||||
let dist = Dist::from_url(metadata.name.clone(), editable_requirement.url().clone())
|
// Convert the editable requirement into a distribution.
|
||||||
|
let dist = Dist::from_editable(metadata.name.clone(), editable_requirement.clone())
|
||||||
.expect("This is a valid distribution");
|
.expect("This is a valid distribution");
|
||||||
|
|
||||||
// Mock editable responses.
|
// Mock editable responses.
|
||||||
index.distributions.register(&dist.package_id());
|
index.distributions.register(&dist.package_id());
|
||||||
index
|
index
|
||||||
.distributions
|
.distributions
|
||||||
.done(dist.package_id(), metadata.clone());
|
.done(dist.package_id(), metadata.clone());
|
||||||
editables.insert(
|
editables.insert(
|
||||||
dist.distribution_id(),
|
dist.name().clone(),
|
||||||
(editable_requirement.clone(), metadata.clone()),
|
(editable_requirement.clone(), metadata.clone()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue