use crate::*;
use leo_errors::{CliError, PackageError, Result, UtilError};
use leo_passes::DiGraph;
use leo_span::Symbol;
use anyhow::anyhow;
use indexmap::{IndexMap, map::Entry};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug)]
pub enum ProgramData {
Bytecode(String),
SourcePath(PathBuf),
}
#[derive(Clone, Debug)]
pub struct Package {
pub base_directory: PathBuf,
pub programs: Vec<Program>,
pub manifest: Manifest,
pub env: Env,
}
impl Package {
pub fn outputs_directory(&self) -> PathBuf {
self.base_directory.join(OUTPUTS_DIRECTORY)
}
pub fn imports_directory(&self) -> PathBuf {
self.base_directory.join(IMPORTS_DIRECTORY)
}
pub fn build_directory(&self) -> PathBuf {
self.base_directory.join(BUILD_DIRECTORY)
}
pub fn source_directory(&self) -> PathBuf {
self.base_directory.join(SOURCE_DIRECTORY)
}
pub fn tests_directory(&self) -> PathBuf {
self.base_directory.join(TESTS_DIRECTORY)
}
pub fn initialize<P: AsRef<Path>>(
package_name: &str,
path: P,
network: NetworkName,
endpoint: &str,
) -> Result<PathBuf> {
Self::initialize_impl(package_name, path.as_ref(), network, endpoint)
}
fn initialize_impl(package_name: &str, path: &Path, network: NetworkName, endpoint: &str) -> Result<PathBuf> {
let package_name =
if package_name.ends_with(".aleo") { package_name.to_string() } else { format!("{package_name}.aleo") };
if !crate::is_valid_aleo_name(&package_name) {
return Err(CliError::invalid_program_name(package_name).into());
}
let path = path.canonicalize().map_err(|e| PackageError::failed_path(path.display(), e))?;
let full_path = path.join(package_name.strip_suffix(".aleo").unwrap());
if full_path.exists() {
return Err(
PackageError::failed_to_initialize_package(package_name, &path, "Directory already exists").into()
);
}
std::fs::create_dir(&full_path)
.map_err(|e| PackageError::failed_to_initialize_package(&package_name, &full_path, e))?;
std::env::set_current_dir(&full_path)
.map_err(|e| PackageError::failed_to_initialize_package(&package_name, &full_path, e))?;
const GITIGNORE_TEMPLATE: &str = ".env\n*.avm\n*.prover\n*.verifier\noutputs/\n";
const GITIGNORE_FILENAME: &str = ".gitignore";
let gitignore_path = full_path.join(GITIGNORE_FILENAME);
std::fs::write(gitignore_path, GITIGNORE_TEMPLATE).map_err(PackageError::io_error_gitignore_file)?;
let env = Env { network, private_key: TEST_PRIVATE_KEY.to_string(), endpoint: endpoint.to_string() };
let env_path = full_path.join(ENV_FILENAME);
env.write_to_file(env_path)?;
let manifest = Manifest {
program: package_name.clone(),
version: "0.1.0".to_string(),
description: String::new(),
license: "MIT".to_string(),
dependencies: None,
dev_dependencies: None,
};
let manifest_path = full_path.join(MANIFEST_FILENAME);
manifest.write_to_file(manifest_path)?;
let source_path = full_path.join(SOURCE_DIRECTORY);
std::fs::create_dir(&source_path)
.map_err(|e| PackageError::failed_to_create_source_directory(source_path.display(), e))?;
let main_path = source_path.join(MAIN_FILENAME);
let name_no_aleo = package_name.strip_suffix(".aleo").unwrap();
std::fs::write(&main_path, main_template(name_no_aleo))
.map_err(|e| UtilError::util_file_io_error(format_args!("Failed to write `{}`", main_path.display()), e))?;
let tests_path = full_path.join(TESTS_DIRECTORY);
std::fs::create_dir(&tests_path)
.map_err(|e| PackageError::failed_to_create_source_directory(tests_path.display(), e))?;
let test_file_path = tests_path.join(format!("test_{name_no_aleo}.leo"));
std::fs::write(&test_file_path, test_template(name_no_aleo))
.map_err(|e| UtilError::util_file_io_error(format_args!("Failed to write `{}`", main_path.display()), e))?;
Ok(full_path)
}
pub fn from_directory_no_graph<P: AsRef<Path>, Q: AsRef<Path>>(path: P, home_path: Q) -> Result<Self> {
Self::from_directory_impl(
path.as_ref(),
home_path.as_ref(),
false,
false,
)
}
pub fn from_directory<P: AsRef<Path>, Q: AsRef<Path>>(path: P, home_path: Q) -> Result<Self> {
Self::from_directory_impl(
path.as_ref(),
home_path.as_ref(),
true,
false,
)
}
pub fn from_directory_with_tests<P: AsRef<Path>, Q: AsRef<Path>>(path: P, home_path: Q) -> Result<Self> {
Self::from_directory_impl(
path.as_ref(),
home_path.as_ref(),
true,
true,
)
}
pub fn test_files(&self) -> impl Iterator<Item = PathBuf> {
let path = self.tests_directory();
let data: Vec<PathBuf> = Self::files_with_extension(&path, "leo").collect();
data.into_iter()
}
pub fn import_files(&self) -> impl Iterator<Item = PathBuf> {
let path = self.imports_directory();
let data: Vec<PathBuf> = Self::files_with_extension(&path, "aleo").collect();
data.into_iter()
}
fn files_with_extension(path: &Path, extension: &'static str) -> impl Iterator<Item = PathBuf> {
path.read_dir()
.ok()
.into_iter()
.flatten()
.flat_map(|maybe_filename| maybe_filename.ok())
.filter(|entry| entry.file_type().ok().map(|filetype| filetype.is_file()).unwrap_or(false))
.flat_map(move |entry| {
let path = entry.path();
if path.extension().is_some_and(|e| e == extension) { Some(path) } else { None }
})
}
fn from_directory_impl(path: &Path, home_path: &Path, build_graph: bool, with_tests: bool) -> Result<Self> {
let map_err = |path: &Path, err| {
UtilError::util_file_io_error(format_args!("Trying to find path at {}", path.display()), err)
};
let path = path.canonicalize().map_err(|err| map_err(path, err))?;
let env = Env::read_from_file_or_environment(path.join(ENV_FILENAME))?;
let manifest = Manifest::read_from_file(path.join(MANIFEST_FILENAME))?;
let programs: Vec<Program> = if build_graph {
let home_path = home_path.canonicalize().map_err(|err| map_err(home_path, err))?;
let mut map: IndexMap<Symbol, (Dependency, Program)> = IndexMap::new();
let mut digraph = DiGraph::<Symbol>::new(Default::default());
let first_dependency = Dependency {
name: manifest.program.clone(),
location: Location::Local,
network: None,
path: Some(path.clone()),
};
let test_dependencies: Vec<Dependency> = if with_tests {
let tests_directory = path.join(TESTS_DIRECTORY);
let mut test_dependencies: Vec<Dependency> = Self::files_with_extension(&tests_directory, "leo")
.map(|path| Dependency {
name: format!("{}.aleo", crate::filename_no_leo_extension(&path).unwrap()),
location: Location::Test,
network: None,
path: Some(path.to_path_buf()),
})
.collect();
if let Some(deps) = manifest.dev_dependencies.as_ref() {
test_dependencies.extend(deps.iter().cloned());
}
test_dependencies
} else {
Vec::new()
};
for dependency in test_dependencies.into_iter().chain(std::iter::once(first_dependency.clone())) {
Self::graph_build(
&home_path,
env.network,
&env.endpoint,
&first_dependency,
dependency,
&mut map,
&mut digraph,
)?;
}
let ordered_dependency_symbols =
digraph.post_order().map_err(|_| UtilError::circular_dependency_error())?;
ordered_dependency_symbols.into_iter().map(|symbol| map.swap_remove(&symbol).unwrap().1).collect()
} else {
Vec::new()
};
Ok(Package { base_directory: path, programs, env, manifest })
}
fn graph_build(
home_path: &Path,
network: NetworkName,
endpoint: &str,
main_program: &Dependency,
new: Dependency,
map: &mut IndexMap<Symbol, (Dependency, Program)>,
graph: &mut DiGraph<Symbol>,
) -> Result<()> {
let name_symbol = crate::symbol(&new.name)?;
let program = match map.entry(name_symbol) {
Entry::Occupied(occupied) => {
let existing_dep = &occupied.get().0;
assert_eq!(new.name, existing_dep.name);
if new.location != existing_dep.location || new.path != existing_dep.path {
return Err(PackageError::conflicting_dependency(format_args!("{name_symbol}.aleo")).into());
}
return Ok(());
}
Entry::Vacant(vacant) => {
let program = match (new.path.as_ref(), new.location) {
(Some(path), Location::Local) => {
Program::from_path(name_symbol, path.clone())?
}
(Some(path), Location::Test) => {
Program::from_path_test(path, main_program.clone())?
}
(_, Location::Network) => {
Program::fetch(name_symbol, home_path, network, endpoint)?
}
_ => return Err(anyhow!("Invalid dependency data for {} (path must be given).", new.name).into()),
};
vacant.insert((new, program.clone()));
program
}
};
graph.add_node(name_symbol);
for dependency in program.dependencies.iter() {
let dependency_symbol = crate::symbol(&dependency.name)?;
graph.add_edge(name_symbol, dependency_symbol);
Self::graph_build(home_path, network, endpoint, main_program, dependency.clone(), map, graph)?;
}
Ok(())
}
}
fn main_template(name: &str) -> String {
format!(
r#"// The '{name}' program.
program {name}.aleo {{
transition main(public a: u32, b: u32) -> u32 {{
let c: u32 = a + b;
return c;
}}
}}
"#
)
}
fn test_template(name: &str) -> String {
format!(
r#"// The 'test_{name}' test program.
import {name}.aleo;
program test_{name}.aleo {{
@test
script test_it() {{
let result: u32 = {name}.aleo/main(1u32, 2u32);
assert_eq(result, 3u32);
}}
@test
@should_fail
transition do_nothing() {{
let result: u32 = {name}.aleo/main(2u32, 3u32);
assert_eq(result, 3u32);
}}
}}
"#
)
}