1use alloc::{borrow::Cow, format, sync::Arc, vec::Vec};
2#[cfg(feature = "std")]
3use alloc::{boxed::Box, string::ToString};
4
5pub use miden_assembly_syntax::{
6 Library as CompiledLibrary, PathBuf as LibraryPath, PathComponent as LibraryPathComponent,
7};
8#[cfg(feature = "std")]
9use miden_core::serde::Deserializable;
10use miden_core_lib::CoreLibrary;
11#[cfg(feature = "std")]
12use miden_mast_package::Package;
13use miden_project::Linkage;
14use midenc_hir_symbol::sync::LazyLock;
15
16#[cfg(feature = "std")]
17use crate::{Options, Path, diagnostics::IntoDiagnostic};
18use crate::{PathBuf, diagnostics::Report};
19
20pub static STDLIB: LazyLock<Arc<CompiledLibrary>> =
21 LazyLock::new(|| Arc::new(CoreLibrary::default().into()));
22
23#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct LinkLibrary {
26 pub name: Cow<'static, str>,
33 pub path: Option<PathBuf>,
35 pub linkage: Linkage,
37}
38
39impl LinkLibrary {
40 pub fn is_core(&self) -> bool {
41 matches!(self.name.as_ref(), "miden-core" | "core" | "std")
42 }
43
44 pub fn is_protocol(&self) -> bool {
45 matches!(self.name.as_ref(), "miden-protocol" | "protocol" | "base")
46 }
47
48 pub fn core() -> Self {
50 LinkLibrary {
51 name: "miden-core".into(),
52 path: None,
53 linkage: Linkage::Dynamic,
54 }
55 }
56
57 pub fn protocol() -> Self {
59 LinkLibrary {
60 name: "miden-protocol".into(),
61 path: None,
62 linkage: Linkage::Dynamic,
63 }
64 }
65
66 #[cfg(not(feature = "std"))]
67 pub fn load(&self, _options: &Options) -> Result<CompiledLibrary, Report> {
68 match self.name.as_ref() {
70 "std" | "core" => {
71 let lib = (*STDLIB).as_ref().clone();
72 Ok(Package::from_library(
73 "miden-core".into(),
74 Version::new(0, 22, 3),
75 miden_project::TargetType::Library,
76 Arc::new(lib),
77 None,
78 )
79 .into())
80 }
81 "base" | "protocol" | "miden-protocol" => {
82 let lib = miden_protocol::ProtocolLib::default().as_ref().clone();
83 return Ok(Package::from_library(
84 "miden-protocol".into(),
85 Version::new(0, 14, 0),
86 miden_project::TargetType::Library,
87 Arc::new(lib),
88 Some(Dependency {
89 name: "miden-core".into(),
90 kind: miden_project::TargetType::Library,
91 version: Version::new(0, 22, 3),
92 digest: *(*STDLIB).digest(),
93 }),
94 )
95 .into());
96 }
97 name => Err(Report::msg(format!(
98 "link library '{name}' cannot be loaded: compiler was built without standard \
99 library"
100 ))),
101 }
102 }
103
104 #[cfg(feature = "std")]
105 pub fn load(&self, options: &Options) -> Result<Arc<Package>, Report> {
106 use miden_mast_package::{Dependency, Version};
107
108 if let Some(path) = self.path.as_deref() {
109 return self.load_from_path(path, options);
110 }
111
112 match self.name.as_ref() {
114 "std" | "core" | "miden-core" => {
115 let lib = (*STDLIB).as_ref().clone();
116 return Ok(Package::from_library(
117 "miden-core".into(),
118 Version::new(0, 22, 3),
119 miden_project::TargetType::Library,
120 Arc::new(lib),
121 None,
122 )
123 .into());
124 }
125 "base" | "protocol" | "miden-protocol" => {
126 let lib = miden_protocol::ProtocolLib::default().as_ref().clone();
127 return Ok(Package::from_library(
128 "miden-protocol".into(),
129 Version::new(0, 14, 0),
130 miden_project::TargetType::Library,
131 Arc::new(lib),
132 Some(Dependency {
133 name: "miden-core".into(),
134 kind: miden_project::TargetType::Library,
135 version: Version::new(0, 22, 3),
136 digest: *(*STDLIB).digest(),
137 }),
138 )
139 .into());
140 }
141 _ => (),
142 }
143
144 let path = self.find(options)?;
146
147 self.load_from_path(&path, options)
148 }
149
150 #[cfg(feature = "std")]
151 fn load_from_path(&self, path: &Path, _options: &Options) -> Result<Arc<Package>, Report> {
152 let package = load_package_from_path(path)?;
153 if package.is_program() {
154 return Err(Report::msg(format!(
155 "Expected Miden package to contain a Library, got Program: '{}'",
156 path.display()
157 )));
158 }
159
160 Ok(package)
161 }
162
163 #[cfg(feature = "std")]
164 fn find(&self, options: &Options) -> Result<PathBuf, Report> {
165 use std::fs;
166
167 for search_path in options.search_paths.iter() {
168 let reader = fs::read_dir(search_path).map_err(|err| {
169 Report::msg(format!(
170 "invalid library search path '{}': {err}",
171 search_path.display()
172 ))
173 })?;
174 for entry in reader {
175 let Ok(entry) = entry else {
176 continue;
177 };
178 let path = entry.path();
179 let Some(stem) = path.file_stem().and_then(|stem| stem.to_str()) else {
180 continue;
181 };
182 if stem != self.name.as_ref() {
183 continue;
184 }
185
186 if !path.is_file() {
187 return Err(Report::msg(format!(
188 "unable to load Miden Assembly package from '{}': not a file",
189 path.display()
190 )));
191 }
192 return Ok(path);
193 }
194 }
195
196 Err(Report::msg(format!(
197 "unable to locate library '{}' using any of the provided search paths",
198 &self.name
199 )))
200 }
201}
202
203#[cfg(feature = "std")]
204pub(crate) fn load_package_from_path(path: &Path) -> Result<Arc<Package>, Report> {
205 let bytes = std::fs::read(path).into_diagnostic()?;
206 miden_mast_package::Package::read_from_bytes(&bytes)
207 .map_err(|e| {
208 Report::msg(format!("failed to load Miden package from {}: {e}", path.display()))
209 })
210 .map(Arc::new)
211}
212
213#[cfg(feature = "std")]
214impl clap::builder::ValueParserFactory for LinkLibrary {
215 type Parser = LinkLibraryParser;
216
217 fn value_parser() -> Self::Parser {
218 LinkLibraryParser
219 }
220}
221
222#[cfg(feature = "std")]
223#[doc(hidden)]
224#[derive(Clone)]
225pub struct LinkLibraryParser;
226
227#[cfg(feature = "std")]
228impl clap::builder::TypedValueParser for LinkLibraryParser {
229 type Value = LinkLibrary;
230
231 fn possible_values(
232 &self,
233 ) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
234 use clap::builder::PossibleValue;
235
236 Some(Box::new(
237 [PossibleValue::new("masp").help("A compiled Miden package")].into_iter(),
238 ))
239 }
240
241 fn parse_ref(
249 &self,
250 _cmd: &clap::Command,
251 _arg: Option<&clap::Arg>,
252 value: &std::ffi::OsStr,
253 ) -> Result<Self::Value, clap::error::Error> {
254 use clap::error::{Error, ErrorKind};
255
256 let value = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
257 let (kind, name) = value
258 .split_once('=')
259 .map(|(kind, name)| (Some(kind), name))
260 .unwrap_or((None, value));
261
262 let linkage = match kind {
263 Some(kind) => match kind.split_once(':') {
264 Some(("masp", "static")) => Linkage::Static,
265 Some(("masp", "dynamic")) => Linkage::Dynamic,
266 Some(("masp", other)) => {
267 return Err(Error::raw(
268 ErrorKind::ValueValidation,
269 format!("unrecognized linkage modifier '{other}'"),
270 ));
271 }
272 None if kind == "masp" => Linkage::Dynamic,
273 Some(_) | None => {
274 return Err(Error::raw(
275 ErrorKind::ValueValidation,
276 "invalid link library kind: supported values are 'masp'",
277 ));
278 }
279 },
280 None => Linkage::Dynamic,
281 };
282
283 if name.is_empty() {
284 return Err(Error::raw(
285 ErrorKind::ValueValidation,
286 "invalid link library: must specify a name or path",
287 ));
288 }
289
290 let maybe_path = Path::new(name);
291 let extension = maybe_path.extension().map(|ext| ext.to_str().unwrap());
292
293 if maybe_path.is_absolute() {
294 let meta = maybe_path.metadata().map_err(|err| {
295 Error::raw(
296 ErrorKind::ValueValidation,
297 format!(
298 "invalid link library: unable to load '{}': {err}",
299 maybe_path.display()
300 ),
301 )
302 })?;
303
304 if !meta.is_file() {
305 return Err(Error::raw(
306 ErrorKind::ValueValidation,
307 format!("invalid link library: '{}' is not a file", maybe_path.display()),
308 ));
309 }
310
311 let name = maybe_path.file_stem().unwrap().to_str().unwrap().to_string();
312
313 Ok(LinkLibrary {
314 name: name.into(),
315 path: Some(maybe_path.to_path_buf()),
316 linkage,
317 })
318 } else if extension.is_some() {
319 let name = name.strip_suffix(unsafe { extension.unwrap_unchecked() }).unwrap();
320 let mut name = name.to_string();
321 name.pop();
322
323 Ok(LinkLibrary {
324 name: name.into(),
325 path: None,
326 linkage,
327 })
328 } else {
329 Ok(LinkLibrary {
330 name: name.to_string().into(),
331 path: None,
332 linkage,
333 })
334 }
335 }
336}
337
338pub fn add_target_link_libraries(link_libraries: &mut Vec<LinkLibrary>, requires_protocol: bool) {
341 if !link_libraries.iter().any(LinkLibrary::is_core) {
342 link_libraries.push(LinkLibrary::core());
343 }
344 if requires_protocol && !link_libraries.iter().any(LinkLibrary::is_protocol) {
345 link_libraries.push(LinkLibrary::protocol());
346 }
347}