mirror of https://github.com/astral-sh/uv
Add support for `--no-deps` to `pip install` (#1191)
## Summary Closes https://github.com/astral-sh/puffin/issues/1188.
This commit is contained in:
parent
8305acc584
commit
c129717b41
|
|
@ -0,0 +1,20 @@
|
|||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub enum DependencyMode {
|
||||
/// Include all dependencies, whether direct or transitive.
|
||||
#[default]
|
||||
Transitive,
|
||||
/// Exclude transitive dependencies, only resolving the root package's immediate dependencies.
|
||||
Direct,
|
||||
}
|
||||
|
||||
impl DependencyMode {
|
||||
/// Returns `true` if transitive dependencies should be included.
|
||||
pub fn is_transitive(self) -> bool {
|
||||
matches!(self, Self::Transitive)
|
||||
}
|
||||
|
||||
/// Returns `true` if (only) direct dependencies should be excluded.
|
||||
pub fn is_direct(self) -> bool {
|
||||
matches!(self, Self::Direct)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
pub use dependency_mode::DependencyMode;
|
||||
pub use error::ResolveError;
|
||||
pub use finder::{DistFinder, Reporter as FinderReporter};
|
||||
pub use manifest::Manifest;
|
||||
|
|
@ -10,6 +11,7 @@ pub use resolver::{
|
|||
};
|
||||
|
||||
mod candidate_selector;
|
||||
mod dependency_mode;
|
||||
mod error;
|
||||
mod finder;
|
||||
mod manifest;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::{PreReleaseMode, ResolutionMode};
|
||||
use crate::{DependencyMode, PreReleaseMode, ResolutionMode};
|
||||
|
||||
/// Options for resolving a manifest.
|
||||
#[derive(Debug, Default, Copy, Clone)]
|
||||
pub struct Options {
|
||||
pub resolution_mode: ResolutionMode,
|
||||
pub prerelease_mode: PreReleaseMode,
|
||||
pub dependency_mode: DependencyMode,
|
||||
pub exclude_newer: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
|
|
@ -15,6 +16,7 @@ pub struct Options {
|
|||
pub struct OptionsBuilder {
|
||||
resolution_mode: ResolutionMode,
|
||||
prerelease_mode: PreReleaseMode,
|
||||
dependency_mode: DependencyMode,
|
||||
exclude_newer: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +40,13 @@ impl OptionsBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the dependency mode.
|
||||
#[must_use]
|
||||
pub fn dependency_mode(mut self, dependency_mode: DependencyMode) -> Self {
|
||||
self.dependency_mode = dependency_mode;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the exclusion date.
|
||||
#[must_use]
|
||||
pub fn exclude_newer(mut self, exclude_newer: Option<DateTime<Utc>>) -> Self {
|
||||
|
|
@ -50,6 +59,7 @@ impl OptionsBuilder {
|
|||
Options {
|
||||
resolution_mode: self.resolution_mode,
|
||||
prerelease_mode: self.prerelease_mode,
|
||||
dependency_mode: self.dependency_mode,
|
||||
exclude_newer: self.exclude_newer,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ pub use crate::resolver::provider::ResolverProvider;
|
|||
use crate::resolver::reporter::Facade;
|
||||
pub use crate::resolver::reporter::{BuildId, Reporter};
|
||||
use crate::version_map::VersionMap;
|
||||
use crate::Options;
|
||||
use crate::{DependencyMode, Options};
|
||||
|
||||
mod allowed_urls;
|
||||
mod index;
|
||||
|
|
@ -63,6 +63,7 @@ pub struct Resolver<'a, Provider: ResolverProvider> {
|
|||
constraints: Vec<Requirement>,
|
||||
overrides: Overrides,
|
||||
allowed_urls: AllowedUrls,
|
||||
dependency_mode: DependencyMode,
|
||||
markers: &'a MarkerEnvironment,
|
||||
python_requirement: PythonRequirement,
|
||||
selector: CandidateSelector,
|
||||
|
|
@ -173,6 +174,7 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
|||
visited: DashSet::default(),
|
||||
selector,
|
||||
allowed_urls,
|
||||
dependency_mode: options.dependency_mode,
|
||||
project: manifest.project,
|
||||
requirements: manifest.requirements,
|
||||
constraints: manifest.constraints,
|
||||
|
|
@ -644,6 +646,11 @@ impl<'a, Provider: ResolverProvider> Resolver<'a, Provider> {
|
|||
}
|
||||
|
||||
PubGrubPackage::Package(package_name, extra, url) => {
|
||||
// If we're excluding transitive dependencies, short-circuit.
|
||||
if self.dependency_mode.is_direct() {
|
||||
return Ok(Dependencies::Available(DependencyConstraints::default()));
|
||||
}
|
||||
|
||||
// Wait for the metadata to be available.
|
||||
let dist = match url {
|
||||
Some(url) => PubGrubDistribution::from_url(package_name, url),
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ use puffin_installer::{
|
|||
use puffin_interpreter::{Interpreter, Virtualenv};
|
||||
use puffin_normalize::PackageName;
|
||||
use puffin_resolver::{
|
||||
InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode, ResolutionGraph,
|
||||
ResolutionMode, Resolver,
|
||||
DependencyMode, InMemoryIndex, Manifest, Options, OptionsBuilder, PreReleaseMode,
|
||||
ResolutionGraph, ResolutionMode, Resolver,
|
||||
};
|
||||
use puffin_traits::{InFlight, SetupPyStrategy};
|
||||
use requirements_txt::EditableRequirement;
|
||||
|
|
@ -46,6 +46,7 @@ pub(crate) async fn pip_install(
|
|||
extras: &ExtrasSpecification<'_>,
|
||||
resolution_mode: ResolutionMode,
|
||||
prerelease_mode: PreReleaseMode,
|
||||
dependency_mode: DependencyMode,
|
||||
index_locations: IndexLocations,
|
||||
reinstall: &Reinstall,
|
||||
link_mode: LinkMode,
|
||||
|
|
@ -154,6 +155,7 @@ pub(crate) async fn pip_install(
|
|||
let options = OptionsBuilder::new()
|
||||
.resolution_mode(resolution_mode)
|
||||
.prerelease_mode(prerelease_mode)
|
||||
.dependency_mode(dependency_mode)
|
||||
.exclude_newer(exclude_newer)
|
||||
.build();
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use puffin_cache::{Cache, CacheArgs, Refresh};
|
|||
use puffin_installer::{NoBinary, Reinstall};
|
||||
use puffin_interpreter::PythonVersion;
|
||||
use puffin_normalize::{ExtraName, PackageName};
|
||||
use puffin_resolver::{PreReleaseMode, ResolutionMode};
|
||||
use puffin_resolver::{DependencyMode, PreReleaseMode, ResolutionMode};
|
||||
use puffin_traits::SetupPyStrategy;
|
||||
use requirements::ExtrasSpecification;
|
||||
|
||||
|
|
@ -445,6 +445,11 @@ struct PipInstallArgs {
|
|||
#[clap(long)]
|
||||
refresh_package: Vec<PackageName>,
|
||||
|
||||
/// Ignore package dependencies, instead only installing those packages explicitly listed
|
||||
/// on the command line or in the requirements files.
|
||||
#[clap(long)]
|
||||
no_deps: bool,
|
||||
|
||||
/// The method to use when installing packages from the global cache.
|
||||
#[clap(long, value_enum, default_value_t = install_wheel_rs::linker::LinkMode::default())]
|
||||
link_mode: install_wheel_rs::linker::LinkMode,
|
||||
|
|
@ -802,6 +807,11 @@ async fn run() -> Result<ExitStatus> {
|
|||
};
|
||||
let reinstall = Reinstall::from_args(args.reinstall, args.reinstall_package);
|
||||
let no_binary = NoBinary::from_args(args.no_binary, args.no_binary_package);
|
||||
let dependency_mode = if args.no_deps {
|
||||
DependencyMode::Direct
|
||||
} else {
|
||||
DependencyMode::Transitive
|
||||
};
|
||||
commands::pip_install(
|
||||
&requirements,
|
||||
&constraints,
|
||||
|
|
@ -809,6 +819,7 @@ async fn run() -> Result<ExitStatus> {
|
|||
&extras,
|
||||
args.resolution,
|
||||
args.prerelease,
|
||||
dependency_mode,
|
||||
index_urls,
|
||||
&reinstall,
|
||||
args.link_mode,
|
||||
|
|
|
|||
|
|
@ -1099,3 +1099,48 @@ fn install_executable_hardlink() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Install a package from the command line into a virtual environment, ignoring its dependencies.
|
||||
#[test]
|
||||
fn no_deps() -> Result<()> {
|
||||
let temp_dir = assert_fs::TempDir::new()?;
|
||||
let cache_dir = assert_fs::TempDir::new()?;
|
||||
let venv = create_venv_py312(&temp_dir, &cache_dir);
|
||||
|
||||
// Install Flask.
|
||||
insta::with_settings!({
|
||||
filters => INSTA_FILTERS.to_vec()
|
||||
}, {
|
||||
assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME))
|
||||
.arg("pip")
|
||||
.arg("install")
|
||||
.arg("Flask")
|
||||
.arg("--no-deps")
|
||||
.arg("--strict")
|
||||
.arg("--cache-dir")
|
||||
.arg(cache_dir.path())
|
||||
.arg("--exclude-newer")
|
||||
.arg(EXCLUDE_NEWER)
|
||||
.env("VIRTUAL_ENV", venv.as_os_str())
|
||||
.current_dir(&temp_dir), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 1 package in [TIME]
|
||||
Downloaded 1 package in [TIME]
|
||||
Installed 1 package in [TIME]
|
||||
+ flask==3.0.0
|
||||
warning: The package `flask` requires `werkzeug >=3.0.0`, but it's not installed.
|
||||
warning: The package `flask` requires `jinja2 >=3.1.2`, but it's not installed.
|
||||
warning: The package `flask` requires `itsdangerous >=2.1.2`, but it's not installed.
|
||||
warning: The package `flask` requires `click >=8.1.3`, but it's not installed.
|
||||
warning: The package `flask` requires `blinker >=1.6.2`, but it's not installed.
|
||||
"###);
|
||||
});
|
||||
|
||||
assert_command(&venv, "import flask", &temp_dir).failure();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue