1use crate::*;
18
19use leo_errors::{PackageError, Result, UtilError};
20use leo_span::Symbol;
21
22use snarkvm::prelude::{Program as SvmProgram, TestnetV0};
23
24use indexmap::IndexSet;
25use std::path::Path;
26
27#[derive(Clone, Debug)]
29pub struct Program {
30 pub name: Symbol,
32 pub data: ProgramData,
33 pub dependencies: IndexSet<Dependency>,
34 pub is_test: bool,
35}
36
37impl Program {
38 pub fn from_path<P: AsRef<Path>>(name: Symbol, path: P) -> Result<Self> {
41 Self::from_path_impl(name, path.as_ref())
42 }
43
44 fn from_path_impl(name: Symbol, path: &Path) -> Result<Self> {
45 let manifest = Manifest::read_from_file(path.join(MANIFEST_FILENAME))?;
46 let manifest_symbol = crate::symbol(&manifest.program)?;
47 if name != manifest_symbol {
48 return Err(PackageError::conflicting_manifest(
49 format_args!("{name}.aleo"),
50 format_args!("{manifest_symbol}.aleo"),
51 )
52 .into());
53 }
54 let source_directory = path.join(SOURCE_DIRECTORY);
55 let count = source_directory
56 .read_dir()
57 .map_err(|e| {
58 UtilError::util_file_io_error(
59 format_args!("Failed to read directory {}", source_directory.display()),
60 e,
61 )
62 })?
63 .count();
64
65 let source_path = source_directory.join(MAIN_FILENAME);
66
67 if !source_path.exists() || count != 1 {
68 return Err(PackageError::source_directory_can_contain_only_one_file(source_directory.display()).into());
69 }
70
71 Ok(Program {
72 name,
73 data: ProgramData::SourcePath(source_path),
74 dependencies: manifest
75 .dependencies
76 .unwrap_or_default()
77 .into_iter()
78 .map(|dependency| canonicalize_dependency_path_relative_to(path, dependency))
79 .collect::<Result<IndexSet<_>, _>>()?,
80 is_test: false,
81 })
82 }
83
84 pub fn from_path_test<P: AsRef<Path>>(source_path: P, main_program: Dependency) -> Result<Self> {
91 Self::from_path_test_impl(source_path.as_ref(), main_program)
92 }
93
94 fn from_path_test_impl(source_path: &Path, main_program: Dependency) -> Result<Self> {
95 let name = filename_no_leo_extension(source_path)
96 .ok_or_else(|| PackageError::failed_path(source_path.display(), ""))?;
97 let package_directory = source_path.parent().and_then(|parent| parent.parent()).ok_or_else(|| {
98 UtilError::failed_to_open_file(format_args!("Failed to find package for test {}", source_path.display()))
99 })?;
100 let manifest = Manifest::read_from_file(package_directory.join(MANIFEST_FILENAME))?;
101 let mut dependencies = manifest
102 .dev_dependencies
103 .unwrap_or_default()
104 .into_iter()
105 .map(|dependency| canonicalize_dependency_path_relative_to(package_directory, dependency))
106 .collect::<Result<IndexSet<_>, _>>()?;
107 dependencies.insert(main_program);
108
109 Ok(Program {
110 name: Symbol::intern(name),
111 data: ProgramData::SourcePath(source_path.to_path_buf()),
112 dependencies,
113 is_test: true,
114 })
115 }
116
117 pub fn fetch<P: AsRef<Path>>(name: Symbol, home_path: P, network: NetworkName, endpoint: &str) -> Result<Self> {
119 Self::fetch_impl(name, home_path.as_ref(), network, endpoint)
120 }
121
122 fn fetch_impl(name: Symbol, home_path: &Path, network: NetworkName, endpoint: &str) -> Result<Self> {
123 let cache_directory = home_path.join(format!("registry/{network}"));
125 let full_cache_path = cache_directory.join(format!("{name}.aleo"));
126
127 let bytecode = if full_cache_path.exists() {
128 std::fs::read_to_string(&full_cache_path).map_err(|e| {
130 UtilError::util_file_io_error(
131 format_args!("Trying to read cached file at {}", full_cache_path.display()),
132 e,
133 )
134 })?
135 } else {
136 let url = format!("{endpoint}/{network}/program/{name}.aleo");
138 let contents = fetch_from_network(&url)?;
139
140 std::fs::create_dir_all(&cache_directory).map_err(|e| {
142 UtilError::util_file_io_error(
143 format_args!("Could not create directory `{}`", cache_directory.display()),
144 e,
145 )
146 })?;
147
148 std::fs::write(&full_cache_path, &contents).map_err(|err| {
150 UtilError::util_file_io_error(format_args!("Could not open file `{}`", full_cache_path.display()), err)
151 })?;
152
153 contents
154 };
155
156 let svm_program: SvmProgram<TestnetV0> =
158 bytecode.parse().map_err(|_| UtilError::snarkvm_parsing_error(name))?;
159 let dependencies = svm_program
160 .imports()
161 .keys()
162 .map(|program_id| {
163 let name = program_id.to_string();
164 Dependency { name, location: Location::Network, network: Some(network), path: None }
165 })
166 .collect();
167
168 Ok(Program { name, data: ProgramData::Bytecode(bytecode), dependencies, is_test: false })
169 }
170}
171
172fn canonicalize_dependency_path_relative_to(base: &Path, mut dependency: Dependency) -> Result<Dependency> {
177 if let Some(path) = &mut dependency.path {
178 if !path.is_absolute() {
179 let joined = base.join(&path);
180 *path = joined.canonicalize().map_err(|e| PackageError::failed_path(joined.display(), e))?;
181 }
182 }
183 Ok(dependency)
184}