Add CI job for no-std build verification and fix Infallible StdError implementation

- Add dedicated no-std CI job that builds for wasm32-unknown-unknown target
- Fix Infallible StdError implementation for no-std environments

The CI job validates:
1. Core no-std functionality (no default features)
2. fancy-no-syscall feature set compatible with no-std environments
This commit is contained in:
François Garillot 2025-10-20 04:46:36 -04:00
parent 79fbd0a07d
commit 18c217f0dc
No known key found for this signature in database
GPG Key ID: E7645C6B883A1E9A
8 changed files with 83 additions and 31 deletions

View File

@ -68,6 +68,21 @@ jobs:
- name: Check wasm target
run: cargo check --target wasm32-unknown-unknown --features fancy-no-syscall
no-std:
name: Check no-std build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: stable
targets: wasm32-unknown-unknown
- name: Check no-std core build
run: cargo check --target wasm32-unknown-unknown --no-default-features
- name: Check no-std with fancy-no-syscall
run: cargo check --target wasm32-unknown-unknown --no-default-features --features fancy-no-syscall
miri:
name: Miri
runs-on: ubuntu-latest

View File

@ -8,6 +8,9 @@ use core::{convert::Infallible, fmt::Display};
use crate::{Diagnostic, LabeledSpan, Severity, SourceCode};
#[cfg(not(feature = "std"))]
impl crate::StdError for Infallible {}
impl Diagnostic for Infallible {
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
match *self {}

View File

@ -209,13 +209,16 @@ impl<C> StdError for WithSourceCode<Report, C> {
}
}
#[cfg(test)]
#[cfg(all(test, feature = "std"))]
mod tests {
#[cfg(feature = "fancy")]
use std::format;
use std::{
boxed::Box,
string::{String, ToString},
};
#[cfg(feature = "fancy")]
use std::{vec, vec::Vec};
use thiserror::Error;
use crate::{Diagnostic, LabeledSpan, Report, SourceCode, SourceSpan};

View File

@ -345,17 +345,29 @@ impl MietteHandlerOpts {
}
}
#[allow(clippy::manual_unwrap_or)]
pub(crate) fn is_graphical(&self) -> bool {
if let Some(force_narrated) = self.force_narrated {
!force_narrated
} else if let Some(force_graphical) = self.force_graphical {
force_graphical
} else if let Ok(env) = std::env::var("NO_GRAPHICS") {
} else {
#[cfg(feature = "fancy-no-syscall")]
{
// In no-std environment, assume graphics are available
true
}
#[cfg(not(feature = "fancy-no-syscall"))]
{
// In std environment, check NO_GRAPHICS env var
if let Ok(env) = std::env::var("NO_GRAPHICS") {
env == "0"
} else {
true
}
}
}
}
// Detects known terminal apps based on env variables and returns true if
// they support rendering links.
@ -501,7 +513,7 @@ mod syscall {
// In no-std environment without color support, default to no color support
false
} else {
supports_color::on(supports_color::Stream::Stderr).is_some()
true // Fallback to assuming color support
}
}
}
@ -515,7 +527,7 @@ mod syscall {
// In no-std environment without color support, default to no RGB color support
Some(false)
} else {
supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m)
Some(true) // Fallback to assuming RGB support
}
}
}

View File

@ -588,7 +588,7 @@ impl GraphicalReportHandler {
// The snippets will overlap, so we create one Big Chunky Boi
let left_end = left.offset() + left.len();
let right_end = right.offset() + right.len();
let new_end = std::cmp::max(left_end, right_end);
let new_end = core::cmp::max(left_end, right_end);
let new_span = LabeledSpan::new(
left.label().map(String::from),
@ -655,7 +655,7 @@ impl GraphicalReportHandler {
num_highlights += 1;
}
}
max_gutter = std::cmp::max(max_gutter, num_highlights);
max_gutter = core::cmp::max(max_gutter, num_highlights);
}
// Oh and one more thing: We need to figure out how much room our line
@ -1179,7 +1179,7 @@ impl GraphicalReportHandler {
.style(hl.style)
.to_string(),
);
highest = std::cmp::max(highest, end);
highest = core::cmp::max(highest, end);
(hl, vbar_offset)
})

View File

@ -71,6 +71,13 @@ impl GraphicalTheme {
impl Default for GraphicalTheme {
fn default() -> Self {
#[cfg(feature = "fancy-no-syscall")]
{
// In no-std environments, default to no-color mode
Self::unicode_nocolor()
}
#[cfg(not(feature = "fancy-no-syscall"))]
{
match std::env::var("NO_COLOR") {
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
Self::none()
@ -80,6 +87,7 @@ impl Default for GraphicalTheme {
}
}
}
}
/**
Styles for various parts of graphical rendering for the

View File

@ -11,7 +11,7 @@
//! * `syntect-highlighter` - Enables [`syntect`](https://docs.rs/syntect/latest/syntect/) syntax highlighting support via the [`SyntectHighlighter`]
//!
use std::{ops::Deref, sync::Arc};
use core::ops::Deref;
extern crate alloc;
use alloc::boxed::Box;
@ -83,8 +83,9 @@ impl MietteHighlighter {
}
impl Default for MietteHighlighter {
#[cfg(feature = "syntect-highlighter")]
fn default() -> Self {
#[cfg(all(feature = "syntect-highlighter", not(feature = "fancy-no-syscall")))]
{
use std::io::IsTerminal;
match std::env::var("NO_COLOR") {
_ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => {
@ -95,11 +96,17 @@ impl Default for MietteHighlighter {
_ => Self(Arc::new(SyntectHighlighter::default())),
}
}
#[cfg(all(feature = "syntect-highlighter", feature = "fancy-no-syscall"))]
{
// In no-std environment, use syntect but without terminal detection
Self(Arc::new(SyntectHighlighter::default()))
}
#[cfg(not(feature = "syntect-highlighter"))]
fn default() -> Self {
{
MietteHighlighter::nocolor()
}
}
}
impl<T: Highlighter + Send + Sync + 'static> From<T> for MietteHighlighter {
fn from(value: T) -> Self {
@ -107,8 +114,8 @@ impl<T: Highlighter + Send + Sync + 'static> From<T> for MietteHighlighter {
}
}
impl std::fmt::Debug for MietteHighlighter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl core::fmt::Debug for MietteHighlighter {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "MietteHighlighter(...)")
}
}

View File

@ -9,6 +9,7 @@ use alloc::boxed::Box;
use alloc::string::String;
use core::fmt::{self, Display};
use core::ops;
#[cfg(feature = "std")]
use core::panic::Location;
#[cfg(feature = "std")]
use std::fs;
@ -16,7 +17,7 @@ use std::fs;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{DiagnosticError, MietteError};
use crate::MietteError;
/// Adds rich metadata to your Error that can be used by
/// [`Report`](crate::Report) to print really nice and human-friendly error
@ -172,6 +173,9 @@ impl From<String> for Box<dyn Diagnostic + Send + Sync> {
}
}
#[cfg(feature = "std")]
use crate::DiagnosticError;
#[cfg(feature = "std")]
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
fn from(s: Box<dyn std::error::Error + Send + Sync>) -> Self {