diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index f0c81fdc4..c8a785421 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -3155,9 +3155,14 @@ pub struct RunArgs { #[arg(long, conflicts_with_all = ["group", "all_groups", "no_dev"])] pub only_dev: bool, + /// Install any non-editable dependencies, including the project and any workspace members, as + /// editable. + #[arg(long, overrides_with = "no_editable", hide = true)] + pub editable: bool, + /// Install any editable dependencies, including the project and any workspace members, as /// non-editable. - #[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)] + #[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)] pub no_editable: bool, /// Do not remove extraneous packages present in the environment. @@ -3463,9 +3468,14 @@ pub struct SyncArgs { #[arg(long, conflicts_with_all = ["only_group", "only_dev"])] pub all_groups: bool, + /// Install any non-editable dependencies, including the project and any workspace members, as + /// editable. + #[arg(long, overrides_with = "no_editable", hide = true)] + pub editable: bool, + /// Install any editable dependencies, including the project and any workspace members, as /// non-editable. - #[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)] + #[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)] pub no_editable: bool, /// Do not remove extraneous packages present in the environment. @@ -4307,9 +4317,14 @@ pub struct ExportArgs { #[arg(long, overrides_with("no_header"), hide = true)] pub header: bool, + /// Export any non-editable dependencies, including the project and any workspace members, as + /// editable. + #[arg(long, overrides_with = "no_editable", hide = true)] + pub editable: bool, + /// Export any editable dependencies, including the project and any workspace members, as /// non-editable. - #[arg(long, value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)] + #[arg(long, overrides_with = "editable", value_parser = clap::builder::BoolishValueParser::new(), env = EnvVars::UV_NO_EDITABLE)] pub no_editable: bool, /// Include hashes for all dependencies. diff --git a/crates/uv-configuration/src/editable.rs b/crates/uv-configuration/src/editable.rs index d2474ffb0..fa6f7a87e 100644 --- a/crates/uv-configuration/src/editable.rs +++ b/crates/uv-configuration/src/editable.rs @@ -5,13 +5,12 @@ pub enum EditableMode { NonEditable, } -impl EditableMode { - /// Determine the editable mode based on the command-line arguments. - pub fn from_args(no_editable: bool) -> Self { - if no_editable { - Self::NonEditable - } else { +impl From for EditableMode { + fn from(value: bool) -> Self { + if value { Self::Editable + } else { + Self::NonEditable } } } diff --git a/crates/uv-resolver/src/lock/export/pylock_toml.rs b/crates/uv-resolver/src/lock/export/pylock_toml.rs index 828c4c5fd..60df87642 100644 --- a/crates/uv-resolver/src/lock/export/pylock_toml.rs +++ b/crates/uv-resolver/src/lock/export/pylock_toml.rs @@ -620,7 +620,7 @@ impl<'lock> PylockToml { extras: &ExtrasSpecificationWithDefaults, dev: &DependencyGroupsWithDefaults, annotate: bool, - editable: EditableMode, + editable: Option, install_options: &'lock InstallOptions, ) -> Result { // Extract the packages from the lock file. @@ -736,8 +736,9 @@ impl<'lock> PylockToml { .into_boxed_path(), ), editable: match editable { - EditableMode::NonEditable => None, - EditableMode::Editable => sdist.editable, + None => sdist.editable, + Some(EditableMode::NonEditable) => None, + Some(EditableMode::Editable) => Some(true), }, subdirectory: None, }), diff --git a/crates/uv-resolver/src/lock/export/requirements_txt.rs b/crates/uv-resolver/src/lock/export/requirements_txt.rs index 4cca73a34..61a8daa44 100644 --- a/crates/uv-resolver/src/lock/export/requirements_txt.rs +++ b/crates/uv-resolver/src/lock/export/requirements_txt.rs @@ -24,7 +24,7 @@ use crate::{Installable, LockError}; pub struct RequirementsTxtExport<'lock> { nodes: Vec>, hashes: bool, - editable: EditableMode, + editable: Option, } impl<'lock> RequirementsTxtExport<'lock> { @@ -34,7 +34,7 @@ impl<'lock> RequirementsTxtExport<'lock> { extras: &ExtrasSpecificationWithDefaults, dev: &DependencyGroupsWithDefaults, annotate: bool, - editable: EditableMode, + editable: Option, hashes: bool, install_options: &'lock InstallOptions, ) -> Result { @@ -129,10 +129,10 @@ impl std::fmt::Display for RequirementsTxtExport<'_> { } } Source::Editable(path) => match self.editable { - EditableMode::Editable => { + None | Some(EditableMode::Editable) => { write!(f, "-e {}", anchor(path).portable_display())?; } - EditableMode::NonEditable => { + Some(EditableMode::NonEditable) => { if path.is_absolute() { write!( f, diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index ac9fce21e..c449416e4 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -18,8 +18,7 @@ use uv_cache_key::RepositoryUrl; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DevMode, DryRun, - EditableMode, ExtrasSpecification, ExtrasSpecificationWithDefaults, InstallOptions, - SourceStrategy, + ExtrasSpecification, ExtrasSpecificationWithDefaults, InstallOptions, SourceStrategy, }; use uv_dispatch::BuildDispatch; use uv_distribution::{DistributionDatabase, LoweredExtraBuildDependencies}; @@ -1149,7 +1148,7 @@ async fn lock_and_sync( venv, extras, groups, - EditableMode::Editable, + None, InstallOptions::new( no_install_project, no_install_workspace, diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index d944feeac..600374218 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -64,7 +64,7 @@ pub(crate) async fn export( output_file: Option, extras: ExtrasSpecification, groups: DependencyGroups, - editable: EditableMode, + editable: Option, locked: bool, frozen: bool, include_annotations: bool, diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index bcc24ee1c..03a3f04fc 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -10,7 +10,7 @@ use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::BaseClientBuilder; use uv_configuration::{ - Concurrency, DependencyGroups, DryRun, EditableMode, ExtrasSpecification, InstallOptions, + Concurrency, DependencyGroups, DryRun, ExtrasSpecification, InstallOptions, }; use uv_fs::Simplified; use uv_normalize::PackageName; @@ -355,7 +355,7 @@ pub(crate) async fn remove( venv, &extras, &groups, - EditableMode::Editable, + None, InstallOptions::default(), Modifications::Exact, None, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 1cdbe6e1f..e6cf2f50f 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -93,7 +93,7 @@ pub(crate) async fn run( no_config: bool, extras: ExtrasSpecification, groups: DependencyGroups, - editable: EditableMode, + editable: Option, modifications: Modifications, python: Option, python_platform: Option, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index fa65a6bc2..c5511134d 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -64,7 +64,7 @@ pub(crate) async fn sync( package: Option, extras: ExtrasSpecification, groups: DependencyGroups, - editable: EditableMode, + editable: Option, install_options: InstallOptions, modifications: Modifications, python: Option, @@ -559,7 +559,7 @@ pub(super) async fn do_sync( venv: &PythonEnvironment, extras: &ExtrasSpecificationWithDefaults, groups: &DependencyGroupsWithDefaults, - editable: EditableMode, + editable: Option, install_options: InstallOptions, modifications: Modifications, python_platform: Option<&TargetTriple>, @@ -826,20 +826,48 @@ fn apply_no_virtual_project(resolution: Resolution) -> Resolution { } /// If necessary, convert any editable requirements to non-editable. -fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolution { +fn apply_editable_mode(resolution: Resolution, editable: Option) -> Resolution { match editable { // No modifications are necessary for editable mode; retain any editable distributions. - EditableMode::Editable => resolution, + None => resolution, - // Filter out any editable distributions. - EditableMode::NonEditable => resolution.map(|dist| { + // Filter out any non-editable distributions. + Some(EditableMode::Editable) => resolution.map(|dist| { let ResolvedDist::Installable { dist, version } = dist else { return None; }; let Dist::Source(SourceDist::Directory(DirectorySourceDist { name, install_path, - editable: Some(true), + editable: None | Some(false), + r#virtual, + url, + })) = dist.as_ref() + else { + return None; + }; + + Some(ResolvedDist::Installable { + dist: Arc::new(Dist::Source(SourceDist::Directory(DirectorySourceDist { + name: name.clone(), + install_path: install_path.clone(), + editable: Some(true), + r#virtual: *r#virtual, + url: url.clone(), + }))), + version: version.clone(), + }) + }), + + // Filter out any editable distributions. + Some(EditableMode::NonEditable) => resolution.map(|dist| { + let ResolvedDist::Installable { dist, version } = dist else { + return None; + }; + let Dist::Source(SourceDist::Directory(DirectorySourceDist { + name, + install_path, + editable: None | Some(true), r#virtual, url, })) = dist.as_ref() diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index 4c60ecae7..213af43ea 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -11,8 +11,8 @@ use uv_cli::version::VersionInfo; use uv_cli::{VersionBump, VersionFormat}; use uv_client::BaseClientBuilder; use uv_configuration::{ - Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode, - ExtrasSpecification, InstallOptions, + Concurrency, DependencyGroups, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, + InstallOptions, }; use uv_fs::Simplified; use uv_normalize::DefaultExtras; @@ -640,7 +640,7 @@ async fn lock_and_sync( venv, &extras, &groups, - EditableMode::Editable, + None, install_options, Modifications::Sufficient, None, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index d5a9145ea..cc597a43b 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -326,7 +326,7 @@ pub(crate) struct RunSettings { pub(crate) frozen: bool, pub(crate) extras: ExtrasSpecification, pub(crate) groups: DependencyGroups, - pub(crate) editable: EditableMode, + pub(crate) editable: Option, pub(crate) modifications: Modifications, pub(crate) with: Vec, pub(crate) with_editable: Vec, @@ -372,6 +372,7 @@ impl RunSettings { all_groups, module: _, only_dev, + editable, no_editable, inexact, exact, @@ -428,7 +429,7 @@ impl RunSettings { only_group, all_groups, ), - editable: EditableMode::from_args(no_editable), + editable: flag(editable, no_editable, "editable").map(EditableMode::from), modifications: if flag(exact, inexact, "inexact").unwrap_or(false) { Modifications::Exact } else { @@ -1200,7 +1201,7 @@ pub(crate) struct SyncSettings { pub(crate) active: Option, pub(crate) extras: ExtrasSpecification, pub(crate) groups: DependencyGroups, - pub(crate) editable: EditableMode, + pub(crate) editable: Option, pub(crate) install_options: InstallOptions, pub(crate) modifications: Modifications, pub(crate) all_packages: bool, @@ -1230,6 +1231,7 @@ impl SyncSettings { no_default_groups, only_group, all_groups, + editable, no_editable, inexact, exact, @@ -1297,7 +1299,7 @@ impl SyncSettings { only_group, all_groups, ), - editable: EditableMode::from_args(no_editable), + editable: flag(editable, no_editable, "editable").map(EditableMode::from), install_options: InstallOptions::new( no_install_project, no_install_workspace, @@ -1794,7 +1796,7 @@ pub(crate) struct ExportSettings { pub(crate) prune: Vec, pub(crate) extras: ExtrasSpecification, pub(crate) groups: DependencyGroups, - pub(crate) editable: EditableMode, + pub(crate) editable: Option, pub(crate) hashes: bool, pub(crate) install_options: InstallOptions, pub(crate) output_file: Option, @@ -1834,6 +1836,7 @@ impl ExportSettings { no_annotate, header, no_header, + editable, no_editable, hashes, no_hashes, @@ -1879,7 +1882,7 @@ impl ExportSettings { only_group, all_groups, ), - editable: EditableMode::from_args(no_editable), + editable: flag(editable, no_editable, "editable").map(EditableMode::from), hashes: flag(hashes, no_hashes, "hashes").unwrap_or(true), install_options: InstallOptions::new( no_emit_project, diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 906cb4a86..5e818f145 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -13743,7 +13743,7 @@ fn toggle_workspace_editable() -> Result<()> { let lock = context.read("uv.lock"); - // The child should be editable by default. + // Setting `--no-editable` should make the child non-editable. insta::with_settings!({ filters => context.filters(), }, { @@ -13796,6 +13796,25 @@ fn toggle_workspace_editable() -> Result<()> { ); }); + // Verify that `_child.pth` does not exist in the site-packages directory. + assert!(!context.site_packages().join("_child.pth").exists()); + + // But `--editable` on the command line should override the lockfile. + uv_snapshot!(context.filters(), context.sync().arg("--editable"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + ~ child==0.1.0 (from file://[TEMP_DIR]/child) + "); + + // Verify that `_child.pth` exists in the site-packages directory. + assert!(context.site_packages().join("_child.pth").exists()); + Ok(()) }