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 uv export --all-packages #8742

Merged
merged 1 commit into from
Nov 2, 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
12 changes: 11 additions & 1 deletion crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3343,10 +3343,20 @@ pub struct ExportArgs {
#[arg(long, value_enum, default_value_t = ExportFormat::default())]
pub format: ExportFormat,

/// Export the entire workspace.
///
/// The dependencies for all workspace members will be included in the
/// exported requirements file.
///
/// Any extras or groups specified via `--extra`, `--group`, or related options
/// will be applied to all workspace members.
#[arg(long, conflicts_with = "package")]
pub all_packages: bool,

/// Export the dependencies for a specific package in the workspace.
///
/// If the workspace member does not exist, uv will exit with an error.
#[arg(long)]
#[arg(long, conflicts_with = "all_packages")]
pub package: Option<PackageName>,

/// Include optional dependencies from the specified extra name.
Expand Down
120 changes: 63 additions & 57 deletions crates/uv-resolver/src/lock/requirements_txt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ use petgraph::{Directed, Graph};
use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
use url::Url;

use crate::graph_ops::marker_reachability;
use crate::lock::{Package, PackageId, Source};
use crate::{Lock, LockError};
use uv_configuration::{DevGroupsManifest, EditableMode, ExtrasSpecification, InstallOptions};
use uv_distribution_filename::{DistExtension, SourceDistExtension};
use uv_fs::Simplified;
use uv_git::GitReference;
use uv_normalize::{ExtraName, PackageName};
use uv_normalize::ExtraName;
use uv_pep508::MarkerTree;
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl};

use crate::graph_ops::marker_reachability;
use crate::lock::{Package, PackageId, Source};
use crate::{Lock, LockError};
use uv_workspace::InstallTarget;

type LockGraph<'lock> = Graph<Node<'lock>, Edge, Directed>;

