Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DAP preflight mode #1

Merged
merged 4 commits into from
Jan 23, 2024
Merged
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
19 changes: 19 additions & 0 deletions tooling/debugger/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use thiserror::Error;

#[derive(Debug, Error)]
pub enum DapError {
#[error("{0}")]
PreFlightGenericError(String),

#[error(transparent)]
LoadError(#[from] LoadError),

#[error(transparent)]
ServerError(#[from] dap::errors::ServerError),
}

#[derive(Debug, Error)]
pub enum LoadError {
#[error("{0}")]
Generic(String),
}
1 change: 1 addition & 0 deletions tooling/debugger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod context;
mod dap;
pub mod errors;
mod foreign_calls;
mod repl;
mod source_code_printer;
Expand Down
98 changes: 83 additions & 15 deletions tooling/nargo_cli/src/cli/dap_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use noirc_frontend::graph::CrateName;
use std::io::{BufReader, BufWriter, Read, Write};
use std::path::Path;

use dap::errors::ServerError;
use dap::requests::Command;
use dap::responses::ResponseBody;
use dap::server::Server;
Expand All @@ -29,10 +28,28 @@ use crate::errors::CliError;

use super::NargoConfig;

use noir_debugger::errors::{DapError, LoadError};

#[derive(Debug, Clone, Args)]
pub(crate) struct DapCommand;
pub(crate) struct DapCommand {
#[clap(long)]
preflight_check: bool,

#[clap(long)]
preflight_project_folder: Option<String>,

#[clap(long)]
preflight_package: Option<String>,

#[clap(long)]
preflight_prover_name: Option<String>,

#[clap(long)]
preflight_generate_acir: bool,

struct LoadError(&'static str);
#[clap(long)]
preflight_skip_instrumentation: bool,
}

fn find_workspace(project_folder: &str, package: Option<&str>) -> Option<Workspace> {
let Ok(toml_path) = get_package_manifest(Path::new(project_folder)) else {
Expand All @@ -54,6 +71,16 @@ fn find_workspace(project_folder: &str, package: Option<&str>) -> Option<Workspa
}
}

fn workspace_not_found_error_msg(project_folder: &str, package: Option<&str>) -> String {
match package {
Some(pkg) => format!(
r#"Noir Debugger could not load program from {}, package {}"#,
project_folder, pkg
),
None => format!(r#"Noir Debugger could not load program from {}"#, project_folder),
}
}

fn load_and_compile_project(
backend: &Backend,
project_folder: &str,
Expand All @@ -62,14 +89,15 @@ fn load_and_compile_project(
generate_acir: bool,
skip_instrumentation: bool,
) -> Result<(CompiledProgram, WitnessMap), LoadError> {
let workspace =
find_workspace(project_folder, package).ok_or(LoadError("Cannot open workspace"))?;
let expression_width =
backend.get_backend_info().map_err(|_| LoadError("Failed to get backend info"))?;
let workspace = find_workspace(project_folder, package)
.ok_or(LoadError::Generic(workspace_not_found_error_msg(project_folder, package)))?;
let expression_width = backend
.get_backend_info()
.map_err(|_| LoadError::Generic("Failed to get backend info".into()))?;
let package = workspace
.into_iter()
.find(|p| p.is_binary())
.ok_or(LoadError("No matching binary packages found in workspace"))?;
.ok_or(LoadError::Generic("No matching binary packages found in workspace".into()))?;

let mut workspace_file_manager = file_manager_with_stdlib(std::path::Path::new(""));
insert_all_files_for_workspace_into_file_manager(&workspace, &mut workspace_file_manager);
Expand Down Expand Up @@ -99,23 +127,25 @@ fn load_and_compile_project(
compile_options.deny_warnings,
compile_options.silence_warnings,
)
.map_err(|_| LoadError("Failed to compile project"))?;
.map_err(|_| LoadError::Generic("Failed to compile project".into()))?;

let (inputs_map, _) =
read_inputs_from_file(&package.root_dir, prover_name, Format::Toml, &compiled_program.abi)
.map_err(|_| LoadError("Failed to read program inputs"))?;
.map_err(|_| {
LoadError::Generic(format!("Failed to read program inputs from {}", prover_name))
})?;
let initial_witness = compiled_program
.abi
.encode(&inputs_map, None)
.map_err(|_| LoadError("Failed to encode inputs"))?;
.map_err(|_| LoadError::Generic("Failed to encode inputs".into()))?;

Ok((compiled_program, initial_witness))
}

fn loop_uninitialized_dap<R: Read, W: Write>(
mut server: Server<R, W>,
backend: &Backend,
) -> Result<(), ServerError> {
) -> Result<(), DapError> {
loop {
let req = match server.poll_request()? {
Some(req) => req,
Expand Down Expand Up @@ -182,8 +212,8 @@ fn loop_uninitialized_dap<R: Read, W: Write>(
)?;
break;
}
Err(LoadError(message)) => {
server.respond(req.error(message))?;
Err(LoadError::Generic(message)) => {
server.respond(req.error(message.as_str()))?;
}
}
}
Expand All @@ -202,11 +232,49 @@ fn loop_uninitialized_dap<R: Read, W: Write>(
Ok(())
}

fn run_preflight_check(backend: &Backend, args: DapCommand) -> Result<(), DapError> {
let project_folder = if let Some(project_folder) = args.preflight_project_folder {
project_folder
} else {
return Err(DapError::PreFlightGenericError("Noir Debugger could not initialize because the IDE (for example, VS Code) did not specify a project folder to debug.".into()));
};

let package = args.preflight_package.as_deref();
let prover_name = args.preflight_prover_name.as_deref().unwrap_or(PROVER_INPUT_FILE);

let _ = load_and_compile_project(
backend,
project_folder.as_str(),
package,
prover_name,
args.preflight_generate_acir,
args.preflight_skip_instrumentation,
)?;

Ok(())
}

pub(crate) fn run(
backend: &Backend,
_args: DapCommand,
args: DapCommand,
_config: NargoConfig,
) -> Result<(), CliError> {
// When the --preflight-check flag is present, we run Noir's DAP server in "pre-flight mode", which test runs
// the DAP initialization code without actually starting the DAP server.
//
// This lets the client IDE present any initialization issues (compiler version mismatches, missing prover files, etc)
// in its own interface.
//
// This was necessary due to the VS Code project being reluctant to let extension authors capture
// stderr output generated by a DAP server wrapped in DebugAdapterExecutable.
//
// Exposing this preflight mode lets us gracefully handle errors that happen *before*
// the DAP loop is established, which otherwise are considered "out of band" by the maintainers of the DAP spec.
// More details here: https://github.com/microsoft/vscode/issues/108138
if args.preflight_check {
return run_preflight_check(backend, args).map_err(CliError::DapError);
}

mverzilli marked this conversation as resolved.
Show resolved Hide resolved
let output = BufWriter::new(std::io::stdout());
let input = BufReader::new(std::io::stdin());
let server = Server::new(input, output);
Expand Down
3 changes: 2 additions & 1 deletion tooling/nargo_cli/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use acvm::acir::native_types::WitnessMapError;
use hex::FromHexError;
use nargo::{errors::CompileError, NargoError};
use nargo_toml::ManifestError;
use noir_debugger::errors::DapError;
use noirc_abi::errors::{AbiError, InputParserError};
use std::path::PathBuf;
use thiserror::Error;
Expand Down Expand Up @@ -54,7 +55,7 @@ pub(crate) enum CliError {
LspError(#[from] async_lsp::Error),

#[error(transparent)]
DapError(#[from] dap::errors::ServerError),
DapError(#[from] DapError),

/// Error from Nargo
#[error(transparent)]
Expand Down
Loading