mirror of https://github.com/astral-sh/uv
Add `--no-editable` support to `uv sync` and `uv export` (#7371)
## Summary Closes https://github.com/astral-sh/uv/issues/5792.
This commit is contained in:
parent
bb0ffa32e4
commit
778da3350a
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -2408,6 +2408,11 @@ pub struct RunArgs {
|
|||
#[arg(long, conflicts_with("no_dev"))]
|
||||
pub only_dev: bool,
|
||||
|
||||
/// Install any editable dependencies, including the project and any workspace members, as
|
||||
/// non-editable.
|
||||
#[arg(long)]
|
||||
pub no_editable: bool,
|
||||
|
||||
/// The command to run.
|
||||
///
|
||||
/// If the path to a Python script (i.e., ending in `.py`), it will be
|
||||
|
|
@ -2560,6 +2565,11 @@ pub struct SyncArgs {
|
|||
#[arg(long, conflicts_with("no_dev"))]
|
||||
pub only_dev: bool,
|
||||
|
||||
/// Install any editable dependencies, including the project and any workspace members, as
|
||||
/// non-editable.
|
||||
#[arg(long)]
|
||||
pub no_editable: bool,
|
||||
|
||||
/// Do not remove extraneous packages present in the environment.
|
||||
///
|
||||
/// When enabled, uv will make the minimum necessary changes to satisfy the requirements.
|
||||
|
|
@ -3002,6 +3012,11 @@ pub struct ExportArgs {
|
|||
#[arg(long, conflicts_with("no_dev"))]
|
||||
pub only_dev: bool,
|
||||
|
||||
/// Install any editable dependencies, including the project and any workspace members, as
|
||||
/// non-editable.
|
||||
#[arg(long)]
|
||||
pub no_editable: bool,
|
||||
|
||||
/// Include hashes for all dependencies.
|
||||
#[arg(long, overrides_with("no_hashes"), hide = true)]
|
||||
pub hashes: bool,
|
||||
|
|
|
|||
|
|
@ -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(no_editable: bool) -> Self {
|
||||
if no_editable {
|
||||
Self::NonEditable
|
||||
} else {
|
||||
Self::Editable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ pub use concurrency::*;
|
|||
pub use config_settings::*;
|
||||
pub use constraints::*;
|
||||
pub use dev::*;
|
||||
pub use editable::*;
|
||||
pub use export_format::*;
|
||||
pub use extras::*;
|
||||
pub use hash::*;
|
||||
|
|
@ -22,6 +23,7 @@ mod concurrency;
|
|||
mod config_settings;
|
||||
mod constraints;
|
||||
mod dev;
|
||||
mod editable;
|
||||
mod export_format;
|
||||
mod extras;
|
||||
mod hash;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use url::Url;
|
|||
use distribution_filename::{DistExtension, SourceDistExtension};
|
||||
use pep508_rs::MarkerTree;
|
||||
use pypi_types::{ParsedArchiveUrl, ParsedGitUrl};
|
||||
use uv_configuration::{DevSpecification, ExtrasSpecification, InstallOptions};
|
||||
use uv_configuration::{DevSpecification, EditableMode, ExtrasSpecification, InstallOptions};
|
||||
use uv_fs::Simplified;
|
||||
use uv_git::GitReference;
|
||||
use uv_normalize::{ExtraName, PackageName};
|
||||
|
|
@ -35,6 +35,7 @@ struct Node<'lock> {
|
|||
pub struct RequirementsTxtExport<'lock> {
|
||||
nodes: Vec<Node<'lock>>,
|
||||
hashes: bool,
|
||||
editable: EditableMode,
|
||||
}
|
||||
|
||||
impl<'lock> RequirementsTxtExport<'lock> {
|
||||
|
|
@ -43,6 +44,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
|||
root_name: &PackageName,
|
||||
extras: &ExtrasSpecification,
|
||||
dev: DevSpecification<'_>,
|
||||
editable: EditableMode,
|
||||
hashes: bool,
|
||||
install_options: &'lock InstallOptions,
|
||||
) -> Result<Self, LockError> {
|
||||
|
|
@ -166,7 +168,11 @@ impl<'lock> RequirementsTxtExport<'lock> {
|
|||
NodeComparator::from(a.package).cmp(&NodeComparator::from(b.package))
|
||||
});
|
||||
|
||||
Ok(Self { nodes, hashes })
|
||||
Ok(Self {
|
||||
nodes,
|
||||
hashes,
|
||||
editable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,9 +222,18 @@ impl std::fmt::Display for RequirementsTxtExport<'_> {
|
|||
write!(f, "{}", anchor(path).portable_display())?;
|
||||
}
|
||||
}
|
||||
Source::Editable(path) => {
|
||||
write!(f, "-e {}", anchor(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ use uv_auth::{store_credentials_from_url, Credentials};
|
|||
use uv_cache::Cache;
|
||||
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
|
||||
use uv_configuration::{
|
||||
Concurrency, Constraints, DevMode, ExtrasSpecification, InstallOptions, SourceStrategy,
|
||||
Concurrency, Constraints, DevMode, EditableMode, ExtrasSpecification, InstallOptions,
|
||||
SourceStrategy,
|
||||
};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_distribution::DistributionDatabase;
|
||||
|
|
@ -755,6 +756,7 @@ async fn lock_and_sync(
|
|||
&lock,
|
||||
&extras,
|
||||
dev,
|
||||
EditableMode::Editable,
|
||||
InstallOptions::default(),
|
||||
Modifications::Sufficient,
|
||||
settings.into(),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ use std::path::PathBuf;
|
|||
use uv_cache::Cache;
|
||||
use uv_client::Connectivity;
|
||||
use uv_configuration::{
|
||||
Concurrency, DevMode, DevSpecification, ExportFormat, ExtrasSpecification, InstallOptions,
|
||||
Concurrency, DevMode, DevSpecification, EditableMode, ExportFormat, ExtrasSpecification,
|
||||
InstallOptions,
|
||||
};
|
||||
use uv_fs::CWD;
|
||||
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
|
||||
|
|
@ -33,6 +34,7 @@ pub(crate) async fn export(
|
|||
output_file: Option<PathBuf>,
|
||||
extras: ExtrasSpecification,
|
||||
dev: DevMode,
|
||||
editable: EditableMode,
|
||||
locked: bool,
|
||||
frozen: bool,
|
||||
python: Option<String>,
|
||||
|
|
@ -130,6 +132,7 @@ pub(crate) async fn export(
|
|||
project.project_name(),
|
||||
&extras,
|
||||
dev,
|
||||
editable,
|
||||
hashes,
|
||||
&install_options,
|
||||
)?;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use owo_colors::OwoColorize;
|
|||
use pep508_rs::PackageName;
|
||||
use uv_cache::Cache;
|
||||
use uv_client::Connectivity;
|
||||
use uv_configuration::{Concurrency, DevMode, ExtrasSpecification, InstallOptions};
|
||||
use uv_configuration::{Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions};
|
||||
use uv_fs::{Simplified, CWD};
|
||||
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
|
||||
use uv_scripts::Pep723Script;
|
||||
|
|
@ -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 = DevMode::Include;
|
||||
let extras = ExtrasSpecification::All;
|
||||
let install_options = InstallOptions::default();
|
||||
|
||||
// Initialize any shared state.
|
||||
|
|
@ -201,6 +201,7 @@ pub(crate) async fn remove(
|
|||
&lock,
|
||||
&extras,
|
||||
dev,
|
||||
EditableMode::Editable,
|
||||
install_options,
|
||||
Modifications::Exact,
|
||||
settings.as_ref().into(),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ use tracing::{debug, warn};
|
|||
use uv_cache::Cache;
|
||||
use uv_cli::ExternalCommand;
|
||||
use uv_client::{BaseClientBuilder, Connectivity};
|
||||
use uv_configuration::{Concurrency, DevMode, ExtrasSpecification, InstallOptions, SourceStrategy};
|
||||
use uv_configuration::{
|
||||
Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions, SourceStrategy,
|
||||
};
|
||||
use uv_distribution::LoweredRequirement;
|
||||
use uv_fs::{PythonExt, Simplified, CWD};
|
||||
use uv_installer::{SatisfiesResult, SitePackages};
|
||||
|
|
@ -58,6 +60,7 @@ pub(crate) async fn run(
|
|||
no_config: bool,
|
||||
extras: ExtrasSpecification,
|
||||
dev: DevMode,
|
||||
editable: EditableMode,
|
||||
python: Option<String>,
|
||||
settings: ResolverInstallerSettings,
|
||||
python_preference: PythonPreference,
|
||||
|
|
@ -501,6 +504,7 @@ pub(crate) async fn run(
|
|||
result.lock(),
|
||||
&extras,
|
||||
dev,
|
||||
editable,
|
||||
install_options,
|
||||
Modifications::Sufficient,
|
||||
settings.as_ref().into(),
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
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, DevMode, DevSpecification, ExtrasSpecification, HashCheckingMode,
|
||||
InstallOptions,
|
||||
Concurrency, Constraints, DevMode, DevSpecification, EditableMode, ExtrasSpecification,
|
||||
HashCheckingMode, InstallOptions,
|
||||
};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_fs::CWD;
|
||||
|
|
@ -36,6 +36,7 @@ pub(crate) async fn sync(
|
|||
package: Option<PackageName>,
|
||||
extras: ExtrasSpecification,
|
||||
dev: DevMode,
|
||||
editable: EditableMode,
|
||||
install_options: InstallOptions,
|
||||
modifications: Modifications,
|
||||
python: Option<String>,
|
||||
|
|
@ -133,6 +134,7 @@ pub(crate) async fn sync(
|
|||
&lock,
|
||||
&extras,
|
||||
dev,
|
||||
editable,
|
||||
install_options,
|
||||
modifications,
|
||||
settings.as_ref().into(),
|
||||
|
|
@ -157,6 +159,7 @@ pub(super) async fn do_sync(
|
|||
lock: &Lock,
|
||||
extras: &ExtrasSpecification,
|
||||
dev: DevMode,
|
||||
editable: EditableMode,
|
||||
install_options: InstallOptions,
|
||||
modifications: Modifications,
|
||||
settings: InstallerSettingsRef<'_>,
|
||||
|
|
@ -242,6 +245,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);
|
||||
|
|
@ -358,3 +364,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,
|
||||
})))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1157,6 +1157,7 @@ async fn run_project(
|
|||
no_config,
|
||||
args.extras,
|
||||
args.dev,
|
||||
args.editable,
|
||||
args.python,
|
||||
args.settings,
|
||||
globals.python_preference,
|
||||
|
|
@ -1187,6 +1188,7 @@ async fn run_project(
|
|||
args.package,
|
||||
args.extras,
|
||||
args.dev,
|
||||
args.editable,
|
||||
args.install_options,
|
||||
args.modifications,
|
||||
args.python,
|
||||
|
|
@ -1355,6 +1357,7 @@ async fn run_project(
|
|||
args.output_file,
|
||||
args.extras,
|
||||
args.dev,
|
||||
args.editable,
|
||||
args.locked,
|
||||
args.frozen,
|
||||
args.python,
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ use uv_cli::{
|
|||
};
|
||||
use uv_client::Connectivity;
|
||||
use uv_configuration::{
|
||||
BuildOptions, Concurrency, ConfigSettings, DevMode, ExportFormat, ExtrasSpecification,
|
||||
HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType, NoBinary, NoBuild,
|
||||
PreviewMode, Reinstall, SourceStrategy, TargetTriple, TrustedHost, Upgrade,
|
||||
BuildOptions, Concurrency, ConfigSettings, DevMode, EditableMode, ExportFormat,
|
||||
ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType,
|
||||
NoBinary, NoBuild, PreviewMode, Reinstall, SourceStrategy, TargetTriple, TrustedHost, Upgrade,
|
||||
};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target};
|
||||
|
|
@ -210,6 +210,7 @@ pub(crate) struct RunSettings {
|
|||
pub(crate) frozen: bool,
|
||||
pub(crate) extras: ExtrasSpecification,
|
||||
pub(crate) dev: DevMode,
|
||||
pub(crate) editable: EditableMode,
|
||||
pub(crate) with: Vec<String>,
|
||||
pub(crate) with_editable: Vec<String>,
|
||||
pub(crate) with_requirements: Vec<PathBuf>,
|
||||
|
|
@ -234,6 +235,7 @@ impl RunSettings {
|
|||
dev,
|
||||
no_dev,
|
||||
only_dev,
|
||||
no_editable,
|
||||
command: _,
|
||||
with,
|
||||
with_editable,
|
||||
|
|
@ -259,6 +261,7 @@ impl RunSettings {
|
|||
extra.unwrap_or_default(),
|
||||
),
|
||||
dev: DevMode::from_args(dev, no_dev, only_dev),
|
||||
editable: EditableMode::from_args(no_editable),
|
||||
with,
|
||||
with_editable,
|
||||
with_requirements: with_requirements
|
||||
|
|
@ -661,6 +664,7 @@ pub(crate) struct SyncSettings {
|
|||
pub(crate) frozen: bool,
|
||||
pub(crate) extras: ExtrasSpecification,
|
||||
pub(crate) dev: DevMode,
|
||||
pub(crate) editable: EditableMode,
|
||||
pub(crate) install_options: InstallOptions,
|
||||
pub(crate) modifications: Modifications,
|
||||
pub(crate) package: Option<PackageName>,
|
||||
|
|
@ -680,6 +684,7 @@ impl SyncSettings {
|
|||
dev,
|
||||
no_dev,
|
||||
only_dev,
|
||||
no_editable,
|
||||
inexact,
|
||||
exact,
|
||||
no_install_project,
|
||||
|
|
@ -707,6 +712,7 @@ impl SyncSettings {
|
|||
extra.unwrap_or_default(),
|
||||
),
|
||||
dev: DevMode::from_args(dev, no_dev, only_dev),
|
||||
editable: EditableMode::from_args(no_editable),
|
||||
install_options: InstallOptions::new(
|
||||
no_install_project,
|
||||
no_install_workspace,
|
||||
|
|
@ -961,6 +967,7 @@ pub(crate) struct ExportSettings {
|
|||
pub(crate) package: Option<PackageName>,
|
||||
pub(crate) extras: ExtrasSpecification,
|
||||
pub(crate) dev: DevMode,
|
||||
pub(crate) editable: EditableMode,
|
||||
pub(crate) hashes: bool,
|
||||
pub(crate) install_options: InstallOptions,
|
||||
pub(crate) output_file: Option<PathBuf>,
|
||||
|
|
@ -984,6 +991,7 @@ impl ExportSettings {
|
|||
dev,
|
||||
no_dev,
|
||||
only_dev,
|
||||
no_editable,
|
||||
hashes,
|
||||
no_hashes,
|
||||
output_file,
|
||||
|
|
@ -1006,6 +1014,7 @@ impl ExportSettings {
|
|||
extra.unwrap_or_default(),
|
||||
),
|
||||
dev: DevMode::from_args(dev, no_dev, only_dev),
|
||||
editable: EditableMode::from_args(no_editable),
|
||||
hashes: flag(hashes, no_hashes).unwrap_or(true),
|
||||
install_options: InstallOptions::new(
|
||||
no_emit_project,
|
||||
|
|
|
|||
|
|
@ -905,3 +905,73 @@ fn no_emit() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_editable() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "project"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["anyio==3.7.0", "child"]
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["child"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { workspace = true }
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let child = context.temp_dir.child("child");
|
||||
child.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["iniconfig>=2"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
context.lock().assert().success();
|
||||
|
||||
uv_snapshot!(context.filters(), context.export().arg("--no-editable"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
# This file was autogenerated by uv via the following command:
|
||||
# uv export --cache-dir [CACHE_DIR] --no-editable
|
||||
.
|
||||
./child
|
||||
anyio==3.7.0 \
|
||||
--hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \
|
||||
--hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0
|
||||
idna==3.6 \
|
||||
--hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \
|
||||
--hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f
|
||||
iniconfig==2.0.0 \
|
||||
--hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \
|
||||
--hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374
|
||||
sniffio==1.3.1 \
|
||||
--hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc \
|
||||
--hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2
|
||||
|
||||
----- stderr -----
|
||||
Resolved 6 packages in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2392,6 +2392,89 @@ fn transitive_dev() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Avoid installing dev dependencies of transitive dependencies.
|
||||
#[test]
|
||||
fn sync_no_editable() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "root"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["child"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.uv.sources]
|
||||
child = { workspace = true }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["child"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let src = context.temp_dir.child("src").child("albatross");
|
||||
src.create_dir_all()?;
|
||||
|
||||
let init = src.child("__init__.py");
|
||||
init.touch()?;
|
||||
|
||||
let child = context.temp_dir.child("child");
|
||||
fs_err::create_dir_all(&child)?;
|
||||
|
||||
let pyproject_toml = child.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let src = child.child("src").child("child");
|
||||
src.create_dir_all()?;
|
||||
|
||||
let init = src.child("__init__.py");
|
||||
init.touch()?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.sync().arg("--no-editable"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||
+ root==0.1.0 (from file://[TEMP_DIR]/)
|
||||
"###);
|
||||
|
||||
// Remove the project.
|
||||
fs_err::remove_dir_all(&child)?;
|
||||
|
||||
// Ensure that we can still import it.
|
||||
uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("python").arg("-c").arg("import child"), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// Check warning message for <https://github.com/astral-sh/uv/issues/6998>
|
||||
/// if no `build-system` section is defined.
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ project should be packaged and installed.
|
|||
uv uses the presence of a build system to determine if a project contains a package that should be
|
||||
installed in the project virtual environment. If a build system is not defined, uv will not attempt
|
||||
to build or install the project itself, just its dependencies. If a build system is defined, uv will
|
||||
build and install the project into the project environment. Projects are installed in
|
||||
build and install the project into the project environment. By default, projects are installed in
|
||||
[editable mode](https://setuptools.pypa.io/en/latest/userguide/development_mode.html) so changes to
|
||||
the source code are reflected immediately, without reinstallation.
|
||||
the source code are reflected immediately, without re-installation.
|
||||
|
||||
### Configuring project packaging
|
||||
|
||||
|
|
@ -297,6 +297,12 @@ use [`uvx`](../guides/tools.md) or
|
|||
managed = false
|
||||
```
|
||||
|
||||
By default, the project will be installed in editable mode, such that changes to the source code are
|
||||
immediately reflected in the environment. `uv sync` and `uv run` both accept a `--no-editable` flag,
|
||||
which instructs uv to install the project in non-editable mode. `--no-editable` is intended for
|
||||
deployment use-cases, such as building a Docker container, in which the project should be included
|
||||
in the deployed environment without a dependency on the originating source code.
|
||||
|
||||
### Configuring the project environment path
|
||||
|
||||
The `UV_PROJECT_ENVIRONMENT` environment variable can be used to configure the project virtual
|
||||
|
|
|
|||
|
|
@ -360,6 +360,50 @@ _contents_ are not copied into the image until the final `uv sync` command.
|
|||
|
||||
If you want to remove specific packages from the sync, use `--no-install-package <name>`.
|
||||
|
||||
### Non-editable installs
|
||||
|
||||
By default, uv installs projects and workspace members in editable mode, such that changes to the
|
||||
source code are immediately reflected in the environment.
|
||||
|
||||
`uv sync` and `uv run` both accept a `--no-editable` flag, which instructs uv to install the project
|
||||
in non-editable mode, removing any dependency on the source code.
|
||||
|
||||
In the context of a multi-stage Docker image, `--no-editable` can be used to include the project in
|
||||
the synced virtual environment from one stage, then copy the virtual environment alone (and not the
|
||||
source code) into the final image.
|
||||
|
||||
For example:
|
||||
|
||||
```dockerfile title="Dockerfile"
|
||||
# Install uv
|
||||
FROM python:3.12-slim AS builder
|
||||
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
|
||||
|
||||
# Change the working directory to the `app` directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||
uv sync --frozen --no-install-project --no-editable
|
||||
|
||||
# Copy the project into the intermediate image
|
||||
ADD . /app
|
||||
|
||||
# Sync the project
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv sync --frozen --no-editable
|
||||
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Copy the environment, but not the source code
|
||||
COPY --from=builder --chown=app:app /app/.venv /app/.venv
|
||||
|
||||
# Run the application
|
||||
CMD ["/app/.venv/bin/hello"]
|
||||
```
|
||||
|
||||
### Using uv temporarily
|
||||
|
||||
If uv isn't needed in the final image, the binary can be mounted in each invocation:
|
||||
|
|
|
|||
|
|
@ -249,6 +249,8 @@ uv run [OPTIONS] <COMMAND>
|
|||
|
||||
<p>This option is only available when running in a project.</p>
|
||||
|
||||
</dd><dt><code>--no-editable</code></dt><dd><p>Install any editable dependencies, including the project and any workspace members, as non-editable</p>
|
||||
|
||||
</dd><dt><code>--no-index</code></dt><dd><p>Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via <code>--find-links</code></p>
|
||||
|
||||
</dd><dt><code>--no-progress</code></dt><dd><p>Hide all progress outputs.</p>
|
||||
|
|
@ -1313,6 +1315,8 @@ uv sync [OPTIONS]
|
|||
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p>
|
||||
</dd><dt><code>--no-dev</code></dt><dd><p>Omit development dependencies</p>
|
||||
|
||||
</dd><dt><code>--no-editable</code></dt><dd><p>Install any editable dependencies, including the project and any workspace members, as non-editable</p>
|
||||
|
||||
</dd><dt><code>--no-index</code></dt><dd><p>Ignore the registry index (e.g., PyPI), instead relying on direct URL dependencies and those provided via <code>--find-links</code></p>
|
||||
|
||||
</dd><dt><code>--no-install-package</code> <i>no-install-package</i></dt><dd><p>Do not install the given package(s).</p>
|
||||
|
|
@ -1869,6 +1873,8 @@ uv export [OPTIONS]
|
|||
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p>
|
||||
</dd><dt><code>--no-dev</code></dt><dd><p>Omit development dependencies</p>
|
||||
|
||||
</dd><dt><code>--no-editable</code></dt><dd><p>Install any editable dependencies, including the project and any workspace members, as non-editable</p>
|
||||
|
||||
</dd><dt><code>--no-emit-package</code> <i>no-emit-package</i></dt><dd><p>Do not emit the given package(s).</p>
|
||||
|
||||
<p>By default, all of the project’s dependencies are included in the exported requirements file. The <code>--no-install-package</code> option allows exclusion of specific packages.</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue