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

feat(nargo): Add commands to install and uninstall custom backends #2575

Merged
merged 2 commits into from
Sep 6, 2023
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
45 changes: 1 addition & 44 deletions crates/acvm_backend_barretenberg/src/bb.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::{io::Cursor, path::Path};

use const_format::formatcp;

const USERNAME: &str = "AztecProtocol";
Expand All @@ -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");

Expand All @@ -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<Vec<u8>> = 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<Cursor<Vec<u8>>, 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()))
}
49 changes: 49 additions & 0 deletions crates/acvm_backend_barretenberg/src/download.rs
Original file line number Diff line number Diff line change
@@ -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<Vec<u8>> = 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<Cursor<Vec<u8>>, 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()))
}
6 changes: 5 additions & 1 deletion crates/acvm_backend_barretenberg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
}
Expand Down
15 changes: 12 additions & 3 deletions crates/nargo_cli/src/backends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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()
Expand Down
13 changes: 13 additions & 0 deletions crates/nargo_cli/src/cli/backend_cmd/current_cmd.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
25 changes: 25 additions & 0 deletions crates/nargo_cli/src/cli/backend_cmd/install_cmd.rs
Original file line number Diff line number Diff line change
@@ -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(())
}
1 change: 1 addition & 0 deletions crates/nargo_cli/src/cli/backend_cmd/ls_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub(crate) fn run(_args: LsCommand) -> Result<(), CliError> {
pub(super) fn get_available_backends() -> Vec<String> {
let backend_directory_contents = std::fs::read_dir(backends_directory()).unwrap();

// TODO: Highlight the currently active backend.
kevaundray marked this conversation as resolved.
Show resolved Hide resolved
backend_directory_contents
.into_iter()
.filter_map(|entry| {
Expand Down
9 changes: 9 additions & 0 deletions crates/nargo_cli/src/cli/backend_cmd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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(())
Expand Down
55 changes: 55 additions & 0 deletions crates/nargo_cli/src/cli/backend_cmd/uninstall_cmd.rs
Original file line number Diff line number Diff line change
@@ -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(())
}