Skip to content

Commit

Permalink
Respect PEP 723 script lockfiles in uv run
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Dec 24, 2024
1 parent 1abfeb4 commit 786928d
Show file tree
Hide file tree
Showing 11 changed files with 487 additions and 249 deletions.
4 changes: 2 additions & 2 deletions crates/uv-configuration/src/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ impl From<GroupsSpecification> for DevGroupsSpecification {

/// The manifest of `dependency-groups` to include, taking into account the user-provided
/// [`DevGroupsSpecification`] and the project-specific default groups.
#[derive(Debug, Clone)]
#[derive(Debug, Default, Clone)]
pub struct DevGroupsManifest {
/// The specification for the development dependencies.
pub(crate) spec: DevGroupsSpecification,
Expand Down Expand Up @@ -347,7 +347,7 @@ impl DevGroupsManifest {
}

/// Returns `true` if the group was enabled by default.
pub fn default(&self, group: &GroupName) -> bool {
pub fn is_default(&self, group: &GroupName) -> bool {
if self.spec.contains(group) {
// If the group was explicitly requested, then it wasn't enabled by default.
false
Expand Down
7 changes: 7 additions & 0 deletions crates/uv-resolver/src/lock/installable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ pub trait Installable<'lock> {
/// Return the [`PackageName`] of the root packages in the target.
fn roots(&self) -> impl Iterator<Item = &PackageName>;

/// Return the [`InstallTarget`] requirements.
///
/// Returns dependencies that apply to the workspace root, but not any of its members. As such,
/// only returns a non-empty iterator for scripts, which include packages directly (unlike
/// workspaces, in which each member has its own dependencies).
fn requirements(&self) -> Option<&[uv_pep508::Requirement<VerbatimParsedUrl>]>;

/// Return the [`InstallTarget`] dependency groups.
///
/// Returns dependencies that apply to the workspace root, but not any of its members. As such,
Expand Down
20 changes: 20 additions & 0 deletions crates/uv-scripts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ impl Pep723Item {
Self::Remote(_) => None,
}
}

/// Return the PEP 723 script, if any.
pub fn as_script(&self) -> Option<&Pep723Script> {
match self {
Self::Script(script) => Some(script),
_ => None,
}
}
}

/// A PEP 723 script, including its [`Pep723Metadata`].
Expand Down Expand Up @@ -193,6 +201,18 @@ impl Pep723Script {

Ok(())
}

/// Return the [`Sources`] defined in the PEP 723 metadata.
pub fn sources(&self) -> &BTreeMap<PackageName, Sources> {
static EMPTY: BTreeMap<PackageName, Sources> = BTreeMap::new();

self.metadata
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.sources.as_ref())
.unwrap_or(&EMPTY)
}
}

/// PEP 723 metadata as parsed from a `script` comment block.
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ pub(crate) async fn add(
Target::Project(project, environment) => (project, environment),
// If `--script`, exit early. There's no reason to lock and sync.
Target::Script(script, _) => {
// TODO(charlie): Lock the script, if a lockfile already exists.
writeln!(
printer.stderr(),
"Updated `{}`",
Expand Down
138 changes: 119 additions & 19 deletions crates/uv/src/commands/project/environment.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use tracing::debug;

use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::{
resolve_environment, sync_environment, EnvironmentSpecification, ProjectError,
};
Expand All @@ -9,10 +10,13 @@ use crate::settings::ResolverInstallerSettings;
use uv_cache::{Cache, CacheBucket};
use uv_cache_key::{cache_digest, hash_digest};
use uv_client::Connectivity;
use uv_configuration::{Concurrency, PreviewMode, TrustedHost};
use uv_configuration::{
Concurrency, DevGroupsManifest, ExtrasSpecification, InstallOptions, PreviewMode, TrustedHost,
};
use uv_dispatch::SharedState;
use uv_distribution_types::{Name, Resolution};
use uv_python::{Interpreter, PythonEnvironment};
use uv_resolver::Installable;

/// A [`PythonEnvironment`] stored in the cache.
#[derive(Debug)]
Expand All @@ -25,9 +29,8 @@ impl From<CachedEnvironment> for PythonEnvironment {
}

impl CachedEnvironment {
/// Get or create an [`CachedEnvironment`] based on a given set of requirements and a base
/// interpreter.
pub(crate) async fn get_or_create(
/// Get or create an [`CachedEnvironment`] based on a given set of requirements.
pub(crate) async fn from_spec(
spec: EnvironmentSpecification<'_>,
interpreter: Interpreter,
settings: &ResolverInstallerSettings,
Expand All @@ -43,21 +46,7 @@ impl CachedEnvironment {
printer: Printer,
preview: PreviewMode,
) -> Result<Self, ProjectError> {
// When caching, always use the base interpreter, rather than that of the virtual
// environment.
let interpreter = if let Some(interpreter) = interpreter.to_base_interpreter(cache)? {
debug!(
"Caching via base interpreter: `{}`",
interpreter.sys_executable().display()
);
interpreter
} else {
debug!(
"Caching via interpreter: `{}`",
interpreter.sys_executable().display()
);
interpreter
};
let interpreter = Self::base_interpreter(interpreter, cache)?;

// Resolve the requirements with the interpreter.
let resolution = Resolution::from(
Expand All @@ -78,6 +67,93 @@ impl CachedEnvironment {
.await?,
);

Self::from_resolution(
resolution,
interpreter,
settings,
state,
install,
installer_metadata,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
cache,
printer,
preview,
)
.await
}

/// Get or create an [`CachedEnvironment`] based on a given [`InstallTarget`].
pub(crate) async fn from_lock(
target: InstallTarget<'_>,
extras: &ExtrasSpecification,
dev: &DevGroupsManifest,
install_options: InstallOptions,
settings: &ResolverInstallerSettings,
interpreter: Interpreter,
state: &SharedState,
install: Box<dyn InstallLogger>,
installer_metadata: bool,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
allow_insecure_host: &[TrustedHost],
cache: &Cache,
printer: Printer,
preview: PreviewMode,
) -> Result<Self, ProjectError> {
let interpreter = Self::base_interpreter(interpreter, cache)?;

// Determine the tags, markers, and interpreter to use for resolution.
let tags = interpreter.tags()?;
let marker_env = interpreter.resolver_marker_environment();

// Read the lockfile.
let resolution = target.to_resolution(
&marker_env,
tags,
extras,
dev,
&settings.build_options,
&install_options,
)?;

Self::from_resolution(
resolution,
interpreter,
settings,
state,
install,
installer_metadata,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
cache,
printer,
preview,
)
.await
}

/// Get or create an [`CachedEnvironment`] based on a given [`Resolution`].
pub(crate) async fn from_resolution(
resolution: Resolution,
interpreter: Interpreter,
settings: &ResolverInstallerSettings,
state: &SharedState,
install: Box<dyn InstallLogger>,
installer_metadata: bool,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
allow_insecure_host: &[TrustedHost],
cache: &Cache,
printer: Printer,
preview: PreviewMode,
) -> Result<Self, ProjectError> {
// Hash the resolution by hashing the generated lockfile.
// TODO(charlie): If the resolution contains any mutable metadata (like a path or URL
// dependency), skip this step.
Expand Down Expand Up @@ -144,4 +220,28 @@ impl CachedEnvironment {
pub(crate) fn into_interpreter(self) -> Interpreter {
self.0.into_interpreter()
}

/// Return the [`Interpreter`] to use for the cached environment, based on a given
/// [`Interpreter`].
///
/// When caching, always use the base interpreter, rather than that of the virtual
/// environment.
fn base_interpreter(
interpreter: Interpreter,
cache: &Cache,
) -> Result<Interpreter, ProjectError> {
if let Some(interpreter) = interpreter.to_base_interpreter(cache)? {
debug!(
"Caching via base interpreter: `{}`",
interpreter.sys_executable().display()
);
Ok(interpreter)
} else {
debug!(
"Caching via interpreter: `{}`",
interpreter.sys_executable().display()
);
Ok(interpreter)
}
}
}
Loading

0 comments on commit 786928d

Please sign in to comment.