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 requirements files in uv run #4973

Merged
merged 4 commits into from
Jul 23, 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
6 changes: 6 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1833,6 +1833,12 @@ pub struct RunArgs {
#[arg(long)]
pub with: Vec<String>,

/// Run with all packages listed in the given `requirements.txt` files.
///
/// Using `pyproject.toml`, `setup.py`, or `setup.cfg` files is not allowed.
#[arg(long, value_parser = parse_maybe_file_path)]
pub with_requirements: Vec<Maybe<PathBuf>>,

/// Assert that the `uv.lock` will remain unchanged.
#[arg(long, conflicts_with = "frozen")]
pub locked: bool,
Expand Down
5 changes: 5 additions & 0 deletions crates/uv-requirements/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,9 @@ impl RequirementsSpecification {
..Self::default()
}
}

/// Return true if the specification does not include any requirements to install.
pub fn is_empty(&self) -> bool {
self.requirements.is_empty() && self.source_trees.is_empty() && self.overrides.is_empty()
}
}
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ pub(crate) async fn add(
&VirtualProject::Project(project),
&venv,
&lock,
extras,
&extras,
dev,
Modifications::Sufficient,
settings.as_ref().into(),
Expand Down
6 changes: 5 additions & 1 deletion crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,11 @@ pub(crate) async fn update_environment(

// Check if the current environment satisfies the requirements
let site_packages = SitePackages::from_environment(&venv)?;
if spec.source_trees.is_empty() && reinstall.is_none() && upgrade.is_none() {
if spec.source_trees.is_empty()
&& reinstall.is_none()
&& upgrade.is_none()
&& spec.overrides.is_empty()
{
match site_packages.satisfies(&spec.requirements, &spec.constraints)? {
// If the requirements are already satisfied, we're done.
SatisfiesResult::Fresh {
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ pub(crate) async fn remove(
&VirtualProject::Project(project),
&venv,
&lock,
extras,
&extras,
dev,
Modifications::Exact,
settings.as_ref().into(),
Expand Down
80 changes: 48 additions & 32 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::borrow::Cow;
use std::ffi::OsString;
use std::fmt::Write;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use anyhow::{bail, Context, Result};
use itertools::Itertools;
use owo_colors::OwoColorize;
use tokio::process::Command;
Expand Down Expand Up @@ -59,6 +59,28 @@ pub(crate) async fn run(
warn_user_once!("`uv run` is experimental and may change without warning");
}

// These cases seem quite complex because (in theory) they should change the "current package".
// Let's ban them entirely for now.
for source in &requirements {
match source {
RequirementsSource::PyprojectToml(_) => {
bail!("Adding requirements from a `pyproject.toml` is not supported in `uv run`");
}
RequirementsSource::SetupPy(_) => {
bail!("Adding requirements from a `setup.py` is not supported in `uv run`");
}
RequirementsSource::SetupCfg(_) => {
bail!("Adding requirements from a `setup.cfg` is not supported in `uv run`");
}
RequirementsSource::RequirementsTxt(path) => {
if path == Path::new("-") {
bail!("Reading requirements from stdin is not supported in `uv run`");
}
}
_ => {}
}
}

// Parse the input command.
let command = RunCommand::from(command);

Expand Down Expand Up @@ -214,7 +236,7 @@ pub(crate) async fn run(
&project,
&venv,
&lock,
extras,
&extras,
dev,
Modifications::Sufficient,
settings.as_ref().into(),
Expand Down Expand Up @@ -262,7 +284,7 @@ pub(crate) async fn run(
);
}

// Read the `--with` requirements.
// Read the requirements.
let spec = if requirements.is_empty() {
None
} else {
Expand All @@ -280,6 +302,7 @@ pub(crate) async fn run(
// any `--with` requirements, and we already have a base environment, then there's no need to
// create an additional environment.
let skip_ephemeral = base_interpreter.as_ref().is_some_and(|base_interpreter| {
// No additional requirements.
let Some(spec) = spec.as_ref() else {
return true;
};
Expand Down Expand Up @@ -362,35 +385,28 @@ pub(crate) async fn run(
false,
)?;

if requirements.is_empty() {
Some(venv)
} else {
debug!("Syncing ephemeral requirements");

let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls);

let spec =
RequirementsSpecification::from_simple_sources(&requirements, &client_builder)
.await?;

// Install the ephemeral requirements.
Some(
project::update_environment(
venv,
spec,
&settings,
&state,
preview,
connectivity,
concurrency,
native_tls,
cache,
printer,
match spec {
None => Some(venv),
Some(spec) if spec.is_empty() => Some(venv),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be able to simplify this with a spec.filter(|spec| !spec.is_empty()) somewhere.

Some(spec) => {
debug!("Syncing ephemeral requirements");
// Install the ephemeral requirements.
Some(
project::update_environment(
venv,
spec,
&settings,
&state,
preview,
connectivity,
concurrency,
native_tls,
cache,
printer,
)
.await?,
)
.await?,
)
}
}
};

Expand Down
6 changes: 3 additions & 3 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ pub(crate) async fn sync(
&project,
&venv,
&lock,
extras,
&extras,
dev,
modifications,
settings.as_ref().into(),
Expand All @@ -117,7 +117,7 @@ pub(super) async fn do_sync(
project: &VirtualProject,
venv: &PythonEnvironment,
lock: &Lock,
extras: ExtrasSpecification,
extras: &ExtrasSpecification,
dev: bool,
modifications: Modifications,
settings: InstallerSettingsRef<'_>,
Expand Down Expand Up @@ -163,7 +163,7 @@ pub(super) async fn do_sync(
let tags = venv.interpreter().tags()?;

// Read the lockfile.
let resolution = lock.to_resolution(project, markers, tags, &extras, &dev)?;
let resolution = lock.to_resolution(project, markers, tags, extras, &dev)?;

// Initialize the registry client.
let client = RegistryClientBuilder::new(cache.clone())
Expand Down
5 changes: 5 additions & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,11 @@ async fn run_project(
.with
.into_iter()
.map(RequirementsSource::from_package)
.chain(
args.with_requirements
.into_iter()
.map(RequirementsSource::from_requirements_file),
)
.collect::<Vec<_>>();

commands::run(
Expand Down
6 changes: 6 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ pub(crate) struct RunSettings {
pub(crate) dev: bool,
pub(crate) command: ExternalCommand,
pub(crate) with: Vec<String>,
pub(crate) with_requirements: Vec<PathBuf>,
pub(crate) package: Option<PackageName>,
pub(crate) python: Option<String>,
pub(crate) refresh: Refresh,
Expand All @@ -204,6 +205,7 @@ impl RunSettings {
no_dev,
command,
with,
with_requirements,
installer,
build,
refresh,
Expand All @@ -221,6 +223,10 @@ impl RunSettings {
dev: flag(dev, no_dev).unwrap_or(true),
command,
with,
with_requirements: with_requirements
.into_iter()
.filter_map(Maybe::into_option)
.collect(),
package,
python,
refresh: Refresh::from(refresh),
Expand Down
Loading
Loading