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