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