From 28a413c5b6a92cbfdb94eca5787e7369ef03f4a3 Mon Sep 17 00:00:00 2001 From: Tom French <15848336+TomAFrench@users.noreply.github.com> Date: Wed, 6 Sep 2023 14:57:37 +0100 Subject: [PATCH] feat(nargo): Add commands to install and uninstall custom backends. (#2575) --- crates/acvm_backend_barretenberg/src/bb.rs | 45 +-------------- .../acvm_backend_barretenberg/src/download.rs | 49 +++++++++++++++++ crates/acvm_backend_barretenberg/src/lib.rs | 6 +- crates/nargo_cli/src/backends.rs | 15 ++++- .../src/cli/backend_cmd/current_cmd.rs | 13 +++++ .../src/cli/backend_cmd/install_cmd.rs | 25 +++++++++ .../nargo_cli/src/cli/backend_cmd/ls_cmd.rs | 1 + crates/nargo_cli/src/cli/backend_cmd/mod.rs | 9 +++ .../src/cli/backend_cmd/uninstall_cmd.rs | 55 +++++++++++++++++++ 9 files changed, 170 insertions(+), 48 deletions(-) create mode 100644 crates/acvm_backend_barretenberg/src/download.rs create mode 100644 crates/nargo_cli/src/cli/backend_cmd/current_cmd.rs create mode 100644 crates/nargo_cli/src/cli/backend_cmd/install_cmd.rs create mode 100644 crates/nargo_cli/src/cli/backend_cmd/uninstall_cmd.rs diff --git a/crates/acvm_backend_barretenberg/src/bb.rs b/crates/acvm_backend_barretenberg/src/bb.rs index 42a90e37cc7..8c2df4e034e 100644 --- a/crates/acvm_backend_barretenberg/src/bb.rs +++ b/crates/acvm_backend_barretenberg/src/bb.rs @@ -1,5 +1,3 @@ -use std::{io::Cursor, path::Path}; - use const_format::formatcp; const USERNAME: &str = "AztecProtocol"; @@ -10,11 +8,7 @@ const TAG: &str = formatcp!("barretenberg-v{}", VERSION); const API_URL: &str = formatcp!("https://github.com/{}/{}/releases/download/{}", USERNAME, REPO, TAG); -fn get_bb_download_url() -> String { - if let Ok(path) = std::env::var("BB_BINARY_URL") { - return path; - } - +pub(crate) fn get_bb_download_url() -> String { let target_os = env!("TARGET_OS"); let target_arch = env!("TARGET_ARCH"); @@ -30,40 +24,3 @@ fn get_bb_download_url() -> String { format!("{API_URL}/{archive_name}") } - -pub(crate) fn download_bb_binary(binary_path: &Path) { - use flate2::read::GzDecoder; - use tar::Archive; - use tempfile::tempdir; - - // Create directory to place binary in. - std::fs::create_dir_all(binary_path.parent().unwrap()).unwrap(); - - // Download sources - let compressed_file: Cursor> = download_binary_from_url(&get_bb_download_url()) - .unwrap_or_else(|error| panic!("\n\nDownload error: {error}\n\n")); - - // Unpack the tarball - let gz_decoder = GzDecoder::new(compressed_file); - let mut archive = Archive::new(gz_decoder); - - let temp_directory = tempdir().expect("could not create a temporary directory"); - archive.unpack(&temp_directory).unwrap(); - let temp_binary_path = temp_directory.path().join("bb"); - - // Rename the binary to the desired name - std::fs::copy(temp_binary_path, binary_path).unwrap(); - - drop(temp_directory); -} - -/// Try to download the specified URL into a buffer which is returned. -fn download_binary_from_url(url: &str) -> Result>, String> { - let response = reqwest::blocking::get(url).map_err(|error| error.to_string())?; - - let bytes = response.bytes().unwrap(); - - // TODO: Check SHA of downloaded binary - - Ok(Cursor::new(bytes.to_vec())) -} diff --git a/crates/acvm_backend_barretenberg/src/download.rs b/crates/acvm_backend_barretenberg/src/download.rs new file mode 100644 index 00000000000..a6e79250597 --- /dev/null +++ b/crates/acvm_backend_barretenberg/src/download.rs @@ -0,0 +1,49 @@ +use std::{io::Cursor, path::Path}; + +/// Downloads a zipped archive and unpacks the backend binary to `destination_path`. +/// +/// # Backend Requirements +/// +/// In order for a backend to be compatible with this function: +/// - `backend_url` must serve a gzipped tarball. +/// - The tarball must only contain the backend's binary. +/// - The binary file must be located at the archive root. +pub fn download_backend(backend_url: &str, destination_path: &Path) { + use flate2::read::GzDecoder; + use tar::Archive; + use tempfile::tempdir; + + // Download sources + let compressed_file: Cursor> = download_binary_from_url(backend_url) + .unwrap_or_else(|error| panic!("\n\nDownload error: {error}\n\n")); + + // Unpack the tarball + let gz_decoder = GzDecoder::new(compressed_file); + let mut archive = Archive::new(gz_decoder); + + let temp_directory = tempdir().expect("could not create a temporary directory"); + archive.unpack(&temp_directory).unwrap(); + + // Assume that the archive contains a single file which is the backend binary. + let mut archive_files = std::fs::read_dir(&temp_directory).unwrap(); + let temp_binary_path = archive_files.next().unwrap().unwrap().path(); + + // Create directory to place binary in. + std::fs::create_dir_all(destination_path.parent().unwrap()).unwrap(); + + // Rename the binary to the desired name + std::fs::copy(temp_binary_path, destination_path).unwrap(); + + drop(temp_directory); +} + +/// Try to download the specified URL into a buffer which is returned. +fn download_binary_from_url(url: &str) -> Result>, String> { + let response = reqwest::blocking::get(url).map_err(|error| error.to_string())?; + + let bytes = response.bytes().unwrap(); + + // TODO: Check SHA of downloaded binary + + Ok(Cursor::new(bytes.to_vec())) +} diff --git a/crates/acvm_backend_barretenberg/src/lib.rs b/crates/acvm_backend_barretenberg/src/lib.rs index 88b096e00fd..56205699589 100644 --- a/crates/acvm_backend_barretenberg/src/lib.rs +++ b/crates/acvm_backend_barretenberg/src/lib.rs @@ -5,10 +5,14 @@ use std::path::PathBuf; mod bb; mod cli; +mod download; mod proof_system; mod smart_contract; +pub use download::download_backend; + const BACKENDS_DIR: &str = ".nargo/backends"; +pub const ACVM_BACKEND_BARRETENBERG: &str = "acvm-backend-barretenberg"; pub fn backends_directory() -> PathBuf { let home_directory = dirs::home_dir().unwrap(); @@ -33,7 +37,7 @@ fn assert_binary_exists(backend: &Backend) -> PathBuf { let binary_path = backend.binary_path(); if !binary_path.is_file() { - bb::download_bb_binary(&binary_path) + download_backend(&bb::get_bb_download_url(), &binary_path) } binary_path } diff --git a/crates/nargo_cli/src/backends.rs b/crates/nargo_cli/src/backends.rs index 10e6d380da0..7695a226d89 100644 --- a/crates/nargo_cli/src/backends.rs +++ b/crates/nargo_cli/src/backends.rs @@ -7,6 +7,16 @@ fn active_backend_file_path() -> PathBuf { backends_directory().join(".selected_backend") } +pub(crate) const ACVM_BACKEND_BARRETENBERG: &str = "acvm-backend-barretenberg"; + +pub(crate) fn clear_active_backend() { + let active_backend_file = active_backend_file_path(); + if active_backend_file.is_file() { + std::fs::remove_file(active_backend_file_path()) + .expect("should delete active backend file"); + } +} + pub(crate) fn set_active_backend(backend_name: &str) { std::fs::create_dir_all( active_backend_file_path().parent().expect("active backend file should have parent"), @@ -19,9 +29,8 @@ pub(crate) fn get_active_backend() -> String { let active_backend_file = active_backend_file_path(); if !active_backend_file.is_file() { - let barretenberg = "acvm-backend-barretenberg"; - set_active_backend(barretenberg); - return barretenberg.to_string(); + set_active_backend(ACVM_BACKEND_BARRETENBERG); + return ACVM_BACKEND_BARRETENBERG.to_string(); } std::fs::read_to_string(active_backend_file).unwrap() diff --git a/crates/nargo_cli/src/cli/backend_cmd/current_cmd.rs b/crates/nargo_cli/src/cli/backend_cmd/current_cmd.rs new file mode 100644 index 00000000000..5aba00764d3 --- /dev/null +++ b/crates/nargo_cli/src/cli/backend_cmd/current_cmd.rs @@ -0,0 +1,13 @@ +use clap::Args; + +use crate::{backends::get_active_backend, errors::CliError}; + +/// Prints the name of the currently active backend +#[derive(Debug, Clone, Args)] +pub(crate) struct CurrentCommand; + +pub(crate) fn run(_args: CurrentCommand) -> Result<(), CliError> { + println!("{}", get_active_backend()); + + Ok(()) +} diff --git a/crates/nargo_cli/src/cli/backend_cmd/install_cmd.rs b/crates/nargo_cli/src/cli/backend_cmd/install_cmd.rs new file mode 100644 index 00000000000..352caa3b3b6 --- /dev/null +++ b/crates/nargo_cli/src/cli/backend_cmd/install_cmd.rs @@ -0,0 +1,25 @@ +use clap::Args; + +use acvm_backend_barretenberg::{backends_directory, download_backend}; + +use crate::errors::CliError; + +use super::ls_cmd::get_available_backends; + +/// Install a new backend +#[derive(Debug, Clone, Args)] +pub(crate) struct InstallCommand { + backend: String, + + url: String, +} + +pub(crate) fn run(args: InstallCommand) -> Result<(), CliError> { + let installed_backends = get_available_backends(); + + assert!(!installed_backends.contains(&args.backend), "backend is already installed"); + + download_backend(&args.url, &backends_directory().join(args.backend).join("backend_binary")); + + Ok(()) +} diff --git a/crates/nargo_cli/src/cli/backend_cmd/ls_cmd.rs b/crates/nargo_cli/src/cli/backend_cmd/ls_cmd.rs index 5a3d07e065a..fee55109291 100644 --- a/crates/nargo_cli/src/cli/backend_cmd/ls_cmd.rs +++ b/crates/nargo_cli/src/cli/backend_cmd/ls_cmd.rs @@ -18,6 +18,7 @@ pub(crate) fn run(_args: LsCommand) -> Result<(), CliError> { pub(super) fn get_available_backends() -> Vec { let backend_directory_contents = std::fs::read_dir(backends_directory()).unwrap(); + // TODO: Highlight the currently active backend. backend_directory_contents .into_iter() .filter_map(|entry| { diff --git a/crates/nargo_cli/src/cli/backend_cmd/mod.rs b/crates/nargo_cli/src/cli/backend_cmd/mod.rs index 23f6e0c2491..530504b6e22 100644 --- a/crates/nargo_cli/src/cli/backend_cmd/mod.rs +++ b/crates/nargo_cli/src/cli/backend_cmd/mod.rs @@ -2,7 +2,10 @@ use clap::{Args, Subcommand}; use crate::errors::CliError; +mod current_cmd; +mod install_cmd; mod ls_cmd; +mod uninstall_cmd; mod use_cmd; #[non_exhaustive] @@ -15,16 +18,22 @@ pub(crate) struct BackendCommand { #[non_exhaustive] #[derive(Subcommand, Clone, Debug)] pub(crate) enum BackendCommands { + Current(current_cmd::CurrentCommand), Ls(ls_cmd::LsCommand), Use(use_cmd::UseCommand), + Install(install_cmd::InstallCommand), + Uninstall(uninstall_cmd::UninstallCommand), } pub(crate) fn run(cmd: BackendCommand) -> Result<(), CliError> { let BackendCommand { command } = cmd; match command { + BackendCommands::Current(args) => current_cmd::run(args), BackendCommands::Ls(args) => ls_cmd::run(args), BackendCommands::Use(args) => use_cmd::run(args), + BackendCommands::Install(args) => install_cmd::run(args), + BackendCommands::Uninstall(args) => uninstall_cmd::run(args), }?; Ok(()) diff --git a/crates/nargo_cli/src/cli/backend_cmd/uninstall_cmd.rs b/crates/nargo_cli/src/cli/backend_cmd/uninstall_cmd.rs new file mode 100644 index 00000000000..aa261639b2a --- /dev/null +++ b/crates/nargo_cli/src/cli/backend_cmd/uninstall_cmd.rs @@ -0,0 +1,55 @@ +use clap::Args; + +use acvm_backend_barretenberg::backends_directory; + +use crate::{ + backends::{ + clear_active_backend, get_active_backend, set_active_backend, ACVM_BACKEND_BARRETENBERG, + }, + errors::CliError, +}; + +use super::ls_cmd::get_available_backends; + +/// Uninstall a backend +#[derive(Debug, Clone, Args)] +pub(crate) struct UninstallCommand { + backend: String, +} + +pub(crate) fn run(args: UninstallCommand) -> Result<(), CliError> { + let installed_backends = get_available_backends(); + + assert!(installed_backends.contains(&args.backend), "backend does not exist"); + let active_backend = get_active_backend(); + + // Handle the case where we're uninstalling the currently active backend. + if active_backend == args.backend { + let barretenberg_is_installed = + installed_backends.iter().any(|backend_name| backend_name == ACVM_BACKEND_BARRETENBERG); + + let new_active_backend = + if args.backend != ACVM_BACKEND_BARRETENBERG && barretenberg_is_installed { + // Prefer switching to barretenberg if possible. + Some(ACVM_BACKEND_BARRETENBERG) + } else { + // Otherwise pick the first backend which isn't being uninstalled. + installed_backends + .iter() + .find(|&backend_name| backend_name != &args.backend) + .map(|name| name.as_str()) + }; + + if let Some(backend) = new_active_backend { + set_active_backend(backend) + } else { + // We've deleted the last backend. Clear the active backend file to be recreated once we install a new one. + clear_active_backend() + } + } + + std::fs::remove_dir_all(&backends_directory().join(args.backend)) + .expect("backend directory should be deleted"); + + Ok(()) +}