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 a --project argument to run a command from a project #7603

Merged
merged 1 commit into from
Sep 21, 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
13 changes: 13 additions & 0 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,19 @@ pub struct GlobalArgs {
/// Change to the given directory prior to running the command.
#[arg(global = true, long, hide = true)]
pub directory: Option<PathBuf>,

/// Run the command within the given project directory.
///
/// All `pyproject.toml`, `uv.toml`, and `.python-version` files will be discovered by walking
/// up the directory tree from the project root, as will the project's virtual environment
/// (`.venv`).
///
/// Other command-line arguments (such as relative paths) will be resolved relative
/// to the current working directory.
///
/// This setting has no effect when used in the `uv pip` interface.
#[arg(global = true, long)]
pub project: Option<PathBuf>,
charliermarsh marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Copy, Clone, clap::ValueEnum)]
Expand Down
16 changes: 8 additions & 8 deletions crates/uv-settings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ impl FilesystemOptions {
///
/// The search starts at the given path and goes up the directory tree until a `uv.toml` file or
/// `pyproject.toml` file is found.
pub fn find(path: impl AsRef<Path>) -> Result<Option<Self>, Error> {
for ancestor in path.as_ref().ancestors() {
pub fn find(path: &Path) -> Result<Option<Self>, Error> {
for ancestor in path.ancestors() {
match Self::from_directory(ancestor) {
Ok(Some(options)) => {
return Ok(Some(options));
Expand All @@ -91,17 +91,17 @@ impl FilesystemOptions {

/// Load a [`FilesystemOptions`] from a directory, preferring a `uv.toml` file over a
/// `pyproject.toml` file.
pub fn from_directory(dir: impl AsRef<Path>) -> Result<Option<Self>, Error> {
pub fn from_directory(dir: &Path) -> Result<Option<Self>, Error> {
// Read a `uv.toml` file in the current directory.
let path = dir.as_ref().join("uv.toml");
let path = dir.join("uv.toml");
match fs_err::read_to_string(&path) {
Ok(content) => {
let options: Options = toml::from_str(&content)
.map_err(|err| Error::UvToml(path.user_display().to_string(), err))?;

// If the directory also contains a `[tool.uv]` table in a `pyproject.toml` file,
// warn.
let pyproject = dir.as_ref().join("pyproject.toml");
let pyproject = dir.join("pyproject.toml");
if let Some(pyproject) = fs_err::read_to_string(pyproject)
.ok()
.and_then(|content| toml::from_str::<PyProjectToml>(&content).ok())
Expand All @@ -121,7 +121,7 @@ impl FilesystemOptions {
}

// Read a `pyproject.toml` file in the current directory.
let path = dir.as_ref().join("pyproject.toml");
let path = dir.join("pyproject.toml");
match fs_err::read_to_string(&path) {
Ok(content) => {
// Parse, but skip any `pyproject.toml` that doesn't have a `[tool.uv]` section.
Expand All @@ -130,14 +130,14 @@ impl FilesystemOptions {
let Some(tool) = pyproject.tool else {
debug!(
"Skipping `pyproject.toml` in `{}` (no `[tool]` section)",
dir.as_ref().display()
dir.display()
);
return Ok(None);
};
let Some(options) = tool.uv else {
debug!(
"Skipping `pyproject.toml` in `{}` (no `[tool.uv]` section)",
dir.as_ref().display()
dir.display()
);
return Ok(None);
};
Expand Down
7 changes: 5 additions & 2 deletions crates/uv/src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{BuildKind, BuildOutput, Concurrency, Constraints, HashCheckingMode};
use uv_dispatch::BuildDispatch;
use uv_fs::{Simplified, CWD};
use uv_fs::Simplified;
use uv_normalize::PackageName;
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation,
Expand All @@ -29,6 +29,7 @@ use uv_workspace::{DiscoveryOptions, Workspace};
/// Build source distributions and wheels.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn build(
project_dir: &Path,
src: Option<PathBuf>,
package: Option<PackageName>,
output_dir: Option<PathBuf>,
Expand All @@ -48,6 +49,7 @@ pub(crate) async fn build(
printer: Printer,
) -> Result<ExitStatus> {
let assets = build_impl(
project_dir,
src.as_deref(),
package.as_ref(),
output_dir.as_deref(),
Expand Down Expand Up @@ -89,6 +91,7 @@ pub(crate) async fn build(

#[allow(clippy::fn_params_excessive_bools)]
async fn build_impl(
project_dir: &Path,
src: Option<&Path>,
package: Option<&PackageName>,
output_dir: Option<&Path>,
Expand Down Expand Up @@ -149,7 +152,7 @@ async fn build_impl(
Source::Directory(Cow::Owned(src))
}
} else {
Source::Directory(Cow::Borrowed(&*CWD))
Source::Directory(Cow::Borrowed(project_dir))
};

// Attempt to discover the workspace; on failure, save the error for later.
Expand Down
11 changes: 6 additions & 5 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use uv_configuration::{
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
use uv_fs::{Simplified, CWD};
use uv_fs::Simplified;
use uv_git::{GitReference, GIT_STORE};
use uv_normalize::PackageName;
use uv_python::{
Expand Down Expand Up @@ -51,6 +51,7 @@ use crate::settings::{ResolverInstallerSettings, ResolverInstallerSettingsRef};
/// Add one or more packages to the project requirements.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn add(
project_dir: &Path,
locked: bool,
frozen: bool,
no_sync: bool,
Expand Down Expand Up @@ -131,7 +132,7 @@ pub(crate) async fn add(
let python_request = if let Some(request) = python.as_deref() {
// (1) Explicit request from user
PythonRequest::parse(request)
} else if let Some(request) = PythonVersionFile::discover(&*CWD, false, false)
} else if let Some(request) = PythonVersionFile::discover(project_dir, false, false)
.await?
.and_then(PythonVersionFile::into_version)
{
Expand Down Expand Up @@ -162,7 +163,7 @@ pub(crate) async fn add(
let python_request = if let Some(request) = python.as_deref() {
// (1) Explicit request from user
Some(PythonRequest::parse(request))
} else if let Some(request) = PythonVersionFile::discover(&*CWD, false, false)
} else if let Some(request) = PythonVersionFile::discover(project_dir, false, false)
.await?
.and_then(PythonVersionFile::into_version)
{
Expand Down Expand Up @@ -196,13 +197,13 @@ pub(crate) async fn add(
// Find the project in the workspace.
let project = if let Some(package) = package {
VirtualProject::Project(
Workspace::discover(&CWD, &DiscoveryOptions::default())
Workspace::discover(project_dir, &DiscoveryOptions::default())
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?,
)
} else {
VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await?
VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await?
};

// For non-project workspace roots, allow dev dependencies, but nothing else.
Expand Down
10 changes: 5 additions & 5 deletions crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ use std::env;
use anyhow::{Context, Result};
use itertools::Itertools;
use owo_colors::OwoColorize;
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::{
Concurrency, DevMode, DevSpecification, EditableMode, ExportFormat, ExtrasSpecification,
InstallOptions,
};
use uv_fs::CWD;
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_resolver::RequirementsTxtExport;
Expand All @@ -27,6 +26,7 @@ use crate::settings::ResolverSettings;
/// Export the project's `uv.lock` in an alternate format.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn export(
project_dir: &Path,
format: ExportFormat,
package: Option<PackageName>,
hashes: bool,
Expand All @@ -51,22 +51,22 @@ pub(crate) async fn export(
// Identify the project.
let project = if let Some(package) = package {
VirtualProject::Project(
Workspace::discover(&CWD, &DiscoveryOptions::default())
Workspace::discover(project_dir, &DiscoveryOptions::default())
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?,
)
} else if frozen {
VirtualProject::discover(
&CWD,
project_dir,
&DiscoveryOptions {
members: MemberDiscovery::None,
..DiscoveryOptions::default()
},
)
.await?
} else {
VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await?
VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await?
};

let VirtualProject::Project(project) = project else {
Expand Down
5 changes: 3 additions & 2 deletions crates/uv/src/commands/project/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use pep508_rs::PackageName;
use tracing::{debug, warn};
use uv_cache::Cache;
use uv_client::{BaseClientBuilder, Connectivity};
use uv_fs::{Simplified, CWD};
use uv_fs::Simplified;
use uv_python::{
EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest,
PythonVersionFile, VersionRequest,
Expand All @@ -25,6 +25,7 @@ use crate::printer::Printer;
/// Add one or more packages to the project requirements.
#[allow(clippy::single_match_else, clippy::fn_params_excessive_bools)]
pub(crate) async fn init(
project_dir: &Path,
explicit_path: Option<String>,
name: Option<PackageName>,
package: bool,
Expand All @@ -42,7 +43,7 @@ pub(crate) async fn init(
) -> Result<ExitStatus> {
// Default to the current directory if a path was not provided.
let path = match explicit_path {
None => CWD.to_path_buf(),
None => project_dir.to_path_buf(),
Some(ref path) => std::path::absolute(path)?,
};

Expand Down
10 changes: 5 additions & 5 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#![allow(clippy::single_match_else)]

use std::collections::BTreeSet;
use std::fmt::Write;

use anstream::eprint;
use owo_colors::OwoColorize;
use rustc_hash::{FxBuildHasher, FxHashMap};
use std::collections::BTreeSet;
use std::fmt::Write;
use std::path::Path;
use tracing::debug;

use distribution_types::{
Expand All @@ -22,7 +22,6 @@ use uv_configuration::{
};
use uv_dispatch::BuildDispatch;
use uv_distribution::DistributionDatabase;
use uv_fs::CWD;
use uv_git::ResolvedRepositoryReference;
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
Expand Down Expand Up @@ -68,6 +67,7 @@ impl LockResult {

/// Resolve the project requirements into a lockfile.
pub(crate) async fn lock(
project_dir: &Path,
locked: bool,
frozen: bool,
python: Option<String>,
Expand All @@ -81,7 +81,7 @@ pub(crate) async fn lock(
printer: Printer,
) -> anyhow::Result<ExitStatus> {
// Find the project requirements.
let workspace = Workspace::discover(&CWD, &DiscoveryOptions::default()).await?;
let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?;

// Find an interpreter for the project
let interpreter = FoundInterpreter::discover(
Expand Down
11 changes: 6 additions & 5 deletions crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::fmt::Write;

use anyhow::{Context, Result};
use std::fmt::Write;
use std::path::Path;

use owo_colors::OwoColorize;
use pep508_rs::PackageName;
use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::{Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions};
use uv_fs::{Simplified, CWD};
use uv_fs::Simplified;
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_scripts::Pep723Script;
use uv_warnings::{warn_user, warn_user_once};
Expand All @@ -24,6 +24,7 @@ use crate::settings::ResolverInstallerSettings;
/// Remove one or more packages from the project requirements.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn remove(
project_dir: &Path,
locked: bool,
frozen: bool,
no_sync: bool,
Expand Down Expand Up @@ -68,13 +69,13 @@ pub(crate) async fn remove(
// Find the project in the workspace.
let project = if let Some(package) = package {
VirtualProject::Project(
Workspace::discover(&CWD, &DiscoveryOptions::default())
Workspace::discover(project_dir, &DiscoveryOptions::default())
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?,
)
} else {
VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await?
VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await?
};

Target::Project(project)
Expand Down
11 changes: 6 additions & 5 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use uv_configuration::{
Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions, SourceStrategy,
};
use uv_distribution::LoweredRequirement;
use uv_fs::{PythonExt, Simplified, CWD};
use uv_fs::{PythonExt, Simplified};
use uv_installer::{SatisfiesResult, SitePackages};
use uv_normalize::PackageName;
use uv_python::{
Expand Down Expand Up @@ -47,6 +47,7 @@ use crate::settings::ResolverInstallerSettings;
/// Run a command.
#[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn run(
project_dir: &Path,
script: Option<Pep723Script>,
command: RunCommand,
requirements: Vec<RequirementsSource>,
Expand Down Expand Up @@ -113,7 +114,7 @@ pub(crate) async fn run(
let source = PythonRequestSource::UserRequest;
let request = Some(PythonRequest::parse(request));
(source, request)
} else if let Some(file) = PythonVersionFile::discover(&*CWD, false, false).await? {
} else if let Some(file) = PythonVersionFile::discover(&project_dir, false, false).await? {
// (2) Request from `.python-version`
let source = PythonRequestSource::DotPythonVersion(file.file_name().to_string());
let request = file.into_version();
Expand Down Expand Up @@ -309,13 +310,13 @@ pub(crate) async fn run(
// We need a workspace, but we don't need to have a current package, we can be e.g. in
// the root of a virtual workspace and then switch into the selected package.
Some(VirtualProject::Project(
Workspace::discover(&CWD, &DiscoveryOptions::default())
Workspace::discover(project_dir, &DiscoveryOptions::default())
.await?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?,
))
} else {
match VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await {
match VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await {
Ok(project) => {
if no_project {
debug!("Ignoring discovered project due to `--no-project`");
Expand Down Expand Up @@ -537,7 +538,7 @@ pub(crate) async fn run(
Some(PythonRequest::parse(request))
// (2) Request from `.python-version`
} else {
PythonVersionFile::discover(&*CWD, no_config, false)
PythonVersionFile::discover(&project_dir, no_config, false)
.await?
.and_then(PythonVersionFile::into_version)
};
Expand Down
Loading
Loading