Skip to content
Draft
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
15 changes: 1 addition & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ itertools = "0.15.0"
jiff = "0.2.8"
jsonrpc-core = "18.0.0"
jsonrpc-http-server = "18.0.0"
jsonrpc-stdio-server = "18.0.0"
jsonrpsee = "=0.26.0"
libc = { version = "0.2.170", default-features = false }
memchr = "2.8"
Expand Down
2 changes: 0 additions & 2 deletions crates/pixi_build_backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ pixi_build_types = { workspace = true }

jsonrpc-core = { workspace = true }
jsonrpc-http-server = { workspace = true }
jsonrpc-stdio-server = { workspace = true }


[dev-dependencies]
insta = { version = "1.42.1", features = ["yaml", "redactions", "filters"] }
Expand Down
86 changes: 76 additions & 10 deletions crates/pixi_build_backend/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
use std::sync::{Arc, atomic::AtomicBool};

use clap::{Parser, Subcommand};
use clap_verbosity_flag::{InfoLevel, Verbosity};
use miette::IntoDiagnostic;
use pixi_build_types::{
BackendCapabilities, FrontendCapabilities,
procedures::log_message::{LOG_LEVEL_ENV, LogMessage},
procedures::negotiate_capabilities::NegotiateCapabilitiesParams,
};
use rattler_build_core::console_utils::{LoggingOutputHandler, get_default_env_filter};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use tokio::sync::mpsc;
use tracing_subscriber::{Layer, layer::SubscriberExt, util::SubscriberInitExt};

use crate::{protocol::ProtocolInstantiator, server::Server};
use crate::{
log_message_layer::{LogMessageLayer, stderr_filter},
protocol::ProtocolInstantiator,
server::Server,
};

#[allow(missing_docs)]
#[derive(Parser)]
Expand All @@ -33,12 +41,22 @@ pub enum Commands {
Capabilities,
}

/// The log channel handed to the server when it runs over stdio.
type LogMessageChannel = (mpsc::UnboundedReceiver<LogMessage>, Arc<AtomicBool>);

/// Run the sever on the specified port or over stdin/stdout.
async fn run_server<T: ProtocolInstantiator>(port: Option<u16>, protocol: T) -> miette::Result<()> {
let server = Server::new(protocol);
async fn run_server<T: ProtocolInstantiator>(
port: Option<u16>,
protocol: T,
log_messages: Option<LogMessageChannel>,
) -> miette::Result<()> {
let mut server = Server::new(protocol);
if let Some(port) = port {
server.run_over_http(port)
} else {
if let Some((receiver, enabled)) = log_messages {
server = server.with_log_messages(receiver, enabled);
}
// running over stdin/stdout
server.run().await
}
Expand All @@ -52,22 +70,55 @@ pub(crate) async fn main_impl<T: ProtocolInstantiator, F: FnOnce(LoggingOutputHa
// Setup logging
let log_handler = LoggingOutputHandler::default();

// The frontend can ask for more verbose logging than our own command line
// requested; take the maximum of the two.
let frontend_level = std::env::var(LOG_LEVEL_ENV)
.ok()
.and_then(|level| level.parse::<clap_verbosity_flag::log::LevelFilter>().ok());
let cli_level = args.verbose.log_level_filter();
let effective_level = frontend_level.map_or(cli_level, |level| level.max(cli_level));

// `get_default_env_filter` only enables `rattler_build` and friends, which
// silently drops events from the backend crates themselves (e.g. the
// "`pypi-conda-map` is set but the mapping is disabled" warning). Add a
// default directive so warnings from any target are surfaced.
// default directive so warnings from any target are surfaced — raised
// further when the frontend asked for more.
let default_directive = log_to_tracing_level_filter(effective_level)
.max(tracing_subscriber::filter::LevelFilter::WARN);
let registry = tracing_subscriber::registry().with(
get_default_env_filter(args.verbose.log_level_filter())
get_default_env_filter(effective_level)
.into_diagnostic()?
.add_directive(tracing_subscriber::filter::LevelFilter::WARN.into()),
.add_directive(default_directive.into()),
);

registry.with(log_handler.clone()).init();
// When we serve a frontend over stdio, structured log records can travel
// to it as `log/message` notifications. The layer stays dormant until
// the frontend advertises support during capability negotiation; until
// then (and for INFO events — the plaintext build-output stream — always)
// the human-readable handler keeps rendering to stderr.
let serves_stdio = args.command.is_none() && args.http_port.is_none();
let log_messages = if serves_stdio {
let (sender, receiver) = mpsc::unbounded_channel();
let enabled = Arc::new(AtomicBool::new(false));

registry
.with(
log_handler
.clone()
.with_filter(stderr_filter(enabled.clone())),
)
.with(LogMessageLayer::new(sender, enabled.clone()))
.init();
Some((receiver, enabled))
} else {
registry.with(log_handler.clone()).init();
None
};

let factory = factory(log_handler);

match args.command {
None => run_server(args.http_port, factory).await,
None => run_server(args.http_port, factory, log_messages).await,
Some(Commands::Capabilities) => {
let backend_capabilities = capabilities::<T>().await?;
eprintln!(
Expand All @@ -85,6 +136,21 @@ pub(crate) async fn main_impl<T: ProtocolInstantiator, F: FnOnce(LoggingOutputHa
}
}

fn log_to_tracing_level_filter(
level: clap_verbosity_flag::log::LevelFilter,
) -> tracing_subscriber::filter::LevelFilter {
use clap_verbosity_flag::log::LevelFilter as LogLevelFilter;
use tracing_subscriber::filter::LevelFilter;
match level {
LogLevelFilter::Off => LevelFilter::OFF,
LogLevelFilter::Error => LevelFilter::ERROR,
LogLevelFilter::Warn => LevelFilter::WARN,
LogLevelFilter::Info => LevelFilter::INFO,
LogLevelFilter::Debug => LevelFilter::DEBUG,
LogLevelFilter::Trace => LevelFilter::TRACE,
}
}

/// The entry point for the CLI which should be called from the backends implementation.
pub async fn main<T: ProtocolInstantiator, F: FnOnce(LoggingOutputHandler) -> T>(
factory: F,
Expand All @@ -105,7 +171,7 @@ pub async fn main_ext<T: ProtocolInstantiator, F: FnOnce(LoggingOutputHandler) -
/// Returns the capabilities of the backend.
async fn capabilities<Factory: ProtocolInstantiator>() -> miette::Result<BackendCapabilities> {
let result = Factory::negotiate_capabilities(NegotiateCapabilitiesParams {
capabilities: FrontendCapabilities {},
capabilities: FrontendCapabilities::default(),
})
.await?;

Expand Down
1 change: 1 addition & 0 deletions crates/pixi_build_backend/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod cli;
pub mod generated_recipe;
pub mod intermediate_backend;
pub mod log_message_layer;
pub mod package_dependency;
pub mod protocol;
pub mod rattler_build_integration;
Expand Down
Loading
Loading