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>>(
119 name: Symbol,
120 home_path: P,
121 network: NetworkName,
122 endpoint: &str,
123 no_cache: bool,
124 ) -> Result<Self> {
125 Self::fetch_impl(name, home_path.as_ref(), network, endpoint, no_cache)
126 }
127
128 fn fetch_impl(
129 name: Symbol,
130 home_path: &Path,
131 network: NetworkName,
132 endpoint: &str,
133 no_cache: bool,
134 ) -> Result<Self> {
135 let cache_directory = home_path.join(format!("registry/{network}"));
137 if !cache_directory.exists() {
138 std::fs::create_dir_all(&cache_directory).map_err(|err| {
140 UtilError::util_file_io_error(format!("Could not write path {}", cache_directory.display()), err)
141 })?;
142 }
143
144 let full_cache_path = cache_directory.join(format!("{name}.aleo"));
145
146 let existing_bytecode = match full_cache_path.exists() {
148 false => None,
149 true => {
150 let existing_contents = std::fs::read_to_string(&full_cache_path).map_err(|e| {
152 UtilError::util_file_io_error(
153 format_args!("Trying to read cached file at {}", full_cache_path.display()),
154 e,
155 )
156 })?;
157 Some(existing_contents)
158 }
159 };
160
161 let bytecode = match (existing_bytecode, no_cache) {
162 (Some(bytecode), false) => bytecode,
164 (existing, _) => {
166 let url = format!("{endpoint}/{network}/program/{name}.aleo");
168 let contents = fetch_from_network(&url)?;
169
170 if let Some(existing_contents) = existing {
172 if existing_contents != contents {
173 println!(
174 "Warning: The cached file at `{}` is different from the one fetched from the network. The cached file will be overwritten.",
175 full_cache_path.display()
176 );
177 }
178 }
179
180 std::fs::write(&full_cache_path, &contents).map_err(|err| {
182 UtilError::util_file_io_error(
183 format_args!("Could not open file `{}`", full_cache_path.display()),
184 err,
185 )
186 })?;
187
188 contents
189 }
190 };
191
192 let svm_program: SvmProgram<TestnetV0> =
194 bytecode.parse().map_err(|_| UtilError::snarkvm_parsing_error(name))?;
195 let dependencies = svm_program
196 .imports()
197 .keys()
198 .map(|program_id| {
199 let name = program_id.to_string();
200 Dependency { name, location: Location::Network, path: None }
201 })
202 .collect();
203
204 Ok(Program { name, data: ProgramData::Bytecode(bytecode), dependencies, is_test: false })
205 }
206}
207
208fn canonicalize_dependency_path_relative_to(base: &Path, mut dependency: Dependency) -> Result<Dependency> {
213 if let Some(path) = &mut dependency.path {
214 if !path.is_absolute() {
215 let joined = base.join(&path);
216 *path = joined.canonicalize().map_err(|e| PackageError::failed_path(joined.display(), e))?;
217 }
218 }
219 Ok(dependency)
220}