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