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 --script support to uv tree for PEP 723 scripts #10159

Open
wants to merge 1 commit into
base: charlie/script-add
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3417,6 +3417,14 @@ pub struct TreeArgs {
#[command(flatten)]
pub resolver: ResolverArgs,

/// Show the dependency tree the specified PEP 723 Python script, rather than the current
/// project.
///
/// If provided, uv will resolve the dependencies based on its inline metadata table, in
/// adherence with PEP 723.
#[arg(long)]
pub script: Option<PathBuf>,

/// The Python version to use when filtering the tree.
///
/// For example, pass `--python-version 3.10` to display the dependencies
Expand Down
8 changes: 8 additions & 0 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ pub(crate) enum ProjectError {
#[error("Group `{0}` is not defined in any project's `dependency-group` table")]
MissingGroupWorkspace(GroupName),

#[error("PEP 723 scripts do not support dependency groups, but group `{0}` was specified")]
MissingGroupScript(GroupName),

#[error("Default group `{0}` (from `tool.uv.default-groups`) is not defined in the project's `dependency-group` table")]
MissingDefaultGroup(GroupName),

Expand Down Expand Up @@ -1729,6 +1732,8 @@ pub(crate) enum DependencyGroupsTarget<'env> {
Workspace(&'env Workspace),
/// The dependency groups must be defined in the target project.
Project(&'env ProjectWorkspace),
/// The dependency groups must be defined in the target script.
Script,
}

impl DependencyGroupsTarget<'_> {
Expand Down Expand Up @@ -1759,6 +1764,9 @@ impl DependencyGroupsTarget<'_> {
return Err(ProjectError::MissingGroupProject(group.clone()));
}
}
Self::Script => {
return Err(ProjectError::MissingGroupScript(group.clone()));
}
}
}
Ok(())
Expand Down
51 changes: 41 additions & 10 deletions crates/uv/src/commands/project/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ use uv_distribution_types::IndexCapabilities;
use uv_pep508::PackageName;
use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion};
use uv_resolver::{PackageMap, TreeDisplay};
use uv_scripts::{Pep723ItemRef, Pep723Script};
use uv_settings::PythonInstallMirrors;
use uv_workspace::{DiscoveryOptions, Workspace};

use crate::commands::pip::latest::LatestClient;
use crate::commands::pip::loggers::DefaultResolveLogger;
use crate::commands::pip::resolution_markers;
use crate::commands::project::lock::{do_safe_lock, LockMode};
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
default_dependency_groups, DependencyGroupsTarget, ProjectError, ProjectInterpreter,
ScriptInterpreter,
};
use crate::commands::reporters::LatestVersionReporter;
use crate::commands::{diagnostics, ExitStatus};
Expand All @@ -49,6 +52,7 @@ pub(crate) async fn tree(
python: Option<String>,
install_mirrors: PythonInstallMirrors,
settings: ResolverSettings,
script: Option<Pep723Script>,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
connectivity: Connectivity,
Expand All @@ -61,24 +65,51 @@ pub(crate) async fn tree(
preview: PreviewMode,
) -> Result<ExitStatus> {
// Find the project requirements.
let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?;
let workspace;
let target = if let Some(script) = script.as_ref() {
LockTarget::Script(script)
} else {
workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?;
LockTarget::Workspace(&workspace)
};

// Validate that any referenced dependency groups are defined in the workspace.
// Validate that any referenced dependency groups are defined in the target.
if !frozen {
let target = DependencyGroupsTarget::Workspace(&workspace);
let target = match &target {
LockTarget::Workspace(workspace) => DependencyGroupsTarget::Workspace(workspace),
LockTarget::Script(..) => DependencyGroupsTarget::Script,
};
target.validate(&dev)?;
}

// Determine the default groups to include.
let defaults = default_dependency_groups(workspace.pyproject_toml())?;
let defaults = match target {
LockTarget::Workspace(workspace) => default_dependency_groups(workspace.pyproject_toml())?,
LockTarget::Script(_) => vec![],
};

// Find an interpreter for the project, unless `--frozen` and `--universal` are both set.
let interpreter = if frozen && universal {
None
} else {
Some(
ProjectInterpreter::discover(
&workspace,
Some(match target {
LockTarget::Script(script) => ScriptInterpreter::discover(
Pep723ItemRef::Script(script),
python.as_deref().map(PythonRequest::parse),
python_preference,
python_downloads,
connectivity,
native_tls,
allow_insecure_host,
&install_mirrors,
no_config,
cache,
printer,
)
.await?
.into_interpreter(),
LockTarget::Workspace(workspace) => ProjectInterpreter::discover(
workspace,
project_dir,
python.as_deref().map(PythonRequest::parse),
python_preference,
Expand All @@ -93,7 +124,7 @@ pub(crate) async fn tree(
)
.await?
.into_interpreter(),
)
})
};

// Determine the lock mode.
Expand All @@ -111,7 +142,7 @@ pub(crate) async fn tree(
// Update the lockfile, if necessary.
let lock = match do_safe_lock(
mode,
(&workspace).into(),
target,
settings.as_ref(),
LowerBound::Allow,
&state,
Expand Down Expand Up @@ -151,7 +182,7 @@ pub(crate) async fn tree(
.packages()
.iter()
.filter_map(|package| {
let index = match package.index(workspace.install_path()) {
let index = match package.index(target.install_path()) {
Ok(Some(index)) => index,
Ok(None) => return None,
Err(err) => return Some(Err(err)),
Expand Down
18 changes: 16 additions & 2 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
script: Some(script),
..
}) = &**command
{
Pep723Script::read(&script).await?.map(Pep723Item::Script)
} else if let ProjectCommand::Tree(uv_cli::TreeArgs {
script: Some(script),
..
}) = &**command
{
Pep723Script::read(&script).await?.map(Pep723Item::Script)
} else {
Expand Down Expand Up @@ -1627,7 +1633,14 @@ async fn run_project(
// Initialize the cache.
let cache = cache.init()?;

commands::tree(
// Unwrap the script.
let script = script.map(|script| match script {
Pep723Item::Script(script) => script,
Pep723Item::Stdin(_) => unreachable!("`uv tree` does not support stdin"),
Pep723Item::Remote(_) => unreachable!("`uv tree` does not support remote files"),
});

Box::pin(commands::tree(
project_dir,
args.dev,
args.locked,
Expand All @@ -1644,6 +1657,7 @@ async fn run_project(
args.python,
args.install_mirrors,
args.resolver,
script,
globals.python_preference,
globals.python_downloads,
globals.connectivity,
Expand All @@ -1654,7 +1668,7 @@ async fn run_project(
&cache,
printer,
globals.preview,
)
))
.await
}
ProjectCommand::Export(args) => {
Expand Down
4 changes: 4 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,8 @@ pub(crate) struct TreeSettings {
pub(crate) no_dedupe: bool,
pub(crate) invert: bool,
pub(crate) outdated: bool,
#[allow(dead_code)]
pub(crate) script: Option<PathBuf>,
pub(crate) python_version: Option<PythonVersion>,
pub(crate) python_platform: Option<TargetTriple>,
pub(crate) python: Option<String>,
Expand All @@ -1297,6 +1299,7 @@ impl TreeSettings {
frozen,
build,
resolver,
script,
python_version,
python_platform,
python,
Expand All @@ -1319,6 +1322,7 @@ impl TreeSettings {
no_dedupe: tree.no_dedupe,
invert: tree.invert,
outdated: tree.outdated,
script,
python_version,
python_platform,
python: python.and_then(Maybe::into_option),
Expand Down
Loading
Loading