Allow --editable to override editable = false annotations (#15712)

## Summary

We support `--no-editable` on the CLI, but now that workspace members
and path dependencies can be marked as `editable = false`, I think it
makes sense for `--editable` to override that.
This commit is contained in:
Charlie Marsh
2025-09-07 14:37:04 -04:00
committed by GitHub
parent e84c9231aa
commit 5012b4e831
12 changed files with 104 additions and 40 deletions
+18 -3
View File
@@ -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.
+5 -6
View File
@@ -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<bool> for EditableMode {
fn from(value: bool) -> Self {
if value {
Self::Editable
} else {
Self::NonEditable
}
}
}
@@ -620,7 +620,7 @@ impl<'lock> PylockToml {
extras: &ExtrasSpecificationWithDefaults,
dev: &DependencyGroupsWithDefaults,
annotate: bool,
editable: EditableMode,
editable: Option<EditableMode>,
install_options: &'lock InstallOptions,
) -> Result<Self, PylockTomlErrorKind> {
// 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,
}),
@@ -24,7 +24,7 @@ use crate::{Installable, LockError};
pub struct RequirementsTxtExport<'lock> {
nodes: Vec<ExportableRequirement<'lock>>,
hashes: bool,
editable: EditableMode,
editable: Option<EditableMode>,
}
impl<'lock> RequirementsTxtExport<'lock> {
@@ -34,7 +34,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
extras: &ExtrasSpecificationWithDefaults,
dev: &DependencyGroupsWithDefaults,
annotate: bool,
editable: EditableMode,
editable: Option<EditableMode>,
hashes: bool,
install_options: &'lock InstallOptions,
) -> Result<Self, LockError> {
@@ -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,
+2 -3
View File
@@ -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,
+1 -1
View File
@@ -64,7 +64,7 @@ pub(crate) async fn export(
output_file: Option<PathBuf>,
extras: ExtrasSpecification,
groups: DependencyGroups,
editable: EditableMode,
editable: Option<EditableMode>,
locked: bool,
frozen: bool,
include_annotations: bool,
+2 -2
View File
@@ -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,
+1 -1
View File
@@ -93,7 +93,7 @@ pub(crate) async fn run(
no_config: bool,
extras: ExtrasSpecification,
groups: DependencyGroups,
editable: EditableMode,
editable: Option<EditableMode>,
modifications: Modifications,
python: Option<String>,
python_platform: Option<TargetTriple>,
+35 -7
View File
@@ -64,7 +64,7 @@ pub(crate) async fn sync(
package: Option<PackageName>,
extras: ExtrasSpecification,
groups: DependencyGroups,
editable: EditableMode,
editable: Option<EditableMode>,
install_options: InstallOptions,
modifications: Modifications,
python: Option<String>,
@@ -559,7 +559,7 @@ pub(super) async fn do_sync(
venv: &PythonEnvironment,
extras: &ExtrasSpecificationWithDefaults,
groups: &DependencyGroupsWithDefaults,
editable: EditableMode,
editable: Option<EditableMode>,
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<EditableMode>) -> 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()
+3 -3
View File
@@ -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,
+9 -6
View File
@@ -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<EditableMode>,
pub(crate) modifications: Modifications,
pub(crate) with: Vec<String>,
pub(crate) with_editable: Vec<String>,
@@ -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<bool>,
pub(crate) extras: ExtrasSpecification,
pub(crate) groups: DependencyGroups,
pub(crate) editable: EditableMode,
pub(crate) editable: Option<EditableMode>,
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<PackageName>,
pub(crate) extras: ExtrasSpecification,
pub(crate) groups: DependencyGroups,
pub(crate) editable: EditableMode,
pub(crate) editable: Option<EditableMode>,
pub(crate) hashes: bool,
pub(crate) install_options: InstallOptions,
pub(crate) output_file: Option<PathBuf>,
@@ -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,
+20 -1
View File
@@ -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(())
}