use crate::*;
use leo_errors::{PackageError, Result, UtilError};
use leo_span::Symbol;
use snarkvm::prelude::{Program as SvmProgram, TestnetV0};
use indexmap::IndexSet;
use std::path::Path;
#[derive(Clone, Debug)]
pub struct Program {
pub name: Symbol,
pub data: ProgramData,
pub dependencies: IndexSet<Dependency>,
pub is_test: bool,
}
impl Program {
pub fn from_path<P: AsRef<Path>>(name: Symbol, path: P) -> Result<Self> {
Self::from_path_impl(name, path.as_ref())
}
fn from_path_impl(name: Symbol, path: &Path) -> Result<Self> {
let manifest = Manifest::read_from_file(path.join(MANIFEST_FILENAME))?;
let manifest_symbol = crate::symbol(&manifest.program)?;
if name != manifest_symbol {
return Err(PackageError::conflicting_manifest(
format_args!("{name}.aleo"),
format_args!("{manifest_symbol}.aleo"),
)
.into());
}
let source_directory = path.join(SOURCE_DIRECTORY);
let count = source_directory
.read_dir()
.map_err(|e| {
UtilError::util_file_io_error(
format_args!("Failed to read directory {}", source_directory.display()),
e,
)
})?
.count();
let source_path = source_directory.join(MAIN_FILENAME);
if !source_path.exists() || count != 1 {
return Err(PackageError::source_directory_can_contain_only_one_file(source_directory.display()).into());
}
Ok(Program {
name,
data: ProgramData::SourcePath(source_path),
dependencies: manifest
.dependencies
.unwrap_or_default()
.into_iter()
.map(|dependency| canonicalize_dependency_path_relative_to(path, dependency))
.collect::<Result<IndexSet<_>, _>>()?,
is_test: false,
})
}
pub fn from_path_test<P: AsRef<Path>>(source_path: P, main_program: Dependency) -> Result<Self> {
Self::from_path_test_impl(source_path.as_ref(), main_program)
}
fn from_path_test_impl(source_path: &Path, main_program: Dependency) -> Result<Self> {
let name = filename_no_leo_extension(source_path)
.ok_or_else(|| PackageError::failed_path(source_path.display(), ""))?;
let package_directory = source_path.parent().and_then(|parent| parent.parent()).ok_or_else(|| {
UtilError::failed_to_open_file(format_args!("Failed to find package for test {}", source_path.display()))
})?;
let manifest = Manifest::read_from_file(package_directory.join(MANIFEST_FILENAME))?;
let mut dependencies = manifest
.dev_dependencies
.unwrap_or_default()
.into_iter()
.map(|dependency| canonicalize_dependency_path_relative_to(package_directory, dependency))
.collect::<Result<IndexSet<_>, _>>()?;
dependencies.insert(main_program);
Ok(Program {
name: Symbol::intern(name),
data: ProgramData::SourcePath(source_path.to_path_buf()),
dependencies,
is_test: true,
})
}
pub fn fetch<P: AsRef<Path>>(name: Symbol, home_path: P, network: NetworkName, endpoint: &str) -> Result<Self> {
Self::fetch_impl(name, home_path.as_ref(), network, endpoint)
}
fn fetch_impl(name: Symbol, home_path: &Path, network: NetworkName, endpoint: &str) -> Result<Self> {
let cache_directory = home_path.join(format!("registry/{network}"));
let full_cache_path = cache_directory.join(format!("{name}.aleo"));
let bytecode = if full_cache_path.exists() {
std::fs::read_to_string(&full_cache_path).map_err(|e| {
UtilError::util_file_io_error(
format_args!("Trying to read cached file at {}", full_cache_path.display()),
e,
)
})?
} else {
let url = format!("{endpoint}/{network}/program/{name}.aleo");
let contents = fetch_from_network(&url)?;
std::fs::create_dir_all(&cache_directory).map_err(|e| {
UtilError::util_file_io_error(
format_args!("Could not create directory `{}`", cache_directory.display()),
e,
)
})?;
std::fs::write(&full_cache_path, &contents).map_err(|err| {
UtilError::util_file_io_error(format_args!("Could not open file `{}`", full_cache_path.display()), err)
})?;
contents
};
let svm_program: SvmProgram<TestnetV0> =
bytecode.parse().map_err(|_| UtilError::snarkvm_parsing_error(name))?;
let dependencies = svm_program
.imports()
.keys()
.map(|program_id| {
let name = program_id.to_string();
Dependency { name, location: Location::Network, network: Some(network), path: None }
})
.collect();
Ok(Program { name, data: ProgramData::Bytecode(bytecode), dependencies, is_test: false })
}
}
fn canonicalize_dependency_path_relative_to(base: &Path, mut dependency: Dependency) -> Result<Dependency> {
if let Some(path) = &mut dependency.path {
if !path.is_absolute() {
let joined = base.join(&path);
*path = joined.canonicalize().map_err(|e| PackageError::failed_path(joined.display(), e))?;
}
}
Ok(dependency)
}