Skip to content

Commit

Permalink
Issue Reporters (vercel/turborepo#3707)
Browse files Browse the repository at this point in the history
Big thanks to @jridgewell for helping me out with a number of Rust-isms
with this change.

This expands `handle_issues` and the `NextDevServerBuilder` to accept an
arbitrary `IssueReporter` -- a trait implementing `report_issues` which
receives captured issues to send somewhere.

This replaces using a fixed `ConsoleUi` to send issues to stdout/stderr,
though `ConsoleUi` now implements `IssueReporter` and is the default
implementation of an issue reporter if no other is provided. It also
moves the responsibility of detecting fatal errors out of `ConsoleUi`
and into `handle_issues` itself.

This lays the foundation for alternative reporters, such as a test
reporter to snapshot or assert against issues emitted, or a
newline-delimited JSON reporter for other tools to consume.

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>

---------

Co-authored-by: Justin Ridgewell <justin@ridgewell.name>
  • Loading branch information
wbinnssmith and jridgewell authored Feb 13, 2023
1 parent 6fb1402 commit 5926753
Showing 1 changed file with 61 additions and 24 deletions.
85 changes: 61 additions & 24 deletions packages/next-swc/crates/next-dev/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ use owo_colors::OwoColorize;
use turbo_malloc::TurboMalloc;
use turbo_tasks::{
util::{FormatBytes, FormatDuration},
RawVc, StatsType, TransientInstance, TransientValue, TurboTasks, TurboTasksBackendApi, Value,
CollectiblesSource, RawVc, StatsType, TransientInstance, TransientValue, TurboTasks,
TurboTasksBackendApi, Value,
};
use turbo_tasks_fs::{DiskFileSystemVc, FileSystem, FileSystemVc};
use turbo_tasks_memory::MemoryBackend;
use turbopack_cli_utils::issue::{ConsoleUi, ConsoleUiVc, LogOptions};
use turbopack_cli_utils::issue::{ConsoleUiVc, LogOptions};
use turbopack_core::{
environment::ServerAddr,
issue::IssueSeverity,
issue::{IssueReporter, IssueReporterVc, IssueSeverity, IssueVc},
resolve::{parse::RequestVc, pattern::QueryMapVc},
server_fs::ServerFileSystemVc,
};
Expand Down Expand Up @@ -62,6 +63,7 @@ pub struct NextDevServerBuilder {
entry_requests: Vec<EntryRequest>,
eager_compile: bool,
hostname: Option<IpAddr>,
issue_reporter: Option<Box<dyn IssueReporterProvider>>,
port: Option<u16>,
browserslist_query: String,
log_level: IssueSeverity,
Expand All @@ -83,6 +85,7 @@ impl NextDevServerBuilder {
entry_requests: vec![],
eager_compile: false,
hostname: None,
issue_reporter: None,
port: None,
browserslist_query: "last 1 Chrome versions, last 1 Firefox versions, last 1 Safari \
versions, last 1 Edge versions"
Expand Down Expand Up @@ -139,6 +142,14 @@ impl NextDevServerBuilder {
self
}

pub fn issue_reporter(
mut self,
issue_reporter: Box<dyn IssueReporterProvider>,
) -> NextDevServerBuilder {
self.issue_reporter = Some(issue_reporter);
self
}

/// Attempts to find an open port to bind.
fn find_port(&self, host: IpAddr, port: u16, max_attempts: u16) -> Result<DevServerBuilder> {
// max_attempts of 1 means we loop 0 times.
Expand Down Expand Up @@ -192,58 +203,72 @@ impl NextDevServerBuilder {
let show_all = self.show_all;
let log_detail = self.log_detail;
let browserslist_query = self.browserslist_query;
let log_options = LogOptions {
let log_options = Arc::new(LogOptions {
current_dir: current_dir().unwrap(),
show_all,
log_detail,
log_level: self.log_level,
};
});
let entry_requests = Arc::new(self.entry_requests);
let console_ui = Arc::new(ConsoleUi::new(log_options));
let console_ui_to_dev_server = console_ui.clone();
let server_addr = Arc::new(server.addr);
let tasks = turbo_tasks.clone();
let issue_provider = self.issue_reporter.unwrap_or_else(|| {
// Initialize a ConsoleUi reporter if no custom reporter was provided
Box::new(move || ConsoleUiVc::new(log_options.clone().into()).into())
});
let issue_reporter_arc = Arc::new(move || issue_provider.get_issue_reporter());

let get_issue_reporter = issue_reporter_arc.clone();
let source = move || {
source(
root_dir.clone(),
project_dir.clone(),
entry_requests.clone().into(),
eager_compile,
turbo_tasks.clone().into(),
console_ui.clone().into(),
get_issue_reporter(),
browserslist_query.clone(),
server_addr.clone().into(),
)
};

Ok(server.serve(tasks, source, console_ui_to_dev_server))
Ok(server.serve(tasks, source, issue_reporter_arc.clone()))
}
}

async fn handle_issues<T: Into<RawVc>>(source: T, console_ui: ConsoleUiVc) -> Result<()> {
let state = console_ui
.group_and_display_issues(TransientValue::new(source.into()))
async fn handle_issues<T: Into<RawVc> + CollectiblesSource + Copy>(
source: T,
issue_reporter: IssueReporterVc,
) -> Result<()> {
let issues = IssueVc::peek_issues_with_path(source)
.await?
.strongly_consistent()
.await?;

if state.has_fatal {
issue_reporter.report_issues(
TransientInstance::new(issues.clone()),
TransientValue::new(source.into()),
);

if issues.has_fatal().await? {
Err(anyhow!("Fatal issue(s) occurred"))
} else {
Ok(())
}
}

#[turbo_tasks::function]
async fn project_fs(project_dir: &str, console_ui: ConsoleUiVc) -> Result<FileSystemVc> {
async fn project_fs(project_dir: &str, issue_reporter: IssueReporterVc) -> Result<FileSystemVc> {
let disk_fs = DiskFileSystemVc::new("project".to_string(), project_dir.to_string());
handle_issues(disk_fs, console_ui).await?;
handle_issues(disk_fs, issue_reporter).await?;
disk_fs.await?.start_watching()?;
Ok(disk_fs.into())
}

#[turbo_tasks::function]
async fn output_fs(project_dir: &str, console_ui: ConsoleUiVc) -> Result<FileSystemVc> {
async fn output_fs(project_dir: &str, issue_reporter: IssueReporterVc) -> Result<FileSystemVc> {
let disk_fs = DiskFileSystemVc::new("output".to_string(), project_dir.to_string());
handle_issues(disk_fs, console_ui).await?;
handle_issues(disk_fs, issue_reporter).await?;
disk_fs.await?.start_watching()?;
Ok(disk_fs.into())
}
Expand All @@ -256,13 +281,12 @@ async fn source(
entry_requests: TransientInstance<Vec<EntryRequest>>,
eager_compile: bool,
turbo_tasks: TransientInstance<TurboTasks<MemoryBackend>>,
console_ui: TransientInstance<ConsoleUi>,
issue_reporter: IssueReporterVc,
browserslist_query: String,
server_addr: TransientInstance<SocketAddr>,
) -> Result<ContentSourceVc> {
let console_ui = (*console_ui).clone().cell();
let output_fs = output_fs(&project_dir, console_ui);
let fs = project_fs(&root_dir, console_ui);
let output_fs = output_fs(&project_dir, issue_reporter);
let fs = project_fs(&root_dir, issue_reporter);
let project_relative = project_dir.strip_prefix(&root_dir).unwrap();
let project_relative = project_relative
.strip_prefix(MAIN_SEPARATOR)
Expand Down Expand Up @@ -372,9 +396,9 @@ async fn source(
.cell()
.into();

handle_issues(dev_server_fs, console_ui).await?;
handle_issues(web_source, console_ui).await?;
handle_issues(page_source, console_ui).await?;
handle_issues(dev_server_fs, issue_reporter).await?;
handle_issues(web_source, issue_reporter).await?;
handle_issues(page_source, issue_reporter).await?;

Ok(source)
}
Expand Down Expand Up @@ -551,3 +575,16 @@ fn profile_timeout<T>(
) -> impl Future<Output = T> {
future
}

pub trait IssueReporterProvider: Send + Sync + 'static {
fn get_issue_reporter(&self) -> IssueReporterVc;
}

impl<T> IssueReporterProvider for T
where
T: Fn() -> IssueReporterVc + Send + Sync + Clone + 'static,
{
fn get_issue_reporter(&self) -> IssueReporterVc {
self()
}
}

0 comments on commit 5926753

Please sign in to comment.