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