From cfc191f51699e45fb5ec05803ac1b54ba0a30c20 Mon Sep 17 00:00:00 2001 From: konstin Date: Tue, 26 Aug 2025 15:57:10 +0200 Subject: [PATCH 1/2] Add logging to the uv build backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for `RUST_LOG` to the uv build backend. While we were previously using logging statements in the uv build backend, they could only be shown when when using the direct build fast path through uv, as there was no tracing subscriber to write log messages out. This means no debug logging when using the build backend through pip, `python -m build`, an incompatible version of uv, or any other build frontend; No option to figure why includes and excludes behave the way they do. This PR closes this gap by adding a tracing subscriber. The only option to enable it is `RUST_LOG`, as we don't have a CLI. The formatting style is the same as for uv, and color is also support in the same way, albeit only through anstream's support for TTYs and environment variables. We recommend only `RUST_LOG=uv=debug` and `RUST_LOG=uv=verbose` in the docs, but this can be used to debug into crates such as `glob`, too. **Before** ``` $ pip wheel . -v [...] Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/scripts/packages/built-by-uv Running command pip subprocess to install build dependencies Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl Installing collected packages: uv_build Successfully installed uv_build-0.8.13 Installing build dependencies ... done Running command Getting requirements to build wheel Getting requirements to build wheel ... done Running command Preparing metadata (pyproject.toml) Preparing metadata (pyproject.toml) ... done Building wheels for collected packages: built-by-uv Running command Building wheel for built-by-uv (pyproject.toml) Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude` Caused by: Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash error: subprocess-exited-with-error × Building wheel for built-by-uv (pyproject.toml) did not run successfully. │ exit code: 1 ╰─> See above for output. note: This error originates from a subprocess, and is likely not a problem with pip. full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpow1illc9 cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv Building wheel for built-by-uv (pyproject.toml) ... error ERROR: Failed building wheel for built-by-uv Failed to build built-by-uv ERROR: Failed to build one or more wheels ``` **After** ``` $ RUST_LOG=uv=debug pip wheel . -v [...] Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/scripts/packages/built-by-uv Running command pip subprocess to install build dependencies Looking in links: /home/konsti/projects/uv/target/wheels/ Processing /home/konsti/projects/uv/target/wheels/uv_build-0.8.13-py3-none-manylinux_2_39_x86_64.whl Installing collected packages: uv_build Successfully installed uv_build-0.8.13 Installing build dependencies ... done Running command Getting requirements to build wheel Getting requirements to build wheel ... done Running command Preparing metadata (pyproject.toml) DEBUG Writing metadata files to /tmp/pip-modern-metadata-l_kh78cj DEBUG Found PEP 639 license declarations, using METADATA 2.4 DEBUG License files match: `LICENSE-APACHE` DEBUG License files match: `LICENSE-MIT` DEBUG License files match: `third-party-licenses/PEP-401.txt` Preparing metadata (pyproject.toml) ... done Building wheels for collected packages: built-by-uv Running command Building wheel for built-by-uv (pyproject.toml) DEBUG Checking metadata directory /tmp/pip-modern-metadata-l_kh78cj/built_by_uv-0.1.0.dist-info DEBUG Found PEP 639 license declarations, using METADATA 2.4 DEBUG License files match: `LICENSE-APACHE` DEBUG License files match: `LICENSE-MIT` DEBUG License files match: `third-party-licenses/PEP-401.txt` DEBUG Writing wheel at /tmp/pip-wheel-bu6to9i7/built_by_uv-0.1.0-py3-none-any.whl DEBUG Wheel excludes: ["__pycache__", "*.pyc", "*.pyo", "build-*!$§%!½¼²¼³¬!§%$§%.h", "/src/built_by_uv/not-packaged.txt"] Error: Unsupported glob expression in: `tool.uv.build-backend.*-exclude` Caused by: Invalid character `!` at position 10 in glob: `**/build-*!$§%!½¼²¼³¬!§%$§%.h`. hint: Characters can be escaped with a backslash error: subprocess-exited-with-error × Building wheel for built-by-uv (pyproject.toml) did not run successfully. │ exit code: 1 ╰─> See above for output. note: This error originates from a subprocess, and is likely not a problem with pip. full command: /usr/bin/python3 /usr/lib/python3/dist-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py build_wheel /tmp/tmpjrxou13a cwd: /home/konsti/projects/uv/scripts/packages/built-by-uv Building wheel for built-by-uv (pyproject.toml) ... error ERROR: Failed building wheel for built-by-uv Failed to build built-by-uv ERROR: Failed to build one or more wheels ``` (There is no color in the above uv log statements, as pip doesn't register as a TTY) Fixes #12723 --- Cargo.lock | 15 +++++- Cargo.toml | 3 +- crates/uv-build/Cargo.toml | 3 ++ crates/uv-build/src/main.rs | 24 ++++++++- crates/uv-logging/Cargo.toml | 22 ++++++++ crates/uv-logging/src/lib.rs | 95 ++++++++++++++++++++++++++++++++++ crates/uv/Cargo.toml | 4 +- crates/uv/src/logging.rs | 92 +------------------------------- docs/concepts/build-backend.md | 7 +++ 9 files changed, 170 insertions(+), 95 deletions(-) create mode 100644 crates/uv-logging/Cargo.toml create mode 100644 crates/uv-logging/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 89ecf256c0c..b33b54d1cdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5014,7 +5014,6 @@ dependencies = [ "indoc", "insta", "itertools 0.14.0", - "jiff", "miette", "nix 0.30.1", "owo-colors", @@ -5063,6 +5062,7 @@ dependencies = [ "uv-git-types", "uv-install-wheel", "uv-installer", + "uv-logging", "uv-normalize", "uv-pep440", "uv-pep508", @@ -5184,8 +5184,11 @@ dependencies = [ name = "uv-build" version = "0.8.13" dependencies = [ + "anstream", "anyhow", + "tracing-subscriber", "uv-build-backend", + "uv-logging", "uv-version", ] @@ -5838,6 +5841,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "uv-logging" +version = "0.0.1" +dependencies = [ + "jiff", + "owo-colors", + "tracing", + "tracing-subscriber", +] + [[package]] name = "uv-macros" version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml index fae199a2f4e..36b81d30e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ uv-git-types = { path = "crates/uv-git-types" } uv-globfilter = { path = "crates/uv-globfilter" } uv-install-wheel = { path = "crates/uv-install-wheel", default-features = false } uv-installer = { path = "crates/uv-installer" } +uv-logging = { path = "crates/uv-logging" } uv-macros = { path = "crates/uv-macros" } uv-metadata = { path = "crates/uv-metadata" } uv-normalize = { path = "crates/uv-normalize" } @@ -180,7 +181,7 @@ toml = { version = "0.9.2", features = ["fast_hash"] } toml_edit = { version = "0.23.2", features = ["serde"] } tracing = { version = "0.1.40" } tracing-durations-export = { version = "0.3.0", features = ["plot"] } -tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json", "registry"] } +tracing-subscriber = { version = "0.3.18" } # Default feature set for uv_build, uv activates extra features tracing-test = { version = "0.2.5" } tracing-tree = { version = "0.4.0" } unicode-width = { version = "0.2.0" } diff --git a/crates/uv-build/Cargo.toml b/crates/uv-build/Cargo.toml index 413a8603eeb..b3e13139d3c 100644 --- a/crates/uv-build/Cargo.toml +++ b/crates/uv-build/Cargo.toml @@ -11,9 +11,12 @@ license = { workspace = true } [dependencies] uv-build-backend = { workspace = true } +uv-logging = { workspace = true } uv-version = { workspace = true } +anstream = { workspace = true } anyhow = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } [lints] workspace = true diff --git a/crates/uv-build/src/main.rs b/crates/uv-build/src/main.rs index 4b9b5b3ee2b..5dd2ce160e1 100644 --- a/crates/uv-build/src/main.rs +++ b/crates/uv-build/src/main.rs @@ -1,10 +1,32 @@ -use anyhow::{Context, Result, bail}; use std::env; use std::io::Write; use std::path::PathBuf; +use anyhow::{Context, Result, bail}; +use tracing_subscriber::filter::LevelFilter; +use tracing_subscriber::layer::SubscriberExt; +use tracing_subscriber::util::SubscriberInitExt; +use tracing_subscriber::{EnvFilter, Layer}; + +use uv_logging::UvFormat; + /// Entrypoint for the `uv-build` Python package. fn main() -> Result<()> { + // Support configuring the log level with `RUST_LOG` (shows only the error level by default) and + // color. + // + // This configuration is a simplified version of the uv logging configuration. When using + // uv_build through uv proper, the uv logging configuration applies. + let filter = EnvFilter::builder() + .with_default_directive(LevelFilter::OFF.into()) + .from_env() + .context("Invalid RUST_LOG directives")?; + let stderr_layer = tracing_subscriber::fmt::layer() + .event_format(UvFormat::default()) + .with_writer(std::sync::Mutex::new(anstream::stderr())) + .with_filter(filter); + tracing_subscriber::registry().with(stderr_layer).init(); + // Handrolled to avoid the large clap dependency let mut args = env::args_os(); // Skip the name of the binary diff --git a/crates/uv-logging/Cargo.toml b/crates/uv-logging/Cargo.toml new file mode 100644 index 00000000000..d660bf45492 --- /dev/null +++ b/crates/uv-logging/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "uv-logging" +version = "0.0.1" +edition = { workspace = true } +rust-version = { workspace = true } +homepage = { workspace = true } +documentation = { workspace = true } +repository = { workspace = true } +authors = { workspace = true } +license = { workspace = true } + +[lib] +doctest = false + +[lints] +workspace = true + +[dependencies] +jiff = { workspace = true } +owo-colors = { workspace = true } +tracing-subscriber = { workspace = true } +tracing = { workspace = true } diff --git a/crates/uv-logging/src/lib.rs b/crates/uv-logging/src/lib.rs new file mode 100644 index 00000000000..7d5aa1575c2 --- /dev/null +++ b/crates/uv-logging/src/lib.rs @@ -0,0 +1,95 @@ +use std::fmt; + +use jiff::Timestamp; +use owo_colors::OwoColorize; +use tracing::{Event, Subscriber}; +use tracing_subscriber::fmt::format::Writer; +use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields}; +use tracing_subscriber::registry::LookupSpan; + +/// The style of a uv logging line. +pub struct UvFormat { + pub display_timestamp: bool, + pub display_level: bool, + pub show_spans: bool, +} + +impl Default for UvFormat { + /// Regardless of the tracing level, show messages without any adornment. + fn default() -> Self { + Self { + display_timestamp: false, + display_level: true, + show_spans: false, + } + } +} + +/// See +impl FormatEvent for UvFormat +where + S: Subscriber + for<'a> LookupSpan<'a>, + N: for<'a> FormatFields<'a> + 'static, +{ + fn format_event( + &self, + ctx: &FmtContext<'_, S, N>, + mut writer: Writer<'_>, + event: &Event<'_>, + ) -> fmt::Result { + let meta = event.metadata(); + let ansi = writer.has_ansi_escapes(); + + if self.display_timestamp { + if ansi { + write!(writer, "{} ", Timestamp::now().dimmed())?; + } else { + write!(writer, "{} ", Timestamp::now())?; + } + } + + if self.display_level { + let level = meta.level(); + // Same colors as tracing + if ansi { + match *level { + tracing::Level::TRACE => write!(writer, "{} ", level.purple())?, + tracing::Level::DEBUG => write!(writer, "{} ", level.blue())?, + tracing::Level::INFO => write!(writer, "{} ", level.green())?, + tracing::Level::WARN => write!(writer, "{} ", level.yellow())?, + tracing::Level::ERROR => write!(writer, "{} ", level.red())?, + } + } else { + write!(writer, "{level} ")?; + } + } + + if self.show_spans { + let span = event.parent(); + let mut seen = false; + + let span = span + .and_then(|id| ctx.span(id)) + .or_else(|| ctx.lookup_current()); + + let scope = span.into_iter().flat_map(|span| span.scope().from_root()); + + for span in scope { + seen = true; + if ansi { + write!(writer, "{}:", span.metadata().name().bold())?; + } else { + write!(writer, "{}:", span.metadata().name())?; + } + } + + if seen { + writer.write_char(' ')?; + } + } + + ctx.field_format().format_fields(writer.by_ref(), event)?; + + writeln!(writer) + } +} diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index e71c587ed65..e10a84e1f66 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -35,6 +35,7 @@ uv-git = { workspace = true } uv-git-types = { workspace = true } uv-install-wheel = { workspace = true, default-features = false } uv-installer = { workspace = true } +uv-logging = { workspace = true } uv-normalize = { workspace = true } uv-pep440 = { workspace = true } uv-pep508 = { workspace = true } @@ -82,7 +83,6 @@ indicatif = { workspace = true } indoc = { workspace = true } itertools = { workspace = true } h2 = { workspace = true } -jiff = { workspace = true } miette = { workspace = true, features = ["fancy-no-backtrace"] } owo-colors = { workspace = true } petgraph = { workspace = true } @@ -102,7 +102,7 @@ toml = { workspace = true } toml_edit = { workspace = true } tracing = { workspace = true } tracing-durations-export = { workspace = true, features = ["plot"], optional = true } -tracing-subscriber = { workspace = true, features = ["json"] } +tracing-subscriber = { workspace = true, features = ["env-filter", "json", "registry"] } tracing-tree = { workspace = true } unicode-width = { workspace = true } url = { workspace = true } diff --git a/crates/uv/src/logging.rs b/crates/uv/src/logging.rs index d09cbc3f9da..9654e3ab24a 100644 --- a/crates/uv/src/logging.rs +++ b/crates/uv/src/logging.rs @@ -1,25 +1,19 @@ -use std::fmt; use std::str::FromStr; use anyhow::Context; -use jiff::Timestamp; -use owo_colors::OwoColorize; -use tracing::{Event, Subscriber}; #[cfg(feature = "tracing-durations-export")] use tracing_durations_export::{ DurationsLayer, DurationsLayerBuilder, DurationsLayerDropGuard, plot::PlotConfig, }; use tracing_subscriber::filter::Directive; -use tracing_subscriber::fmt::format::Writer; -use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields}; use tracing_subscriber::layer::SubscriberExt; -use tracing_subscriber::registry::LookupSpan; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::{EnvFilter, Layer, Registry}; use tracing_tree::HierarchicalLayer; use tracing_tree::time::Uptime; use uv_cli::ColorChoice; +use uv_logging::UvFormat; use uv_static::EnvVars; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] @@ -31,81 +25,6 @@ pub(crate) enum Level { TraceAll, } -struct UvFormat { - display_timestamp: bool, - display_level: bool, - show_spans: bool, -} - -/// See -impl FormatEvent for UvFormat -where - S: Subscriber + for<'a> LookupSpan<'a>, - N: for<'a> FormatFields<'a> + 'static, -{ - fn format_event( - &self, - ctx: &FmtContext<'_, S, N>, - mut writer: Writer<'_>, - event: &Event<'_>, - ) -> fmt::Result { - let meta = event.metadata(); - let ansi = writer.has_ansi_escapes(); - - if self.display_timestamp { - if ansi { - write!(writer, "{} ", Timestamp::now().dimmed())?; - } else { - write!(writer, "{} ", Timestamp::now())?; - } - } - - if self.display_level { - let level = meta.level(); - // Same colors as tracing - if ansi { - match *level { - tracing::Level::TRACE => write!(writer, "{} ", level.purple())?, - tracing::Level::DEBUG => write!(writer, "{} ", level.blue())?, - tracing::Level::INFO => write!(writer, "{} ", level.green())?, - tracing::Level::WARN => write!(writer, "{} ", level.yellow())?, - tracing::Level::ERROR => write!(writer, "{} ", level.red())?, - } - } else { - write!(writer, "{level} ")?; - } - } - - if self.show_spans { - let span = event.parent(); - let mut seen = false; - - let span = span - .and_then(|id| ctx.span(id)) - .or_else(|| ctx.lookup_current()); - - let scope = span.into_iter().flat_map(|span| span.scope().from_root()); - - for span in scope { - seen = true; - if ansi { - write!(writer, "{}:", span.metadata().name().bold())?; - } else { - write!(writer, "{}:", span.metadata().name())?; - } - } - - if seen { - writer.write_char(' ')?; - } - } - - ctx.field_format().format_fields(writer.by_ref(), event)?; - - writeln!(writer) - } -} - /// Configure `tracing` based on the given [`Level`], taking into account the `RUST_LOG` environment /// variable. /// @@ -183,18 +102,11 @@ pub(crate) fn setup_logging( ) .init(); } else { - // Regardless of the tracing level, show messages without any adornment. - let format = UvFormat { - display_timestamp: false, - display_level: true, - show_spans: false, - }; - tracing_subscriber::registry() .with(durations_layer) .with( tracing_subscriber::fmt::layer() - .event_format(format) + .event_format(UvFormat::default()) .with_writer(writer) .with_ansi(ansi) .with_filter(filter), diff --git a/docs/concepts/build-backend.md b/docs/concepts/build-backend.md index bd9a912fbc6..1e86d35ab5c 100644 --- a/docs/concepts/build-backend.md +++ b/docs/concepts/build-backend.md @@ -237,6 +237,13 @@ must either be under the module root or in the appropriate [data directory](../reference/settings.md#build-backend_data). Most packages store small data in the module root alongside the source code. +!!! tip + + When using the uv build backend through a frontend that is not uv, such as pip or + `pythom -m build`, debug logging can be enabled through environment variables with + `RUST_LOG=uv=debug` or `RUST_LOG=uv=verbose`. When used through uv, the uv build backend shares + the verbosity level of uv. + ### Include and exclude syntax Includes are anchored, which means that `pyproject.toml` includes only `/pyproject.toml` and From aecce98a711e42671fec13ecfd6dd601ccd2fac2 Mon Sep 17 00:00:00 2001 From: konstin Date: Tue, 26 Aug 2025 16:48:13 +0200 Subject: [PATCH 2/2] Fix uv-dev --- crates/uv-dev/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-dev/Cargo.toml b/crates/uv-dev/Cargo.toml index 23b571c6fb3..8eb62ce61af 100644 --- a/crates/uv-dev/Cargo.toml +++ b/crates/uv-dev/Cargo.toml @@ -59,7 +59,7 @@ tokio = { workspace = true } tokio-util = { workspace = true } tracing = { workspace = true } tracing-durations-export = { workspace = true, features = ["plot"] } -tracing-subscriber = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } uv-performance-memory-allocator = { path = "../uv-performance-memory-allocator", optional = true } walkdir = { workspace = true }