diff --git a/crates/distribution-types/src/resolution.rs b/crates/distribution-types/src/resolution.rs index 4171b4ddeb46..8501d4a30855 100644 --- a/crates/distribution-types/src/resolution.rs +++ b/crates/distribution-types/src/resolution.rs @@ -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::>(); 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::>(); + 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 { diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index bed35cbee13e..789014a6daef 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -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 @@ -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. @@ -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, diff --git a/crates/uv-configuration/src/editable.rs b/crates/uv-configuration/src/editable.rs new file mode 100644 index 000000000000..08b088a0a891 --- /dev/null +++ b/crates/uv-configuration/src/editable.rs @@ -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 + } + } +} diff --git a/crates/uv-configuration/src/lib.rs b/crates/uv-configuration/src/lib.rs index fad24007e19f..0b04d943d25d 100644 --- a/crates/uv-configuration/src/lib.rs +++ b/crates/uv-configuration/src/lib.rs @@ -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::*; @@ -20,6 +21,7 @@ mod build_options; mod concurrency; mod config_settings; mod constraints; +mod editable; mod export_format; mod extras; mod hash; diff --git a/crates/uv-resolver/src/lock/requirements_txt.rs b/crates/uv-resolver/src/lock/requirements_txt.rs index b7f25dba4b90..a86398d93b6e 100644 --- a/crates/uv-resolver/src/lock/requirements_txt.rs +++ b/crates/uv-resolver/src/lock/requirements_txt.rs @@ -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; @@ -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}; @@ -34,6 +35,7 @@ struct Node<'lock> { pub struct RequirementsTxtExport<'lock> { nodes: Vec>, hashes: bool, + editable: EditableMode, } impl<'lock> RequirementsTxtExport<'lock> { @@ -42,6 +44,7 @@ impl<'lock> RequirementsTxtExport<'lock> { root_name: &PackageName, extras: &ExtrasSpecification, dev: &[GroupName], + editable: EditableMode, hashes: bool, install_options: &'lock InstallOptions, ) -> Result { @@ -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, + }) } } @@ -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; } @@ -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)), + } +} diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index fc266178695a..9e36f6c7370a 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -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; @@ -726,6 +726,7 @@ async fn lock_and_sync( &lock, &extras, dev, + EditableMode::Editable, InstallOptions::default(), Modifications::Sufficient, settings.into(), diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index dbf60af823f0..e642f0e4efb0 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -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}; @@ -31,6 +33,7 @@ pub(crate) async fn export( output_file: Option, extras: ExtrasSpecification, dev: bool, + editable: EditableMode, locked: bool, frozen: bool, python: Option, @@ -128,6 +131,7 @@ pub(crate) async fn export( project.project_name(), &extras, &dev, + editable, hashes, &install_options, )?; diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index e7644354652f..0b8e186ad141 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -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; @@ -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. @@ -201,6 +201,7 @@ pub(crate) async fn remove( &lock, &extras, dev, + EditableMode::Editable, install_options, Modifications::Exact, settings.as_ref().into(), diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 253087c9680d..6efe1cc971d7 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -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}; @@ -58,6 +58,7 @@ pub(crate) async fn run( no_config: bool, extras: ExtrasSpecification, dev: bool, + editable: EditableMode, python: Option, settings: ResolverInstallerSettings, python_preference: PythonPreference, @@ -487,6 +488,7 @@ pub(crate) async fn run( result.lock(), &extras, dev, + editable, install_options, Modifications::Sufficient, settings.as_ref().into(), diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 31ed1c1a88e8..9947dff29a66 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -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; @@ -34,6 +34,7 @@ pub(crate) async fn sync( package: Option, extras: ExtrasSpecification, dev: bool, + editable: EditableMode, install_options: InstallOptions, modifications: Modifications, python: Option, @@ -123,6 +124,7 @@ pub(crate) async fn sync( &lock, &extras, dev, + editable, install_options, modifications, settings.as_ref().into(), @@ -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<'_>, @@ -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); @@ -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, + }))) + }), + } +} diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 0ee3a7c2720f..a8f0ac93f51a 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1134,6 +1134,7 @@ async fn run_project( no_config, args.extras, args.dev, + args.editable, args.python, args.settings, globals.python_preference, @@ -1164,6 +1165,7 @@ async fn run_project( args.package, args.extras, args.dev, + args.editable, args.install_options, args.modifications, args.python, @@ -1332,6 +1334,7 @@ async fn run_project( args.output_file, args.extras, args.dev, + args.editable, args.locked, args.frozen, args.python, diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 1bf7a398d050..8cb26c4e4120 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -22,9 +22,9 @@ use uv_cli::{ }; use uv_client::Connectivity; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, ExportFormat, ExtrasSpecification, HashCheckingMode, - IndexStrategy, InstallOptions, KeyringProviderType, NoBinary, NoBuild, PreviewMode, Reinstall, - SourceStrategy, TargetTriple, TrustedHost, Upgrade, + BuildOptions, Concurrency, ConfigSettings, 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: bool, + pub(crate) editable: EditableMode, pub(crate) with: Vec, pub(crate) with_editable: Vec, pub(crate) with_requirements: Vec, @@ -233,6 +234,7 @@ impl RunSettings { no_all_extras, dev, no_dev, + non_editable, command: _, with, with_editable, @@ -258,6 +260,7 @@ impl RunSettings { extra.unwrap_or_default(), ), dev: flag(dev, no_dev).unwrap_or(true), + editable: EditableMode::from_args(non_editable), with, with_editable, with_requirements: with_requirements @@ -660,6 +663,7 @@ pub(crate) struct SyncSettings { pub(crate) frozen: bool, pub(crate) extras: ExtrasSpecification, pub(crate) dev: bool, + pub(crate) editable: EditableMode, pub(crate) install_options: InstallOptions, pub(crate) modifications: Modifications, pub(crate) package: Option, @@ -678,6 +682,7 @@ impl SyncSettings { no_all_extras, dev, no_dev, + non_editable, inexact, exact, no_install_project, @@ -705,6 +710,7 @@ impl SyncSettings { extra.unwrap_or_default(), ), dev: flag(dev, no_dev).unwrap_or(true), + editable: EditableMode::from_args(non_editable), install_options: InstallOptions::new( no_install_project, no_install_workspace, @@ -959,6 +965,7 @@ pub(crate) struct ExportSettings { pub(crate) package: Option, pub(crate) extras: ExtrasSpecification, pub(crate) dev: bool, + pub(crate) editable: EditableMode, pub(crate) hashes: bool, pub(crate) install_options: InstallOptions, pub(crate) output_file: Option, @@ -981,6 +988,7 @@ impl ExportSettings { no_all_extras, dev, no_dev, + non_editable, hashes, no_hashes, output_file, @@ -1003,6 +1011,7 @@ impl ExportSettings { extra.unwrap_or_default(), ), dev: flag(dev, no_dev).unwrap_or(true), + editable: EditableMode::from_args(non_editable), hashes: flag(hashes, no_hashes).unwrap_or(true), install_options: InstallOptions::new( no_emit_project, diff --git a/crates/uv/tests/export.rs b/crates/uv/tests/export.rs index 6839b36fec35..f275fbf3c729 100644 --- a/crates/uv/tests/export.rs +++ b/crates/uv/tests/export.rs @@ -451,7 +451,7 @@ fn non_root() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv export --cache-dir [CACHE_DIR] --package child - -e child + -e ./child iniconfig==2.0.0 \ --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 @@ -767,7 +767,7 @@ fn no_emit() -> Result<()> { # This file was autogenerated by uv via the following command: # uv export --cache-dir [CACHE_DIR] --no-emit-package anyio -e . - -e child + -e ./child idna==3.6 \ --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f @@ -789,7 +789,7 @@ fn no_emit() -> Result<()> { ----- stdout ----- # This file was autogenerated by uv via the following command: # uv export --cache-dir [CACHE_DIR] --no-emit-project - -e child + -e ./child anyio==3.7.0 \ --hash=sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce \ --hash=sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0 @@ -885,3 +885,73 @@ fn no_emit() -> Result<()> { Ok(()) } + +#[test] +fn non_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("--non-editable"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv export --cache-dir [CACHE_DIR] --non-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(()) +} diff --git a/crates/uv/tests/sync.rs b/crates/uv/tests/sync.rs index bff1a3def208..234304fbe24a 100644 --- a/crates/uv/tests/sync.rs +++ b/crates/uv/tests/sync.rs @@ -2328,3 +2328,86 @@ fn transitive_dev() -> Result<()> { Ok(()) } + +/// Avoid installing dev dependencies of transitive dependencies. +#[test] +fn sync_non_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("--non-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(()) +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a14a93eff1d5..1429201c5ba3 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -269,6 +269,8 @@ uv run [OPTIONS]

Implies --frozen, as the project dependencies will be ignored (i.e., the lockfile will not be updated, since the environment will not be synced regardless).

+
--non-editable

Install the project and any local dependencies as non-editable

+
--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

@@ -1331,6 +1333,8 @@ uv sync [OPTIONS]
--no-sources

Ignore the tool.uv.sources table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources

+
--non-editable

Install the project and any local dependencies as non-editable

+
--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

@@ -1885,6 +1889,8 @@ uv export [OPTIONS]
--no-sources

Ignore the tool.uv.sources table when resolving dependencies. Used to lock against the standards-compliant, publishable package metadata, as opposed to using any local or Git sources

+
--non-editable

Install the project and any local dependencies as non-editable

+
--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.