1use anyhow::{Context, anyhow};
2use camino::{Utf8Path, Utf8PathBuf};
3use camino_tempfile::Utf8TempDir;
4use heck::{ToLowerCamelCase, ToSnakeCase};
5use include_dir::{Dir, include_dir};
6use lazy_static::lazy_static;
7use log::debug;
8use log::info;
9use serde::Deserialize;
10use std::collections::{BTreeMap, HashSet};
11use std::fmt::Display;
12use std::ops::Range;
13use std::sync::atomic::{AtomicBool, Ordering};
14use topologic::AcyclicDependencyGraph;
15use wit_component::{ComponentEncoder, StringEncoding};
16use wit_parser::{PackageId, PackageName, Resolve, WorldId};
17
18mod moonc_wasm;
19
20#[cfg(feature = "get-script")]
22pub mod get_script;
23
24#[cfg(feature = "typed-config")]
26pub mod typed_config;
27
28static MOONBIT_CORE: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/bundled-core");
29
30#[derive(Default)]
31struct MoonC {
32 initialized: AtomicBool,
33}
34
35impl MoonC {
36 pub fn run(&self, mut args: Vec<String>) -> anyhow::Result<()> {
37 self.ensure_initialized()?;
38 debug!("Running the MoonBit compiler with args: {}", args.join(" "));
39 args.insert(0, "moonc".to_string());
40 moonc_wasm::run_wasmoo(args).context("Running the MoonBit compiler")?;
41 Ok(())
42 }
43
44 fn ensure_initialized(&self) -> anyhow::Result<()> {
45 if !self.initialized.load(Ordering::Acquire) {
46 debug!("Initializing V8...");
47 moonc_wasm::initialize_v8()?;
48 self.initialized.store(true, Ordering::Release);
49 }
50 Ok(())
51 }
52}
53
54lazy_static! {
55 static ref MOONC: MoonC = MoonC::default();
56}
57
58pub struct MoonBitComponent {
59 dir: Utf8PathBuf,
60 temp: Option<Utf8TempDir>,
61 packages: BTreeMap<String, MoonBitPackage>,
62 resolve: Option<Resolve>,
63 world_id: Option<WorldId>,
64 root_package_id: Option<PackageId>,
65}
66
67impl MoonBitComponent {
68 pub fn empty_from_wit(
72 wit: impl AsRef<str>,
73 selected_world: Option<&str>,
74 ) -> anyhow::Result<Self> {
75 let temp_dir = Utf8TempDir::new().context("Creating temporary directory")?;
76 let dir = temp_dir.path().to_path_buf();
77
78 info!("Creating MoonBit component in temporary directory: {dir}");
79
80 let mut component = MoonBitComponent {
81 dir,
82 temp: Some(temp_dir),
83 packages: BTreeMap::new(),
84 resolve: None,
85 world_id: None,
86 root_package_id: None,
87 };
88
89 info!("Saving WIT package to {}/package.wit", component.wit_dir());
90 std::fs::create_dir_all(component.wit_dir()).context("Creating WIT package directory")?;
91 std::fs::write(
92 component.wit_dir().join("package.wit"),
93 wit.as_ref().as_bytes(),
94 )?;
95
96 info!("Resolving WIT package");
97 let mut resolve = Resolve::default();
98 let (root_package_id, _) = resolve
99 .push_dir(component.wit_dir())
100 .context("Resolving WIT packages")?;
101 let world_id = resolve
102 .select_world(root_package_id, selected_world)
103 .context("Selecting the WIT world")?;
104
105 info!("Generating MoonBit WIT bindings");
106 let mut wit_bindgen = wit_bindgen_moonbit::Opts {
107 gen_dir: "gen".to_string(),
108 derive_eq: true,
109 derive_show: true,
110 ..Default::default()
111 }
112 .build();
113 let mut bindgen_files = wit_bindgen_core::Files::default();
114 wit_bindgen
115 .generate(&resolve, world_id, &mut bindgen_files)
116 .context("Generating MoonBit WIT bindings")?;
117
118 for (name, contents) in bindgen_files.iter() {
119 let dst = if let Some(stripped_name) = name.strip_prefix('/') {
120 component.dir.join(stripped_name)
121 } else {
122 component.dir.join(name)
123 };
124 debug!("Writing binding file {dst}");
125
126 if let Some(parent) = dst.parent() {
127 std::fs::create_dir_all(parent)
128 .context("Creating directory for generated MoonBit WIT bindings")?;
129 }
130 std::fs::write(&dst, contents).context("Writing generated MoonBit WIT bindings")?;
131 }
132
133 component.extract_core()?;
134
135 component.resolve = Some(resolve);
136 component.world_id = Some(world_id);
137 component.root_package_id = Some(root_package_id);
138 Ok(component)
139 }
140
141 pub fn disable_cleanup(&mut self) {
143 if let Some(temp) = &mut self.temp {
144 temp.disable_cleanup(true);
145 }
146 }
147
148 pub fn existing(path: &Utf8Path, selected_world: Option<&str>) -> anyhow::Result<Self> {
152 let mut component = MoonBitComponent {
153 dir: path.to_path_buf(),
154 temp: None,
155 packages: BTreeMap::new(),
156 resolve: None,
157 world_id: None,
158 root_package_id: None,
159 };
160 component.extract_core()?;
161
162 info!("Resolving WIT package");
163 let mut resolve = Resolve::default();
164 let (root_package_id, _) = resolve
165 .push_dir(component.wit_dir())
166 .context("Resolving WIT package")?;
167 let world_id = resolve
168 .select_world(root_package_id, selected_world)
169 .context("Selecting WIT world")?;
170
171 component.resolve = Some(resolve);
172 component.world_id = Some(world_id);
173 component.root_package_id = Some(root_package_id);
174
175 Ok(component)
176 }
177
178 pub fn define_bindgen_packages(&mut self) -> anyhow::Result<()> {
180 let moonbit_root_package = self.moonbit_root_package()?;
181 let world_name = self.world_name()?;
182 let world_snake = world_name.to_snake_case();
183
184 let imported_interfaces = self.get_imported_interfaces()?;
185 let exported_interfaces = self.get_exported_interfaces()?;
186
187 debug!("Imported interfaces: {imported_interfaces:?}");
188 debug!("Exported interfaces: {exported_interfaces:?}");
189
190 let mut gen_dependencies = Vec::new();
191 let mut gen_mbt_files = Vec::new();
192
193 self.define_package(MoonBitPackage {
194 name: format!("{moonbit_root_package}/ffi"),
195 mbt_files: vec![Utf8Path::new("ffi").join("top.mbt")],
196 warning_control: vec![WarningControl::Disable(Warning::Specific(44))],
197 output: Utf8Path::new("target")
198 .join("wasm")
199 .join("release")
200 .join("build")
201 .join("ffi")
202 .join("ffi.core"),
203 dependencies: vec![],
204 package_sources: vec![(
205 format!("{moonbit_root_package}/ffi"),
206 Utf8Path::new("ffi").to_path_buf(),
207 )],
208 });
209 let ffi_dep = (
210 Utf8Path::new("target")
211 .join("wasm")
212 .join("release")
213 .join("build")
214 .join("ffi")
215 .join("ffi.mi"),
216 "ffi".to_string(),
217 );
218 gen_dependencies.push(ffi_dep.clone());
219
220 self.define_package(MoonBitPackage {
221 name: format!("{moonbit_root_package}/gen/world/{world_name}"),
222 mbt_files: vec![
223 Utf8Path::new("gen")
224 .join("world")
225 .join(&world_name)
226 .join("stub.mbt"),
227 ],
228 warning_control: vec![],
229 output: Utf8Path::new("target")
230 .join("wasm")
231 .join("release")
232 .join("build")
233 .join("gen")
234 .join("world")
235 .join(&world_name)
236 .join(format!("{world_name}.core")),
237
238 dependencies: vec![],
239 package_sources: vec![(
240 format!("{moonbit_root_package}/gen/world/{world_name}"),
241 Utf8Path::new("gen").join("world").join(&world_name),
242 )],
243 });
244 gen_dependencies.push((
245 Utf8Path::new("target")
246 .join("wasm")
247 .join("release")
248 .join("build")
249 .join("gen")
250 .join("world")
251 .join(&world_name)
252 .join(format!("{world_name}.mi")),
253 world_name.clone(),
254 ));
255 gen_mbt_files.push(Utf8Path::new("gen").join(format!("world_{world_snake}_export.mbt")));
256
257 for (package_name, interface_name) in &imported_interfaces {
258 let pkg_namespace = package_name.namespace.to_snake_case();
259 let pkg_name = package_name.name.to_snake_case();
260 let interface_name = interface_name.to_lower_camel_case();
261
262 let name = format!(
263 "{moonbit_root_package}/interface/{pkg_namespace}/{pkg_name}/{interface_name}"
264 );
265 let src = Utf8Path::new("interface")
266 .join(&pkg_namespace)
267 .join(&pkg_name)
268 .join(&interface_name);
269 let output = Utf8Path::new("target")
270 .join("wasm")
271 .join("release")
272 .join("build")
273 .join("interface")
274 .join(&pkg_namespace)
275 .join(&pkg_name)
276 .join(&interface_name);
277 self.define_package(MoonBitPackage {
278 name: name.clone(),
279 mbt_files: vec![src.join("top.mbt"), src.join("ffi.mbt")],
280 warning_control: vec![],
281 output: output.join(format!("{interface_name}.core")),
282 dependencies: vec![ffi_dep.clone()],
283 package_sources: vec![(name, src)],
284 });
285 }
286
287 for (package_name, interface_name) in &exported_interfaces {
288 let pkg_namespace = package_name.namespace.to_snake_case();
289 let pkg_name = package_name.name.to_snake_case();
290 let interface_name = interface_name.to_lower_camel_case();
291
292 let name = format!(
293 "{moonbit_root_package}/gen/interface/{pkg_namespace}/{pkg_name}/{interface_name}"
294 );
295 let src = Utf8Path::new("gen")
296 .join("interface")
297 .join(&pkg_namespace)
298 .join(&pkg_name)
299 .join(&interface_name);
300 let output = Utf8Path::new("target")
301 .join("wasm")
302 .join("release")
303 .join("build")
304 .join("gen")
305 .join("interface")
306 .join(&pkg_namespace)
307 .join(&pkg_name)
308 .join(&interface_name);
309 self.define_package(MoonBitPackage {
310 name: name.clone(),
311 mbt_files: vec![src.join("top.mbt"), src.join("stub.mbt")],
312 warning_control: vec![],
313 output: output.join(format!("{interface_name}.core")),
314
315 dependencies: vec![],
316 package_sources: vec![(name, src)],
317 });
318 gen_dependencies.push((
319 output.join(format!("{interface_name}.mi")),
320 interface_name.clone(),
321 ));
322 gen_mbt_files.push(Utf8Path::new("gen").join(format!(
323 "gen_interface_{pkg_namespace}_{pkg_name}_{interface_name}_export.mbt"
324 )));
325 }
326
327 gen_mbt_files.push(Utf8Path::new("gen").join("ffi.mbt"));
328 self.define_package(MoonBitPackage {
329 name: format!("{moonbit_root_package}/gen"),
330 mbt_files: gen_mbt_files,
331 warning_control: vec![],
332 output: Utf8Path::new("target")
333 .join("wasm")
334 .join("release")
335 .join("build")
336 .join("gen")
337 .join("gen.core"),
338 dependencies: gen_dependencies,
339 package_sources: vec![(
340 format!("{moonbit_root_package}/gen"),
341 Utf8Path::new("gen").to_path_buf(),
342 )],
343 });
344
345 Ok(())
346 }
347
348 pub fn set_warning_control(
349 &mut self,
350 package_name: &str,
351 warning_control: Vec<WarningControl>,
352 ) -> anyhow::Result<()> {
353 let package = self
354 .packages
355 .get_mut(package_name)
356 .ok_or_else(|| anyhow::anyhow!("Package '{package_name}' not found"))?;
357 package.warning_control = warning_control;
358 Ok(())
359 }
360
361 pub fn define_package(&mut self, package: MoonBitPackage) {
363 debug!("Adding MoonBit package: {}", package.name);
364 self.packages.insert(package.name.clone(), package);
365 }
366
367 pub fn add_dependency(
369 &mut self,
370 package_name: &str,
371 mi_path: &Utf8Path,
372 alias: &str,
373 ) -> anyhow::Result<()> {
374 debug!("Adding dependency: {package_name} ({mi_path}) as {alias}");
375 let package = self
376 .packages
377 .get_mut(package_name)
378 .ok_or_else(|| anyhow::anyhow!("Package '{package_name}' not found"))?;
379 package
380 .dependencies
381 .push((Utf8PathBuf::from(mi_path), alias.to_string()));
382 Ok(())
383 }
384
385 pub fn write_file(&self, relative_path: &Utf8Path, contents: &str) -> anyhow::Result<()> {
386 let path = self.dir.join(relative_path);
387 info!("Writing file: {path:?}");
388 if let Some(parent) = path.parent() {
389 std::fs::create_dir_all(parent).context("Creating directory for generated file")?;
390 }
391 std::fs::write(path, contents)?;
392 Ok(())
393 }
394
395 pub fn write_world_stub(&self, moonbit_source: &str) -> anyhow::Result<()> {
397 let world_name = self.world_name()?;
398 let path = self
399 .dir
400 .join("gen")
401 .join("world")
402 .join(world_name)
403 .join("stub.mbt");
404 info!("Writing world stub to {path}");
405 std::fs::write(path, moonbit_source)?;
406 Ok(())
407 }
408
409 pub fn write_interface_stub(
411 &self,
412 package_name: &PackageName,
413 interface_name: &str,
414 moonbit_source: &str,
415 ) -> anyhow::Result<()> {
416 let package_name_snake = package_name.name.to_snake_case();
417 let package_namespace_snake = package_name.namespace.to_snake_case();
418 let path = self
419 .dir
420 .join("gen")
421 .join("interface")
422 .join(package_namespace_snake)
423 .join(package_name_snake)
424 .join(interface_name.to_lower_camel_case())
425 .join("stub.mbt");
426 info!("Writing interface stub to {path}");
427 std::fs::create_dir_all(path.parent().unwrap())
428 .context("Creating directory for interface stub")?;
429 std::fs::write(path, moonbit_source).context("Writing interface stub")?;
430 Ok(())
431 }
432
433 pub fn write_interface_package_json(
435 &self,
436 package_name: &PackageName,
437 interface_name: &str,
438 json: serde_json::Value,
439 ) -> anyhow::Result<()> {
440 let package_name_snake = package_name.name.to_snake_case();
441 let package_namespace_snake = package_name.namespace.to_snake_case();
442 let path = self
443 .dir
444 .join("gen")
445 .join("interface")
446 .join(package_namespace_snake)
447 .join(package_name_snake)
448 .join(interface_name.to_lower_camel_case())
449 .join("moon.pkg.json");
450 info!("Writing interface definition to {path}");
451 std::fs::create_dir_all(path.parent().unwrap())
452 .context("Creating directory for interface definition")?;
453 std::fs::write(
454 path,
455 serde_json::to_string_pretty(&json).context("Writing interface definition")?,
456 )?;
457 Ok(())
458 }
459
460 pub fn build(&self, main_package_name: Option<&str>, target: &Utf8Path) -> anyhow::Result<()> {
465 let main_package_name = match main_package_name {
466 Some(name) => name.to_string(),
467 None => {
468 let root_package = self.moonbit_root_package()?;
469 format!("{root_package}/gen")
470 }
471 };
472
473 let sorted_packages = self.sorted_packages()?;
474 debug!(
475 "Package build order: {}",
476 sorted_packages
477 .iter()
478 .map(|p| p.name.clone())
479 .collect::<Vec<_>>()
480 .join(", ")
481 );
482
483 for package in &sorted_packages {
484 self.build_package(
485 &package.mbt_files,
486 &package.warning_control,
487 &package.output,
488 &package.name,
489 &package.dependencies,
490 &package.package_sources,
491 )
492 .context(format!("Building package {}", package.name))?;
493 }
494
495 let mut core_files = vec![
496 self.core_bundle_dir().join("abort").join("abort.core"),
497 self.core_bundle_dir().join("core.core"),
498 ];
499 let mut package_sources = BTreeMap::new();
500
501 for package in &sorted_packages {
502 core_files.push(package.output.clone());
503 for (name, source) in &package.package_sources {
504 package_sources.insert(name.clone(), source.clone());
505 }
506 }
507 package_sources.insert("moonbitlang/core".to_string(), self.core_dir());
508 let package_sources: Vec<(String, Utf8PathBuf)> = package_sources.into_iter().collect();
509
510 let main_package = self
511 .packages
512 .get(&main_package_name)
513 .ok_or_else(|| anyhow!(format!("Main package '{main_package_name}' not found")))?;
514 let (_, main_package_source) = main_package
515 .package_sources
516 .iter()
517 .find(|(name, _)| name == &main_package_name)
518 .ok_or_else(|| {
519 anyhow!(format!(
520 "Main package sources '{main_package_name}' not found"
521 ))
522 })?;
523
524 let main_package_json = self.dir.join(main_package_source).join("moon.pkg.json");
525 let linker_config = Self::extract_wasm_linker_config(&main_package_json)
526 .context("Extracting linker config")?;
527
528 self.link_core(
529 &core_files,
530 &main_package_name,
531 &main_package_json,
532 &package_sources,
533 &linker_config.export_memory_name,
534 &linker_config.exports,
535 linker_config.heap_start_address,
536 )
537 .context("Linking")?;
538
539 self.embed_wit().context("Embedding WIT")?;
540 self.create_component(target)
541 .context("Creating component")?;
542
543 Ok(())
544 }
545
546 fn extract_core(&self) -> anyhow::Result<()> {
547 let core_dir = self.core_dir();
548 info!("Extracting MoonBit core to {core_dir}");
549 std::fs::create_dir_all(&core_dir)?;
550 MOONBIT_CORE.extract(&core_dir)?;
551 Ok(())
552 }
553
554 fn build_package(
555 &self,
556 mbt_files: &[Utf8PathBuf],
557 warning_control: &[WarningControl],
558 output: &Utf8Path,
559 package: &str,
560 dependencies: &[(Utf8PathBuf, String)],
561 package_sources: &[(String, Utf8PathBuf)],
562 ) -> anyhow::Result<()> {
563 info!("Building MoonBit package: {package}");
564
565 let mut args = vec!["build-package".to_string()];
566 for file in mbt_files {
567 let full_path = self.dir.join(file);
568 args.push(full_path.to_string());
569 }
570 for w in warning_control {
571 args.push("-w".to_string());
572 args.push(w.to_string());
573 }
574 args.push("-o".to_string());
575 args.push(self.dir.join(output).to_string());
576 args.push("-pkg".to_string());
577 args.push(package.to_string());
578 args.push("-std-path".to_string());
579 args.push(self.core_bundle_dir().to_string());
580 for (dep_path, dep_name) in dependencies {
581 args.push("-i".to_string());
582 let full_path = self.dir.join(dep_path);
583 args.push(format!("{full_path}:{dep_name}"));
584 }
585 self.add_package_sources(&mut args, package_sources);
586 args.push("-target".to_string());
587 args.push("wasm".to_string());
588
589 MOONC.run(args)?;
590 Ok(())
591 }
592
593 #[allow(clippy::too_many_arguments)]
594 fn link_core(
595 &self,
596 core_files: &[Utf8PathBuf],
597 main_package_name: &str,
598 main_package_json: &Utf8Path,
599 package_sources: &[(String, Utf8PathBuf)],
600 exported_memory_name: &str,
601 exported_functions: &[String],
602 heap_start_address: usize,
603 ) -> anyhow::Result<()> {
604 info!("Linking MoonBit component");
605 let mut args = vec!["link-core".to_string()];
606
607 for file in core_files {
608 let full_path = self.dir.join(file);
609 args.push(full_path.to_string());
610 }
611 args.push("-main".to_string());
612 args.push(main_package_name.to_string());
613 args.push("-o".to_string());
614 args.push(self.module_wasm().to_string());
615 args.push("-pkg-config-path".to_string());
616 args.push(self.dir.join(main_package_json).to_string());
617 self.add_package_sources(&mut args, package_sources);
618 args.push("-pkg-sources".to_string());
619 args.push(format!("moonbitlang/core:{}", self.core_dir()));
620 args.push("-target".to_string());
621 args.push("wasm".to_string());
622 args.push(format!(
623 "-exported_functions={}",
624 exported_functions.join(",")
625 ));
626 args.push("-export-memory-name".to_string());
627 args.push(exported_memory_name.to_string());
628 args.push("-heap-start-address".to_string());
629 args.push(heap_start_address.to_string());
630
631 MOONC.run(args)?;
632 Ok(())
633 }
634
635 fn add_package_sources(
636 &self,
637 args: &mut Vec<String>,
638 package_sources: &[(String, Utf8PathBuf)],
639 ) {
640 for (source_name, source_path) in package_sources {
641 args.push("-pkg-sources".to_string());
642 let full_path = self.dir.join(source_path);
643 args.push(format!("{source_name}:{full_path}"));
644 }
645 }
646
647 fn embed_wit(&self) -> anyhow::Result<()> {
648 info!("Embedding WIT in the compiled MoonBit WASM module");
649
650 let resolve = self.resolve.as_ref().unwrap();
652 let world = &self.world_id.unwrap();
653
654 let module_wasm = self.module_wasm();
655 let mut wasm = std::fs::read(&module_wasm)
656 .context(format!("Failed to read module WASM from {module_wasm}"))?;
657
658 wit_component::embed_component_metadata(&mut wasm, resolve, *world, StringEncoding::UTF16)
659 .context("Embedding component metadata")?;
660
661 std::fs::write(self.module_with_embed_wasm(), wasm)
662 .context("Writing WASM with embedded metadata")?;
663
664 Ok(())
665 }
666
667 fn create_component(&self, target: &Utf8Path) -> anyhow::Result<()> {
668 info!("Creating the final WASM component at {target}");
669
670 let wasm = std::fs::read(self.module_with_embed_wasm())
671 .context("Reading WASM with embedded metadata")?;
672 let mut encoder = ComponentEncoder::default()
673 .validate(true)
674 .reject_legacy_names(false)
675 .merge_imports_based_on_semver(true)
676 .realloc_via_memory_grow(false)
677 .module(&wasm)?;
678
679 let component = encoder.encode().context("Encoding WASM component")?;
680
681 if let Some(parent) = target.parent() {
682 std::fs::create_dir_all(parent).context("Creating directory for WASM component")?;
683 }
684 std::fs::write(target, component).context("Writing WASM component")?;
685
686 Ok(())
687 }
688
689 fn sorted_packages(&self) -> anyhow::Result<Vec<&MoonBitPackage>> {
690 let mut graph = AcyclicDependencyGraph::new();
691 let root_package = self.moonbit_root_package()?;
692
693 for package in self.packages.values() {
694 for (path, dep) in &package.dependencies {
695 let path_components = path.components().map(|c| c.to_string()).collect::<Vec<_>>();
696 let full_dep = if path_components.starts_with(&[
697 "target".to_string(),
698 "wasm".to_string(),
699 "release".to_string(),
700 "build".to_string(),
701 ]) {
702 let relevant_path = &path_components[4..path_components.len() - 1];
703 format!("{}/{}", root_package, relevant_path.join("/"))
704 } else {
705 format!("{root_package}/{dep}")
706 };
707
708 graph.depend_on(package.name.clone(), full_dep)?;
709 }
710 }
711
712 let mut sorted = Vec::new();
713 let mut names = HashSet::new();
714 for layer in graph.get_forward_dependency_topological_layers() {
715 for package_name in layer {
716 sorted.push(&self.packages[&package_name]);
717 names.insert(package_name);
718 }
719 }
720
721 for package in self.packages.values() {
722 if !names.contains(&package.name) {
723 sorted.push(package);
724 }
725 }
726
727 Ok(sorted)
728 }
729
730 fn wit_dir(&self) -> Utf8PathBuf {
731 self.dir.join("wit")
732 }
733
734 fn core_dir(&self) -> Utf8PathBuf {
735 self.dir.join("core")
736 }
737
738 fn core_bundle_dir(&self) -> Utf8PathBuf {
739 self.dir
740 .join("core")
741 .join("target")
742 .join("wasm")
743 .join("release")
744 .join("bundle")
745 }
746
747 fn module_wasm(&self) -> Utf8PathBuf {
748 self.dir.join("target").join("module.wasm")
749 }
750
751 fn module_with_embed_wasm(&self) -> Utf8PathBuf {
752 self.dir.join("target").join("module.embed.wasm")
753 }
754
755 fn extract_wasm_linker_config(
756 package_json_path: &Utf8Path,
757 ) -> anyhow::Result<WasmLinkerConfig> {
758 debug!("Extracting Wasm linker config from {package_json_path}");
759 let json_str = std::fs::read_to_string(package_json_path)?;
760 let pkg: PackageJsonWithWasmLinkerConfig = serde_json::from_str(&json_str)?;
761
762 Ok(pkg.link.wasm)
763 }
764
765 pub fn moonbit_root_package(&self) -> anyhow::Result<String> {
766 Ok(format!(
767 "{}/{}",
768 self.root_pkg_namespace()?,
769 self.root_pkg_name()?
770 ))
771 }
772
773 pub fn root_pkg_namespace(&self) -> anyhow::Result<String> {
774 let root_package_id = self.root_package_id.as_ref().unwrap();
775 let resolve = self.resolve.as_ref().unwrap();
776
777 let root_package = resolve
778 .packages
779 .get(*root_package_id)
780 .ok_or_else(|| anyhow!("Root package not found"))?;
781 Ok(root_package.name.namespace.to_string())
782 }
783
784 pub fn root_pkg_name(&self) -> anyhow::Result<String> {
785 let root_package_id = self.root_package_id.as_ref().unwrap();
786 let resolve = self.resolve.as_ref().unwrap();
787
788 let root_package = resolve
789 .packages
790 .get(*root_package_id)
791 .ok_or_else(|| anyhow!("Root package not found"))?;
792 Ok(root_package.name.name.to_string())
793 }
794
795 fn world_name(&self) -> anyhow::Result<String> {
796 Ok(self
797 .resolve
798 .as_ref()
799 .and_then(|r| r.worlds.get(self.world_id?))
800 .map(|w| w.name.to_string())
801 .ok_or_else(|| anyhow::anyhow!("Could not find world"))?
802 .to_lower_camel_case())
803 }
804
805 fn get_imported_interfaces(&self) -> anyhow::Result<Vec<(PackageName, String)>> {
806 let world = self
807 .resolve
808 .as_ref()
809 .and_then(|r| r.worlds.get(self.world_id?))
810 .ok_or_else(|| anyhow::anyhow!("Could not find world"))?;
811 let mut imported_interfaces = Vec::new();
812 for (_, item) in &world.imports {
813 if let wit_parser::WorldItem::Interface { id, .. } = item {
814 if let Some(interface) = self.resolve.as_ref().and_then(|r| r.interfaces.get(*id)) {
815 if let Some(interface_name) = interface.name.as_ref() {
816 let owner_package = interface.package.ok_or_else(|| {
817 anyhow::anyhow!(
818 "Interface '{}' does not have a package",
819 interface_name
820 )
821 })?;
822 let package = self
823 .resolve
824 .as_ref()
825 .and_then(|r| r.packages.get(owner_package))
826 .ok_or_else(|| {
827 anyhow::anyhow!(
828 "Package for interface '{}' not found",
829 interface_name
830 )
831 })?;
832
833 imported_interfaces
834 .push((package.name.clone(), interface_name.to_string()));
835 } else {
836 return Err(anyhow::anyhow!(
837 "Anonymous imported interfaces are not supported"
838 ));
839 }
840 }
841 }
842 }
843 Ok(imported_interfaces)
844 }
845
846 fn get_exported_interfaces(&self) -> anyhow::Result<Vec<(PackageName, String)>> {
847 let world = self
848 .resolve
849 .as_ref()
850 .and_then(|r| r.worlds.get(self.world_id?))
851 .ok_or_else(|| anyhow::anyhow!("Could not find world"))?;
852 let mut exported_interfaces = Vec::new();
853 for (_, item) in &world.exports {
854 if let wit_parser::WorldItem::Interface { id, .. } = item {
855 if let Some(interface) = self.resolve.as_ref().and_then(|r| r.interfaces.get(*id)) {
856 if let Some(interface_name) = interface.name.as_ref() {
857 let owner_package = interface.package.ok_or_else(|| {
858 anyhow::anyhow!(
859 "Interface '{}' does not have a package",
860 interface_name
861 )
862 })?;
863 let package = self
864 .resolve
865 .as_ref()
866 .and_then(|r| r.packages.get(owner_package))
867 .ok_or_else(|| {
868 anyhow::anyhow!(
869 "Package for interface '{}' not found",
870 interface_name
871 )
872 })?;
873
874 exported_interfaces
875 .push((package.name.clone(), interface_name.to_string()));
876 } else {
877 return Err(anyhow::anyhow!(
878 "Anonymous exported interfaces are not supported"
879 ));
880 }
881 }
882 }
883 }
884 Ok(exported_interfaces)
885 }
886}
887
888#[derive(Debug, Deserialize)]
889struct PackageJsonWithWasmLinkerConfig {
890 link: LinkConfig,
891}
892
893#[derive(Debug, Deserialize)]
894struct LinkConfig {
895 wasm: WasmLinkerConfig,
896}
897
898#[derive(Debug, Deserialize)]
899#[serde(rename_all = "kebab-case")]
900struct WasmLinkerConfig {
901 export_memory_name: String,
902 exports: Vec<String>,
903 heap_start_address: usize,
904}
905
906pub struct MoonBitPackage {
907 pub name: String,
908 pub mbt_files: Vec<Utf8PathBuf>,
909 pub warning_control: Vec<WarningControl>,
910 pub output: Utf8PathBuf,
911 pub dependencies: Vec<(Utf8PathBuf, String)>,
912 pub package_sources: Vec<(String, Utf8PathBuf)>,
913}
914
915pub enum Warning {
916 Specific(u16),
917 Range(Range<u16>),
918}
919
920impl Display for Warning {
921 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
922 match self {
923 Warning::Specific(code) => write!(f, "{code}"),
924 Warning::Range(range) => write!(f, "{}..{}", range.start, range.end),
925 }
926 }
927}
928
929pub enum WarningControl {
930 Enable(Warning),
931 Disable(Warning),
932 EnableAsError(Warning),
933}
934
935impl Display for WarningControl {
936 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
937 match self {
938 WarningControl::Enable(code) => write!(f, "+{code}"),
939 WarningControl::Disable(code) => write!(f, "-{code}"),
940 WarningControl::EnableAsError(code) => write!(f, "@{code}"),
941 }
942 }
943}
944
945#[cfg(test)]
946test_r::enable!();
947
948#[cfg(test)]
949struct Trace;
950
951#[cfg(test)]
952#[test_r::test_dep]
953fn initialize_trace() -> Trace {
954 pretty_env_logger::formatted_builder()
955 .filter_level(log::LevelFilter::Debug)
956 .write_style(pretty_env_logger::env_logger::WriteStyle::Always)
957 .init();
958 Trace
959}