1use self::utils::normalize_atom_name;
4use super::CliCommand;
5use crate::{backend::RuntimeOptions, common::normalize_path, config::WasmerEnv};
6use anyhow::{Context, Result, anyhow, bail};
7use clap::Parser;
8use object::ObjectSection;
9use serde::{Deserialize, Serialize};
10use std::{
11 collections::BTreeMap,
12 env,
13 path::{Path, PathBuf},
14 process::{Command, Stdio},
15};
16use tar::Archive;
17use target_lexicon::BinaryFormat;
18use wasmer::{
19 sys::{engine::NativeEngineExt, *},
20 *,
21};
22use wasmer_compiler::{
23 object::{emit_serialized, get_object_for_target},
24 types::symbols::{ModuleMetadataSymbolRegistry, Symbol, SymbolRegistry},
25};
26use wasmer_package::utils::from_disk;
27use wasmer_types::ModuleInfo;
28use webc::{Container, Metadata, PathSegments, Volume as WebcVolume};
29
30const LINK_SYSTEM_LIBRARIES_WINDOWS: &[&str] = &["userenv", "Ws2_32", "advapi32", "bcrypt"];
31
32const LINK_SYSTEM_LIBRARIES_UNIX: &[&str] = &["dl", "m", "pthread"];
33
34#[derive(Debug, Parser)]
35pub struct CreateExe {
37 #[clap(flatten)]
38 env: WasmerEnv,
39
40 #[clap(name = "FILE")]
42 path: PathBuf,
43
44 #[clap(name = "OUTPUT PATH", short = 'o')]
46 output: PathBuf,
47
48 #[clap(long, name = "DEBUG PATH")]
51 debug_dir: Option<PathBuf>,
52
53 #[clap(
60 long,
61 use_value_delimiter = true,
62 value_delimiter = ',',
63 name = "FILE:PREFIX:PATH"
64 )]
65 precompiled_atom: Vec<String>,
66
67 #[clap(long = "target")]
80 target_triple: Option<Triple>,
81
82 #[clap(long, name = "URL_OR_RELEASE_VERSION")]
86 use_wasmer_release: Option<String>,
87
88 #[clap(long, short = 'm', number_of_values = 1)]
89 cpu_features: Vec<CpuFeature>,
90
91 #[clap(long, short = 'l')]
94 libraries: Vec<String>,
95
96 #[clap(flatten)]
97 cross_compile: CrossCompile,
98
99 #[clap(flatten)]
100 compiler: RuntimeOptions,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105pub enum UrlOrVersion {
106 Url(url::Url),
108 Version(semver::Version),
110}
111
112impl UrlOrVersion {
113 fn from_str(s: &str) -> Result<Self, anyhow::Error> {
114 let mut err;
115 let s = s.strip_prefix('v').unwrap_or(s);
116 match url::Url::parse(s) {
117 Ok(o) => return Ok(Self::Url(o)),
118 Err(e) => {
119 err = anyhow::anyhow!("could not parse as URL: {e}");
120 }
121 }
122
123 match semver::Version::parse(s) {
124 Ok(o) => return Ok(Self::Version(o)),
125 Err(e) => {
126 err = anyhow::anyhow!("could not parse as URL or version: {e}").context(err);
127 }
128 }
129
130 Err(err)
131 }
132}
133
134#[derive(Debug, Clone, Default, Parser)]
136pub(crate) struct CrossCompile {
137 #[clap(long)]
139 use_system_linker: bool,
140
141 #[clap(long = "library-path")]
143 library_path: Option<PathBuf>,
144
145 #[clap(long = "tarball")]
147 tarball: Option<PathBuf>,
148
149 #[clap(long = "zig-binary-path", env)]
151 zig_binary_path: Option<PathBuf>,
152}
153
154#[derive(Debug)]
155pub(crate) struct CrossCompileSetup {
156 pub(crate) target: Triple,
157 pub(crate) zig_binary_path: Option<PathBuf>,
158 pub(crate) library: PathBuf,
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct Entrypoint {
165 pub atoms: Vec<CommandEntrypoint>,
167 pub volumes: Vec<Volume>,
169}
170
171#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct CommandEntrypoint {
174 pub command: String,
176 pub atom: String,
178 pub path: PathBuf,
180 pub header: Option<PathBuf>,
182 pub module_info: Option<ModuleInfo>,
184}
185
186#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188pub struct Volume {
189 pub name: String,
191 pub obj_file: PathBuf,
193}
194
195impl CliCommand for CreateExe {
196 type Output = ();
197
198 fn run(self) -> Result<Self::Output, anyhow::Error> {
200 let path = normalize_path(&format!("{}", self.path.display()));
201 let target_triple = self.target_triple.clone().unwrap_or_else(Triple::host);
202 let mut cc = self.cross_compile.clone();
203 let target = utils::target_triple_to_target(&target_triple, &self.cpu_features);
204
205 let starting_cd = env::current_dir()?;
206 let input_path = starting_cd.join(path);
207 let output_path = starting_cd.join(&self.output);
208
209 let url_or_version = match self
210 .use_wasmer_release
211 .as_deref()
212 .map(UrlOrVersion::from_str)
213 {
214 Some(Ok(o)) => Some(o),
215 Some(Err(e)) => return Err(e),
216 None => None,
217 };
218
219 let cross_compilation = utils::get_cross_compile_setup(
220 &self.env,
221 &mut cc,
222 &target_triple,
223 &starting_cd,
224 url_or_version,
225 )?;
226
227 if input_path.is_dir() {
228 return Err(anyhow::anyhow!("input path cannot be a directory"));
229 }
230
231 let _backends = self.compiler.get_available_backends()?;
232 let engine = self.compiler.get_engine(&target)?;
233
234 println!("Compiler: {}", engine.deterministic_id());
235 println!("Target: {}", target.triple());
236 println!(
237 "Using path `{}` as libwasmer path.",
238 cross_compilation.library.display()
239 );
240
241 if !cross_compilation.library.exists() {
242 return Err(anyhow::anyhow!("library path does not exist"));
243 }
244
245 let temp = tempfile::tempdir();
246 let tempdir = match self.debug_dir.as_ref() {
247 Some(s) => s.clone(),
248 None => temp?.path().to_path_buf(),
249 };
250 std::fs::create_dir_all(&tempdir)?;
251
252 let atoms = if let Ok(pirita) = from_disk(&input_path) {
253 compile_pirita_into_directory(
255 &pirita,
256 &tempdir,
257 &self.compiler,
258 &self.cpu_features,
259 &cross_compilation.target,
260 &self.precompiled_atom,
261 AllowMultiWasm::Allow,
262 self.debug_dir.is_some(),
263 )
264 } else {
265 prepare_directory_from_single_wasm_file(
267 &input_path,
268 &tempdir,
269 &self.compiler,
270 &cross_compilation.target,
271 &self.cpu_features,
272 &self.precompiled_atom,
273 self.debug_dir.is_some(),
274 )
275 }?;
276
277 get_module_infos(&engine, &tempdir, &atoms)?;
278 let mut entrypoint = get_entrypoint(&tempdir)?;
279 create_header_files_in_dir(
280 &tempdir,
281 &mut entrypoint,
282 &atoms,
283 &self.precompiled_atom,
284 &target_triple.binary_format,
285 )?;
286 link_exe_from_dir(
287 &self.env,
288 &tempdir,
289 output_path,
290 &cross_compilation,
291 &self.libraries,
292 self.debug_dir.is_some(),
293 &atoms,
294 &self.precompiled_atom,
295 )?;
296
297 if self.target_triple.is_some() {
298 eprintln!(
299 "✔ Cross-compiled executable for `{}` target compiled successfully to `{}`.",
300 target.triple(),
301 self.output.display(),
302 );
303 } else {
304 eprintln!(
305 "✔ Native executable compiled successfully to `{}`.",
306 self.output.display(),
307 );
308 }
309
310 Ok(())
311 }
312}
313
314fn write_entrypoint(directory: &Path, entrypoint: &Entrypoint) -> Result<(), anyhow::Error> {
315 std::fs::write(
316 directory.join("entrypoint.json"),
317 serde_json::to_string_pretty(&entrypoint).unwrap(),
318 )
319 .map_err(|e| {
320 anyhow::anyhow!(
321 "cannot create entrypoint.json dir in {}: {e}",
322 directory.display()
323 )
324 })
325}
326
327fn get_entrypoint(directory: &Path) -> Result<Entrypoint, anyhow::Error> {
328 let entrypoint_json =
329 std::fs::read_to_string(directory.join("entrypoint.json")).map_err(|e| {
330 anyhow::anyhow!(
331 "could not read entrypoint.json in {}: {e}",
332 directory.display()
333 )
334 })?;
335
336 let entrypoint: Entrypoint = serde_json::from_str(&entrypoint_json).map_err(|e| {
337 anyhow::anyhow!(
338 "could not parse entrypoint.json in {}: {e}",
339 directory.display()
340 )
341 })?;
342
343 if entrypoint.atoms.is_empty() {
344 return Err(anyhow::anyhow!("file has no atoms to compile"));
345 }
346
347 Ok(entrypoint)
348}
349
350#[derive(Debug, PartialEq, Eq, Clone)]
353pub enum AllowMultiWasm {
354 Allow,
356 Reject(Option<String>),
358}
359
360#[allow(clippy::too_many_arguments)]
362pub(super) fn compile_pirita_into_directory(
363 pirita: &Container,
364 target_dir: &Path,
365 compiler: &RuntimeOptions,
366 cpu_features: &[CpuFeature],
367 triple: &Triple,
368 prefixes: &[String],
369 allow_multi_wasm: AllowMultiWasm,
370 debug: bool,
371) -> anyhow::Result<Vec<(String, Vec<u8>)>> {
372 let all_atoms = match &allow_multi_wasm {
373 AllowMultiWasm::Allow | AllowMultiWasm::Reject(None) => {
374 pirita.atoms().into_iter().collect::<Vec<_>>()
375 }
376 AllowMultiWasm::Reject(Some(s)) => {
377 let atom = pirita
378 .get_atom(s)
379 .with_context(|| format!("could not find atom \"{s}\""))?;
380 vec![(s.to_string(), atom)]
381 }
382 };
383
384 allow_multi_wasm.validate(&all_atoms)?;
385
386 std::fs::create_dir_all(target_dir)
387 .map_err(|e| anyhow::anyhow!("cannot create / dir in {}: {e}", target_dir.display()))?;
388
389 let target_dir = target_dir.canonicalize()?;
390 let target = &utils::target_triple_to_target(triple, cpu_features);
391
392 std::fs::create_dir_all(target_dir.join("volumes")).map_err(|e| {
393 anyhow::anyhow!(
394 "cannot create /volumes dir in {}: {e}",
395 target_dir.display()
396 )
397 })?;
398
399 let volumes = pirita.volumes();
400 let volume_bytes = volume_file_block(&volumes);
401 let volume_name = "VOLUMES";
402 let volume_path = target_dir.join("volumes").join("volume.o");
403 write_volume_obj(&volume_bytes, volume_name, &volume_path, target)?;
404 let volume_path = volume_path.canonicalize()?;
405 let volume_path = pathdiff::diff_paths(volume_path, &target_dir).unwrap();
406
407 std::fs::create_dir_all(target_dir.join("atoms")).map_err(|e| {
408 anyhow::anyhow!("cannot create /atoms dir in {}: {e}", target_dir.display())
409 })?;
410
411 let mut atoms_from_file = Vec::new();
412 let mut target_paths = Vec::new();
413
414 for (atom_name, atom_bytes) in all_atoms {
415 atoms_from_file.push((utils::normalize_atom_name(&atom_name), atom_bytes.to_vec()));
416 let atom_path = target_dir
417 .join("atoms")
418 .join(format!("{}.o", utils::normalize_atom_name(&atom_name)));
419 let header_path = {
420 std::fs::create_dir_all(target_dir.join("include")).map_err(|e| {
421 anyhow::anyhow!(
422 "cannot create /include dir in {}: {e}",
423 target_dir.display()
424 )
425 })?;
426
427 let header_path = target_dir.join("include").join(format!(
428 "static_defs_{}.h",
429 utils::normalize_atom_name(&atom_name)
430 ));
431
432 Some(header_path)
433 };
434 target_paths.push((
435 atom_name.clone(),
436 utils::normalize_atom_name(&atom_name),
437 atom_path,
438 header_path,
439 ));
440 }
441
442 let prefix_map = PrefixMapCompilation::from_input(&atoms_from_file, prefixes, false)
443 .with_context(|| anyhow::anyhow!("compile_pirita_into_directory"))?;
444
445 let module_infos = compile_atoms(
446 &atoms_from_file,
447 &target_dir.join("atoms"),
448 compiler,
449 target,
450 &prefix_map,
451 debug,
452 )?;
453
454 let mut atoms = Vec::new();
456 for (command_name, atom_name, a, opt_header_path) in target_paths {
457 let mut atom_path = a;
458 let mut header_path = opt_header_path;
459 if let Ok(a) = atom_path.canonicalize() {
460 let opt_header_path = header_path.and_then(|p| p.canonicalize().ok());
461 atom_path = pathdiff::diff_paths(&a, &target_dir).unwrap_or_else(|| a.clone());
462 header_path = opt_header_path.and_then(|h| pathdiff::diff_paths(h, &target_dir));
463 }
464 atoms.push(CommandEntrypoint {
465 command: command_name,
467 atom: atom_name.clone(),
468 path: atom_path,
469 header: header_path,
470 module_info: module_infos.get(&atom_name).cloned(),
471 });
472 }
473
474 let entrypoint = Entrypoint {
475 atoms,
476 volumes: vec![Volume {
477 name: volume_name.to_string(),
478 obj_file: volume_path,
479 }],
480 };
481
482 write_entrypoint(&target_dir, &entrypoint)?;
483
484 Ok(atoms_from_file)
485}
486
487fn volume_file_block(volumes: &BTreeMap<String, WebcVolume>) -> Vec<u8> {
495 let serialized_volumes: Vec<(&String, Vec<u8>)> = volumes
496 .iter()
497 .map(|(name, volume)| (name, serialize_volume_to_webc_v1(volume)))
498 .collect();
499
500 let parsed_volumes: indexmap::IndexMap<String, webc::v1::Volume<'_>> = serialized_volumes
501 .iter()
502 .filter_map(|(name, serialized_volume)| {
503 let volume = webc::v1::Volume::parse(serialized_volume).ok()?;
504 Some((name.to_string(), volume))
505 })
506 .collect();
507
508 let webc = webc::v1::WebC {
509 version: 0,
510 checksum: None,
511 signature: None,
512 manifest: webc::metadata::Manifest::default(),
513 atoms: webc::v1::Volume::default(),
514 volumes: parsed_volumes,
515 };
516
517 webc.get_volumes_as_fileblock()
518}
519
520fn serialize_volume_to_webc_v1(volume: &WebcVolume) -> Vec<u8> {
521 fn read_dir(
522 volume: &WebcVolume,
523 path: &mut PathSegments,
524 files: &mut BTreeMap<webc::v1::DirOrFile, Vec<u8>>,
525 ) {
526 for (segment, _, meta) in volume.read_dir(&*path).unwrap_or_default() {
527 path.push(segment);
528
529 match meta {
530 Metadata::Dir { .. } => {
531 files.insert(
532 webc::v1::DirOrFile::Dir(path.to_string().into()),
533 Vec::new(),
534 );
535 read_dir(volume, path, files);
536 }
537 Metadata::File { .. } => {
538 if let Some((contents, _)) = volume.read_file(&*path) {
539 files.insert(
540 webc::v1::DirOrFile::File(path.to_string().into()),
541 contents.to_vec(),
542 );
543 }
544 }
545 }
546
547 path.pop();
548 }
549 }
550
551 let mut path = PathSegments::ROOT;
552 let mut files = BTreeMap::new();
553
554 read_dir(volume, &mut path, &mut files);
555
556 webc::v1::Volume::serialize_files(files)
557}
558
559impl AllowMultiWasm {
560 fn validate(
561 &self,
562 all_atoms: &[(String, webc::compat::SharedBytes)],
563 ) -> Result<(), anyhow::Error> {
564 if matches!(self, AllowMultiWasm::Reject(None)) && all_atoms.len() > 1 {
565 let keys = all_atoms
566 .iter()
567 .map(|(name, _)| name.clone())
568 .collect::<Vec<_>>();
569
570 return Err(anyhow::anyhow!(
571 "where <ATOM> is one of: {}",
572 keys.join(", ")
573 ))
574 .context(anyhow::anyhow!(
575 "note: use --atom <ATOM> to specify which atom to compile"
576 ))
577 .context(anyhow::anyhow!(
578 "cannot compile more than one atom at a time"
579 ));
580 }
581
582 Ok(())
583 }
584}
585
586#[derive(Debug, Default, PartialEq)]
588pub(crate) struct PrefixMapCompilation {
589 input_hashes: BTreeMap<String, String>,
591 manual_prefixes: BTreeMap<String, String>,
593 #[allow(dead_code)]
595 compilation_objects: BTreeMap<String, Vec<u8>>,
596}
597
598impl PrefixMapCompilation {
599 fn from_input(
601 atoms: &[(String, Vec<u8>)],
602 prefixes: &[String],
603 only_validate_prefixes: bool,
604 ) -> Result<Self, anyhow::Error> {
605 if atoms.is_empty() {
607 return Ok(Self::default());
608 }
609
610 if prefixes.is_empty() {
613 return Ok(Self {
614 input_hashes: atoms
615 .iter()
616 .map(|(name, bytes)| (normalize_atom_name(name), Self::hash_for_bytes(bytes)))
617 .collect(),
618 manual_prefixes: BTreeMap::new(),
619 compilation_objects: BTreeMap::new(),
620 });
621 }
622
623 if prefixes.len() != atoms.len() {
625 println!(
626 "WARNING: invalid mapping of prefix and atoms: expected prefixes for {} atoms, got {} prefixes",
627 atoms.len(),
628 prefixes.len()
629 );
630 }
631
632 let available_atoms = atoms.iter().map(|(k, _)| k.clone()).collect::<Vec<_>>();
633 let mut manual_prefixes = BTreeMap::new();
634 let mut compilation_objects = BTreeMap::new();
635
636 for p in prefixes.iter() {
637 let prefix_split = Self::split_prefix(p);
640
641 match prefix_split.as_slice() {
642 [atom, prefix, path] => {
644 if only_validate_prefixes {
645 manual_prefixes.insert(normalize_atom_name(atom), prefix.to_string());
647 } else {
648 let atom_hash = atoms
649 .iter()
650 .find_map(|(name, _)| if normalize_atom_name(name) == normalize_atom_name(atom) { Some(prefix.to_string()) } else { None })
651 .ok_or_else(|| anyhow::anyhow!("no atom {atom:?} found, for prefix {p:?}, available atoms are {available_atoms:?}"))?;
652
653 let current_dir = std::env::current_dir().unwrap().canonicalize().unwrap();
654 let path = current_dir.join(path.replace("./", ""));
655 let bytes = std::fs::read(&path).map_err(|e| {
656 anyhow::anyhow!("could not read file for atom {atom:?} (prefix {p}, path {} in dir {}): {e}", path.display(), current_dir.display())
657 })?;
658
659 compilation_objects.insert(normalize_atom_name(atom), bytes);
660 manual_prefixes.insert(normalize_atom_name(atom), atom_hash.to_string());
661 }
662 }
663 [atom, path] => {
665 let atom_hash = atoms
666 .iter()
667 .find_map(|(name, bytes)| if normalize_atom_name(name) == normalize_atom_name(atom) { Some(Self::hash_for_bytes(bytes)) } else { None })
668 .ok_or_else(|| anyhow::anyhow!("no atom {atom:?} found, for prefix {p:?}, available atoms are {available_atoms:?}"))?;
669 manual_prefixes.insert(normalize_atom_name(atom), atom_hash.to_string());
670
671 if !only_validate_prefixes {
672 let current_dir = std::env::current_dir().unwrap().canonicalize().unwrap();
673 let path = current_dir.join(path.replace("./", ""));
674 let bytes = std::fs::read(&path).map_err(|e| {
675 anyhow::anyhow!("could not read file for atom {atom:?} (prefix {p}, path {} in dir {}): {e}", path.display(), current_dir.display())
676 })?;
677 compilation_objects.insert(normalize_atom_name(atom), bytes);
678 }
679 }
680 [prefix] if atoms.len() == 1 => {
682 manual_prefixes.insert(normalize_atom_name(&atoms[0].0), prefix.to_string());
683 }
684 _ => {
685 return Err(anyhow::anyhow!(
686 "invalid --precompiled-atom {p:?} - correct format is ATOM:PREFIX:PATH or ATOM:PATH"
687 ));
688 }
689 }
690 }
691
692 Ok(Self {
693 input_hashes: BTreeMap::new(),
694 manual_prefixes,
695 compilation_objects,
696 })
697 }
698
699 fn split_prefix(s: &str) -> Vec<String> {
700 let regex =
701 regex::Regex::new(r"^([a-zA-Z0-9\-_]+)(:([a-zA-Z0-9\.\-_]+))?(:(.+*))?").unwrap();
702 let mut captures = regex
703 .captures(s.trim())
704 .map(|c| {
705 c.iter()
706 .skip(1)
707 .flatten()
708 .map(|m| m.as_str().to_owned())
709 .collect::<Vec<_>>()
710 })
711 .unwrap_or_default();
712 if captures.is_empty() {
713 vec![s.to_string()]
714 } else if captures.len() == 5 {
715 captures.remove(1);
716 captures.remove(2);
717 captures
718 } else if captures.len() == 3 {
719 s.splitn(2, ':').map(|s| s.to_string()).collect()
720 } else {
721 captures
722 }
723 }
724
725 pub(crate) fn hash_for_bytes(bytes: &[u8]) -> String {
726 use sha2::{Digest, Sha256};
727 let mut hasher = Sha256::new();
728 hasher.update(bytes);
729 let result = hasher.finalize();
730
731 hex::encode(&result[..])
732 }
733
734 fn get_prefix_for_atom(&self, atom_name: &str) -> Option<String> {
735 self.manual_prefixes
736 .get(atom_name)
737 .or_else(|| self.input_hashes.get(atom_name))
738 .cloned()
739 }
740
741 #[allow(dead_code)]
742 fn get_compilation_object_for_atom(&self, atom_name: &str) -> Option<&[u8]> {
743 self.compilation_objects
744 .get(atom_name)
745 .map(|s| s.as_slice())
746 }
747}
748
749#[test]
750fn test_prefix_parsing() {
751 let tempdir = tempfile::TempDir::new().unwrap();
752 let path = tempdir.path();
753 std::fs::write(path.join("test.obj"), b"").unwrap();
754 let str1 = format!("ATOM_NAME:PREFIX:{}", path.join("test.obj").display());
755 let prefix =
756 PrefixMapCompilation::from_input(&[("ATOM_NAME".to_string(), b"".to_vec())], &[str1], true);
757 assert_eq!(
758 prefix.unwrap(),
759 PrefixMapCompilation {
760 input_hashes: BTreeMap::new(),
761 manual_prefixes: vec![("ATOM_NAME".to_string(), "PREFIX".to_string())]
762 .into_iter()
763 .collect(),
764 compilation_objects: Vec::new().into_iter().collect(),
765 }
766 );
767}
768
769#[test]
770fn test_split_prefix() {
771 let split = PrefixMapCompilation::split_prefix(
772 "qjs:abc123:C:\\Users\\felix\\AppData\\Local\\Temp\\.tmpoccCjV\\wasm.obj",
773 );
774 assert_eq!(
775 split,
776 vec![
777 "qjs".to_string(),
778 "abc123".to_string(),
779 "C:\\Users\\felix\\AppData\\Local\\Temp\\.tmpoccCjV\\wasm.obj".to_string(),
780 ]
781 );
782 let split = PrefixMapCompilation::split_prefix("qjs:./tmp.obj");
783 assert_eq!(split, vec!["qjs".to_string(), "./tmp.obj".to_string(),]);
784 let split2 = PrefixMapCompilation::split_prefix(
785 "qjs:abc123:/var/folders/65/2zzy98b16xz254jccxjzqb8w0000gn/T/.tmpNdgVaq/wasm.o",
786 );
787 assert_eq!(
788 split2,
789 vec![
790 "qjs".to_string(),
791 "abc123".to_string(),
792 "/var/folders/65/2zzy98b16xz254jccxjzqb8w0000gn/T/.tmpNdgVaq/wasm.o".to_string(),
793 ]
794 );
795 let split3 = PrefixMapCompilation::split_prefix(
796 "qjs:/var/folders/65/2zzy98b16xz254jccxjzqb8w0000gn/T/.tmpNdgVaq/wasm.o",
797 );
798 assert_eq!(
799 split3,
800 vec![
801 "qjs".to_string(),
802 "/var/folders/65/2zzy98b16xz254jccxjzqb8w0000gn/T/.tmpNdgVaq/wasm.o".to_string(),
803 ]
804 );
805}
806
807fn compile_atoms(
808 atoms: &[(String, Vec<u8>)],
809 output_dir: &Path,
810 compiler: &RuntimeOptions,
811 target: &Target,
812 prefixes: &PrefixMapCompilation,
813 debug: bool,
814) -> Result<BTreeMap<String, ModuleInfo>, anyhow::Error> {
815 use std::{
816 fs::File,
817 io::{BufWriter, Write},
818 };
819
820 let mut module_infos = BTreeMap::new();
821 for (a, data) in atoms {
822 let prefix = prefixes
823 .get_prefix_for_atom(&normalize_atom_name(a))
824 .ok_or_else(|| anyhow::anyhow!("no prefix given for atom {a}"))?;
825 let atom_name = utils::normalize_atom_name(a);
826 let output_object_path = output_dir.join(format!("{atom_name}.o"));
827 if let Some(atom) = prefixes.get_compilation_object_for_atom(a) {
828 std::fs::write(&output_object_path, atom)
829 .map_err(|e| anyhow::anyhow!("{}: {e}", output_object_path.display()))?;
830 if debug {
831 println!("Using cached object file for atom {a:?}.");
832 }
833 continue;
834 }
835 let engine = compiler.get_sys_compiler_engine_for_target(target.clone())?;
836 let engine_inner = engine.as_sys().inner();
837 let compiler = engine_inner.compiler()?;
838 let features = engine_inner.features();
839 let tunables = engine.tunables();
840 let (module_info, obj, _, _) = Artifact::generate_object(
841 compiler,
842 data,
843 Some(prefix.as_str()),
844 target,
845 tunables,
846 features,
847 )?;
848 module_infos.insert(atom_name, module_info);
849 let mut writer = BufWriter::new(File::create(&output_object_path)?);
851 obj.write_stream(&mut writer)
852 .map_err(|err| anyhow::anyhow!(err.to_string()))?;
853 writer.flush()?;
854 }
855
856 Ok(module_infos)
857}
858
859fn run_c_compile(
861 env: &WasmerEnv,
862 path_to_c_src: &Path,
863 output_name: &Path,
864 target: &Triple,
865 debug: bool,
866 include_dirs: &[PathBuf],
867) -> anyhow::Result<()> {
868 #[cfg(not(windows))]
869 let c_compiler = "cc";
870 #[cfg(windows)]
873 let c_compiler = "clang++";
874
875 let mut command = Command::new(c_compiler);
876 let mut command = command
877 .arg("-Wall")
878 .arg("-O2")
879 .arg("-c")
880 .arg(path_to_c_src)
881 .arg("-I")
882 .arg(utils::get_wasmer_include_directory(env)?);
883
884 for i in include_dirs {
885 command = command.arg("-I");
886 command = command.arg(normalize_path(&i.display().to_string()));
887 }
888
889 if *target != Triple::host() {
891 command = command.arg("-target").arg(format!("{target}"));
892 }
893
894 let command = command.arg("-o").arg(output_name);
895
896 if debug {
897 println!("{command:#?}");
898 }
899
900 let output = command.output()?;
901 if debug {
902 eprintln!(
903 "run_c_compile: stdout: {}\n\nstderr: {}",
904 String::from_utf8_lossy(&output.stdout),
905 String::from_utf8_lossy(&output.stderr)
906 );
907 }
908
909 if !output.status.success() {
910 bail!(
911 "C code compile failed with: stdout: {}\n\nstderr: {}",
912 String::from_utf8_lossy(&output.stdout),
913 String::from_utf8_lossy(&output.stderr)
914 );
915 }
916
917 Ok(())
918}
919
920fn write_volume_obj(
921 volume_bytes: &[u8],
922 object_name: &str,
923 output_path: &Path,
924 target: &Target,
925) -> anyhow::Result<()> {
926 use std::{
927 fs::File,
928 io::{BufWriter, Write},
929 };
930
931 let mut volumes_object = get_object_for_target(target.triple())?;
932 emit_serialized(
933 &mut volumes_object,
934 volume_bytes,
935 target.triple(),
936 object_name,
937 )?;
938
939 let mut writer = BufWriter::new(File::create(output_path)?);
940 volumes_object
941 .write_stream(&mut writer)
942 .map_err(|err| anyhow::anyhow!(err.to_string()))?;
943 writer.flush()?;
944 drop(writer);
945
946 Ok(())
947}
948
949#[allow(clippy::too_many_arguments)]
951pub(super) fn prepare_directory_from_single_wasm_file(
952 wasm_file: &Path,
953 target_dir: &Path,
954 compiler: &RuntimeOptions,
955 triple: &Triple,
956 cpu_features: &[CpuFeature],
957 prefix: &[String],
958 debug: bool,
959) -> anyhow::Result<Vec<(String, Vec<u8>)>, anyhow::Error> {
960 let bytes = std::fs::read(wasm_file)?;
961 let target = &utils::target_triple_to_target(triple, cpu_features);
962
963 std::fs::create_dir_all(target_dir)
964 .map_err(|e| anyhow::anyhow!("cannot create / dir in {}: {e}", target_dir.display()))?;
965
966 let target_dir = target_dir.canonicalize()?;
967
968 std::fs::create_dir_all(target_dir.join("atoms")).map_err(|e| {
969 anyhow::anyhow!("cannot create /atoms dir in {}: {e}", target_dir.display())
970 })?;
971
972 let mut atoms_from_file = Vec::new();
973 let mut target_paths = Vec::new();
974
975 let all_files = vec![(
976 wasm_file
977 .file_stem()
978 .and_then(|f| f.to_str())
979 .unwrap_or("main")
980 .to_string(),
981 bytes,
982 )];
983
984 for (atom_name, atom_bytes) in all_files.iter() {
985 atoms_from_file.push((atom_name.clone(), atom_bytes.to_vec()));
986 let atom_path = target_dir.join("atoms").join(format!("{atom_name}.o"));
987 target_paths.push((atom_name, atom_path));
988 }
989
990 let prefix_map = PrefixMapCompilation::from_input(&atoms_from_file, prefix, false)
991 .with_context(|| anyhow::anyhow!("prepare_directory_from_single_wasm_file"))?;
992
993 let module_infos = compile_atoms(
994 &atoms_from_file,
995 &target_dir.join("atoms"),
996 compiler,
997 target,
998 &prefix_map,
999 debug,
1000 )?;
1001
1002 let mut atoms = Vec::new();
1003 for (atom_name, atom_path) in target_paths {
1004 atoms.push(CommandEntrypoint {
1005 command: atom_name.clone(),
1007 atom: atom_name.clone(),
1008 path: atom_path,
1009 header: None,
1010 module_info: module_infos.get(atom_name).cloned(),
1011 });
1012 }
1013
1014 let entrypoint = Entrypoint {
1015 atoms,
1016 volumes: Vec::new(),
1017 };
1018
1019 write_entrypoint(&target_dir, &entrypoint)?;
1020
1021 Ok(all_files)
1022}
1023
1024fn get_module_infos(
1028 engine: &Engine,
1029 directory: &Path,
1030 atoms: &[(String, Vec<u8>)],
1031) -> Result<BTreeMap<String, ModuleInfo>, anyhow::Error> {
1032 let mut entrypoint =
1033 get_entrypoint(directory).with_context(|| anyhow::anyhow!("get module infos"))?;
1034
1035 let mut module_infos = BTreeMap::new();
1036 for (atom_name, atom_bytes) in atoms {
1037 let module = Module::new(engine, atom_bytes.as_slice())?;
1038 let module_info = module.info();
1039 if let Some(s) = entrypoint
1040 .atoms
1041 .iter_mut()
1042 .find(|a| a.atom.as_str() == atom_name.as_str())
1043 {
1044 s.module_info = Some(module_info.clone());
1045 module_infos.insert(atom_name.clone(), module_info.clone());
1046 }
1047 }
1048
1049 write_entrypoint(directory, &entrypoint)?;
1050
1051 Ok(module_infos)
1052}
1053
1054pub(crate) fn create_header_files_in_dir(
1056 directory: &Path,
1057 entrypoint: &mut Entrypoint,
1058 atoms: &[(String, Vec<u8>)],
1059 prefixes: &[String],
1060 binary_fmt: &BinaryFormat,
1061) -> anyhow::Result<()> {
1062 use object::{Object, ObjectSymbol};
1063
1064 std::fs::create_dir_all(directory.join("include")).map_err(|e| {
1065 anyhow::anyhow!("cannot create /include dir in {}: {e}", directory.display())
1066 })?;
1067
1068 let prefixes = PrefixMapCompilation::from_input(atoms, prefixes, false)
1069 .with_context(|| anyhow::anyhow!("create_header_files_in_dir"))?;
1070
1071 for atom in entrypoint.atoms.iter_mut() {
1072 let atom_name = &atom.atom;
1073 let prefix = prefixes
1074 .get_prefix_for_atom(atom_name)
1075 .ok_or_else(|| anyhow::anyhow!("cannot get prefix for atom {atom_name}"))?;
1076 let symbol_registry = ModuleMetadataSymbolRegistry {
1077 prefix: prefix.clone(),
1078 };
1079
1080 let object_file_src = directory.join(&atom.path);
1081 let object_file = std::fs::read(&object_file_src)
1082 .map_err(|e| anyhow::anyhow!("could not read {}: {e}", object_file_src.display()))?;
1083 let obj_file = object::File::parse(&*object_file)?;
1084 let mut symbol_name = symbol_registry.symbol_to_name(Symbol::Metadata);
1085 if matches!(binary_fmt, BinaryFormat::Macho) {
1086 symbol_name = format!("_{symbol_name}");
1087 }
1088
1089 let mut metadata_length = obj_file.symbol_by_name(&symbol_name).unwrap().size() as usize;
1090 if metadata_length == 0 {
1091 let metadata_obj = obj_file.symbol_by_name(&symbol_name).unwrap();
1092 let sec = obj_file
1093 .section_by_index(metadata_obj.section().index().unwrap())
1094 .unwrap();
1095 let mut syms_in_data_sec = obj_file
1096 .symbols()
1097 .filter(|v| v.section_index().is_some_and(|i| i == sec.index()))
1098 .collect::<Vec<_>>();
1099
1100 syms_in_data_sec.sort_by_key(|v| v.address());
1101
1102 let metadata_obj_idx = syms_in_data_sec
1103 .iter()
1104 .position(|v| v.name().is_ok_and(|v| v == symbol_name))
1105 .unwrap();
1106
1107 metadata_length = if metadata_obj_idx == syms_in_data_sec.len() - 1 {
1108 (sec.address() + sec.size()) - syms_in_data_sec[metadata_obj_idx].address()
1109 } else {
1110 syms_in_data_sec[metadata_obj_idx + 1].address()
1111 - syms_in_data_sec[metadata_obj_idx].address()
1112 } as usize;
1113 }
1114
1115 let module_info = atom
1116 .module_info
1117 .as_ref()
1118 .ok_or_else(|| anyhow::anyhow!("no module info for atom {atom_name:?}"))?;
1119
1120 let base_path = Path::new("include").join(format!("static_defs_{prefix}.h"));
1121 let header_file_path = directory.join(&base_path);
1122
1123 let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
1124 &prefix,
1125 module_info,
1126 &symbol_registry,
1127 metadata_length,
1128 );
1129
1130 std::fs::write(&header_file_path, &header_file_src).map_err(|e| {
1131 anyhow::anyhow!(
1132 "could not write static_defs.h for atom {atom_name} in generate-header step: {e}"
1133 )
1134 })?;
1135
1136 atom.header = Some(base_path);
1137 }
1138
1139 write_entrypoint(directory, entrypoint)?;
1140
1141 Ok(())
1142}
1143
1144#[allow(clippy::too_many_arguments)]
1146fn link_exe_from_dir(
1147 env: &WasmerEnv,
1148 directory: &Path,
1149 output_path: PathBuf,
1150 cross_compilation: &CrossCompileSetup,
1151 additional_libraries: &[String],
1152 debug: bool,
1153 atoms: &[(String, Vec<u8>)],
1154 prefixes: &[String],
1155) -> anyhow::Result<()> {
1156 let entrypoint =
1157 get_entrypoint(directory).with_context(|| anyhow::anyhow!("link exe from dir"))?;
1158
1159 let prefixes = PrefixMapCompilation::from_input(atoms, prefixes, false)
1160 .with_context(|| anyhow::anyhow!("link_exe_from_dir"))?;
1161
1162 let wasmer_main_c = generate_wasmer_main_c(&entrypoint, &prefixes).map_err(|e| {
1163 anyhow::anyhow!(
1164 "could not generate wasmer_main.c in dir {}: {e}",
1165 directory.display()
1166 )
1167 })?;
1168
1169 std::fs::write(directory.join("wasmer_main.c"), wasmer_main_c.as_bytes()).map_err(|e| {
1170 anyhow::anyhow!(
1171 "could not write wasmer_main.c in dir {}: {e}",
1172 directory.display()
1173 )
1174 })?;
1175
1176 let library_path = &cross_compilation.library;
1177
1178 let mut object_paths = entrypoint
1179 .atoms
1180 .iter()
1181 .filter_map(|a| directory.join(&a.path).canonicalize().ok())
1182 .collect::<Vec<_>>();
1183
1184 object_paths.extend(
1185 entrypoint
1186 .volumes
1187 .iter()
1188 .filter_map(|v| directory.join(&v.obj_file).canonicalize().ok()),
1189 );
1190
1191 let zig_triple = utils::triple_to_zig_triple(&cross_compilation.target);
1192 let include_dirs = entrypoint
1193 .atoms
1194 .iter()
1195 .filter_map(|a| {
1196 Some(
1197 directory
1198 .join(a.header.as_deref()?)
1199 .canonicalize()
1200 .ok()?
1201 .parent()?
1202 .to_path_buf(),
1203 )
1204 })
1205 .collect::<Vec<_>>();
1206
1207 let mut include_dirs = include_dirs;
1208 include_dirs.sort();
1209 include_dirs.dedup();
1210
1211 if cross_compilation.target == Triple::host()
1215 && cross_compilation.target.operating_system == OperatingSystem::Windows
1216 {
1217 run_c_compile(
1218 env,
1219 &directory.join("wasmer_main.c"),
1220 &directory.join("wasmer_main.o"),
1221 &cross_compilation.target,
1222 debug,
1223 &include_dirs,
1224 )
1225 .map_err(|e| {
1226 anyhow::anyhow!(
1227 "could not run c compile of wasmer_main.c in dir {}: {e}",
1228 directory.display()
1229 )
1230 })?;
1231 }
1232
1233 if cross_compilation.zig_binary_path.is_none()
1235 || (cross_compilation.target == Triple::host()
1236 && cross_compilation.target.operating_system == OperatingSystem::Windows)
1237 {
1238 #[cfg(not(windows))]
1239 let linker = "cc";
1240 #[cfg(windows)]
1241 let linker = "clang";
1242 let optimization_flag = "-O2";
1243
1244 let object_path = match directory.join("wasmer_main.o").canonicalize() {
1245 Ok(s) => s,
1246 Err(_) => directory.join("wasmer_main.c"),
1247 };
1248
1249 object_paths.push(object_path);
1250
1251 return link_objects_system_linker(
1252 library_path,
1253 linker,
1254 optimization_flag,
1255 &include_dirs,
1256 &object_paths,
1257 &cross_compilation.target,
1258 additional_libraries,
1259 &output_path,
1260 debug,
1261 );
1262 }
1263
1264 let zig_binary_path = cross_compilation
1265 .zig_binary_path
1266 .as_ref()
1267 .ok_or_else(|| anyhow::anyhow!("could not find zig in $PATH {}", directory.display()))?;
1268
1269 let mut cmd = Command::new(zig_binary_path);
1270 cmd.arg("build-exe");
1271 cmd.arg("--verbose-cc");
1272 cmd.arg("--verbose-link");
1273 cmd.arg("-target");
1274 cmd.arg(&zig_triple);
1275
1276 #[cfg(not(target_os = "windows"))]
1279 if zig_triple.contains("windows") {
1280 cmd.arg("-lc++");
1281 } else {
1282 cmd.arg("-lc");
1283 }
1284
1285 #[cfg(target_os = "windows")]
1286 cmd.arg("-lc");
1287
1288 for include_dir in include_dirs {
1289 cmd.arg("-I");
1290 cmd.arg(normalize_path(&format!("{}", include_dir.display())));
1291 }
1292
1293 let mut include_path = library_path.clone();
1294 include_path.pop();
1295 include_path.pop();
1296 include_path.push("include");
1297 if !include_path.exists() {
1298 return Err(anyhow::anyhow!(
1300 "Wasmer include path {} does not exist, maybe library path {} is wrong (expected /lib/libwasmer.a)?",
1301 include_path.display(),
1302 library_path.display()
1303 ));
1304 }
1305 cmd.arg("-I");
1306 cmd.arg(normalize_path(&format!("{}", include_path.display())));
1307
1308 #[cfg(not(target_os = "windows"))]
1311 cmd.arg("-lunwind");
1312
1313 #[cfg(target_os = "windows")]
1314 if !zig_triple.contains("windows") {
1315 cmd.arg("-lunwind");
1316 }
1317
1318 cmd.arg("-OReleaseSafe");
1319 cmd.arg("-fno-compiler-rt");
1320 cmd.arg("-fno-lto");
1321 #[cfg(target_os = "windows")]
1322 let out_path = directory.join("wasmer_main.exe");
1323 #[cfg(not(target_os = "windows"))]
1324 let out_path = directory.join("wasmer_main");
1325 cmd.arg(format!("-femit-bin={}", out_path.display()));
1326
1327 cmd.args(
1328 object_paths
1329 .iter()
1330 .map(|o| normalize_path(&format!("{}", o.display())))
1331 .collect::<Vec<_>>(),
1332 );
1333 cmd.arg(normalize_path(&format!("{}", library_path.display())));
1334 cmd.arg(normalize_path(&format!(
1335 "{}",
1336 directory
1337 .join("wasmer_main.c")
1338 .canonicalize()
1339 .expect("could not find wasmer_main.c / wasmer_main.o")
1340 .display()
1341 )));
1342
1343 if zig_triple.contains("windows") {
1344 let mut winsdk_path = library_path.clone();
1345 winsdk_path.pop();
1346 winsdk_path.pop();
1347 winsdk_path.push("winsdk");
1348
1349 let files_winsdk = std::fs::read_dir(winsdk_path)
1350 .ok()
1351 .map(|res| {
1352 res.filter_map(|r| Some(normalize_path(&format!("{}", r.ok()?.path().display()))))
1353 .collect::<Vec<_>>()
1354 })
1355 .unwrap_or_default();
1356
1357 cmd.args(files_winsdk);
1358 }
1359
1360 if zig_triple.contains("macos") {
1361 let framework = include_bytes!("security_framework.tgz").to_vec();
1365 let tar = flate2::read::GzDecoder::new(framework.as_slice());
1367 let mut archive = Archive::new(tar);
1368 archive.unpack(directory)?;
1370 cmd.arg("-framework");
1372 cmd.arg("Security");
1373 cmd.arg(format!("-F{}", directory.display()));
1374 }
1375
1376 if debug {
1377 println!("running cmd: {cmd:?}");
1378 cmd.stdout(Stdio::inherit());
1379 cmd.stderr(Stdio::inherit());
1380 }
1381
1382 let compilation = cmd
1383 .output()
1384 .context(anyhow!("Could not execute `zig`: {cmd:?}"))?;
1385
1386 if !compilation.status.success() {
1387 return Err(anyhow::anyhow!(
1388 String::from_utf8_lossy(&compilation.stderr).to_string()
1389 ));
1390 }
1391
1392 let output_path_normalized = normalize_path(&format!("{}", output_path.display()));
1394 let _ = std::fs::remove_file(output_path_normalized);
1395 std::fs::copy(
1396 normalize_path(&format!("{}", out_path.display())),
1397 normalize_path(&format!("{}", output_path.display())),
1398 )
1399 .map_err(|e| {
1400 anyhow::anyhow!(
1401 "could not copy from {} to {}: {e}",
1402 normalize_path(&format!("{}", out_path.display())),
1403 normalize_path(&format!("{}", output_path.display()))
1404 )
1405 })?;
1406
1407 Ok(())
1408}
1409
1410#[allow(clippy::too_many_arguments)]
1412fn link_objects_system_linker(
1413 libwasmer_path: &Path,
1414 linker_cmd: &str,
1415 optimization_flag: &str,
1416 include_dirs: &[PathBuf],
1417 object_paths: &[PathBuf],
1418 target: &Triple,
1419 additional_libraries: &[String],
1420 output_path: &Path,
1421 debug: bool,
1422) -> Result<(), anyhow::Error> {
1423 let libwasmer_path = libwasmer_path
1424 .canonicalize()
1425 .context("Failed to find libwasmer")?;
1426 let mut command = Command::new(linker_cmd);
1427 let mut command = command
1428 .arg("-Wall")
1429 .arg(optimization_flag)
1430 .args(object_paths.iter().map(|path| path.canonicalize().unwrap()))
1431 .arg(&libwasmer_path);
1432
1433 if *target != Triple::host() {
1434 command = command.arg("-target");
1435 command = command.arg(format!("{target}"));
1436 }
1437
1438 for include_dir in include_dirs {
1439 command = command.arg("-I");
1440 command = command.arg(normalize_path(&format!("{}", include_dir.display())));
1441 }
1442 let mut include_path = libwasmer_path.clone();
1443 include_path.pop();
1444 include_path.pop();
1445 include_path.push("include");
1446 if !include_path.exists() {
1447 return Err(anyhow::anyhow!(
1449 "Wasmer include path {} does not exist, maybe library path {} is wrong (expected /lib/libwasmer.a)?",
1450 include_path.display(),
1451 libwasmer_path.display()
1452 ));
1453 }
1454 command = command.arg("-I");
1455 command = command.arg(normalize_path(&format!("{}", include_path.display())));
1456
1457 let mut additional_libraries = additional_libraries.to_vec();
1460 if target.operating_system == OperatingSystem::Windows {
1461 additional_libraries.extend(LINK_SYSTEM_LIBRARIES_WINDOWS.iter().map(|s| s.to_string()));
1462 } else {
1463 additional_libraries.extend(LINK_SYSTEM_LIBRARIES_UNIX.iter().map(|s| s.to_string()));
1464 }
1465 let link_against_extra_libs = additional_libraries.iter().map(|lib| format!("-l{lib}"));
1466 let command = command.args(link_against_extra_libs);
1467 let command = command.arg("-o").arg(output_path);
1468 if debug {
1469 println!("{command:#?}");
1470 }
1471 let output = command.output()?;
1472
1473 if !output.status.success() {
1474 bail!(
1475 "linking failed with command line:{:#?} stdout: {}\n\nstderr: {}",
1476 command,
1477 String::from_utf8_lossy(&output.stdout),
1478 String::from_utf8_lossy(&output.stderr),
1479 );
1480 }
1481 Ok(())
1482}
1483
1484fn generate_wasmer_main_c(
1487 entrypoint: &Entrypoint,
1488 prefixes: &PrefixMapCompilation,
1489) -> Result<String, anyhow::Error> {
1490 use std::fmt::Write;
1491
1492 const WASMER_MAIN_C_SOURCE: &str = include_str!("wasmer_create_exe_main.c");
1493
1494 let atom_names = entrypoint
1496 .atoms
1497 .iter()
1498 .map(|a| &a.command)
1499 .collect::<Vec<_>>();
1500
1501 let c_code_to_add = String::new();
1502 let mut c_code_to_instantiate = String::new();
1503 let mut deallocate_module = String::new();
1504 let mut extra_headers = Vec::new();
1505
1506 for a in atom_names.iter() {
1507 let prefix = prefixes
1508 .get_prefix_for_atom(&utils::normalize_atom_name(a))
1509 .ok_or_else(|| {
1510 let formatted_prefixes = format!("{prefixes:#?}");
1511 anyhow::anyhow!(
1512 "cannot find prefix for atom {a} when generating wasmer_main.c ({formatted_prefixes})"
1513 )
1514 })?;
1515 let atom_name = prefix.clone();
1516
1517 extra_headers.push(format!("#include \"static_defs_{atom_name}.h\""));
1518
1519 write!(c_code_to_instantiate, "
1520 wasm_module_t *atom_{atom_name} = wasmer_object_module_new_{atom_name}(store, \"{atom_name}\");
1521 if (!atom_{atom_name}) {{
1522 fprintf(stderr, \"Failed to create module from atom \\\"{a}\\\"\\n\");
1523 print_wasmer_error();
1524 return -1;
1525 }}
1526 ")?;
1527
1528 write!(deallocate_module, "wasm_module_delete(atom_{atom_name});")?;
1529 }
1530
1531 let volumes_str = entrypoint
1532 .volumes
1533 .iter()
1534 .map(|v| utils::normalize_atom_name(&v.name).to_uppercase())
1535 .map(|uppercase| {
1536 format!(
1537 "extern size_t {uppercase}_LENGTH asm(\"{uppercase}_LENGTH\");\r\nextern char {uppercase}_DATA asm(\"{uppercase}_DATA\");"
1538 )
1539 })
1540 .collect::<Vec<_>>();
1541
1542 let base_str = WASMER_MAIN_C_SOURCE;
1543 let volumes_str = volumes_str.join("\r\n");
1544 let return_str = base_str
1545 .replace(
1546 "#define WASI",
1547 if !volumes_str.trim().is_empty() {
1548 "#define WASI\r\n#define WASI_PIRITA"
1549 } else {
1550 "#define WASI"
1551 },
1552 )
1553 .replace("// DECLARE_MODULES", &c_code_to_add)
1554 .replace("// DECLARE_VOLUMES", &volumes_str)
1555 .replace(
1556 "// SET_NUMBER_OF_COMMANDS",
1557 &format!("number_of_commands = {};", atom_names.len()),
1558 )
1559 .replace("// EXTRA_HEADERS", &extra_headers.join("\r\n"))
1560 .replace("wasm_module_delete(module);", &deallocate_module);
1561
1562 if atom_names.len() == 1 {
1563 let prefix = prefixes
1564 .get_prefix_for_atom(&utils::normalize_atom_name(atom_names[0]))
1565 .ok_or_else(|| {
1566 let formatted_prefixes = format!("{prefixes:#?}");
1567 anyhow::anyhow!(
1568 "cannot find prefix for atom {} when generating wasmer_main.c ({formatted_prefixes})",
1569 &atom_names[0]
1570 )
1571 })?;
1572 write!(c_code_to_instantiate, "module = atom_{prefix};")?;
1573 } else {
1574 for a in atom_names.iter() {
1575 let prefix = prefixes
1576 .get_prefix_for_atom(&utils::normalize_atom_name(a))
1577 .ok_or_else(|| {
1578 let formatted_prefixes = format!("{prefixes:#?}");
1579 anyhow::anyhow!(
1580 "cannot find prefix for atom {a} when generating wasmer_main.c ({formatted_prefixes})"
1581 )
1582 })?;
1583 writeln!(
1584 c_code_to_instantiate,
1585 "if (strcmp(selected_atom, \"{a}\") == 0) {{ module = atom_{prefix}; }}",
1586 )?;
1587 }
1588 }
1589
1590 write!(
1591 c_code_to_instantiate,
1592 "
1593 if (!module) {{
1594 fprintf(stderr, \"No --command given, available commands are:\\n\");
1595 fprintf(stderr, \"\\n\");
1596 {commands}
1597 fprintf(stderr, \"\\n\");
1598 return -1;
1599 }}
1600 ",
1601 commands = atom_names
1602 .iter()
1603 .map(|a| format!("fprintf(stderr, \" {a}\\n\");"))
1604 .collect::<Vec<_>>()
1605 .join("\n")
1606 )?;
1607
1608 Ok(return_str.replace("// INSTANTIATE_MODULES", &c_code_to_instantiate))
1609}
1610
1611#[allow(dead_code)]
1612pub(super) mod utils {
1613
1614 use std::{
1615 ffi::OsStr,
1616 path::{Path, PathBuf},
1617 };
1618
1619 use anyhow::{Context, anyhow};
1620 use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
1621 use wasmer_types::target::{CpuFeature, Target};
1622
1623 use crate::config::WasmerEnv;
1624
1625 use super::{CrossCompile, CrossCompileSetup, UrlOrVersion};
1626
1627 pub(in crate::commands) fn target_triple_to_target(
1628 target_triple: &Triple,
1629 cpu_features: &[CpuFeature],
1630 ) -> Target {
1631 let mut features = cpu_features.iter().fold(CpuFeature::set(), |a, b| a | *b);
1632 if target_triple.architecture == Architecture::X86_64 {
1635 features |= CpuFeature::SSE2;
1636 }
1637 Target::new(target_triple.clone(), features)
1638 }
1639
1640 pub(in crate::commands) fn get_cross_compile_setup(
1641 env: &WasmerEnv,
1642 cross_subc: &mut CrossCompile,
1643 target_triple: &Triple,
1644 starting_cd: &Path,
1645 specific_release: Option<UrlOrVersion>,
1646 ) -> Result<CrossCompileSetup, anyhow::Error> {
1647 let target = target_triple;
1648
1649 if let Some(tarball_path) = cross_subc.tarball.as_mut()
1650 && tarball_path.is_relative()
1651 {
1652 *tarball_path = starting_cd.join(&tarball_path);
1653 if !tarball_path.exists() {
1654 return Err(anyhow!(
1655 "Tarball path `{}` does not exist.",
1656 tarball_path.display()
1657 ));
1658 } else if tarball_path.is_dir() {
1659 return Err(anyhow!(
1660 "Tarball path `{}` is a directory.",
1661 tarball_path.display()
1662 ));
1663 }
1664 }
1665
1666 let zig_binary_path = if !cross_subc.use_system_linker {
1667 find_zig_binary(cross_subc.zig_binary_path.as_ref().and_then(|p| {
1668 if p.is_absolute() {
1669 p.canonicalize().ok()
1670 } else {
1671 starting_cd.join(p).canonicalize().ok()
1672 }
1673 }))
1674 .ok()
1675 } else {
1676 None
1677 };
1678
1679 let library = if let Some(v) = cross_subc.library_path.clone() {
1680 Some(v.canonicalize().unwrap_or(v))
1681 } else if let Some(local_tarball) = cross_subc.tarball.as_ref() {
1682 let (filename, tarball_dir) = find_filename(local_tarball, target)?;
1683 Some(tarball_dir.join(filename))
1684 } else {
1685 let wasmer_cache_dir =
1686 if *target_triple == Triple::host() && std::env::var("WASMER_DIR").is_ok() {
1687 Some(env.cache_dir().to_path_buf())
1688 } else {
1689 get_libwasmer_cache_path(env).ok()
1690 };
1691
1692 let local_tarball = wasmer_cache_dir.as_ref().and_then(|wc| {
1694 let wasmer_cache = std::fs::read_dir(wc).ok()?;
1695 wasmer_cache
1696 .filter_map(|e| e.ok())
1697 .filter_map(|e| {
1698 let path = format!("{}", e.path().display());
1699 if path.ends_with(".tar.gz") {
1700 Some(e.path())
1701 } else {
1702 None
1703 }
1704 })
1705 .find(|p| crate::commands::utils::filter_tarball(p, target))
1706 });
1707
1708 if let Some(UrlOrVersion::Url(wasmer_release)) = specific_release.as_ref() {
1709 let tarball = super::http_fetch::download_url(env, wasmer_release.as_ref())?;
1710 let (filename, tarball_dir) = find_filename(&tarball, target)?;
1711 Some(tarball_dir.join(filename))
1712 } else if let Some(UrlOrVersion::Version(wasmer_release)) = specific_release.as_ref() {
1713 let release = super::http_fetch::get_release(Some(wasmer_release.clone()))?;
1714 let tarball = super::http_fetch::download_release(env, release, target.clone())?;
1715 let (filename, tarball_dir) = find_filename(&tarball, target)?;
1716 Some(tarball_dir.join(filename))
1717 } else if let Some(local_tarball) = local_tarball.as_ref() {
1718 let (filename, tarball_dir) = find_filename(local_tarball, target)?;
1719 Some(tarball_dir.join(filename))
1720 } else {
1721 let release = super::http_fetch::get_release(None)?;
1722 let tarball = super::http_fetch::download_release(env, release, target.clone())?;
1723 let (filename, tarball_dir) = find_filename(&tarball, target)?;
1724 Some(tarball_dir.join(filename))
1725 }
1726 };
1727
1728 let library = library.ok_or_else(|| anyhow!("libwasmer.a / wasmer.lib not found"))?;
1729
1730 let ccs = CrossCompileSetup {
1731 target: target.clone(),
1732 zig_binary_path,
1733 library,
1734 };
1735 Ok(ccs)
1736 }
1737
1738 pub(super) fn filter_tarball(p: &Path, target: &Triple) -> bool {
1739 filter_tarball_internal(p, target).unwrap_or(false)
1740 }
1741
1742 fn filter_tarball_internal(p: &Path, target: &Triple) -> Option<bool> {
1743 let filename = p.file_name()?.to_str()?;
1754
1755 if !filename.ends_with(".tar.gz") {
1756 return None;
1757 }
1758
1759 if filename.contains("wamr") || filename.contains("v8") || filename.contains("wasmi") {
1760 return None;
1761 }
1762
1763 if target.environment == Environment::Musl && !filename.contains("musl")
1764 || filename.contains("musl") && target.environment != Environment::Musl
1765 {
1766 return None;
1767 }
1768
1769 if let Architecture::Aarch64(_) = target.architecture
1770 && !(filename.contains("aarch64") || filename.contains("arm64"))
1771 {
1772 return None;
1773 }
1774
1775 if let Architecture::X86_64 = target.architecture {
1776 if target.operating_system == OperatingSystem::Windows {
1777 if !filename.contains("gnu64") {
1778 return None;
1779 }
1780 } else if !(filename.contains("x86_64") || filename.contains("amd64")) {
1781 return None;
1782 }
1783 }
1784
1785 if let OperatingSystem::Windows = target.operating_system
1786 && !filename.contains("windows")
1787 {
1788 return None;
1789 }
1790
1791 if let OperatingSystem::Darwin(_) = target.operating_system
1792 && !(filename.contains("apple") || filename.contains("darwin"))
1793 {
1794 return None;
1795 }
1796
1797 if let OperatingSystem::Linux = target.operating_system
1798 && !filename.contains("linux")
1799 {
1800 return None;
1801 }
1802
1803 Some(true)
1804 }
1805
1806 pub(super) fn find_filename(
1807 local_tarball: &Path,
1808 target: &Triple,
1809 ) -> Result<(PathBuf, PathBuf), anyhow::Error> {
1810 let target_file_path = local_tarball
1811 .parent()
1812 .and_then(|parent| Some(parent.join(local_tarball.file_stem()?)))
1813 .unwrap_or_else(|| local_tarball.to_path_buf());
1814
1815 let target_file_path = target_file_path
1816 .parent()
1817 .and_then(|parent| Some(parent.join(target_file_path.file_stem()?)))
1818 .unwrap_or_else(|| target_file_path.clone());
1819
1820 std::fs::create_dir_all(&target_file_path)
1821 .map_err(|e| anyhow!("{e}"))
1822 .with_context(|| anyhow!("{}", target_file_path.display()))?;
1823 let files =
1824 super::http_fetch::untar(local_tarball, &target_file_path).with_context(|| {
1825 anyhow!(
1826 "{} -> {}",
1827 local_tarball.display(),
1828 target_file_path.display()
1829 )
1830 })?;
1831 let tarball_dir = target_file_path.canonicalize().unwrap_or(target_file_path);
1832 let file = find_libwasmer_in_files(target, &files)?;
1833 Ok((file, tarball_dir))
1834 }
1835
1836 fn find_libwasmer_in_files(
1837 target: &Triple,
1838 files: &[PathBuf],
1839 ) -> Result<PathBuf, anyhow::Error> {
1840 let target_files = &[
1841 OsStr::new("libwasmer-headless.a"),
1842 OsStr::new("wasmer-headless.lib"),
1843 OsStr::new("libwasmer.a"),
1844 OsStr::new("wasmer.lib"),
1845 ];
1846 target_files
1847 .iter()
1848 .find_map(|q| {
1849 files.iter().find(|f| f.file_name() == Some(*q))
1850 })
1851 .cloned()
1852 .ok_or_else(|| {
1853 anyhow!(
1854 "Could not find libwasmer.a for {target} target in the provided tarball path (files = {files:#?})"
1855 )
1856 })
1857 }
1858
1859 pub(super) fn normalize_atom_name(s: &str) -> String {
1860 s.chars()
1861 .filter_map(|c| {
1862 if char::is_alphabetic(c) {
1863 Some(c)
1864 } else if c == '-' || c == '_' {
1865 Some('_')
1866 } else {
1867 None
1868 }
1869 })
1870 .collect()
1871 }
1872
1873 pub(super) fn triple_to_zig_triple(target_triple: &Triple) -> String {
1874 let arch = match target_triple.architecture {
1875 Architecture::X86_64 => "x86_64".into(),
1876 Architecture::Aarch64(wasmer_types::target::Aarch64Architecture::Aarch64) => {
1877 "aarch64".into()
1878 }
1879 v => v.to_string(),
1880 };
1881 let os = match target_triple.operating_system {
1882 OperatingSystem::Linux => "linux".into(),
1883 OperatingSystem::Darwin(_) => "macos".into(),
1884 OperatingSystem::Windows => "windows".into(),
1885 v => v.to_string(),
1886 };
1887 let env = match target_triple.environment {
1888 Environment::Musl => "musl",
1889 Environment::Gnu => "gnu",
1890 Environment::Msvc => "msvc",
1891 _ => "none",
1892 };
1893 format!("{arch}-{os}-{env}")
1894 }
1895
1896 pub(super) fn get_wasmer_include_directory(env: &WasmerEnv) -> anyhow::Result<PathBuf> {
1897 let mut path = env.dir().to_path_buf();
1898 if path.clone().join("wasmer.h").exists() {
1899 return Ok(path);
1900 }
1901 path.push("include");
1902 if !path.clone().join("wasmer.h").exists() {
1903 if !path.exists() {
1904 return Err(anyhow!("WASMER_DIR path {} does not exist", path.display()));
1905 }
1906 println!(
1907 "wasmer.h does not exist in {}, will probably default to the system path",
1908 path.canonicalize().unwrap().display()
1909 );
1910 }
1911 Ok(path)
1912 }
1913
1914 pub(super) fn get_libwasmer_path(env: &WasmerEnv) -> anyhow::Result<PathBuf> {
1916 let path = env.dir().to_path_buf();
1917
1918 #[cfg(not(windows))]
1920 let libwasmer_static_name = "libwasmer.a";
1921 #[cfg(windows)]
1922 let libwasmer_static_name = "libwasmer.lib";
1923
1924 if path.exists() && path.join(libwasmer_static_name).exists() {
1925 Ok(path.join(libwasmer_static_name))
1926 } else {
1927 Ok(path.join("lib").join(libwasmer_static_name))
1928 }
1929 }
1930
1931 pub(super) fn get_libwasmer_cache_path(env: &WasmerEnv) -> anyhow::Result<PathBuf> {
1933 let mut path = env.dir().to_path_buf();
1934 path.push("cache");
1935 std::fs::create_dir_all(&path)?;
1936 Ok(path)
1937 }
1938
1939 pub(super) fn get_zig_exe_str() -> &'static str {
1940 #[cfg(target_os = "windows")]
1941 {
1942 "zig.exe"
1943 }
1944 #[cfg(not(target_os = "windows"))]
1945 {
1946 "zig"
1947 }
1948 }
1949
1950 pub(super) fn find_zig_binary(path: Option<PathBuf>) -> Result<PathBuf, anyhow::Error> {
1951 use std::env::split_paths;
1952 #[cfg(not(unix))]
1953 use std::ffi::OsStr;
1954 #[cfg(unix)]
1955 use std::ffi::OsString;
1956 #[cfg(unix)]
1957 use std::os::unix::ffi::OsStringExt;
1958 let path_var = std::env::var("PATH").unwrap_or_default();
1959 #[cfg(unix)]
1960 let system_path_var = std::process::Command::new("getconf")
1961 .args(["PATH"])
1962 .output()
1963 .map(|output| OsString::from_vec(output.stdout))
1964 .unwrap_or_default();
1965 let retval = if let Some(p) = path {
1966 if p.exists() {
1967 p
1968 } else {
1969 return Err(anyhow!("Could not find `zig` binary in {}.", p.display()));
1970 }
1971 } else {
1972 let mut retval = None;
1973 #[cfg(unix)]
1974 let combined_paths =
1975 split_paths(&path_var).chain(split_paths(system_path_var.as_os_str()));
1976 #[cfg(not(unix))]
1977 let combined_paths = split_paths(&path_var).chain(split_paths(OsStr::new("")));
1978 for mut p in combined_paths {
1979 p.push(get_zig_exe_str());
1980 if p.exists() {
1981 retval = Some(p);
1982 break;
1983 }
1984 }
1985 retval.ok_or_else(|| anyhow!("Could not find `zig` binary in PATH."))?
1986 };
1987
1988 let version = std::process::Command::new(&retval)
1989 .arg("version")
1990 .output()
1991 .with_context(|| {
1992 format!(
1993 "Could not execute `zig` binary at path `{}`",
1994 retval.display()
1995 )
1996 })?
1997 .stdout;
1998 let version_slice = if let Some(pos) = version
1999 .iter()
2000 .position(|c| !(c.is_ascii_digit() || (*c == b'.')))
2001 {
2002 &version[..pos]
2003 } else {
2004 &version[..]
2005 };
2006
2007 let version_slice = String::from_utf8_lossy(version_slice);
2008 let version_semver = semver::Version::parse(&version_slice)
2009 .map_err(|e| anyhow!("could not parse zig version: {version_slice}: {e}"))?;
2010
2011 if version_semver < semver::Version::parse("0.10.0").unwrap() {
2012 Err(anyhow!(
2013 "`zig` binary in PATH (`{}`) is not a new enough version (`{version_slice}`): please use version `0.10.0` or newer.",
2014 retval.display()
2015 ))
2016 } else {
2017 Ok(retval)
2018 }
2019 }
2020
2021 #[test]
2022 fn test_filter_tarball() {
2023 use std::str::FromStr;
2024 let test_paths = [
2025 "/test/wasmer-darwin-amd64.tar.gz",
2026 "/test/wasmer-darwin-arm64.tar.gz",
2027 "/test/wasmer-linux-aarch64.tar.gz",
2028 "/test/wasmer-linux-amd64.tar.gz",
2029 "/test/wasmer-linux-musl-amd64.tar.gz",
2030 "/test/wasmer-windows-amd64.tar.gz",
2031 "/test/wasmer-windows-gnu64.tar.gz",
2032 "/test/wasmer-windows.exe",
2033 ];
2034
2035 let paths = test_paths.iter().map(Path::new).collect::<Vec<_>>();
2036 assert_eq!(
2037 paths
2038 .iter()
2039 .filter(|p| crate::commands::utils::filter_tarball(
2040 p,
2041 &Triple::from_str("x86_64-windows").unwrap()
2042 ))
2043 .collect::<Vec<_>>(),
2044 vec![&Path::new("/test/wasmer-windows-gnu64.tar.gz")],
2045 );
2046
2047 let paths = test_paths.iter().map(Path::new).collect::<Vec<_>>();
2048 assert_eq!(
2049 paths
2050 .iter()
2051 .filter(|p| crate::commands::utils::filter_tarball(
2052 p,
2053 &Triple::from_str("x86_64-windows-gnu").unwrap()
2054 ))
2055 .collect::<Vec<_>>(),
2056 vec![&Path::new("/test/wasmer-windows-gnu64.tar.gz")],
2057 );
2058
2059 let paths = test_paths.iter().map(Path::new).collect::<Vec<_>>();
2060 assert_eq!(
2061 paths
2062 .iter()
2063 .filter(|p| crate::commands::utils::filter_tarball(
2064 p,
2065 &Triple::from_str("x86_64-windows-msvc").unwrap()
2066 ))
2067 .collect::<Vec<_>>(),
2068 vec![&Path::new("/test/wasmer-windows-gnu64.tar.gz")],
2069 );
2070
2071 assert_eq!(
2072 paths
2073 .iter()
2074 .filter(|p| crate::commands::utils::filter_tarball(
2075 p,
2076 &Triple::from_str("x86_64-darwin").unwrap()
2077 ))
2078 .collect::<Vec<_>>(),
2079 vec![&Path::new("/test/wasmer-darwin-amd64.tar.gz")],
2080 );
2081
2082 assert_eq!(
2083 paths
2084 .iter()
2085 .filter(|p| crate::commands::utils::filter_tarball(
2086 p,
2087 &Triple::from_str("x86_64-unknown-linux-gnu").unwrap()
2088 ))
2089 .collect::<Vec<_>>(),
2090 vec![&Path::new("/test/wasmer-linux-amd64.tar.gz")],
2091 );
2092
2093 assert_eq!(
2094 paths
2095 .iter()
2096 .filter(|p| crate::commands::utils::filter_tarball(
2097 p,
2098 &Triple::from_str("x86_64-linux-gnu").unwrap()
2099 ))
2100 .collect::<Vec<_>>(),
2101 vec![&Path::new("/test/wasmer-linux-amd64.tar.gz")],
2102 );
2103
2104 assert_eq!(
2105 paths
2106 .iter()
2107 .filter(|p| crate::commands::utils::filter_tarball(
2108 p,
2109 &Triple::from_str("aarch64-linux-gnu").unwrap()
2110 ))
2111 .collect::<Vec<_>>(),
2112 vec![&Path::new("/test/wasmer-linux-aarch64.tar.gz")],
2113 );
2114
2115 assert_eq!(
2116 paths
2117 .iter()
2118 .filter(|p| crate::commands::utils::filter_tarball(
2119 p,
2120 &Triple::from_str("x86_64-windows-gnu").unwrap()
2121 ))
2122 .collect::<Vec<_>>(),
2123 vec![&Path::new("/test/wasmer-windows-gnu64.tar.gz")],
2124 );
2125
2126 assert_eq!(
2127 paths
2128 .iter()
2129 .filter(|p| crate::commands::utils::filter_tarball(
2130 p,
2131 &Triple::from_str("aarch64-darwin").unwrap()
2132 ))
2133 .collect::<Vec<_>>(),
2134 vec![&Path::new("/test/wasmer-darwin-arm64.tar.gz")],
2135 );
2136 }
2137
2138 #[test]
2139 fn test_normalize_atom_name() {
2140 assert_eq!(
2141 normalize_atom_name("atom-name-with-dash"),
2142 "atom_name_with_dash".to_string()
2143 );
2144 }
2145}
2146
2147mod http_fetch {
2148 use std::path::Path;
2149
2150 use anyhow::{Context, Result, anyhow};
2151
2152 pub(super) fn get_release(
2153 release_version: Option<semver::Version>,
2154 ) -> Result<serde_json::Value> {
2155 let uri = "https://api.github.com/repos/wasmerio/wasmer/releases";
2156
2157 let auth = std::env::var("GITHUB_TOKEN");
2159
2160 let client = reqwest::blocking::Client::new();
2161 let mut req = client.get(uri);
2162 if let Ok(token) = auth {
2163 req = req.header("Authorization", &format!("Bearer {token}"));
2164 }
2165
2166 let response = req
2167 .header("User-Agent", "wasmerio")
2168 .header("Accept", "application/vnd.github.v3+json")
2169 .send()
2170 .map_err(anyhow::Error::new)
2171 .context("Could not lookup wasmer repository on Github.")?;
2172
2173 let status = response.status();
2174
2175 log::info!("GitHub api response status: {status}");
2176
2177 let body = response
2178 .bytes()
2179 .map_err(anyhow::Error::new)
2180 .context("Could not retrieve wasmer release history body")?;
2181
2182 if status != reqwest::StatusCode::OK {
2183 log::warn!(
2184 "Warning: Github API replied with non-200 status code: {}. Response: {}",
2185 status,
2186 String::from_utf8_lossy(&body),
2187 );
2188 }
2189
2190 let mut response = serde_json::from_slice::<serde_json::Value>(&body)?;
2191
2192 if let Some(releases) = response.as_array_mut() {
2193 releases.retain(|r| {
2194 r["tag_name"].is_string() && !r["tag_name"].as_str().unwrap().is_empty()
2195 });
2196 releases.sort_by_cached_key(|r| r["tag_name"].as_str().unwrap_or_default().to_string());
2197 match release_version {
2198 Some(specific_version) => {
2199 let mut all_versions = Vec::new();
2200 for r in releases.iter() {
2201 if r["tag_name"].as_str().unwrap_or_default()
2202 == specific_version.to_string()
2203 {
2204 return Ok(r.clone());
2205 } else {
2206 all_versions
2207 .push(r["tag_name"].as_str().unwrap_or_default().to_string());
2208 }
2209 }
2210 return Err(anyhow::anyhow!(
2211 "could not find release version {}, available versions are: {}",
2212 specific_version,
2213 all_versions.join(", ")
2214 ));
2215 }
2216 None => {
2217 if let Some(latest) = releases.pop() {
2218 return Ok(latest);
2219 }
2220 }
2221 }
2222 }
2223
2224 Err(anyhow!(
2225 "Could not get expected Github API response.\n\nReason: response format is not recognized:\n{response:#?}",
2226 ))
2227 }
2228
2229 pub(super) fn download_release(
2230 env: &WasmerEnv,
2231 mut release: serde_json::Value,
2232 target_triple: wasmer::sys::Triple,
2233 ) -> Result<std::path::PathBuf> {
2234 if let Ok(mut cache_path) = super::utils::get_libwasmer_cache_path(env) {
2236 let paths = std::fs::read_dir(&cache_path).and_then(|r| {
2237 r.map(|res| res.map(|e| e.path()))
2238 .collect::<Result<Vec<_>, std::io::Error>>()
2239 });
2240
2241 if let Ok(mut entries) = paths {
2242 entries.retain(|p| p.to_str().map(|p| p.ends_with(".tar.gz")).unwrap_or(false));
2243 entries.retain(|p| super::utils::filter_tarball(p, &target_triple));
2244 if !entries.is_empty() {
2245 cache_path.push(&entries[0]);
2246 if cache_path.exists() {
2247 eprintln!(
2248 "Using cached tarball to cache path `{}`.",
2249 cache_path.display()
2250 );
2251 return Ok(cache_path);
2252 }
2253 }
2254 }
2255 }
2256
2257 let assets = match release["assets"].as_array_mut() {
2258 Some(s) => s,
2259 None => {
2260 return Err(anyhow!(
2261 "GitHub API: no [assets] array in JSON response for latest releases"
2262 ));
2263 }
2264 };
2265
2266 assets.retain(|a| {
2267 let name = match a["name"].as_str() {
2268 Some(s) => s,
2269 None => return false,
2270 };
2271 super::utils::filter_tarball(Path::new(&name), &target_triple)
2272 });
2273
2274 if assets.len() != 1 {
2275 return Err(anyhow!(
2276 "GitHub API: more than one release selected for target {target_triple}: {assets:#?}"
2277 ));
2278 }
2279
2280 let browser_download_url = if let Some(url) = assets[0]["browser_download_url"].as_str() {
2281 url.to_string()
2282 } else {
2283 return Err(anyhow!(
2284 "Could not get download url from Github API response."
2285 ));
2286 };
2287
2288 download_url(env, &browser_download_url)
2289 }
2290
2291 pub(crate) fn download_url(
2292 env: &WasmerEnv,
2293 browser_download_url: &str,
2294 ) -> Result<std::path::PathBuf, anyhow::Error> {
2295 let filename = browser_download_url
2296 .split('/')
2297 .next_back()
2298 .unwrap_or("output")
2299 .to_string();
2300
2301 let download_tempdir = tempfile::TempDir::new()?;
2302 let download_path = download_tempdir.path().join(&filename);
2303
2304 let mut file = std::fs::File::create(&download_path)?;
2305 log::debug!(
2306 "Downloading {} to {}",
2307 browser_download_url,
2308 download_path.display()
2309 );
2310
2311 let mut response = reqwest::blocking::Client::builder()
2312 .redirect(reqwest::redirect::Policy::limited(10))
2313 .timeout(std::time::Duration::from_secs(10))
2314 .build()
2315 .map_err(anyhow::Error::new)
2316 .context("Could not lookup wasmer artifact on Github.")?
2317 .get(browser_download_url)
2318 .send()
2319 .map_err(anyhow::Error::new)
2320 .context("Could not lookup wasmer artifact on Github.")?;
2321
2322 response
2323 .copy_to(&mut file)
2324 .map_err(|e| anyhow::anyhow!("{e}"))?;
2325
2326 match super::utils::get_libwasmer_cache_path(env) {
2327 Ok(mut cache_path) => {
2328 cache_path.push(&filename);
2329 if let Err(err) = std::fs::copy(&download_path, &cache_path) {
2330 eprintln!(
2331 "Could not store tarball to cache path `{}`: {}",
2332 cache_path.display(),
2333 err
2334 );
2335 Err(anyhow!(
2336 "Could not copy from {} to {}",
2337 download_path.display(),
2338 cache_path.display()
2339 ))
2340 } else {
2341 eprintln!("Cached tarball to cache path `{}`.", cache_path.display());
2342 Ok(cache_path)
2343 }
2344 }
2345 Err(err) => {
2346 eprintln!("Could not determine cache path for downloaded binaries.: {err}");
2347 Err(anyhow!("Could not determine libwasmer cache path"))
2348 }
2349 }
2350 }
2351
2352 use std::path::PathBuf;
2353
2354 use crate::{config::WasmerEnv, utils::unpack::try_unpack_targz};
2355
2356 pub(crate) fn list_dir(target: &Path) -> Vec<PathBuf> {
2357 use walkdir::WalkDir;
2358 WalkDir::new(target)
2359 .into_iter()
2360 .filter_map(|e| e.ok())
2361 .map(|entry| entry.path().to_path_buf())
2362 .collect()
2363 }
2364
2365 pub(super) fn untar(tarball: &Path, target: &Path) -> Result<Vec<PathBuf>> {
2366 let _ = std::fs::remove_dir(target);
2367 try_unpack_targz(tarball, target, false)?;
2368 Ok(list_dir(target))
2369 }
2370}