1use crate::*;
18
19use leo_errors::{CliError, PackageError, Result, UtilError};
20use leo_passes::DiGraph;
21use leo_span::Symbol;
22
23use anyhow::anyhow;
24use indexmap::{IndexMap, map::Entry};
25use std::path::{Path, PathBuf};
26
27#[derive(Clone, Debug)]
30pub enum ProgramData {
31 Bytecode(String),
32 SourcePath(PathBuf),
33}
34
35#[derive(Clone, Debug)]
37pub struct Package {
38 pub base_directory: PathBuf,
40
41 pub programs: Vec<Program>,
48
49 pub manifest: Manifest,
51
52 pub env: Env,
54}
55
56impl Package {
57 pub fn outputs_directory(&self) -> PathBuf {
58 self.base_directory.join(OUTPUTS_DIRECTORY)
59 }
60
61 pub fn imports_directory(&self) -> PathBuf {
62 self.base_directory.join(IMPORTS_DIRECTORY)
63 }
64
65 pub fn build_directory(&self) -> PathBuf {
66 self.base_directory.join(BUILD_DIRECTORY)
67 }
68
69 pub fn source_directory(&self) -> PathBuf {
70 self.base_directory.join(SOURCE_DIRECTORY)
71 }
72
73 pub fn tests_directory(&self) -> PathBuf {
74 self.base_directory.join(TESTS_DIRECTORY)
75 }
76
77 pub fn initialize<P: AsRef<Path>>(
79 package_name: &str,
80 path: P,
81 network: NetworkName,
82 endpoint: &str,
83 ) -> Result<PathBuf> {
84 Self::initialize_impl(package_name, path.as_ref(), network, endpoint)
85 }
86
87 fn initialize_impl(package_name: &str, path: &Path, network: NetworkName, endpoint: &str) -> Result<PathBuf> {
88 let package_name =
89 if package_name.ends_with(".aleo") { package_name.to_string() } else { format!("{package_name}.aleo") };
90
91 if !crate::is_valid_aleo_name(&package_name) {
92 return Err(CliError::invalid_program_name(package_name).into());
93 }
94
95 let path = path.canonicalize().map_err(|e| PackageError::failed_path(path.display(), e))?;
96 let full_path = path.join(package_name.strip_suffix(".aleo").unwrap());
97
98 if full_path.exists() {
100 return Err(
101 PackageError::failed_to_initialize_package(package_name, &path, "Directory already exists").into()
102 );
103 }
104
105 std::fs::create_dir(&full_path)
107 .map_err(|e| PackageError::failed_to_initialize_package(&package_name, &full_path, e))?;
108
109 std::env::set_current_dir(&full_path)
111 .map_err(|e| PackageError::failed_to_initialize_package(&package_name, &full_path, e))?;
112
113 const GITIGNORE_TEMPLATE: &str = ".env\n*.avm\n*.prover\n*.verifier\noutputs/\n";
115
116 const GITIGNORE_FILENAME: &str = ".gitignore";
117
118 let gitignore_path = full_path.join(GITIGNORE_FILENAME);
119
120 std::fs::write(gitignore_path, GITIGNORE_TEMPLATE).map_err(PackageError::io_error_gitignore_file)?;
121
122 let env = Env { network, private_key: TEST_PRIVATE_KEY.to_string(), endpoint: endpoint.to_string() };
124
125 let env_path = full_path.join(ENV_FILENAME);
126
127 env.write_to_file(env_path)?;
128
129 let manifest = Manifest {
131 program: package_name.clone(),
132 version: "0.1.0".to_string(),
133 description: String::new(),
134 license: "MIT".to_string(),
135 dependencies: None,
136 dev_dependencies: None,
137 };
138
139 let manifest_path = full_path.join(MANIFEST_FILENAME);
140
141 manifest.write_to_file(manifest_path)?;
142
143 let source_path = full_path.join(SOURCE_DIRECTORY);
145
146 std::fs::create_dir(&source_path)
147 .map_err(|e| PackageError::failed_to_create_source_directory(source_path.display(), e))?;
148
149 let main_path = source_path.join(MAIN_FILENAME);
151
152 let name_no_aleo = package_name.strip_suffix(".aleo").unwrap();
153
154 std::fs::write(&main_path, main_template(name_no_aleo))
155 .map_err(|e| UtilError::util_file_io_error(format_args!("Failed to write `{}`", main_path.display()), e))?;
156
157 let tests_path = full_path.join(TESTS_DIRECTORY);
159
160 std::fs::create_dir(&tests_path)
161 .map_err(|e| PackageError::failed_to_create_source_directory(tests_path.display(), e))?;
162
163 let test_file_path = tests_path.join(format!("test_{name_no_aleo}.leo"));
164 std::fs::write(&test_file_path, test_template(name_no_aleo))
165 .map_err(|e| UtilError::util_file_io_error(format_args!("Failed to write `{}`", main_path.display()), e))?;
166
167 Ok(full_path)
168 }
169
170 pub fn from_directory_no_graph<P: AsRef<Path>, Q: AsRef<Path>>(path: P, home_path: Q) -> Result<Self> {
174 Self::from_directory_impl(
175 path.as_ref(),
176 home_path.as_ref(),
177 false,
178 false,
179 false,
180 )
181 }
182
183 pub fn from_directory<P: AsRef<Path>, Q: AsRef<Path>>(path: P, home_path: Q, no_cache: bool) -> Result<Self> {
186 Self::from_directory_impl(
187 path.as_ref(),
188 home_path.as_ref(),
189 true,
190 false,
191 no_cache,
192 )
193 }
194
195 pub fn from_directory_with_tests<P: AsRef<Path>, Q: AsRef<Path>>(
198 path: P,
199 home_path: Q,
200 no_cache: bool,
201 ) -> Result<Self> {
202 Self::from_directory_impl(
203 path.as_ref(),
204 home_path.as_ref(),
205 true,
206 true,
207 no_cache,
208 )
209 }
210
211 pub fn test_files(&self) -> impl Iterator<Item = PathBuf> {
212 let path = self.tests_directory();
213 let data: Vec<PathBuf> = Self::files_with_extension(&path, "leo").collect();
216 data.into_iter()
217 }
218
219 pub fn import_files(&self) -> impl Iterator<Item = PathBuf> {
220 let path = self.imports_directory();
221 let data: Vec<PathBuf> = Self::files_with_extension(&path, "aleo").collect();
224 data.into_iter()
225 }
226
227 fn files_with_extension(path: &Path, extension: &'static str) -> impl Iterator<Item = PathBuf> {
228 path.read_dir()
229 .ok()
230 .into_iter()
231 .flatten()
232 .flat_map(|maybe_filename| maybe_filename.ok())
233 .filter(|entry| entry.file_type().ok().map(|filetype| filetype.is_file()).unwrap_or(false))
234 .flat_map(move |entry| {
235 let path = entry.path();
236 if path.extension().is_some_and(|e| e == extension) { Some(path) } else { None }
237 })
238 }
239
240 fn from_directory_impl(
241 path: &Path,
242 home_path: &Path,
243 build_graph: bool,
244 with_tests: bool,
245 no_cache: bool,
246 ) -> Result<Self> {
247 let map_err = |path: &Path, err| {
248 UtilError::util_file_io_error(format_args!("Trying to find path at {}", path.display()), err)
249 };
250
251 let path = path.canonicalize().map_err(|err| map_err(path, err))?;
252
253 let env = Env::read_from_file_or_environment(&path)?;
254
255 let manifest = Manifest::read_from_file(path.join(MANIFEST_FILENAME))?;
256
257 let programs: Vec<Program> = if build_graph {
258 let home_path = home_path.canonicalize().map_err(|err| map_err(home_path, err))?;
259
260 let mut map: IndexMap<Symbol, (Dependency, Program)> = IndexMap::new();
261
262 let mut digraph = DiGraph::<Symbol>::new(Default::default());
263
264 let first_dependency =
265 Dependency { name: manifest.program.clone(), location: Location::Local, path: Some(path.clone()) };
266
267 let test_dependencies: Vec<Dependency> = if with_tests {
268 let tests_directory = path.join(TESTS_DIRECTORY);
269 let mut test_dependencies: Vec<Dependency> = Self::files_with_extension(&tests_directory, "leo")
270 .map(|path| Dependency {
271 name: format!("{}.aleo", crate::filename_no_leo_extension(&path).unwrap()),
273 location: Location::Test,
274 path: Some(path.to_path_buf()),
275 })
276 .collect();
277 if let Some(deps) = manifest.dev_dependencies.as_ref() {
278 test_dependencies.extend(deps.iter().cloned());
279 }
280 test_dependencies
281 } else {
282 Vec::new()
283 };
284
285 for dependency in test_dependencies.into_iter().chain(std::iter::once(first_dependency.clone())) {
286 Self::graph_build(
287 &home_path,
288 env.network,
289 &env.endpoint,
290 &first_dependency,
291 dependency,
292 &mut map,
293 &mut digraph,
294 no_cache,
295 )?;
296 }
297
298 let ordered_dependency_symbols =
299 digraph.post_order().map_err(|_| UtilError::circular_dependency_error())?;
300
301 ordered_dependency_symbols.into_iter().map(|symbol| map.swap_remove(&symbol).unwrap().1).collect()
302 } else {
303 Vec::new()
304 };
305
306 Ok(Package { base_directory: path, programs, env, manifest })
307 }
308
309 #[allow(clippy::too_many_arguments)]
310 fn graph_build(
311 home_path: &Path,
312 network: NetworkName,
313 endpoint: &str,
314 main_program: &Dependency,
315 new: Dependency,
316 map: &mut IndexMap<Symbol, (Dependency, Program)>,
317 graph: &mut DiGraph<Symbol>,
318 no_cache: bool,
319 ) -> Result<()> {
320 let name_symbol = crate::symbol(&new.name)?;
321
322 let program = match map.entry(name_symbol) {
323 Entry::Occupied(occupied) => {
324 let existing_dep = &occupied.get().0;
327 assert_eq!(new.name, existing_dep.name);
328 if new.location != existing_dep.location || new.path != existing_dep.path {
329 return Err(PackageError::conflicting_dependency(format_args!("{name_symbol}.aleo")).into());
330 }
331 return Ok(());
332 }
333 Entry::Vacant(vacant) => {
334 let program = match (new.path.as_ref(), new.location) {
335 (Some(path), Location::Local) => {
336 Program::from_path(name_symbol, path.clone())?
338 }
339 (Some(path), Location::Test) => {
340 Program::from_path_test(path, main_program.clone())?
343 }
344 (_, Location::Network) => {
345 Program::fetch(name_symbol, home_path, network, endpoint, no_cache)?
347 }
348 _ => return Err(anyhow!("Invalid dependency data for {} (path must be given).", new.name).into()),
349 };
350
351 vacant.insert((new, program.clone()));
352
353 program
354 }
355 };
356
357 graph.add_node(name_symbol);
358
359 for dependency in program.dependencies.iter() {
360 let dependency_symbol = crate::symbol(&dependency.name)?;
361 graph.add_edge(name_symbol, dependency_symbol);
362 Self::graph_build(home_path, network, endpoint, main_program, dependency.clone(), map, graph, no_cache)?;
363 }
364
365 Ok(())
366 }
367
368 #[allow(clippy::type_complexity)]
371 pub fn get_programs_and_manifests<P: AsRef<Path>>(
372 &self,
373 home_path: P,
374 ) -> Result<Vec<(String, String, Option<Manifest>)>> {
375 self.get_programs_and_manifests_impl(home_path.as_ref())
376 }
377
378 #[allow(clippy::type_complexity)]
379 fn get_programs_and_manifests_impl(&self, home_path: &Path) -> Result<Vec<(String, String, Option<Manifest>)>> {
380 self.programs
381 .iter()
382 .map(|program| {
383 match &program.data {
384 ProgramData::Bytecode(bytecode) => Ok((program.name.to_string(), bytecode.clone(), None)),
385 ProgramData::SourcePath(path) => {
386 let bytecode_path = if path.as_path() == self.source_directory().join("main.leo") {
388 self.build_directory().join("main.aleo")
389 } else {
390 self.imports_directory().join(format!("{}.aleo", program.name))
391 };
392 let bytecode = std::fs::read_to_string(&bytecode_path)
394 .map_err(|e| PackageError::failed_to_read_file(bytecode_path.display(), e))?;
395 let mut path = path.clone();
397 path.pop();
398 path.pop();
399 let package = Package::from_directory_no_graph(&path, home_path)?;
400 Ok((program.name.to_string(), bytecode, Some(package.manifest.clone())))
402 }
403 }
404 })
405 .collect::<Result<Vec<_>>>()
406 }
407}
408
409fn main_template(name: &str) -> String {
410 format!(
411 r#"// The '{name}' program.
412program {name}.aleo {{
413 transition main(public a: u32, b: u32) -> u32 {{
414 let c: u32 = a + b;
415 return c;
416 }}
417}}
418"#
419 )
420}
421
422fn test_template(name: &str) -> String {
423 format!(
424 r#"// The 'test_{name}' test program.
425import {name}.aleo;
426program test_{name}.aleo {{
427 @test
428 script test_it() {{
429 let result: u32 = {name}.aleo/main(1u32, 2u32);
430 assert_eq(result, 3u32);
431 }}
432
433 @test
434 @should_fail
435 transition do_nothing() {{
436 let result: u32 = {name}.aleo/main(2u32, 3u32);
437 assert_eq(result, 3u32);
438 }}
439}}
440"#
441 )
442}