leo_lang/cli/commands/common/
util.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use leo_package::{Package, ProgramData};
18use leo_span::Symbol;
19
20use indexmap::IndexSet;
21use std::path::PathBuf;
22use walkdir::WalkDir;
23
24/// Collects paths to Leo source files for each program in the package.
25///
26/// For each non-test program, it searches the `src` directory for `.leo` files.
27/// It separates the `main.leo` file from the rest and returns a tuple:
28/// (`main.leo` path, list of other `.leo` file paths).
29/// Test programs are included with an empty list of additional files.
30/// Programs with bytecode data are ignored.
31///
32/// # Arguments
33/// * `package` - Reference to the package containing programs.
34///
35/// # Returns
36/// A vector of tuples with the main file and other source files.
37pub fn collect_leo_paths(package: &Package) -> Vec<(PathBuf, Vec<PathBuf>)> {
38    let mut partitioned_leo_paths = Vec::new();
39    for program in &package.programs {
40        match &program.data {
41            ProgramData::SourcePath { directory, source } => {
42                if program.is_test {
43                    partitioned_leo_paths.push((source.clone(), vec![]));
44                } else {
45                    let src_dir = directory.join("src");
46                    if !src_dir.exists() {
47                        continue;
48                    }
49
50                    let mut all_files: Vec<PathBuf> = WalkDir::new(&src_dir)
51                        .into_iter()
52                        .filter_map(Result::ok)
53                        .filter(|entry| entry.path().extension().and_then(|s| s.to_str()) == Some("leo"))
54                        .map(|entry| entry.into_path())
55                        .collect();
56
57                    if let Some(index) =
58                        all_files.iter().position(|p| p.file_name().and_then(|s| s.to_str()) == Some("main.leo"))
59                    {
60                        let main = all_files.remove(index);
61                        partitioned_leo_paths.push((main, all_files));
62                    }
63                }
64            }
65            ProgramData::Bytecode(..) => {}
66        }
67    }
68    partitioned_leo_paths
69}
70
71/// Collects paths to `.aleo` files that are external (non-local) dependencies.
72///
73/// Scans the package's `imports` directory and filters out files that match
74/// the names of local source-based dependencies.
75/// Only retains `.aleo` files corresponding to true external dependencies.
76///
77/// # Arguments
78/// * `package` - Reference to the package whose imports are being examined.
79///
80/// # Returns
81/// A vector of paths to `.aleo` files not associated with local source dependencies.
82pub fn collect_aleo_paths(package: &Package) -> Vec<PathBuf> {
83    let local_dependency_symbols: IndexSet<Symbol> = package
84        .programs
85        .iter()
86        .flat_map(|program| match &program.data {
87            ProgramData::SourcePath { .. } => {
88                // It's a local Leo dependency.
89                Some(program.name)
90            }
91            ProgramData::Bytecode(..) => {
92                // It's a network dependency or local .aleo dependency.
93                None
94            }
95        })
96        .collect();
97
98    package
99        .imports_directory()
100        .read_dir()
101        .ok()
102        .into_iter()
103        .flatten()
104        .flat_map(|maybe_filename| maybe_filename.ok())
105        .filter(|entry| entry.file_type().ok().map(|filetype| filetype.is_file()).unwrap_or(false))
106        .flat_map(|entry| {
107            let path = entry.path();
108            if let Some(filename) = leo_package::filename_no_aleo_extension(&path) {
109                let symbol = Symbol::intern(filename);
110                if local_dependency_symbols.contains(&symbol) { None } else { Some(path) }
111            } else {
112                None
113            }
114        })
115        .collect()
116}