Skip to content

Commit 6274c00

Browse files
committed
Show build output by default in uv build
1 parent 5d8e990 commit 6274c00

File tree

9 files changed

+717
-42
lines changed

9 files changed

+717
-42
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/uv-build/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ uv-python = { workspace = true }
2424
uv-types = { workspace = true }
2525
uv-virtualenv = { workspace = true }
2626

27+
anstream = { workspace = true }
2728
anyhow = { workspace = true }
2829
fs-err = { workspace = true }
2930
indoc = { workspace = true }

crates/uv-build/src/lib.rs

Lines changed: 106 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_hash::FxHashMap;
1010
use serde::de::{value, SeqAccess, Visitor};
1111
use serde::{de, Deserialize, Deserializer};
1212
use std::ffi::OsString;
13+
use std::fmt::Write;
1314
use std::fmt::{Display, Formatter};
1415
use std::io;
1516
use std::path::{Path, PathBuf};
@@ -29,7 +30,7 @@ use distribution_types::Resolution;
2930
use pep440_rs::Version;
3031
use pep508_rs::PackageName;
3132
use pypi_types::{Requirement, VerbatimParsedUrl};
32-
use uv_configuration::{BuildKind, ConfigSettings};
33+
use uv_configuration::{BuildKind, BuildOutput, ConfigSettings};
3334
use uv_fs::{rename_with_retry, PythonExt, Simplified};
3435
use uv_python::{Interpreter, PythonEnvironment};
3536
use uv_types::{BuildContext, BuildIsolation, SourceBuildTrait};
@@ -93,22 +94,34 @@ pub enum Error {
9394
#[error("Failed to run `{0}`")]
9495
CommandFailed(PathBuf, #[source] io::Error),
9596
#[error("{message} with {exit_code}\n--- stdout:\n{stdout}\n--- stderr:\n{stderr}\n---")]
96-
BuildBackend {
97+
BuildBackendOutput {
9798
message: String,
9899
exit_code: ExitStatus,
99100
stdout: String,
100101
stderr: String,
101102
},
102103
/// Nudge the user towards installing the missing dev library
103104
#[error("{message} with {exit_code}\n--- stdout:\n{stdout}\n--- stderr:\n{stderr}\n---")]
104-
MissingHeader {
105+
MissingHeaderOutput {
105106
message: String,
106107
exit_code: ExitStatus,
107108
stdout: String,
108109
stderr: String,
109110
#[source]
110111
missing_header_cause: MissingHeaderCause,
111112
},
113+
#[error("{message} with {exit_code}")]
114+
BuildBackend {
115+
message: String,
116+
exit_code: ExitStatus,
117+
},
118+
#[error("{message} with {exit_code}")]
119+
MissingHeader {
120+
message: String,
121+
exit_code: ExitStatus,
122+
#[source]
123+
missing_header_cause: MissingHeaderCause,
124+
},
112125
#[error("Failed to build PATH for build script")]
113126
BuildScriptPath(#[source] env::JoinPathsError),
114127
}
@@ -161,6 +174,7 @@ impl Error {
161174
fn from_command_output(
162175
message: String,
163176
output: &PythonRunnerOutput,
177+
level: BuildOutput,
164178
version_id: impl Into<String>,
165179
) -> Self {
166180
// In the cases I've seen it was the 5th and 3rd last line (see test case), 10 seems like a reasonable cutoff.
@@ -186,24 +200,71 @@ impl Error {
186200
});
187201

188202
if let Some(missing_library) = missing_library {
189-
return Self::MissingHeader {
203+
return match level {
204+
BuildOutput::Stderr => Self::MissingHeader {
205+
message,
206+
exit_code: output.status,
207+
missing_header_cause: MissingHeaderCause {
208+
missing_library,
209+
version_id: version_id.into(),
210+
},
211+
},
212+
BuildOutput::Debug => Self::MissingHeaderOutput {
213+
message,
214+
exit_code: output.status,
215+
stdout: output.stdout.iter().join("\n"),
216+
stderr: output.stderr.iter().join("\n"),
217+
missing_header_cause: MissingHeaderCause {
218+
missing_library,
219+
version_id: version_id.into(),
220+
},
221+
},
222+
};
223+
}
224+
225+
match level {
226+
BuildOutput::Stderr => Self::BuildBackend {
227+
message,
228+
exit_code: output.status,
229+
},
230+
BuildOutput::Debug => Self::BuildBackendOutput {
190231
message,
191232
exit_code: output.status,
192233
stdout: output.stdout.iter().join("\n"),
193234
stderr: output.stderr.iter().join("\n"),
194-
missing_header_cause: MissingHeaderCause {
195-
missing_library,
196-
version_id: version_id.into(),
197-
},
198-
};
235+
},
236+
}
237+
}
238+
}
239+
240+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
241+
pub enum Printer {
242+
/// Send the build backend output to `stderr`.
243+
Stderr,
244+
/// Send the build backend output to `tracing`.
245+
Debug,
246+
}
247+
248+
impl From<BuildOutput> for Printer {
249+
fn from(output: BuildOutput) -> Self {
250+
match output {
251+
BuildOutput::Stderr => Self::Stderr,
252+
BuildOutput::Debug => Self::Debug,
199253
}
254+
}
255+
}
200256

201-
Self::BuildBackend {
202-
message,
203-
exit_code: output.status,
204-
stdout: output.stdout.iter().join("\n"),
205-
stderr: output.stderr.iter().join("\n"),
257+
impl Write for Printer {
258+
fn write_str(&mut self, s: &str) -> std::fmt::Result {
259+
match self {
260+
Self::Stderr => {
261+
anstream::eprint!("{s}");
262+
}
263+
Self::Debug => {
264+
debug!("{}", s);
265+
}
206266
}
267+
Ok(())
207268
}
208269
}
209270

@@ -380,6 +441,8 @@ pub struct SourceBuild {
380441
version_id: String,
381442
/// Whether we do a regular PEP 517 build or an PEP 660 editable build
382443
build_kind: BuildKind,
444+
/// Whether to send build output to `stderr` or `tracing`, etc.
445+
level: BuildOutput,
383446
/// Modified PATH that contains the `venv_bin`, `user_path` and `system_path` variables in that order
384447
modified_path: OsString,
385448
/// Environment variables to be passed in during metadata or wheel building
@@ -405,6 +468,7 @@ impl SourceBuild {
405468
build_isolation: BuildIsolation<'_>,
406469
build_kind: BuildKind,
407470
mut environment_variables: FxHashMap<OsString, OsString>,
471+
level: BuildOutput,
408472
concurrent_builds: usize,
409473
) -> Result<Self, Error> {
410474
let temp_dir = build_context.cache().environment()?;
@@ -488,7 +552,7 @@ impl SourceBuild {
488552

489553
// Create the PEP 517 build environment. If build isolation is disabled, we assume the build
490554
// environment is already setup.
491-
let runner = PythonRunner::new(concurrent_builds);
555+
let runner = PythonRunner::new(concurrent_builds, level);
492556
if build_isolation.is_isolated(package_name) {
493557
create_pep517_build_environment(
494558
&runner,
@@ -498,6 +562,7 @@ impl SourceBuild {
498562
build_context,
499563
&version_id,
500564
build_kind,
565+
level,
501566
&config_settings,
502567
&environment_variables,
503568
&modified_path,
@@ -513,6 +578,7 @@ impl SourceBuild {
513578
project,
514579
venv,
515580
build_kind,
581+
level,
516582
config_settings,
517583
metadata_directory: None,
518584
version_id,
@@ -698,6 +764,7 @@ impl SourceBuild {
698764
return Err(Error::from_command_output(
699765
format!("Build backend failed to determine metadata through `prepare_metadata_for_build_{}`", self.build_kind),
700766
&output,
767+
self.level,
701768
&self.version_id,
702769
));
703770
}
@@ -827,6 +894,7 @@ impl SourceBuild {
827894
self.build_kind, self.build_kind,
828895
),
829896
&output,
897+
self.level,
830898
&self.version_id,
831899
));
832900
}
@@ -839,6 +907,7 @@ impl SourceBuild {
839907
self.build_kind, self.build_kind,
840908
),
841909
&output,
910+
self.level,
842911
&self.version_id,
843912
));
844913
}
@@ -871,6 +940,7 @@ async fn create_pep517_build_environment(
871940
build_context: &impl BuildContext,
872941
version_id: &str,
873942
build_kind: BuildKind,
943+
level: BuildOutput,
874944
config_settings: &ConfigSettings,
875945
environment_variables: &FxHashMap<OsString, OsString>,
876946
modified_path: &OsString,
@@ -924,6 +994,7 @@ async fn create_pep517_build_environment(
924994
return Err(Error::from_command_output(
925995
format!("Build backend failed to determine extra requires with `build_{build_kind}()`"),
926996
&output,
997+
level,
927998
version_id,
928999
));
9291000
}
@@ -935,6 +1006,7 @@ async fn create_pep517_build_environment(
9351006
"Build backend failed to read extra requires from `get_requires_for_build_{build_kind}`: {err}"
9361007
),
9371008
&output,
1009+
level,
9381010
version_id,
9391011
)
9401012
})?;
@@ -946,6 +1018,7 @@ async fn create_pep517_build_environment(
9461018
"Build backend failed to return extra requires with `get_requires_for_build_{build_kind}`: {err}"
9471019
),
9481020
&output,
1021+
level,
9491022
version_id,
9501023
)
9511024
})?;
@@ -985,6 +1058,7 @@ async fn create_pep517_build_environment(
9851058
#[derive(Debug)]
9861059
struct PythonRunner {
9871060
control: Semaphore,
1061+
level: BuildOutput,
9881062
}
9891063

9901064
#[derive(Debug)]
@@ -995,10 +1069,11 @@ struct PythonRunnerOutput {
9951069
}
9961070

9971071
impl PythonRunner {
998-
/// Create a `PythonRunner` with the provided concurrency limit.
999-
fn new(concurrency: usize) -> PythonRunner {
1000-
PythonRunner {
1072+
/// Create a `PythonRunner` with the provided concurrency limit and output level.
1073+
fn new(concurrency: usize, level: BuildOutput) -> Self {
1074+
Self {
10011075
control: Semaphore::new(concurrency),
1076+
level,
10021077
}
10031078
}
10041079

@@ -1019,12 +1094,13 @@ impl PythonRunner {
10191094
/// Read lines from a reader and store them in a buffer.
10201095
async fn read_from(
10211096
mut reader: tokio::io::Lines<tokio::io::BufReader<impl tokio::io::AsyncRead + Unpin>>,
1097+
mut printer: Printer,
10221098
buffer: &mut Vec<String>,
10231099
) -> io::Result<()> {
10241100
loop {
10251101
match reader.next_line().await? {
10261102
Some(line) => {
1027-
debug!("{line}");
1103+
let _ = writeln!(printer, "{line}");
10281104
buffer.push(line);
10291105
}
10301106
None => return Ok(()),
@@ -1055,9 +1131,10 @@ impl PythonRunner {
10551131
let stderr_reader = tokio::io::BufReader::new(child.stderr.take().unwrap()).lines();
10561132

10571133
// Asynchronously read from the in-memory pipes.
1134+
let printer = Printer::from(self.level);
10581135
let result = tokio::join!(
1059-
read_from(stdout_reader, &mut stdout_buf),
1060-
read_from(stderr_reader, &mut stderr_buf),
1136+
read_from(stdout_reader, printer, &mut stdout_buf),
1137+
read_from(stderr_reader, printer, &mut stderr_buf),
10611138
);
10621139
match result {
10631140
(Ok(()), Ok(())) => {}
@@ -1087,9 +1164,9 @@ impl PythonRunner {
10871164
mod test {
10881165
use std::process::ExitStatus;
10891166

1090-
use indoc::indoc;
1091-
10921167
use crate::{Error, PythonRunnerOutput};
1168+
use indoc::indoc;
1169+
use uv_configuration::BuildOutput;
10931170

10941171
#[test]
10951172
fn missing_header() {
@@ -1120,9 +1197,10 @@ mod test {
11201197
let err = Error::from_command_output(
11211198
"Failed building wheel through setup.py".to_string(),
11221199
&output,
1200+
BuildOutput::Debug,
11231201
"pygraphviz-1.11",
11241202
);
1125-
assert!(matches!(err, Error::MissingHeader { .. }));
1203+
assert!(matches!(err, Error::MissingHeaderOutput { .. }));
11261204
// Unix uses exit status, Windows uses exit code.
11271205
let formatted = err.to_string().replace("exit status: ", "exit code: ");
11281206
insta::assert_snapshot!(formatted, @r###"
@@ -1172,9 +1250,10 @@ mod test {
11721250
let err = Error::from_command_output(
11731251
"Failed building wheel through setup.py".to_string(),
11741252
&output,
1253+
BuildOutput::Debug,
11751254
"pygraphviz-1.11",
11761255
);
1177-
assert!(matches!(err, Error::MissingHeader { .. }));
1256+
assert!(matches!(err, Error::MissingHeaderOutput { .. }));
11781257
// Unix uses exit status, Windows uses exit code.
11791258
let formatted = err.to_string().replace("exit status: ", "exit code: ");
11801259
insta::assert_snapshot!(formatted, @r###"
@@ -1217,9 +1296,10 @@ mod test {
12171296
let err = Error::from_command_output(
12181297
"Failed building wheel through setup.py".to_string(),
12191298
&output,
1299+
BuildOutput::Debug,
12201300
"pygraphviz-1.11",
12211301
);
1222-
assert!(matches!(err, Error::MissingHeader { .. }));
1302+
assert!(matches!(err, Error::MissingHeaderOutput { .. }));
12231303
// Unix uses exit status, Windows uses exit code.
12241304
let formatted = err.to_string().replace("exit status: ", "exit code: ");
12251305
insta::assert_snapshot!(formatted, @r###"

crates/uv-configuration/src/build_options.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ impl Display for BuildKind {
2525
}
2626
}
2727

28+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
29+
pub enum BuildOutput {
30+
/// Send the build backend output to `stderr`.
31+
Stderr,
32+
/// Send the build backend output to `tracing`.
33+
Debug,
34+
}
35+
2836
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2937
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
3038
pub struct BuildOptions {

0 commit comments

Comments
 (0)