Expand All @@ -35,7 +35,7 @@ pub struct RequirementsTxtExport<'lock> {
impl<'lock> RequirementsTxtExport<'lock> {
pub fn from_lock(
lock: &'lock Lock,
root_name: &PackageName,
target: InstallTarget<'lock>,
extras: &ExtrasSpecification,
dev: &DevGroupsManifest,
editable: EditableMode,
Expand All @@ -52,65 +52,67 @@ impl<'lock> RequirementsTxtExport<'lock> {
let root = petgraph.add_node(Node::Root);

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

if dev.prod() {
// Add the workspace package to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dist.id) {
entry.insert(petgraph.add_node(Node::Package(dist)));
}
for root_name in target.packages() {
let dist = lock
.find_by_name(root_name)
.expect("found too many packages matching root")
.expect("could not find root");

if dev.prod() {
// Add the workspace package to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dist.id) {
entry.insert(petgraph.add_node(Node::Package(dist)));
}

// Add an edge from the root.
let index = inverse[&dist.id];
petgraph.add_edge(root, index, MarkerTree::TRUE);

// Add an edge from the root.
let index = inverse[&dist.id];
petgraph.add_edge(root, index, MarkerTree::TRUE);

// Push its dependencies on the queue.
queue.push_back((dist, None));
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in dist.optional_dependencies.keys() {
queue.push_back((dist, Some(extra)));
// Push its dependencies on the queue.
queue.push_back((dist, None));
match extras {
ExtrasSpecification::None => {}
ExtrasSpecification::All => {
for extra in dist.optional_dependencies.keys() {
queue.push_back((dist, Some(extra)));
}
}
}
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((dist, Some(extra)));
ExtrasSpecification::Some(extras) => {
for extra in extras {
queue.push_back((dist, Some(extra)));
}
}
}
}
}

// Add any development dependencies.
for group in dev.iter() {
for dep in dist.dependency_groups.get(group).into_iter().flatten() {
let dep_dist = lock.find_by_id(&dep.package_id);
// Add any development dependencies.
for group in dev.iter() {
for dep in dist.dependency_groups.get(group).into_iter().flatten() {
let dep_dist = lock.find_by_id(&dep.package_id);

// Add the dependency to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) {
entry.insert(petgraph.add_node(Node::Package(dep_dist)));
}
// Add the dependency to the graph.
if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) {
entry.insert(petgraph.add_node(Node::Package(dep_dist)));
}

// Add an edge from the root. Development dependencies may be installed without
// installing the workspace package itself (which can never have markers on it
// anyway), so they're directly connected to the root.
let dep_index = inverse[&dep.package_id];
petgraph.add_edge(
root,
dep_index,
dep.simplified_marker.as_simplified_marker_tree().clone(),
);
// Add an edge from the root. Development dependencies may be installed without
// installing the workspace package itself (which can never have markers on it
// anyway), so they're directly connected to the root.
let dep_index = inverse[&dep.package_id];
petgraph.add_edge(
root,
dep_index,
dep.simplified_marker.as_simplified_marker_tree().clone(),
);

// Push its dependencies on the queue.
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)));
// Push its dependencies on the queue.
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)));
}
}
}
}
Expand Down Expand Up @@ -170,7 +172,11 @@ impl<'lock> RequirementsTxtExport<'lock> {
Node::Package(package) => Some((index, package)),
})
.filter(|(_index, package)| {
install_options.include_package(&package.id.name, Some(root_name), lock.members())
install_options.include_package(
&package.id.name,
target.project_name(),
lock.members(),
)
})
.map(|(index, package)| Requirement {
package,
Expand Down
38 changes: 24 additions & 14 deletions crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use crate::settings::ResolverSettings;
pub(crate) async fn export(
project_dir: &Path,
format: ExportFormat,
all_packages: bool,
package: Option<PackageName>,
hashes: bool,
install_options: InstallOptions,
Expand All @@ -52,14 +53,7 @@ pub(crate) async fn export(
printer: Printer,
) -> Result<ExitStatus> {
// Identify the project.
let project = if let Some(package) = package {
VirtualProject::Project(
Workspace::discover(project_dir, &DiscoveryOptions::default())
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?,
)
} else if frozen {
let project = if frozen && !all_packages {
VirtualProject::discover(
project_dir,
&DiscoveryOptions {
Expand All @@ -68,15 +62,18 @@ pub(crate) async fn export(
},
)
.await?
} else if let Some(package) = package.as_ref() {
VirtualProject::Project(
Workspace::discover(project_dir, &DiscoveryOptions::default())
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?,
)
} else {
VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await?
};

// Determine the default groups to include.
validate_dependency_groups(InstallTarget::from_project(&project), &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?;

let VirtualProject::Project(project) = project else {
if project.is_non_project() {
return Err(anyhow::anyhow!("Legacy non-project roots are not supported in `uv export`; add a `[project]` table to your `pyproject.toml` to enable exports"));
};

Expand Down Expand Up @@ -147,6 +144,19 @@ pub(crate) async fn export(
Err(err) => return Err(err.into()),
};

// Identify the target.
let target = if let Some(package) = package.as_ref().filter(|_| frozen) {
InstallTarget::frozen(&project, package)
} else if all_packages {
InstallTarget::from_workspace(&project)
} else {
InstallTarget::from_project(&project)
};

// Determine the default groups to include.
validate_dependency_groups(target, &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?;

// Write the resolved dependencies to the output channel.
let mut writer = OutputWriter::new(!quiet || output_file.is_none(), output_file.as_deref());

Expand All @@ -155,7 +165,7 @@ pub(crate) async fn export(
ExportFormat::RequirementsTxt => {
let export = RequirementsTxtExport::from_lock(
&lock,
project.project_name(),
target,
&extras,
&dev.with_defaults(defaults),
editable,
Expand Down
16 changes: 8 additions & 8 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ pub(crate) async fn sync(
VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await?
};

// TODO(lucab): improve warning content
// <https://github.com/astral-sh/uv/issues/7428>
if project.workspace().pyproject_toml().has_scripts()
&& !project.workspace().pyproject_toml().is_package()
{
warn_user!("Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`");
}

// Identify the target.
let target = if let Some(package) = package.as_ref().filter(|_| frozen) {
InstallTarget::frozen(&project, package)
Expand All @@ -90,14 +98,6 @@ pub(crate) async fn sync(
InstallTarget::from_project(&project)
};

// TODO(lucab): improve warning content
// <https://github.com/astral-sh/uv/issues/7428>
if project.workspace().pyproject_toml().has_scripts()
&& !project.workspace().pyproject_toml().is_package()
{
warn_user!("Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`");
}

// Determine the default groups to include.
validate_dependency_groups(target, &dev)?;
let defaults = default_dependency_groups(project.pyproject_toml())?;
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1510,6 +1510,7 @@ async fn run_project(
commands::export(
project_dir,
args.format,
args.all_packages,
args.package,
args.hashes,
args.install_options,
Expand Down
5 changes: 4 additions & 1 deletion crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,7 @@ impl TreeSettings {
#[derive(Debug, Clone)]
pub(crate) struct ExportSettings {
pub(crate) format: ExportFormat,
pub(crate) all_packages: bool,
pub(crate) package: Option<PackageName>,
pub(crate) extras: ExtrasSpecification,
pub(crate) dev: DevGroupsSpecification,
Expand All @@ -1115,6 +1116,7 @@ impl ExportSettings {
pub(crate) fn resolve(args: ExportArgs, filesystem: Option<FilesystemOptions>) -> Self {
let ExportArgs {
format,
all_packages,
package,
extra,
all_extras,
Expand Down Expand Up @@ -1143,8 +1145,9 @@ impl ExportSettings {
} = args;

Self {
package,
format,
all_packages,
package,
extras: ExtrasSpecification::from_args(
flag(all_extras, no_all_extras).unwrap_or_default(),
extra.unwrap_or_default(),
Expand Down
Loading
Loading