Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for --only-dev to uv sync and uv export #7367

Merged
merged 1 commit into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2395,6 +2395,12 @@ pub struct RunArgs {
#[arg(long, overrides_with("dev"))]
pub no_dev: bool,

/// Omit non-development dependencies.
///
/// The project itself will also be omitted.
#[arg(long, conflicts_with("no_dev"))]
pub only_dev: bool,

/// The command to run.
///
/// If the path to a Python script (i.e., ending in `.py`), it will be
Expand Down Expand Up @@ -2541,6 +2547,12 @@ pub struct SyncArgs {
#[arg(long, overrides_with("dev"))]
pub no_dev: bool,

/// Omit non-development dependencies.
///
/// The project itself will also be omitted.
#[arg(long, conflicts_with("no_dev"))]
pub only_dev: bool,

/// Do not remove extraneous packages present in the environment.
///
/// When enabled, uv will make the minimum necessary changes to satisfy the requirements.
Expand Down Expand Up @@ -2977,6 +2989,12 @@ pub struct ExportArgs {
#[arg(long, overrides_with("dev"))]
pub no_dev: bool,

/// Omit non-development dependencies.
///
/// The project itself will also be omitted.
#[arg(long, conflicts_with("no_dev"))]
pub only_dev: bool,

/// Include hashes for all dependencies.
#[arg(long, overrides_with("no_hashes"), hide = true)]
pub hashes: bool,
Expand Down
53 changes: 53 additions & 0 deletions crates/uv-configuration/src/dev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use either::Either;
use uv_normalize::GroupName;

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum DevMode {
/// Include development dependencies.
#[default]
Include,
/// Exclude development dependencies.
Exclude,
/// Only include development dependencies, excluding all other dependencies.
Only,
}

impl DevMode {
/// Determine the [`DevMode`] policy from the command-line arguments.
pub fn from_args(dev: bool, no_dev: bool, only_dev: bool) -> Self {
if only_dev {
Self::Only
} else if no_dev {
Self::Exclude
} else if dev {
Self::Include
} else {
Self::default()
}
}
}

#[derive(Debug, Copy, Clone)]
pub enum DevSpecification<'group> {
/// Include dev dependencies from the specified group.
Include(&'group [GroupName]),
/// Do not include dev dependencies.
Exclude,
/// Include dev dependencies from the specified group, and exclude all non-dev dependencies.
Only(&'group [GroupName]),
}

impl<'group> DevSpecification<'group> {
/// Returns an [`Iterator`] over the group names to include.
pub fn iter(&self) -> impl Iterator<Item = &GroupName> {
match self {
Self::Exclude => Either::Left(std::iter::empty()),
Self::Include(groups) | Self::Only(groups) => Either::Right(groups.iter()),
}
}

/// Returns `true` if the specification allows for production dependencies.
pub fn prod(&self) -> bool {
matches!(self, Self::Exclude | Self::Include(_))
}
}
2 changes: 2 additions & 0 deletions crates/uv-configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub use build_options::*;
pub use concurrency::*;
pub use config_settings::*;
pub use constraints::*;
pub use dev::*;
pub use export_format::*;
pub use extras::*;
pub use hash::*;
Expand All @@ -20,6 +21,7 @@ mod build_options;
mod concurrency;
mod config_settings;
mod constraints;
mod dev;
mod export_format;
mod extras;
mod hash;
Expand Down
34 changes: 18 additions & 16 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use pypi_types::{
redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement,
RequirementSource, ResolverMarkerEnvironment,
};
use uv_configuration::{BuildOptions, ExtrasSpecification, InstallOptions};
use uv_configuration::{BuildOptions, DevSpecification, ExtrasSpecification, InstallOptions};
use uv_distribution::DistributionDatabase;
use uv_fs::{relative_to, PortablePath, PortablePathBuf};
use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference};
Expand Down Expand Up @@ -540,7 +540,7 @@ impl Lock {
marker_env: &ResolverMarkerEnvironment,
tags: &Tags,
extras: &ExtrasSpecification,
dev: &[GroupName],
dev: DevSpecification<'_>,
build_options: &BuildOptions,
install_options: &InstallOptions,
) -> Result<Resolution, LockError> {
Expand All @@ -558,26 +558,28 @@ impl Lock {
name: root_name.clone(),
})?;

// Add the base package.
queue.push_back((root, None));
if dev.prod() {
// Add the base package.
queue.push_back((root, None));

// Add any extras.
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in root.optional_dependencies.keys() {
queue.push_back((root, Some(extra)));
// Add any extras.
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in root.optional_dependencies.keys() {
queue.push_back((root, Some(extra)));
}
}
}
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((root, Some(extra)));
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((root, Some(extra)));
}
}
}
}

