Skip to content

Commit c1a3a47

Browse files
committed
Add --package support to uv build
1 parent 8618054 commit c1a3a47

File tree

6 files changed

+321
-21
lines changed

6 files changed

+321
-21
lines changed

crates/uv-cli/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1955,6 +1955,15 @@ pub struct BuildArgs {
19551955
#[arg(value_parser = parse_file_path)]
19561956
pub src: Option<PathBuf>,
19571957

1958+
/// Build a specific package in the workspace.
1959+
///
1960+
/// The workspace will be discovered from the provided source directory, or the current
1961+
/// directory if no source directory is provided.
1962+
///
1963+
/// If the workspace member does not exist, uv will exit with an error.
1964+
#[arg(long)]
1965+
pub package: Option<PackageName>,
1966+
19581967
/// The output directory to which distributions should be written.
19591968
///
19601969
/// Defaults to the `dist` subdirectory within the source directory, or the

crates/uv/src/commands/build.rs

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,20 @@ use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClient
1515
use uv_configuration::{BuildKind, BuildOutput, Concurrency};
1616
use uv_dispatch::BuildDispatch;
1717
use uv_fs::{Simplified, CWD};
18+
use uv_normalize::PackageName;
1819
use uv_python::{
1920
EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation,
2021
PythonPreference, PythonRequest, PythonVersionFile, VersionRequest,
2122
};
2223
use uv_resolver::{FlatIndex, RequiresPython};
2324
use uv_types::{BuildContext, BuildIsolation, HashStrategy};
24-
use uv_warnings::warn_user_once;
25-
use uv_workspace::{DiscoveryOptions, VirtualProject, WorkspaceError};
25+
use uv_workspace::{DiscoveryOptions, Workspace};
2626

2727
/// Build source distributions and wheels.
2828
#[allow(clippy::fn_params_excessive_bools)]
2929
pub(crate) async fn build(
3030
src: Option<PathBuf>,
31+
package: Option<PackageName>,
3132
output_dir: Option<PathBuf>,
3233
sdist: bool,
3334
wheel: bool,
@@ -44,6 +45,7 @@ pub(crate) async fn build(
4445
) -> Result<ExitStatus> {
4546
let assets = build_impl(
4647
src.as_deref(),
48+
package.as_ref(),
4749
output_dir.as_deref(),
4850
sdist,
4951
wheel,
@@ -82,6 +84,7 @@ pub(crate) async fn build(
8284
#[allow(clippy::fn_params_excessive_bools)]
8385
async fn build_impl(
8486
src: Option<&Path>,
87+
package: Option<&PackageName>,
8588
output_dir: Option<&Path>,
8689
sdist: bool,
8790
wheel: bool,
@@ -118,6 +121,7 @@ async fn build_impl(
118121
.connectivity(connectivity)
119122
.native_tls(native_tls);
120123

124+
// Determine the source to build.
121125
let src = if let Some(src) = src {
122126
let src = std::path::absolute(src)?;
123127
let metadata = match fs_err::tokio::metadata(&src).await {
@@ -139,9 +143,37 @@ async fn build_impl(
139143
Source::Directory(Cow::Borrowed(&*CWD))
140144
};
141145

142-
let src_dir = match src {
143-
Source::Directory(ref src) => src,
144-
Source::File(ref src) => src.parent().unwrap(),
146+
// Attempt to discover the workspace; on failure, save the error for later.
147+
let workspace = Workspace::discover(src.directory(), &DiscoveryOptions::default()).await;
148+
149+
// If a `--package` was provided, adjust the source directory.
150+
let src = if let Some(package) = package {
151+
if matches!(src, Source::File(_)) {
152+
return Err(anyhow::anyhow!(
153+
"Cannot specify a `--package` when building from a file"
154+
));
155+
}
156+
157+
let workspace = match workspace {
158+
Ok(ref workspace) => workspace,
159+
Err(err) => {
160+
return Err(
161+
anyhow::anyhow!("`--package` was provided, but no workspace was found")
162+
.context(err),
163+
)
164+
}
165+
};
166+
167+
let project = workspace
168+
.packages()
169+
.get(package)
170+
.ok_or_else(|| anyhow::anyhow!("Package `{}` not found in workspace", package))?
171+
.root()
172+
.clone();
173+
174+
Source::Directory(Cow::Owned(project))
175+
} else {
176+
src
145177
};
146178

147179
let output_dir = if let Some(output_dir) = output_dir {
@@ -158,26 +190,15 @@ async fn build_impl(
158190

159191
// (2) Request from `.python-version`
160192
if interpreter_request.is_none() {
161-
interpreter_request = PythonVersionFile::discover(&src_dir, no_config, false)
193+
interpreter_request = PythonVersionFile::discover(src.directory(), no_config, false)
162194
.await?
163195
.and_then(PythonVersionFile::into_version);
164196
}
165197

166198
// (3) `Requires-Python` in `pyproject.toml`
167199
if interpreter_request.is_none() {
168-
let project = match VirtualProject::discover(src_dir, &DiscoveryOptions::default()).await {
169-
Ok(project) => Some(project),
170-
Err(WorkspaceError::MissingProject(_)) => None,
171-
Err(WorkspaceError::MissingPyprojectToml) => None,
172-
Err(WorkspaceError::NonWorkspace(_)) => None,
173-
Err(err) => {
174-
warn_user_once!("{err}");
175-
None
176-
}
177-
};
178-
179-
if let Some(project) = project {
180-
interpreter_request = find_requires_python(project.workspace())?
200+
if let Ok(ref workspace) = workspace {
201+
interpreter_request = find_requires_python(workspace)?
181202
.as_ref()
182203
.map(RequiresPython::specifiers)
183204
.map(|specifiers| {
@@ -463,8 +484,15 @@ enum Source<'a> {
463484
impl<'a> Source<'a> {
464485
fn path(&self) -> &Path {
465486
match self {
466-
Source::File(path) => path.as_ref(),
467-
Source::Directory(path) => path.as_ref(),
487+
Self::File(path) => path.as_ref(),
488+
Self::Directory(path) => path.as_ref(),
489+
}
490+
}
491+
492+
fn directory(&self) -> &Path {
493+
match self {
494+
Self::File(path) => path.parent().unwrap(),
495+
Self::Directory(path) => path,
468496
}
469497
}
470498
}

crates/uv/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ async fn run(cli: Cli) -> Result<ExitStatus> {
672672

673673
commands::build(
674674
args.src,
675+
args.package,
675676
args.out_dir,
676677
args.sdist,
677678
args.wheel,

crates/uv/src/settings.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1616,6 +1616,7 @@ impl PipCheckSettings {
16161616
#[derive(Debug, Clone)]
16171617
pub(crate) struct BuildSettings {
16181618
pub(crate) src: Option<PathBuf>,
1619+
pub(crate) package: Option<PackageName>,
16191620
pub(crate) out_dir: Option<PathBuf>,
16201621
pub(crate) sdist: bool,
16211622
pub(crate) wheel: bool,
@@ -1630,6 +1631,7 @@ impl BuildSettings {
16301631
let BuildArgs {
16311632
src,
16321633
out_dir,
1634+
package,
16331635
sdist,
16341636
wheel,
16351637
python,
@@ -1640,6 +1642,7 @@ impl BuildSettings {
16401642

16411643
Self {
16421644
src,
1645+
package,
16431646
out_dir,
16441647
sdist,
16451648
wheel,

0 commit comments

Comments
 (0)