@@ -15,19 +15,20 @@ use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClient
15
15
use uv_configuration:: { BuildKind , BuildOutput , Concurrency } ;
16
16
use uv_dispatch:: BuildDispatch ;
17
17
use uv_fs:: { Simplified , CWD } ;
18
+ use uv_normalize:: PackageName ;
18
19
use uv_python:: {
19
20
EnvironmentPreference , PythonDownloads , PythonEnvironment , PythonInstallation ,
20
21
PythonPreference , PythonRequest , PythonVersionFile , VersionRequest ,
21
22
} ;
22
23
use uv_resolver:: { FlatIndex , RequiresPython } ;
23
24
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 } ;
26
26
27
27
/// Build source distributions and wheels.
28
28
#[ allow( clippy:: fn_params_excessive_bools) ]
29
29
pub ( crate ) async fn build (
30
30
src : Option < PathBuf > ,
31
+ package : Option < PackageName > ,
31
32
output_dir : Option < PathBuf > ,
32
33
sdist : bool ,
33
34
wheel : bool ,
@@ -44,6 +45,7 @@ pub(crate) async fn build(
44
45
) -> Result < ExitStatus > {
45
46
let assets = build_impl (
46
47
src. as_deref ( ) ,
48
+ package. as_ref ( ) ,
47
49
output_dir. as_deref ( ) ,
48
50
sdist,
49
51
wheel,
@@ -82,6 +84,7 @@ pub(crate) async fn build(
82
84
#[ allow( clippy:: fn_params_excessive_bools) ]
83
85
async fn build_impl (
84
86
src : Option < & Path > ,
87
+ package : Option < & PackageName > ,
85
88
output_dir : Option < & Path > ,
86
89
sdist : bool ,
87
90
wheel : bool ,
@@ -118,6 +121,7 @@ async fn build_impl(
118
121
. connectivity ( connectivity)
119
122
. native_tls ( native_tls) ;
120
123
124
+ // Determine the source to build.
121
125
let src = if let Some ( src) = src {
122
126
let src = std:: path:: absolute ( src) ?;
123
127
let metadata = match fs_err:: tokio:: metadata ( & src) . await {
@@ -139,9 +143,37 @@ async fn build_impl(
139
143
Source :: Directory ( Cow :: Borrowed ( & * CWD ) )
140
144
} ;
141
145
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
145
177
} ;
146
178
147
179
let output_dir = if let Some ( output_dir) = output_dir {
@@ -158,26 +190,15 @@ async fn build_impl(
158
190
159
191
// (2) Request from `.python-version`
160
192
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 )
162
194
. await ?
163
195
. and_then ( PythonVersionFile :: into_version) ;
164
196
}
165
197
166
198
// (3) `Requires-Python` in `pyproject.toml`
167
199
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) ?
181
202
. as_ref ( )
182
203
. map ( RequiresPython :: specifiers)
183
204
. map ( |specifiers| {
@@ -463,8 +484,15 @@ enum Source<'a> {
463
484
impl < ' a > Source < ' a > {
464
485
fn path ( & self ) -> & Path {
465
486
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,
468
496
}
469
497
}
470
498
}
0 commit comments