Visitar URL original
Add `--no-editable` support to `uv pip install` (#19306) · astral-sh/uv@44609be · GitHub
Skip to content

Commit 44609be

Browse files
authored
Add --no-editable support to uv pip install (#19306)
Similar to the behavior of `uv sync --no-editable`
1 parent 4bd0ae7 commit 44609be

9 files changed

Lines changed: 179 additions & 70 deletions

File tree

‎crates/uv-cli/src/lib.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,10 @@ pub struct PipInstallArgs {
21112111
#[arg(long, short, group = "sources")]
21122112
pub editable: Vec<String>,
21132113

2114+
/// Install any editable dependencies as non-editable [env: UV_NO_EDITABLE=]
2115+
#[arg(long, value_parser = clap::builder::BoolishValueParser::new())]
2116+
pub no_editable: bool,
2117+
21142118
/// Constrain versions using the given requirements files.
21152119
///
21162120
/// Constraints files are `requirements.txt`-like files that only control the _version_ of a

‎crates/uv/src/commands/editable.rs‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use std::sync::Arc;
2+
3+
use uv_configuration::EditableMode;
4+
use uv_distribution_types::{DirectorySourceDist, Dist, Resolution, ResolvedDist, SourceDist};
5+
6+
/// If necessary, convert editable distributions to non-editable.
7+
pub(crate) fn apply_editable_mode(
8+
resolution: Resolution,
9+
editable: Option<EditableMode>,
10+
) -> Resolution {
11+
match editable {
12+
// No modifications are necessary for editable mode; retain any editable distributions.
13+
None => resolution,
14+
15+
// Filter out any non-editable distributions.
16+
Some(EditableMode::Editable) => resolution.map(|dist| {
17+
let ResolvedDist::Installable { dist, version } = dist else {
18+
return None;
19+
};
20+
let Dist::Source(SourceDist::Directory(DirectorySourceDist {
21+
name,
22+
install_path,
23+
editable: None | Some(false),
24+
r#virtual,
25+
url,
26+
})) = dist.as_ref()
27+
else {
28+
return None;
29+
};
30+
31+
Some(ResolvedDist::Installable {
32+
dist: Arc::new(Dist::Source(SourceDist::Directory(DirectorySourceDist {
33+
name: name.clone(),
34+
install_path: install_path.clone(),
35+
editable: Some(true),
36+
r#virtual: *r#virtual,
37+
url: url.clone(),
38+
}))),
39+
version: version.clone(),
40+
})
41+
}),
42+
43+
// If a package is editable, map it to a non-editable distribution.
44+
Some(EditableMode::NonEditable) => resolution.map(|dist| {
45+
let ResolvedDist::Installable { dist, version } = dist else {
46+
return None;
47+
};
48+
let Dist::Source(SourceDist::Directory(DirectorySourceDist {
49+
name,
50+
install_path,
51+
editable: None | Some(true),
52+
r#virtual,
53+
url,
54+
})) = dist.as_ref()
55+
else {
56+
return None;
57+
};
58+
59+
Some(ResolvedDist::Installable {
60+
dist: Arc::new(Dist::Source(SourceDist::Directory(DirectorySourceDist {
61+
name: name.clone(),
62+
install_path: install_path.clone(),
63+
editable: Some(false),
64+
r#virtual: *r#virtual,
65+
url: url.clone(),
66+
}))),
67+
version: version.clone(),
68+
})
69+
}),
70+
}
71+
}

‎crates/uv/src/commands/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ mod cache_dir;
8383
mod cache_prune;
8484
mod cache_size;
8585
mod diagnostics;
86+
mod editable;
8687
mod help;
8788
pub(crate) mod pip;
8889
mod project;

‎crates/uv/src/commands/pip/install.rs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use tracing::{Level, debug, enabled, warn};
77
use uv_cache::Cache;
88
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
99
use uv_configuration::{
10-
BuildIsolation, BuildOptions, Concurrency, Constraints, DryRun, ExtrasSpecification,
11-
HashCheckingMode, IndexStrategy, NoSources, Reinstall, Upgrade,
10+
BuildIsolation, BuildOptions, Concurrency, Constraints, DryRun, EditableMode,
11+
ExtrasSpecification, HashCheckingMode, IndexStrategy, NoSources, Reinstall, Upgrade,
1212
};
1313
use uv_configuration::{KeyringProviderType, TargetTriple};
1414
use uv_dispatch::{BuildDispatch, SharedState};
@@ -40,6 +40,7 @@ use uv_warnings::warn_user;
4040
use uv_workspace::WorkspaceCache;
4141
use uv_workspace::pyproject::ExtraBuildDependencies;
4242

43+
use crate::commands::editable::apply_editable_mode;
4344
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
4445
use crate::commands::pip::operations::Modifications;
4546
use crate::commands::pip::operations::{report_interpreter, report_target_environment};
@@ -61,6 +62,7 @@ pub(crate) async fn pip_install(
6162
overrides_from_workspace: Vec<Requirement>,
6263
excludes_from_workspace: Vec<uv_normalize::PackageName>,
6364
build_constraints_from_workspace: Vec<Requirement>,
65+
editable: Option<EditableMode>,
6466
extras: &ExtrasSpecification,
6567
groups: &GroupsSpecification,
6668
resolution_mode: ResolutionMode,
@@ -581,6 +583,9 @@ pub(crate) async fn pip_install(
581583
(resolution, hasher)
582584
};
583585

586+
// If necessary, convert editable distributions to non-editable.
587+
let resolution = apply_editable_mode(resolution, editable);
588+
584589
// Constrain any build requirements marked as `match-runtime = true`.
585590
let extra_build_requires = extra_build_requires.match_runtime(&resolution)?;
586591

‎crates/uv/src/commands/project/sync.rs‎

Lines changed: 2 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::fmt::Write;
22
use std::ops::Deref;
33
use std::path::Path;
4-
use std::sync::Arc;
54

65
use anyhow::Result;
76
use itertools::Itertools;
@@ -19,9 +18,7 @@ use uv_configuration::{
1918
};
2019
use uv_dispatch::BuildDispatch;
2120
use uv_distribution::LoweredExtraBuildDependencies;
22-
use uv_distribution_types::{
23-
DirectorySourceDist, Dist, Index, Name, Requirement, Resolution, ResolvedDist, SourceDist,
24-
};
21+
use uv_distribution_types::{Dist, Index, Name, Requirement, Resolution, ResolvedDist, SourceDist};
2522
use uv_fs::{PortablePathBuf, Simplified};
2623
use uv_installer::{InstallationStrategy, SitePackages};
2724
use uv_normalize::{DefaultExtras, DefaultGroups, PackageName};
@@ -37,6 +34,7 @@ use uv_warnings::warn_user;
3734
use uv_workspace::pyproject::Source;
3835
use uv_workspace::{DiscoveryOptions, MemberDiscovery, VirtualProject, Workspace, WorkspaceCache};
3936

37+
use crate::commands::editable::apply_editable_mode;
4038
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger, InstallLogger};
4139
use crate::commands::pip::operations::{ChangedDist, Changelog, Modifications};
4240
use crate::commands::pip::resolution_markers;
@@ -882,70 +880,6 @@ pub(super) async fn do_sync(
882880
Ok(changelog)
883881
}
884882

885-
/// If necessary, convert any editable requirements to non-editable.
886-
fn apply_editable_mode(resolution: Resolution, editable: Option<EditableMode>) -> Resolution {
887-
match editable {
888-
// No modifications are necessary for editable mode; retain any editable distributions.
889-
None => resolution,
890-
891-
// Filter out any non-editable distributions.
892-
Some(EditableMode::Editable) => resolution.map(|dist| {
893-
let ResolvedDist::Installable { dist, version } = dist else {
894-
return None;
895-
};
896-
let Dist::Source(SourceDist::Directory(DirectorySourceDist {
897-
name,
898-
install_path,
899-
editable: None | Some(false),
900-
r#virtual,
901-
url,
902-
})) = dist.as_ref()
903-
else {
904-
return None;
905-
};
906-
907-
Some(ResolvedDist::Installable {
908-
dist: Arc::new(Dist::Source(SourceDist::Directory(DirectorySourceDist {
909-
name: name.clone(),
910-
install_path: install_path.clone(),
911-
editable: Some(true),
912-
r#virtual: *r#virtual,
913-
url: url.clone(),
914-
}))),
915-
version: version.clone(),
916-
})
917-
}),
918-
919-
// If a package is editable, map it to a non-editable distribution.
920-
Some(EditableMode::NonEditable) => resolution.map(|dist| {
921-
let ResolvedDist::Installable { dist, version } = dist else {
922-
return None;
923-
};
924-
let Dist::Source(SourceDist::Directory(DirectorySourceDist {
925-
name,
926-
install_path,
927-
editable: None | Some(true),
928-
r#virtual,
929-
url,
930-
})) = dist.as_ref()
931-
else {
932-
return None;
933-
};
934-
935-
Some(ResolvedDist::Installable {
936-
dist: Arc::new(Dist::Source(SourceDist::Directory(DirectorySourceDist {
937-
name: name.clone(),
938-
install_path: install_path.clone(),
939-
editable: Some(false),
940-
r#virtual: *r#virtual,
941-
url: url.clone(),
942-
}))),
943-
version: version.clone(),
944-
})
945-
}),
946-
}
947-
}
948-
949883
/// Filter out any virtual workspace members.
950884
fn apply_no_virtual_project(resolution: Resolution) -> Resolution {
951885
resolution.filter(|dist| {

‎crates/uv/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
911911
args.overrides_from_workspace,
912912
args.excludes_from_workspace,
913913
args.build_constraints_from_workspace,
914+
args.editable,
914915
&args.settings.extras,
915916
&groups,
916917
args.settings.resolution,

‎crates/uv/src/settings.rs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3033,6 +3033,7 @@ pub(crate) struct PipInstallSettings {
30333033
pub(crate) package: Vec<String>,
30343034
pub(crate) requirements: Vec<PathBuf>,
30353035
pub(crate) editables: Vec<String>,
3036+
pub(crate) editable: Option<EditableMode>,
30363037
pub(crate) constraints: Vec<PathBuf>,
30373038
pub(crate) overrides: Vec<PathBuf>,
30383039
pub(crate) excludes: Vec<PathBuf>,
@@ -3058,6 +3059,7 @@ impl PipInstallSettings {
30583059
package,
30593060
requirements,
30603061
editable,
3062+
no_editable,
30613063
constraints,
30623064
overrides,
30633065
excludes,
@@ -3177,6 +3179,11 @@ impl PipInstallSettings {
31773179
} else {
31783180
Modifications::Sufficient
31793181
},
3182+
editable: if no_editable || environment.no_editable.value == Some(true) {
3183+
Some(EditableMode::NonEditable)
3184+
} else {
3185+
None
3186+
},
31803187
refresh: Refresh::from(refresh),
31813188
settings: PipSettings::combine(
31823189
PipOptions {

‎crates/uv/tests/it/pip_install.rs‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,86 @@ fn install_editable() {
15961596
);
15971597
}
15981598

1599+
#[test]
1600+
fn install_no_editable() {
1601+
let context = uv_test::test_context!("3.12");
1602+
let package = context.workspace_root.join("test/packages/executable_file");
1603+
1604+
uv_snapshot!(context.filters(), context.pip_install()
1605+
.arg("-e")
1606+
.arg(&package)
1607+
.arg("--no-editable"), @"
1608+
success: true
1609+
exit_code: 0
1610+
----- stdout -----
1611+
1612+
----- stderr -----
1613+
Resolved 1 package in [TIME]
1614+
Prepared 1 package in [TIME]
1615+
Installed 1 package in [TIME]
1616+
+ executable-file==1.0.0 (from file://[WORKSPACE]/test/packages/executable_file)
1617+
"
1618+
);
1619+
1620+
let path = context.site_packages().join("executable_file.pth");
1621+
assert!(!path.exists());
1622+
}
1623+
1624+
#[test]
1625+
fn install_no_editable_requirements_txt() -> Result<()> {
1626+
let context = uv_test::test_context!("3.12");
1627+
let package = context.workspace_root.join("test/packages/executable_file");
1628+
1629+
let requirements_txt = context.temp_dir.child("requirements.txt");
1630+
requirements_txt.write_str(&format!("-e {}", package.simplified_display()))?;
1631+
1632+
uv_snapshot!(context.filters(), context.pip_install()
1633+
.arg("-r")
1634+
.arg("requirements.txt")
1635+
.arg("--no-editable"), @"
1636+
success: true
1637+
exit_code: 0
1638+
----- stdout -----
1639+
1640+
----- stderr -----
1641+
Resolved 1 package in [TIME]
1642+
Prepared 1 package in [TIME]
1643+
Installed 1 package in [TIME]
1644+
+ executable-file==1.0.0 (from file://[WORKSPACE]/test/packages/executable_file)
1645+
"
1646+
);
1647+
1648+
let path = context.site_packages().join("executable_file.pth");
1649+
assert!(!path.exists());
1650+
1651+
Ok(())
1652+
}
1653+
1654+
#[test]
1655+
fn install_no_editable_env_var() {
1656+
let context = uv_test::test_context!("3.12");
1657+
let package = context.workspace_root.join("test/packages/executable_file");
1658+
1659+
uv_snapshot!(context.filters(), context.pip_install()
1660+
.env(EnvVars::UV_NO_EDITABLE, "1")
1661+
.arg("-e")
1662+
.arg(&package), @"
1663+
success: true
1664+
exit_code: 0
1665+
----- stdout -----
1666+
1667+
----- stderr -----
1668+
Resolved 1 package in [TIME]
1669+
Prepared 1 package in [TIME]
1670+
Installed 1 package in [TIME]
1671+
+ executable-file==1.0.0 (from file://[WORKSPACE]/test/packages/executable_file)
1672+
"
1673+
);
1674+
1675+
let path = context.site_packages().join("executable_file.pth");
1676+
assert!(!path.exists());
1677+
}
1678+
15991679
#[test]
16001680
fn install_editable_and_registry() {
16011681
let context = uv_test::test_context!("3.12");

‎crates/uv/tests/it/show_settings.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7095,6 +7095,7 @@ fn verify_hashes() -> anyhow::Result<()> {
70957095
"requirements.in",
70967096
],
70977097
editables: [],
7098+
editable: None,
70987099
constraints: [],
70997100
overrides: [],
71007101
excludes: [],
@@ -7270,6 +7271,7 @@ fn verify_hashes() -> anyhow::Result<()> {
72707271
"requirements.in",
72717272
],
72727273
editables: [],
7274+
editable: None,
72737275
constraints: [],
72747276
overrides: [],
72757277
excludes: [],
@@ -7443,6 +7445,7 @@ fn verify_hashes() -> anyhow::Result<()> {
74437445
"requirements.in",
74447446
],
74457447
editables: [],
7448+
editable: None,
74467449
constraints: [],
74477450
overrides: [],
74487451
excludes: [],
@@ -7618,6 +7621,7 @@ fn verify_hashes() -> anyhow::Result<()> {
76187621
"requirements.in",
76197622
],
76207623
editables: [],
7624+
editable: None,
76217625
constraints: [],
76227626
overrides: [],
76237627
excludes: [],
@@ -7791,6 +7795,7 @@ fn verify_hashes() -> anyhow::Result<()> {
77917795
"requirements.in",
77927796
],
77937797
editables: [],
7798+
editable: None,
77947799
constraints: [],
77957800
overrides: [],
77967801
excludes: [],
@@ -7965,6 +7970,7 @@ fn verify_hashes() -> anyhow::Result<()> {
79657970
"requirements.in",
79667971
],
79677972
editables: [],
7973+
editable: None,
79687974
constraints: [],
79697975
overrides: [],
79707976
excludes: [],

0 commit comments

Comments
 (0)