Skip to content

Commit

Permalink
DAP preflight mode (#1)
Browse files Browse the repository at this point in the history
* DAP preflight mode

* Properly handle generate_acir and skip_instrumentation

* Add more context on preflight mode
  • Loading branch information
mverzilli authored and ggiraldez committed Jan 24, 2024
1 parent b409f81 commit 93d1fd6
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 16 deletions.
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 @@ -98,25 +126,27 @@ 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 compiled_program = nargo::ops::transform_program(compiled_program, expression_width);

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 @@ -183,8 +213,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 @@ -203,11 +233,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);
}

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

0 comments on commit 93d1fd6

Please sign in to comment.