// Add any dev dependencies.
for group in dev {
for group in dev.iter() {
for dep in root.dev_dependencies.get(group).into_iter().flatten() {
if dep.complexified_marker.evaluate(marker_env, &[]) {
let dep_dist = self.find_by_id(&dep.package_id);
Expand All @@ -596,7 +598,7 @@ impl Lock {

// Add any dependency groups that are exclusive to the workspace root (e.g., dev
// dependencies in (legacy) non-project workspace roots).
for group in dev {
for group in dev.iter() {
for dependency in project.group(group) {
if dependency.marker.evaluate(marker_env, &[]) {
let root_name = &dependency.name;
Expand Down
70 changes: 44 additions & 26 deletions crates/uv-resolver/src/lock/requirements_txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@ use std::path::{Component, Path, PathBuf};
use either::Either;
use petgraph::visit::IntoNodeReferences;
use petgraph::{Directed, Graph};
use rustc_hash::{FxHashMap, FxHashSet};
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
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::{DevSpecification, ExtrasSpecification, InstallOptions};
use uv_fs::Simplified;
use uv_git::GitReference;
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_normalize::{ExtraName, PackageName};

use crate::graph_ops::marker_reachability;
use crate::lock::{Package, PackageId, Source};
Expand All @@ -42,46 +42,68 @@ impl<'lock> RequirementsTxtExport<'lock> {
lock: &'lock Lock,
root_name: &PackageName,
extras: &ExtrasSpecification,
dev: &[GroupName],
dev: DevSpecification<'_>,
hashes: bool,
install_options: &'lock InstallOptions,
) -> Result<Self, LockError> {
let size_guess = lock.packages.len();
let mut petgraph = LockGraph::with_capacity(size_guess, size_guess);
let mut inverse = FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher);

let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new();
let mut inverse = FxHashMap::default();
let mut seen = FxHashSet::default();

// Add the workspace package to the queue.
let root = lock
.find_by_name(root_name)
.expect("found too many packages matching root")
.expect("could not find root");

// Add the base package.
queue.push_back((root, None));
if dev.prod() {
// Add the base package.
queue.push_back((root, None));

// Add any extras.
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in root.optional_dependencies.keys() {
queue.push_back((root, Some(extra)));
// Add any extras.
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in root.optional_dependencies.keys() {
queue.push_back((root, Some(extra)));
}
}
}
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((root, Some(extra)));
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((root, Some(extra)));
}
}
}

// Add the root package to the graph.
inverse.insert(&root.id, petgraph.add_node(root));
}

// Add the root package to the graph.
inverse.insert(&root.id, petgraph.add_node(root));
// Add any dev dependencies.
for group in dev.iter() {
for dep in root.dev_dependencies.get(group).into_iter().flatten() {
let dep_dist = lock.find_by_id(&dep.package_id);

// Create all the relevant nodes.
let mut seen = FxHashSet::default();
// Add the dependency to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) {
entry.insert(petgraph.add_node(dep_dist));
}

if seen.insert((&dep.package_id, None)) {
queue.push_back((dep_dist, None));
}
for extra in &dep.extra {
if seen.insert((&dep.package_id, Some(extra))) {
queue.push_back((dep_dist, Some(extra)));
}
}
}
}

// Create all the relevant nodes.
while let Some((package, extra)) = queue.pop_front() {
let index = inverse[&package.id];

Expand All @@ -94,11 +116,7 @@ impl<'lock> RequirementsTxtExport<'lock> {
.flatten(),
)
} else {
Either::Right(package.dependencies.iter().chain(
dev.iter().flat_map(|group| {
package.dev_dependencies.get(group).into_iter().flatten()
}),
))
Either::Right(package.dependencies.iter())
};

for dep in deps {
Expand Down
8 changes: 4 additions & 4 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,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, DevMode, ExtrasSpecification, InstallOptions, SourceStrategy,
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
Expand Down Expand Up @@ -729,17 +729,17 @@ async fn lock_and_sync(
let (extras, dev) = match dependency_type {
DependencyType::Production => {
let extras = ExtrasSpecification::None;
let dev = false;
let dev = DevMode::Exclude;
(extras, dev)
}
DependencyType::Dev => {
let extras = ExtrasSpecification::None;
let dev = true;
let dev = DevMode::Include;
(extras, dev)
}
DependencyType::Optional(ref group_name) => {
let extras = ExtrasSpecification::Some(vec![group_name.clone()]);
let dev = false;
let dev = DevMode::Exclude;
(extras, dev)
}
};
Expand Down
16 changes: 9 additions & 7 deletions crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, DevMode, DevSpecification, ExportFormat, ExtrasSpecification, InstallOptions,
};
use uv_fs::CWD;
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
Expand All @@ -30,7 +32,7 @@ pub(crate) async fn export(
install_options: InstallOptions,
output_file: Option<PathBuf>,
extras: ExtrasSpecification,
dev: bool,
dev: DevMode,
locked: bool,
frozen: bool,
python: Option<String>,
Expand Down Expand Up @@ -111,10 +113,10 @@ pub(crate) async fn export(
};

// Include development dependencies, if requested.
let dev = if dev {
vec![DEV_DEPENDENCIES.clone()]
} else {
vec![]
let dev = match dev {
DevMode::Include => DevSpecification::Include(std::slice::from_ref(&DEV_DEPENDENCIES)),
DevMode::Exclude => DevSpecification::Exclude,
DevMode::Only => DevSpecification::Only(std::slice::from_ref(&DEV_DEPENDENCIES)),
};

// Write the resolved dependencies to the output channel.
Expand All @@ -127,7 +129,7 @@ pub(crate) async fn export(
&lock,
project.project_name(),
&extras,
&dev,
dev,
hashes,
&install_options,
)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, DevMode, ExtrasSpecification, InstallOptions};
use uv_fs::{Simplified, CWD};
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_scripts::Pep723Script;
Expand Down Expand Up @@ -189,7 +189,7 @@ 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 dev = DevMode::Include;
let install_options = InstallOptions::default();

// Initialize any shared state.
Expand Down
Loading
Loading