servo/ports/servoshell/lib.rs
Narfinger ed66e0b0ca
servoshell: Replace getopts with bpaf for argument parsing (#37194)
This includes some small refactoring and some small breaking changes as
listed below. Other than these I tried to keep the functionality exactly
the same but because in the old code the parsing and settings of
preferences was intermingled it was difficult to figure out.

Small Breaking:
- Size and resources-path were unused but appeared in the help.
- soft-fail and hard-fail: Soft-fail flag got removed because it is too
  difficult to keep both. The default is now soft-fail and hard-fail can
be enabled.
- The help strings are obviously formatted differently now.
- -V does not work anymore but -v and --version.

Ideally, we want to have the ServoShellPreferences and Preferences be
directly the Argument structure but that needs a bit more discussion
because it would break backwards compatibility with the commandline.

This increases the binary size by ~280kb.

Testing: The testcases are still working but they do not cover much.
I added a unit test for the -p flag because it is the most difficult to
parse in general.
Fixes: This will fix a small number of various parsing misshaps. It will
also show if we are removing an option via unused lint.

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
2025-09-05 08:17:38 +00:00

207 lines
8.9 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use cfg_if::cfg_if;
#[cfg(test)]
mod test;
#[cfg(not(target_os = "android"))]
mod backtrace;
#[cfg(not(target_env = "ohos"))]
mod crash_handler;
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
pub(crate) mod desktop;
#[cfg(any(target_os = "android", target_env = "ohos"))]
mod egl;
mod output_image;
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
mod panic_hook;
mod parser;
mod prefs;
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
mod resources;
pub mod platform {
#[cfg(target_os = "macos")]
pub use crate::platform::macos::deinit;
#[cfg(target_os = "macos")]
pub mod macos;
#[cfg(not(target_os = "macos"))]
pub fn deinit(_clean_shutdown: bool) {}
}
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
pub fn main() {
desktop::cli::main()
}
pub fn init_crypto() {
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.expect("Error initializing crypto provider");
}
pub fn init_tracing(filter_directives: Option<&str>) {
#[cfg(not(feature = "tracing"))]
{
if filter_directives.is_some() {
log::debug!("The tracing feature was not selected - ignoring trace filter directives");
}
}
#[cfg(feature = "tracing")]
{
use tracing_subscriber::layer::SubscriberExt;
let subscriber = tracing_subscriber::registry();
#[cfg(feature = "tracing-perfetto")]
let subscriber = {
// Set up a PerfettoLayer for performance tracing.
// The servo.pftrace file can be uploaded to https://ui.perfetto.dev for analysis.
let file = std::fs::File::create("servo.pftrace").unwrap();
let perfetto_layer = tracing_perfetto::PerfettoLayer::new(std::sync::Mutex::new(file))
.with_filter_by_marker(|field_name| field_name == "servo_profiling")
.with_debug_annotations(true);
subscriber.with(perfetto_layer)
};
#[cfg(feature = "tracing-hitrace")]
let subscriber = {
// Set up a HitraceLayer for performance tracing.
subscriber.with(HitraceLayer::default())
};
// Filter events and spans by the directives in SERVO_TRACING, using EnvFilter as a global filter.
// <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/layer/index.html#global-filtering>
let filter_builder = tracing_subscriber::EnvFilter::builder()
.with_default_directive(tracing::level_filters::LevelFilter::OFF.into());
let filter = if let Some(filters) = &filter_directives {
filter_builder.parse_lossy(filters)
} else {
filter_builder
.with_env_var("SERVO_TRACING")
.from_env_lossy()
};
let subscriber = subscriber.with(filter);
// Same as SubscriberInitExt::init, but avoids initialising the tracing-log compat layer,
// since it would break Servos FromScriptLogger and FromEmbederLogger.
// <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/util/trait.SubscriberInitExt.html#method.init>
// <https://docs.rs/tracing/0.1.40/tracing/#consuming-log-records>
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to set tracing subscriber");
}
}
pub const VERSION: &str = concat!("Servo ", env!("CARGO_PKG_VERSION"), "-", env!("GIT_SHA"));
/// Plumbs tracing spans into HiTrace, with the following caveats:
///
/// - We ignore spans unless they have a `servo_profiling` field.
/// - We map span entry ([`Layer::on_enter`]) to `OH_HiTrace_StartTrace(metadata.name())`.
/// - We map span exit ([`Layer::on_exit`]) to `OH_HiTrace_FinishTrace()`.
///
/// As a result, within each thread, spans must exit in reverse order of their entry, otherwise the
/// resultant profiling data will be incorrect (see the section below). This is not necessarily the
/// case for tracing spans, since there can be multiple [trace trees], so we check that this
/// invariant is upheld when debug assertions are enabled, logging errors if it is violated.
///
/// [trace trees]: https://docs.rs/tracing/0.1.40/tracing/span/index.html#span-relationships
///
/// # Uniquely identifying spans
///
/// We need to ensure that the start and end points of one span are not mixed up with other spans.
/// For now, we use the HiTrace [synchronous API], which restricts how spans must behave.
///
/// In the HiTrace [synchronous API], spans must have stack-like behaviour, because spans are keyed
/// entirely on their *name* string, and OH_HiTrace_FinishTrace always ends the most recent span.
/// While synchronous API spans are thread-local, callers could still violate this invariant with
/// reentrant or asynchronous code.
///
/// In the [asynchronous API], spans are keyed on a (*name*,*taskId*) pair, where *name* is again
/// a string, and *taskId* is an arbitrary [`i32`]. This makes *taskId* a good place for a unique
/// identifier, but asynchronous spans can cross thread boundaries, so the identifier needs to be
/// temporally unique in the whole process.
///
/// Tracing spans have such an identifier ([`Id`]), but theyre [`u64`]-based, and their format
/// is an internal implementation detail of the [`Subscriber`]. For [`Registry`], those values
/// [come from] a [packed representation] of a generation number, thread number, page number, and
/// variable-length index. This makes them hard to compress robustly into an [`i32`].
///
/// If we move to the asynchronous API, we will need to generate our own *taskId* values, perhaps
/// by combining some sort of thread id with a thread-local atomic counter. [`ThreadId`] is opaque
/// in stable Rust, and converts to a [`u64`] in unstable Rust, so we would also need to make our
/// own thread ids, perhaps by having a global atomic counter cached in a thread-local.
///
/// [synchronous API]: https://docs.rs/hitrace-sys/0.1.2/hitrace_sys/fn.OH_HiTrace_StartTrace.html
/// [asynchronous API]: https://docs.rs/hitrace-sys/0.1.2/hitrace_sys/fn.OH_HiTrace_StartAsyncTrace.html
/// [`Registry`]: tracing_subscriber::Registry
/// [come from]: https://docs.rs/tracing-subscriber/0.3.18/src/tracing_subscriber/registry/sharded.rs.html#237-269
/// [packed representation]: https://docs.rs/sharded-slab/0.1.7/sharded_slab/trait.Config.html
/// [`ThreadId`]: std::thread::ThreadId
#[cfg(feature = "tracing-hitrace")]
#[derive(Default)]
struct HitraceLayer {}
cfg_if! {
if #[cfg(feature = "tracing-hitrace")] {
use std::cell::RefCell;
use tracing::span::Id;
use tracing::Subscriber;
use tracing_subscriber::Layer;
#[cfg(debug_assertions)]
thread_local! {
/// Stack of span names, to ensure the HiTrace synchronous API is not misused.
static HITRACE_NAME_STACK: RefCell<Vec<String>> = RefCell::default();
}
impl<S: Subscriber + for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>>
Layer<S> for HitraceLayer
{
fn on_enter(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
if let Some(metadata) = ctx.metadata(id) {
// TODO: is this expensive? Would extensions be faster?
// <https://docs.rs/tracing-subscriber/0.3.18/tracing_subscriber/registry/struct.ExtensionsMut.html>
if metadata.fields().field("servo_profiling").is_some() {
#[cfg(debug_assertions)]
HITRACE_NAME_STACK.with_borrow_mut(|stack|
stack.push(metadata.name().to_owned()));
hitrace::start_trace(
&std::ffi::CString::new(metadata.name())
.expect("Failed to convert str to CString"),
);
}
}
}
fn on_exit(&self, id: &Id, ctx: tracing_subscriber::layer::Context<'_, S>) {
if let Some(metadata) = ctx.metadata(id) {
if metadata.fields().field("servo_profiling").is_some() {
hitrace::finish_trace();
#[cfg(debug_assertions)]
HITRACE_NAME_STACK.with_borrow_mut(|stack| {
if stack.last().map(|name| &**name) != Some(metadata.name()) {
log::error!(
"Tracing span out of order: {} (stack: {:?})",
metadata.name(),
stack
);
}
if !stack.is_empty() {
stack.pop();
}
});
}
}
}
}
}
}