foundry_cli/opts/build/
utils.rs1use eyre::Result;
2use foundry_compilers::{
3 CompilerInput, Graph, Project, ProjectCompileOutput, ProjectPathsConfig,
4 artifacts::{Source, Sources},
5 multi::{MultiCompilerLanguage, MultiCompilerParser},
6 solc::{SOLC_EXTENSIONS, SolcLanguage, SolcVersionedInput},
7};
8use foundry_config::Config;
9use rayon::prelude::*;
10use solar::{interface::MIN_SOLIDITY_VERSION, sema::ParsingContext};
11use std::{
12 collections::{HashSet, VecDeque},
13 path::{Path, PathBuf},
14};
15
16pub fn configure_pcx(
24 pcx: &mut ParsingContext<'_>,
25 config: &Config,
26 project: Option<&Project>,
27 target_paths: Option<&[PathBuf]>,
28) -> Result<()> {
29 let project = match project {
31 Some(project) => project,
32 None => &config.ephemeral_project()?,
33 };
34
35 let sources = match target_paths {
36 Some(targets) => {
38 let mut sources = Sources::new();
39 for t in targets {
40 let path = dunce::canonicalize(t)?;
41 let source = Source::read(&path)?;
42 sources.insert(path, source);
43 }
44 sources
45 }
46 None => project.paths.read_input_files()?,
48 };
49
50 let graph = Graph::<MultiCompilerParser>::resolve_sources(&project.paths, sources)?;
52 let (version, sources) = graph
53 .into_sources_by_version(project)?
55 .sources
56 .into_iter()
57 .find(|(lang, _)| *lang == MultiCompilerLanguage::Solc(SolcLanguage::Solidity))
59 .ok_or_else(|| eyre::eyre!("no Solidity sources"))?
60 .1
61 .into_iter()
62 .filter(|(v, _, _)| v >= &MIN_SOLIDITY_VERSION)
64 .max_by(|(v1, _, _), (v2, _, _)| v1.cmp(v2))
66 .map_or((MIN_SOLIDITY_VERSION, Sources::default()), |(v, s, _)| (v, s));
67
68 if sources.is_empty() {
69 sh_warn!("no files found. Solar doesn't support Solidity versions prior to 0.8.0")?;
70 }
71
72 let solc = SolcVersionedInput::build(
73 sources,
74 config.solc_settings()?,
75 SolcLanguage::Solidity,
76 version,
77 );
78
79 configure_pcx_from_solc(pcx, &project.paths, &solc, true);
80
81 Ok(())
82}
83
84pub fn get_solar_sources_from_compile_output(
91 config: &Config,
92 output: &ProjectCompileOutput,
93 target_paths: Option<&[PathBuf]>,
94 ignored_paths: Option<&[PathBuf]>,
95) -> Result<SolcVersionedInput> {
96 let is_solidity_file = |path: &Path| -> bool {
97 path.extension().and_then(|s| s.to_str()).is_some_and(|ext| SOLC_EXTENSIONS.contains(&ext))
98 };
99
100 let is_ignored = |path: &Path| -> bool {
101 if let Some(ignored) = ignored_paths {
102 ignored.iter().any(|ignored_path| path == ignored_path)
103 } else {
104 false
105 }
106 };
107
108 let mut source_paths: HashSet<PathBuf> = if let Some(targets) = target_paths
110 && !targets.is_empty()
111 {
112 let mut source_paths = HashSet::new();
113 let mut queue: VecDeque<PathBuf> = targets
114 .iter()
115 .filter_map(|path| {
116 is_solidity_file(path).then(|| dunce::canonicalize(path).ok()).flatten()
117 })
118 .collect();
119
120 while let Some(path) = queue.pop_front() {
121 if source_paths.insert(path.clone()) {
122 for import in output.graph().imports(path.as_path()) {
123 if !is_ignored(import) {
125 queue.push_back(import.to_path_buf());
126 }
127 }
128 }
129 }
130
131 source_paths
132 } else {
133 output
134 .graph()
135 .source_files()
136 .filter_map(|idx| {
137 let path = output.graph().node_path(idx).to_path_buf();
138 is_solidity_file(&path).then_some(path)
139 })
140 .collect()
141 };
142
143 let (version, sources) = {
145 let (mut max_version, mut sources) = (MIN_SOLIDITY_VERSION, Sources::new());
146 for (id, _) in output.artifact_ids() {
147 if let Ok(path) = dunce::canonicalize(&id.source)
148 && source_paths.remove(&path)
149 {
150 if id.version < MIN_SOLIDITY_VERSION {
151 continue;
152 } else if max_version < id.version {
153 max_version = id.version;
154 };
155
156 let source = Source::read(&path)?;
157 sources.insert(path, source);
158 }
159 }
160
161 (max_version, sources)
162 };
163
164 let solc = SolcVersionedInput::build(
165 sources,
166 config.solc_settings()?,
167 SolcLanguage::Solidity,
168 version,
169 );
170
171 Ok(solc)
172}
173
174pub fn configure_pcx_from_compile_output(
176 pcx: &mut ParsingContext<'_>,
177 config: &Config,
178 output: &ProjectCompileOutput,
179 target_paths: Option<&[PathBuf]>,
180) -> Result<()> {
181 let solc = get_solar_sources_from_compile_output(config, output, target_paths, None)?;
182 configure_pcx_from_solc(pcx, &config.project_paths(), &solc, true);
183 Ok(())
184}
185
186pub fn configure_pcx_from_solc(
191 pcx: &mut ParsingContext<'_>,
192 project_paths: &ProjectPathsConfig,
193 vinput: &SolcVersionedInput,
194 add_source_files: bool,
195) {
196 configure_pcx_from_solc_cli(pcx, project_paths, &vinput.cli_settings);
197 if add_source_files {
198 let sources = vinput
199 .input
200 .sources
201 .par_iter()
202 .filter_map(|(path, source)| {
203 pcx.sess.source_map().new_source_file(path.clone(), source.content.as_str()).ok()
204 })
205 .collect::<Vec<_>>();
206 pcx.add_files(sources);
207 }
208}
209
210fn configure_pcx_from_solc_cli(
211 pcx: &mut ParsingContext<'_>,
212 project_paths: &ProjectPathsConfig,
213 cli_settings: &foundry_compilers::solc::CliSettings,
214) {
215 pcx.file_resolver
216 .set_current_dir(cli_settings.base_path.as_ref().unwrap_or(&project_paths.root));
217 for remapping in &project_paths.remappings {
218 pcx.file_resolver.add_import_remapping(solar::sema::interface::config::ImportRemapping {
219 context: remapping.context.clone().unwrap_or_default(),
220 prefix: remapping.name.clone(),
221 path: remapping.path.clone(),
222 });
223 }
224 pcx.file_resolver.add_include_paths(cli_settings.include_paths.iter().cloned());
225}