Skip to content

Commit

Permalink
Add --non-editable support to uv sync and uv export
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Sep 13, 2024
1 parent c188836 commit 4ef8dd0
Show file tree
Hide file tree
Showing 15 changed files with 321 additions and 30 deletions.
27 changes: 23 additions & 4 deletions crates/distribution-types/src/resolution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,15 +76,34 @@ impl Resolution {
pub fn filter(self, predicate: impl Fn(&ResolvedDist) -> bool) -> Self {
let packages = self
.packages
.iter()
.into_iter()
.filter(|(_, dist)| predicate(dist))
.map(|(name, dist)| (name.clone(), dist.clone()))
.collect::<BTreeMap<_, _>>();
let hashes = self
.hashes
.iter()
.into_iter()
.filter(|(name, _)| packages.contains_key(name))
.collect();
let diagnostics = self.diagnostics.clone();
Self {
packages,
hashes,
diagnostics,
}
}

/// Map over the resolved distributions in this resolution.
#[must_use]
pub fn map(self, predicate: impl Fn(ResolvedDist) -> ResolvedDist) -> Self {
let packages = self
.packages
.into_iter()
.map(|(name, dist)| (name, predicate(dist)))
.collect::<BTreeMap<_, _>>();
let hashes = self
.hashes
.into_iter()
.filter(|(name, _)| packages.contains_key(name))
.map(|(name, hashes)| (name.clone(), hashes.clone()))
.collect();
let diagnostics = self.diagnostics.clone();
Self {
Expand Down
12 changes: 12 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,10 @@ pub struct RunArgs {
#[arg(long, overrides_with("dev"))]
pub no_dev: bool,

/// Install the project and any local dependencies as non-editable.
#[arg(long)]
pub non_editable: bool,

/// The command to run.
///
/// If the path to a Python script (i.e., ending in `.py`), it will be
Expand Down Expand Up @@ -2541,6 +2545,10 @@ pub struct SyncArgs {
#[arg(long, overrides_with("dev"))]
pub no_dev: bool,

/// Install the project and any local dependencies as non-editable.
#[arg(long)]
pub non_editable: bool,

/// Do not remove extraneous packages present in the environment.
///
/// When enabled, uv will make the minimum necessary changes to satisfy the requirements.
Expand Down Expand Up @@ -2977,6 +2985,10 @@ pub struct ExportArgs {
#[arg(long, overrides_with("dev"))]
pub no_dev: bool,

/// Install the project and any local dependencies as non-editable.
#[arg(long)]
pub non_editable: bool,

/// Include hashes for all dependencies.
#[arg(long, overrides_with("no_hashes"), hide = true)]
pub hashes: bool,
Expand Down
17 changes: 17 additions & 0 deletions crates/uv-configuration/src/editable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum EditableMode {
#[default]
Editable,
NonEditable,
}

impl EditableMode {
/// Determine the editable mode based on the command-line arguments.
pub fn from_args(non_editable: bool) -> Self {
if non_editable {
Self::NonEditable
} else {
Self::Editable
}
}
}
2 changes: 2 additions & 0 deletions crates/uv-configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub use build_options::*;
pub use concurrency::*;
pub use config_settings::*;
pub use constraints::*;
pub use editable::*;
pub use export_format::*;
pub use extras::*;
pub use hash::*;
Expand All @@ -20,6 +21,7 @@ mod build_options;
mod concurrency;
mod config_settings;
mod constraints;
mod editable;
mod export_format;
mod extras;
mod hash;
Expand Down
47 changes: 34 additions & 13 deletions crates/uv-resolver/src/lock/requirements_txt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::collections::VecDeque;
use std::fmt::Formatter;
use std::path::{Path, PathBuf};
use std::path::{Component, Path, PathBuf};

use either::Either;
use petgraph::visit::IntoNodeReferences;
Expand All @@ -12,7 +13,7 @@ use url::Url;
use distribution_filename::{DistExtension, SourceDistExtension};
use pep508_rs::MarkerTree;
use pypi_types::{ParsedArchiveUrl, ParsedGitUrl};
use uv_configuration::{ExtrasSpecification, InstallOptions};
use uv_configuration::{EditableMode, ExtrasSpecification, InstallOptions};
use uv_fs::Simplified;
use uv_git::GitReference;
use uv_normalize::{ExtraName, GroupName, PackageName};
Expand All @@ -34,6 +35,7 @@ struct Node<'lock> {
pub struct RequirementsTxtExport<'lock> {
nodes: Vec<Node<'lock>>,
hashes: bool,
editable: EditableMode,
}

impl<'lock> RequirementsTxtExport<'lock> {
Expand All @@ -42,6 +44,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
root_name: &PackageName,
extras: &ExtrasSpecification,
dev: &[GroupName],
editable: EditableMode,
hashes: bool,
install_options: &'lock InstallOptions,
) -> Result<Self, LockError> {
Expand Down Expand Up @@ -147,7 +150,11 @@ impl<'lock> RequirementsTxtExport<'lock> {
NodeComparator::from(a.package).cmp(&NodeComparator::from(b.package))
});

Ok(Self { nodes, hashes })
Ok(Self {
nodes,
hashes,
editable,
})
}
}

Expand Down Expand Up @@ -191,21 +198,24 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
write!(f, "{} @ {}", package.id.name, url)?;
}
Source::Path(path) | Source::Directory(path) => {
if path.as_os_str().is_empty() {
write!(f, ".")?;
} else if path.is_absolute() {
if path.is_absolute() {
write!(f, "{}", Url::from_file_path(path).unwrap())?;
} else {
write!(f, "{}", path.portable_display())?;
write!(f, "{}", anchor(path).portable_display())?;
}
}
Source::Editable(path) => {
if path.as_os_str().is_empty() {
write!(f, "-e .")?;
} else {
write!(f, "-e {}", path.portable_display())?;
Source::Editable(path) => match self.editable {
EditableMode::Editable => {
write!(f, "-e {}", anchor(path).portable_display())?;
}
}
EditableMode::NonEditable => {
if path.is_absolute() {
write!(f, "{}", Url::from_file_path(path).unwrap())?;
} else {
write!(f, "{}", anchor(path).portable_display())?;
}
}
},
Source::Virtual(_) => {
continue;
}
Expand Down Expand Up @@ -252,3 +262,14 @@ impl<'lock> From<&'lock Package> for NodeComparator<'lock> {
}
}
}

/// Modify a relative [`Path`] to anchor it at the current working directory.
///
/// For example, given `foo/bar`, returns `./foo/bar`.
fn anchor(path: &Path) -> Cow<'_, Path> {
match path.components().next() {
None => Cow::Owned(PathBuf::from(".")),
Some(Component::CurDir | Component::ParentDir) => Cow::Borrowed(path),
_ => Cow::Owned(PathBuf::from("./").join(path)),
}
}
3 changes: 2 additions & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use uv_auth::{store_credentials_from_url, Credentials};
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, ExtrasSpecification, InstallOptions, SourceStrategy,
Concurrency, Constraints, EditableMode, ExtrasSpecification, InstallOptions, SourceStrategy,
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
Expand Down Expand Up @@ -726,6 +726,7 @@ async fn lock_and_sync(
&lock,
&extras,
dev,
EditableMode::Editable,
InstallOptions::default(),
Modifications::Sufficient,
settings.into(),
Expand Down
6 changes: 5 additions & 1 deletion crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use std::path::PathBuf;

use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::{Concurrency, ExportFormat, ExtrasSpecification, InstallOptions};
use uv_configuration::{
Concurrency, EditableMode, ExportFormat, ExtrasSpecification, InstallOptions,
};
use uv_fs::CWD;
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
Expand All @@ -31,6 +33,7 @@ pub(crate) async fn export(
output_file: Option<PathBuf>,
extras: ExtrasSpecification,
dev: bool,
editable: EditableMode,
locked: bool,
frozen: bool,
python: Option<String>,
Expand Down Expand Up @@ -128,6 +131,7 @@ pub(crate) async fn export(
project.project_name(),
&extras,
&dev,
editable,
hashes,
&install_options,
)?;
Expand Down
5 changes: 3 additions & 2 deletions crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use owo_colors::OwoColorize;
use pep508_rs::PackageName;
use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::{Concurrency, ExtrasSpecification, InstallOptions};
use uv_configuration::{Concurrency, EditableMode, ExtrasSpecification, InstallOptions};
use uv_fs::{Simplified, CWD};
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_scripts::Pep723Script;
Expand Down Expand Up @@ -188,8 +188,8 @@ pub(crate) async fn remove(

// Perform a full sync, because we don't know what exactly is affected by the removal.
// TODO(ibraheem): Should we accept CLI overrides for this? Should we even sync here?
let extras = ExtrasSpecification::All;
let dev = true;
let extras = ExtrasSpecification::All;
let install_options = InstallOptions::default();

// Initialize any shared state.
Expand All @@ -201,6 +201,7 @@ pub(crate) async fn remove(
&lock,
&extras,
dev,
EditableMode::Editable,
install_options,
Modifications::Exact,
settings.as_ref().into(),
Expand Down
4 changes: 3 additions & 1 deletion crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tracing::{debug, warn};
use uv_cache::Cache;
use uv_cli::ExternalCommand;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::{Concurrency, ExtrasSpecification, InstallOptions};
use uv_configuration::{Concurrency, EditableMode, ExtrasSpecification, InstallOptions};
use uv_distribution::LoweredRequirement;
use uv_fs::{PythonExt, Simplified, CWD};
use uv_installer::{SatisfiesResult, SitePackages};
Expand Down Expand Up @@ -58,6 +58,7 @@ pub(crate) async fn run(
no_config: bool,
extras: ExtrasSpecification,
dev: bool,
editable: EditableMode,
python: Option<String>,
settings: ResolverInstallerSettings,
python_preference: PythonPreference,
Expand Down Expand Up @@ -487,6 +488,7 @@ pub(crate) async fn run(
result.lock(),
&extras,
dev,
editable,
install_options,
Modifications::Sufficient,
settings.as_ref().into(),
Expand Down
45 changes: 43 additions & 2 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use anyhow::{Context, Result};
use itertools::Itertools;

use distribution_types::{Dist, ResolvedDist, SourceDist};
use distribution_types::{DirectorySourceDist, Dist, ResolvedDist, SourceDist};
use pep508_rs::MarkerTree;
use uv_auth::store_credentials_from_url;
use uv_cache::Cache;
use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, ExtrasSpecification, HashCheckingMode, InstallOptions,
Concurrency, Constraints, EditableMode, ExtrasSpecification, HashCheckingMode, InstallOptions,
};
use uv_dispatch::BuildDispatch;
use uv_fs::CWD;
Expand All @@ -34,6 +34,7 @@ pub(crate) async fn sync(
package: Option<PackageName>,
extras: ExtrasSpecification,
dev: bool,
editable: EditableMode,
install_options: InstallOptions,
modifications: Modifications,
python: Option<String>,
Expand Down Expand Up @@ -123,6 +124,7 @@ pub(crate) async fn sync(
&lock,
&extras,
dev,
editable,
install_options,
modifications,
settings.as_ref().into(),
Expand All @@ -147,6 +149,7 @@ pub(super) async fn do_sync(
lock: &Lock,
extras: &ExtrasSpecification,
dev: bool,
editable: EditableMode,
install_options: InstallOptions,
modifications: Modifications,
settings: InstallerSettingsRef<'_>,
Expand Down Expand Up @@ -232,6 +235,9 @@ pub(super) async fn do_sync(
// Always skip virtual projects, which shouldn't be built or installed.
let resolution = apply_no_virtual_project(resolution);

// If necessary, convert editable to non-editable distributions.
let resolution = apply_editable_mode(resolution, editable);

// Add all authenticated sources to the cache.
for url in index_locations.urls() {
store_credentials_from_url(url);
Expand Down Expand Up @@ -348,3 +354,38 @@ fn apply_no_virtual_project(
!dist.r#virtual
})
}

/// If necessary, convert any editable requirements to non-editable.
fn apply_editable_mode(
resolution: distribution_types::Resolution,
editable: EditableMode,
) -> distribution_types::Resolution {
match editable {
// No modifications are necessary for editable mode; retain any editable distributions.
EditableMode::Editable => resolution,

// Filter out any editable distributions.
EditableMode::NonEditable => resolution.map(|dist| {
let ResolvedDist::Installable(Dist::Source(SourceDist::Directory(
DirectorySourceDist {
name,
install_path,
editable: true,
r#virtual: false,
url,
},
))) = dist
else {
return dist;
};

ResolvedDist::Installable(Dist::Source(SourceDist::Directory(DirectorySourceDist {
name,
install_path,
editable: false,
r#virtual: false,
url,
})))
}),
}
}
Loading

0 comments on commit 4ef8dd0

Please sign in to comment.