mirror of
https://github.com/mii443/wasmer.git
synced 2025-12-07 13:18:20 +00:00
1724 lines
61 KiB
Rust
1724 lines
61 KiB
Rust
//! Create a standalone native executable for a given Wasm file.
|
|
|
|
use super::ObjectFormat;
|
|
use crate::store::CompilerOptions;
|
|
use anyhow::{Context, Result};
|
|
use clap::Parser;
|
|
use std::env;
|
|
use std::fmt::Write as _;
|
|
use std::fs;
|
|
use std::fs::File;
|
|
use std::io::prelude::*;
|
|
use std::io::BufWriter;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
use wasmer::*;
|
|
use wasmer_object::{emit_serialized, get_object_for_target};
|
|
#[cfg(feature = "webc_runner")]
|
|
use webc::{ParseOptions, WebCMmap};
|
|
|
|
/// The `prefixer` returns the a String to prefix each of the
|
|
/// functions in the static object generated by the
|
|
/// so we can assure no collisions.
|
|
#[cfg(feature = "static-artifact-create")]
|
|
pub type PrefixerFn = Box<dyn Fn(&[u8]) -> String + Send>;
|
|
|
|
const WASMER_MAIN_C_SOURCE: &str = include_str!("wasmer_create_exe_main.c");
|
|
#[cfg(feature = "static-artifact-create")]
|
|
const WASMER_STATIC_MAIN_C_SOURCE: &str = include_str!("wasmer_static_create_exe_main.c");
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct CrossCompile {
|
|
/// Cross-compilation library path.
|
|
pub(crate) library_path: Option<PathBuf>,
|
|
|
|
/// Cross-compilation tarball library path.
|
|
pub(crate) tarball: Option<PathBuf>,
|
|
|
|
/// Specify `zig` binary path
|
|
pub(crate) zig_binary_path: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct CrossCompileSetup {
|
|
pub(crate) target: Triple,
|
|
pub(crate) zig_binary_path: PathBuf,
|
|
pub(crate) library: PathBuf,
|
|
}
|
|
|
|
#[derive(Debug, Parser)]
|
|
/// The options for the `wasmer create-exe` subcommand
|
|
pub struct CreateExe {
|
|
/// Input file
|
|
#[clap(name = "FILE", parse(from_os_str))]
|
|
path: PathBuf,
|
|
|
|
/// Output file
|
|
#[clap(name = "OUTPUT PATH", short = 'o', parse(from_os_str))]
|
|
output: PathBuf,
|
|
|
|
/// Compilation Target triple
|
|
///
|
|
/// Accepted target triple values must follow the
|
|
/// ['target_lexicon'](https://crates.io/crates/target-lexicon) crate format.
|
|
///
|
|
/// The recommended targets we try to support are:
|
|
///
|
|
/// - "x86_64-linux-gnu"
|
|
/// - "aarch64-linux-gnu"
|
|
/// - "x86_64-apple-darwin"
|
|
/// - "arm64-apple-darwin"
|
|
#[clap(long = "target")]
|
|
target_triple: Option<Triple>,
|
|
|
|
// Cross-compile with `zig`
|
|
/// Cross-compilation library path.
|
|
#[clap(long = "library-path")]
|
|
library_path: Option<PathBuf>,
|
|
|
|
/// Cross-compilation tarball library path.
|
|
#[clap(long = "tarball")]
|
|
tarball: Option<PathBuf>,
|
|
|
|
/// Specify `zig` binary path
|
|
#[clap(long = "zig-binary-path")]
|
|
zig_binary_path: Option<PathBuf>,
|
|
|
|
/// When compiling .webc files in separate steps
|
|
/// (create-obj, then create-exe on the resulting dir)
|
|
/// the create-exe step needs the path to the .webc file again
|
|
#[clap(long = "webc-volume-path")]
|
|
webc_volume_path: Option<PathBuf>,
|
|
|
|
/// Object format options
|
|
///
|
|
/// This flag accepts two options: `symbols` or `serialized`.
|
|
/// - (default) `symbols` creates an
|
|
/// executable where all functions and metadata of the module are regular object symbols
|
|
/// - `serialized` creates an executable where the module is zero-copy serialized as raw data
|
|
#[clap(name = "OBJECT_FORMAT", long = "object-format", verbatim_doc_comment)]
|
|
object_format: Option<ObjectFormat>,
|
|
|
|
/// Header file for object input
|
|
///
|
|
/// If given, the input `PATH` is assumed to be an object created with `wasmer create-obj` and
|
|
/// this is its accompanying header file.
|
|
#[clap(name = "HEADER", long = "header", verbatim_doc_comment)]
|
|
header: Option<PathBuf>,
|
|
|
|
#[clap(short = 'm')]
|
|
cpu_features: Vec<CpuFeature>,
|
|
|
|
/// Additional libraries to link against.
|
|
/// This is useful for fixing linker errors that may occur on some systems.
|
|
#[clap(short = 'l')]
|
|
libraries: Vec<String>,
|
|
|
|
#[clap(flatten)]
|
|
compiler: CompilerOptions,
|
|
}
|
|
|
|
impl CreateExe {
|
|
/// Runs logic for the `compile` subcommand
|
|
pub fn execute(&self) -> Result<()> {
|
|
/* Making library_path, tarball zig_binary_path flags require that target_triple flag
|
|
* is set cannot be encoded with structopt, so we have to perform cli flag validation
|
|
* manually here */
|
|
let cross_compile: Option<CrossCompile> = if self.target_triple.is_none()
|
|
&& (self.library_path.is_some()
|
|
|| self.tarball.is_some()
|
|
|| self.zig_binary_path.is_some())
|
|
{
|
|
return Err(anyhow!(
|
|
"To cross-compile an executable, you must specify a target triple with --target"
|
|
));
|
|
} else if self.target_triple.is_some() {
|
|
Some(CrossCompile {
|
|
library_path: self.library_path.clone(),
|
|
zig_binary_path: self.zig_binary_path.clone(),
|
|
tarball: self.tarball.clone(),
|
|
})
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let target = self
|
|
.target_triple
|
|
.as_ref()
|
|
.map(|target_triple| {
|
|
let mut features = self
|
|
.cpu_features
|
|
.clone()
|
|
.into_iter()
|
|
.fold(CpuFeature::set(), |a, b| a | b);
|
|
// Cranelift requires SSE2, so we have this "hack" for now to facilitate
|
|
// usage
|
|
if target_triple.architecture == Architecture::X86_64 {
|
|
features |= CpuFeature::SSE2;
|
|
}
|
|
Target::new(target_triple.clone(), features)
|
|
})
|
|
.unwrap_or_default();
|
|
|
|
let starting_cd = env::current_dir()?;
|
|
let wasm_module_path = starting_cd.join(&self.path);
|
|
let output_path = starting_cd.join(&self.output);
|
|
let object_format = self.object_format.unwrap_or(ObjectFormat::Symbols);
|
|
|
|
let cross_compilation = Self::get_cross_compile_setup(
|
|
cross_compile,
|
|
self.target_triple.clone(),
|
|
object_format,
|
|
&starting_cd,
|
|
)?;
|
|
|
|
#[cfg(feature = "webc_runner")]
|
|
{
|
|
let working_dir = tempdir::TempDir::new("testpirita")?;
|
|
let working_dir = working_dir.path().to_path_buf();
|
|
|
|
if let Ok(pirita) = WebCMmap::parse(wasm_module_path.clone(), &ParseOptions::default())
|
|
{
|
|
return self.create_exe_pirita(
|
|
&pirita,
|
|
target,
|
|
cross_compilation,
|
|
&working_dir,
|
|
output_path,
|
|
object_format,
|
|
);
|
|
}
|
|
}
|
|
|
|
let (store, compiler_type) = self.compiler.get_store_for_target(target.clone())?;
|
|
|
|
// Object is likely a file created from create-obj
|
|
#[cfg(feature = "webc_runner")]
|
|
if self.path.is_dir() {
|
|
let pirita_volume_path = self.webc_volume_path.clone()
|
|
.ok_or_else(|| anyhow::anyhow!("If compiling using a directory (created by create-obj), you need to also specify the webc path again using --webc-volume-path because the volumes.o is not part of the directory"))?;
|
|
|
|
let file = WebCMmap::parse(pirita_volume_path.clone(), &ParseOptions::default())
|
|
.map_err(|e| {
|
|
anyhow::anyhow!(
|
|
"could not parse {} as webc: {e}",
|
|
pirita_volume_path.display()
|
|
)
|
|
})?;
|
|
|
|
let volume_bytes = file.get_volumes_as_fileblock();
|
|
|
|
return self.link_exe_from_dir(
|
|
volume_bytes.as_slice(),
|
|
&store,
|
|
&target,
|
|
cross_compilation,
|
|
&self.path,
|
|
output_path,
|
|
object_format,
|
|
);
|
|
}
|
|
|
|
println!("Compiler: {}", compiler_type.to_string());
|
|
println!("Target: {}", target.triple());
|
|
println!("Format: {:?}", object_format);
|
|
|
|
#[cfg(not(windows))]
|
|
let wasm_object_path = PathBuf::from("wasm.o");
|
|
#[cfg(windows)]
|
|
let wasm_object_path = PathBuf::from("wasm.obj");
|
|
|
|
if let Some(header_path) = self.header.as_ref() {
|
|
/* In this case, since a header file is given, the input file is expected to be an
|
|
* object created with `create-obj` subcommand */
|
|
let header_path = starting_cd.join(&header_path);
|
|
std::fs::copy(&header_path, Path::new("static_defs.h"))
|
|
.context("Could not access given header file")?;
|
|
if let Some(setup) = cross_compilation.as_ref() {
|
|
self.compile_zig(
|
|
output_path,
|
|
&[wasm_module_path],
|
|
&[std::path::Path::new("static_defs.h").into()],
|
|
setup,
|
|
&[],
|
|
None,
|
|
None,
|
|
)?;
|
|
} else {
|
|
self.link(
|
|
output_path,
|
|
wasm_module_path,
|
|
std::path::Path::new("static_defs.h").into(),
|
|
&[],
|
|
None,
|
|
None,
|
|
)?;
|
|
}
|
|
} else {
|
|
match object_format {
|
|
ObjectFormat::Serialized => {
|
|
let module = Module::from_file(&store, &wasm_module_path)
|
|
.context("failed to compile Wasm")?;
|
|
let bytes = module.serialize()?;
|
|
let mut obj = get_object_for_target(target.triple())?;
|
|
emit_serialized(&mut obj, &bytes, target.triple(), "WASMER_MODULE")?;
|
|
let mut writer = BufWriter::new(File::create(&wasm_object_path)?);
|
|
obj.write_stream(&mut writer)
|
|
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
|
|
writer.flush()?;
|
|
drop(writer);
|
|
|
|
let cli_given_triple = self.target_triple.clone();
|
|
self.compile_c(wasm_object_path, cli_given_triple, output_path)?;
|
|
}
|
|
#[cfg(not(feature = "static-artifact-create"))]
|
|
ObjectFormat::Symbols => {
|
|
return Err(anyhow!("This version of wasmer-cli hasn't been compiled with static artifact support. You need to enable the `static-artifact-create` feature during compilation."));
|
|
}
|
|
#[cfg(feature = "static-artifact-create")]
|
|
ObjectFormat::Symbols => {
|
|
let engine = store.engine();
|
|
let engine_inner = engine.inner();
|
|
let compiler = engine_inner.compiler()?;
|
|
let features = engine_inner.features();
|
|
let tunables = store.tunables();
|
|
let data: Vec<u8> = fs::read(wasm_module_path)?;
|
|
let prefixer: Option<PrefixerFn> = None;
|
|
let (module_info, obj, metadata_length, symbol_registry) =
|
|
Artifact::generate_object(
|
|
compiler, &data, prefixer, &target, tunables, features,
|
|
)?;
|
|
|
|
let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
|
|
&module_info,
|
|
&*symbol_registry,
|
|
metadata_length,
|
|
);
|
|
// Write object file with functions
|
|
let object_file_path: std::path::PathBuf =
|
|
std::path::Path::new("functions.o").into();
|
|
let mut writer = BufWriter::new(File::create(&object_file_path)?);
|
|
obj.write_stream(&mut writer)
|
|
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
|
|
writer.flush()?;
|
|
// Write down header file that includes pointer arrays and the deserialize function
|
|
let mut writer = BufWriter::new(File::create("static_defs.h")?);
|
|
writer.write_all(header_file_src.as_bytes())?;
|
|
writer.flush()?;
|
|
if let Some(setup) = cross_compilation.as_ref() {
|
|
self.compile_zig(
|
|
output_path,
|
|
&[object_file_path],
|
|
&[std::path::Path::new("static_defs.h").into()],
|
|
setup,
|
|
&[],
|
|
None,
|
|
None,
|
|
)?;
|
|
} else {
|
|
self.link(
|
|
output_path,
|
|
object_file_path,
|
|
std::path::Path::new("static_defs.h").into(),
|
|
&[],
|
|
None,
|
|
None,
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if cross_compilation.is_some() {
|
|
eprintln!(
|
|
"✔ Cross-compiled executable for `{}` target compiled successfully to `{}`.",
|
|
target.triple(),
|
|
self.output.display(),
|
|
);
|
|
} else {
|
|
eprintln!(
|
|
"✔ Native executable compiled successfully to `{}`.",
|
|
self.output.display(),
|
|
);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn get_cross_compile_setup(
|
|
cross_compile: Option<CrossCompile>,
|
|
target_triple: Option<Triple>,
|
|
object_format: ObjectFormat,
|
|
starting_cd: &Path,
|
|
) -> Result<Option<CrossCompileSetup>, anyhow::Error> {
|
|
if let Some(mut cross_subc) = cross_compile.or_else(|| {
|
|
if target_triple.is_some() {
|
|
Some(CrossCompile {
|
|
library_path: None,
|
|
tarball: None,
|
|
zig_binary_path: None,
|
|
})
|
|
} else {
|
|
None
|
|
}
|
|
}) {
|
|
if let ObjectFormat::Serialized = object_format {
|
|
return Err(anyhow!(
|
|
"Cross-compilation with serialized object format is not implemented."
|
|
));
|
|
}
|
|
|
|
let target = if let Some(target_triple) = target_triple {
|
|
target_triple
|
|
} else {
|
|
return Err(anyhow!(
|
|
"To cross-compile an executable, you must specify a target triple with --target"
|
|
));
|
|
};
|
|
if let Some(tarball_path) = cross_subc.tarball.as_mut() {
|
|
if tarball_path.is_relative() {
|
|
*tarball_path = starting_cd.join(&tarball_path);
|
|
if !tarball_path.exists() {
|
|
return Err(anyhow!(
|
|
"Tarball path `{}` does not exist.",
|
|
tarball_path.display()
|
|
));
|
|
} else if tarball_path.is_dir() {
|
|
return Err(anyhow!(
|
|
"Tarball path `{}` is a directory.",
|
|
tarball_path.display()
|
|
));
|
|
}
|
|
}
|
|
}
|
|
let zig_binary_path =
|
|
find_zig_binary(cross_subc.zig_binary_path.as_ref().and_then(|p| {
|
|
if p.is_absolute() {
|
|
p.canonicalize().ok()
|
|
} else {
|
|
starting_cd.join(p).canonicalize().ok()
|
|
}
|
|
}))?;
|
|
let library = if let Some(v) = cross_subc.library_path.clone() {
|
|
v.canonicalize().unwrap_or(v)
|
|
} else {
|
|
let (filename, tarball_dir) =
|
|
if let Some(local_tarball) = cross_subc.tarball.as_ref() {
|
|
Self::find_filename(local_tarball, &target)
|
|
} else {
|
|
// check if the tarball for the target already exists locally
|
|
let local_tarball = std::fs::read_dir(get_libwasmer_cache_path()?)?
|
|
.filter_map(|e| e.ok())
|
|
.filter_map(|e| {
|
|
let path = format!("{}", e.path().display());
|
|
if path.ends_with(".tar.gz") {
|
|
Some(e.path())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.filter_map(|p| Self::filter_tarballs(&p, &target))
|
|
.next();
|
|
|
|
if let Some(local_tarball) = local_tarball.as_ref() {
|
|
Self::find_filename(local_tarball, &target)
|
|
} else {
|
|
let release = http_fetch::get_latest_release()?;
|
|
let tarball = http_fetch::download_release(release, target.clone())?;
|
|
Self::find_filename(&tarball, &target)
|
|
}
|
|
}?;
|
|
|
|
tarball_dir.join(&filename)
|
|
};
|
|
let ccs = CrossCompileSetup {
|
|
target,
|
|
zig_binary_path,
|
|
library,
|
|
};
|
|
Ok(Some(ccs))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn find_filename(
|
|
local_tarball: &Path,
|
|
target: &Triple,
|
|
) -> Result<(String, PathBuf), anyhow::Error> {
|
|
let target_file_path = local_tarball
|
|
.parent()
|
|
.and_then(|parent| Some(parent.join(local_tarball.file_stem()?)))
|
|
.unwrap_or_else(|| local_tarball.to_path_buf());
|
|
|
|
let target_file_path = target_file_path
|
|
.parent()
|
|
.and_then(|parent| Some(parent.join(target_file_path.file_stem()?)))
|
|
.unwrap_or_else(|| target_file_path.clone());
|
|
|
|
std::fs::create_dir_all(&target_file_path)
|
|
.map_err(|e| anyhow::anyhow!("{e}"))
|
|
.context(anyhow::anyhow!("{}", target_file_path.display()))?;
|
|
let files = untar(local_tarball.to_path_buf(), target_file_path.clone())?;
|
|
let tarball_dir = target_file_path.canonicalize().unwrap_or(target_file_path);
|
|
|
|
let file = files
|
|
.iter()
|
|
.find(|f| f.ends_with("libwasmer.a")).cloned()
|
|
.ok_or_else(|| {
|
|
anyhow!("Could not find libwasmer.a for {} target in the provided tarball path (files = {files:#?})", target)
|
|
})?;
|
|
|
|
Ok((file, tarball_dir))
|
|
}
|
|
|
|
fn filter_tarballs(p: &Path, target: &Triple) -> Option<PathBuf> {
|
|
if let Architecture::Aarch64(_) = target.architecture {
|
|
if !p.file_name()?.to_str()?.contains("aarch64") {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
if let Architecture::X86_64 = target.architecture {
|
|
if !p.file_name()?.to_str()?.contains("x86_64") {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
if let OperatingSystem::Windows = target.operating_system {
|
|
if !p.file_name()?.to_str()?.contains("windows") {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
if let OperatingSystem::Darwin = target.operating_system {
|
|
if !(p.file_name()?.to_str()?.contains("apple")
|
|
|| p.file_name()?.to_str()?.contains("darwin"))
|
|
{
|
|
return None;
|
|
}
|
|
}
|
|
|
|
if let OperatingSystem::Linux = target.operating_system {
|
|
if !p.file_name()?.to_str()?.contains("linux") {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
Some(p.to_path_buf())
|
|
}
|
|
|
|
fn compile_c(
|
|
&self,
|
|
wasm_object_path: PathBuf,
|
|
target_triple: Option<wasmer::Triple>,
|
|
output_path: PathBuf,
|
|
) -> anyhow::Result<()> {
|
|
let tempdir = tempdir::TempDir::new("compile-c")?;
|
|
let tempdir_path = tempdir.path();
|
|
|
|
// write C src to disk
|
|
let c_src_path = tempdir_path.join("wasmer_main.c");
|
|
#[cfg(not(windows))]
|
|
let c_src_obj = tempdir_path.join("wasmer_main.o");
|
|
#[cfg(windows)]
|
|
let c_src_obj = tempdir_path.join("wasmer_main.obj");
|
|
|
|
std::fs::write(
|
|
&c_src_path,
|
|
WASMER_MAIN_C_SOURCE
|
|
.replace("// WASI_DEFINES", "#define WASI")
|
|
.as_bytes(),
|
|
)?;
|
|
|
|
run_c_compile(&c_src_path, &c_src_obj, target_triple.clone())
|
|
.context("Failed to compile C source code")?;
|
|
|
|
LinkCode {
|
|
object_paths: vec![c_src_obj, wasm_object_path],
|
|
output_path,
|
|
additional_libraries: self.libraries.clone(),
|
|
target: target_triple,
|
|
..Default::default()
|
|
}
|
|
.run()
|
|
.context("Failed to link objects together")?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn compile_zig(
|
|
&self,
|
|
output_path: PathBuf,
|
|
object_paths: &[PathBuf],
|
|
header_code_paths: &[PathBuf],
|
|
setup: &CrossCompileSetup,
|
|
pirita_atoms: &[String],
|
|
pirita_main_atom: Option<&str>,
|
|
pirita_volume_path: Option<PathBuf>,
|
|
) -> anyhow::Result<()> {
|
|
let tempdir = tempdir::TempDir::new("wasmer-static-compile-zig")?;
|
|
let tempdir_path = tempdir.path();
|
|
let c_src_path = tempdir_path.join("wasmer_main.c");
|
|
let CrossCompileSetup {
|
|
ref target,
|
|
ref zig_binary_path,
|
|
ref library,
|
|
} = setup;
|
|
let mut libwasmer_path = library.to_path_buf();
|
|
println!("Library Path: {}", libwasmer_path.display());
|
|
/* Cross compilation is only possible with zig */
|
|
println!("Using zig binary: {}", zig_binary_path.display());
|
|
let zig_triple = triple_to_zig_triple(target);
|
|
println!("Using zig target triple: {}", &zig_triple);
|
|
|
|
let lib_filename = libwasmer_path
|
|
.file_name()
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.to_string();
|
|
libwasmer_path.pop();
|
|
|
|
if let Some(entrypoint) = pirita_main_atom.as_ref() {
|
|
let c_code = Self::generate_pirita_wasmer_main_c_static(pirita_atoms, entrypoint);
|
|
std::fs::write(&c_src_path, c_code)?;
|
|
} else {
|
|
std::fs::write(&c_src_path, WASMER_STATIC_MAIN_C_SOURCE)?;
|
|
}
|
|
|
|
let mut header_code_paths = header_code_paths.to_vec();
|
|
|
|
for h in header_code_paths.iter_mut() {
|
|
if !h.is_dir() {
|
|
h.pop();
|
|
}
|
|
|
|
if h.display().to_string().is_empty() {
|
|
*h = std::env::current_dir()?;
|
|
}
|
|
}
|
|
|
|
/* Compile main function */
|
|
let compilation = {
|
|
let mut include_dir = libwasmer_path.clone();
|
|
include_dir.pop();
|
|
include_dir.push("include");
|
|
|
|
let mut cmd = Command::new(zig_binary_path);
|
|
cmd.arg("build-exe");
|
|
cmd.arg("-target");
|
|
cmd.arg(&zig_triple);
|
|
cmd.arg(&format!("-I{}/", include_dir.display()));
|
|
for h in header_code_paths {
|
|
cmd.arg(&format!("-I{}/", h.display()));
|
|
}
|
|
if zig_triple.contains("windows") {
|
|
cmd.arg("-lc++");
|
|
} else {
|
|
cmd.arg("-lc");
|
|
}
|
|
cmd.arg("-lunwind");
|
|
cmd.arg("-fno-compiler-rt");
|
|
cmd.arg(&format!("-femit-bin={}", output_path.display()));
|
|
|
|
for o in object_paths {
|
|
cmd.arg(o);
|
|
}
|
|
cmd.arg(&c_src_path);
|
|
cmd.arg(libwasmer_path.join(&lib_filename));
|
|
if zig_triple.contains("windows") {
|
|
let mut libwasmer_parent = libwasmer_path.clone();
|
|
libwasmer_parent.pop();
|
|
let files_winsdk = std::fs::read_dir(libwasmer_parent.join("winsdk"))
|
|
.ok()
|
|
.map(|res| res.filter_map(|r| Some(r.ok()?.path())).collect::<Vec<_>>())
|
|
.unwrap_or_default();
|
|
for f in files_winsdk {
|
|
cmd.arg(f);
|
|
}
|
|
}
|
|
if let Some(volume_obj) = pirita_volume_path.as_ref() {
|
|
cmd.arg(volume_obj.clone());
|
|
}
|
|
|
|
#[cfg(feature = "debug")]
|
|
log::debug!("{:?}", cmd);
|
|
cmd.output().context("Could not execute `zig`")?
|
|
};
|
|
if !compilation.status.success() {
|
|
return Err(anyhow::anyhow!(String::from_utf8_lossy(
|
|
&compilation.stderr
|
|
)
|
|
.to_string()));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
// Write the volumes.o file
|
|
#[cfg(feature = "webc_runner")]
|
|
fn write_volume_obj(
|
|
volume_bytes: &[u8],
|
|
target: &Target,
|
|
output_path: &Path,
|
|
) -> anyhow::Result<PathBuf> {
|
|
#[cfg(not(windows))]
|
|
let volume_object_path = output_path.join("volumes.o");
|
|
#[cfg(windows)]
|
|
let volume_object_path = output_path.join("volumes.obj");
|
|
|
|
let mut volumes_object = get_object_for_target(target.triple())?;
|
|
emit_serialized(
|
|
&mut volumes_object,
|
|
volume_bytes,
|
|
target.triple(),
|
|
"VOLUMES",
|
|
)?;
|
|
|
|
let mut writer = BufWriter::new(File::create(&volume_object_path)?);
|
|
volumes_object
|
|
.write_stream(&mut writer)
|
|
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
|
|
writer.flush()?;
|
|
drop(writer);
|
|
|
|
Ok(volume_object_path)
|
|
}
|
|
|
|
#[cfg(feature = "webc_runner")]
|
|
pub(crate) fn create_objs_pirita(
|
|
store: &Store,
|
|
file: &WebCMmap,
|
|
target: &Target,
|
|
output_path: &Path,
|
|
object_format: ObjectFormat,
|
|
) -> anyhow::Result<()> {
|
|
if !output_path.is_dir() {
|
|
return Err(anyhow::anyhow!(
|
|
"Expected {} to be an output directory, not a file",
|
|
output_path.display()
|
|
));
|
|
}
|
|
|
|
std::fs::create_dir_all(output_path)?;
|
|
std::fs::create_dir_all(output_path.join("atoms"))?;
|
|
|
|
let atom_to_run = file
|
|
.manifest
|
|
.entrypoint
|
|
.as_ref()
|
|
.and_then(|s| file.get_atom_name_for_command("wasi", s).ok());
|
|
if let Some(atom_to_run) = atom_to_run.as_ref() {
|
|
std::fs::write(output_path.join("entrypoint"), atom_to_run)?;
|
|
}
|
|
|
|
for (atom_name, atom_bytes) in file.get_all_atoms() {
|
|
std::fs::create_dir_all(output_path.join("atoms"))?;
|
|
|
|
#[cfg(not(windows))]
|
|
let object_path = output_path.join("atoms").join(&format!("{atom_name}.o"));
|
|
#[cfg(windows)]
|
|
let object_path = output_path.join("atoms").join(&format!("{atom_name}.obj"));
|
|
|
|
std::fs::create_dir_all(output_path.join("atoms").join(&atom_name))?;
|
|
|
|
let header_path = output_path
|
|
.join("atoms")
|
|
.join(&atom_name)
|
|
.join("static_defs.h");
|
|
|
|
match object_format {
|
|
ObjectFormat::Serialized => {
|
|
let module = Module::new(&store, &atom_bytes)
|
|
.context(format!("Failed to compile atom {atom_name:?} to wasm"))?;
|
|
let bytes = module.serialize()?;
|
|
let mut obj = get_object_for_target(target.triple())?;
|
|
let atom_name_uppercase = atom_name.to_uppercase();
|
|
emit_serialized(&mut obj, &bytes, target.triple(), &atom_name_uppercase)?;
|
|
|
|
let mut writer = BufWriter::new(File::create(&object_path)?);
|
|
obj.write_stream(&mut writer)
|
|
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
|
|
writer.flush()?;
|
|
drop(writer);
|
|
}
|
|
#[cfg(feature = "static-artifact-create")]
|
|
ObjectFormat::Symbols => {
|
|
let engine = store.engine();
|
|
let engine_inner = engine.inner();
|
|
let compiler = engine_inner.compiler()?;
|
|
let features = engine_inner.features();
|
|
let tunables = store.tunables();
|
|
let prefixer: Option<PrefixerFn> = None;
|
|
let (module_info, obj, metadata_length, symbol_registry) =
|
|
Artifact::generate_object(
|
|
compiler, atom_bytes, prefixer, target, tunables, features,
|
|
)?;
|
|
|
|
let header_file_src = crate::c_gen::staticlib_header::generate_header_file(
|
|
&module_info,
|
|
&*symbol_registry,
|
|
metadata_length,
|
|
);
|
|
|
|
let mut writer = BufWriter::new(File::create(&object_path)?);
|
|
obj.write_stream(&mut writer)
|
|
.map_err(|err| anyhow::anyhow!(err.to_string()))?;
|
|
writer.flush()?;
|
|
|
|
let mut writer = BufWriter::new(File::create(&header_path)?);
|
|
writer.write_all(header_file_src.as_bytes())?;
|
|
writer.flush()?;
|
|
}
|
|
#[cfg(not(feature = "static-artifact-create"))]
|
|
ObjectFormat::Symbols => {
|
|
return Err(anyhow!("Objects cannot be compiled in format \"symbols\" without static-artifact-create feature"));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "webc_runner")]
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn link_exe_from_dir(
|
|
&self,
|
|
volume_bytes: &[u8],
|
|
_store: &Store,
|
|
target: &Target,
|
|
cross_compilation: Option<CrossCompileSetup>,
|
|
working_dir: &Path,
|
|
output_path: PathBuf,
|
|
object_format: ObjectFormat,
|
|
) -> anyhow::Result<()> {
|
|
let tempdir = tempdir::TempDir::new("link-exe-from-dir")?;
|
|
let tempdir_path = tempdir.path();
|
|
|
|
let entrypoint = std::fs::read_to_string(working_dir.join("entrypoint"))
|
|
.map_err(|_| anyhow::anyhow!("file has no entrypoint to run"))?;
|
|
|
|
if !working_dir.join("atoms").exists() {
|
|
return Err(anyhow::anyhow!("file has no atoms to compile"));
|
|
}
|
|
|
|
let mut atom_names = Vec::new();
|
|
for obj in std::fs::read_dir(working_dir.join("atoms"))? {
|
|
let path = obj?.path();
|
|
if !path.is_dir() {
|
|
if let Some(s) = path.file_stem() {
|
|
atom_names.push(
|
|
s.to_str()
|
|
.ok_or_else(|| anyhow::anyhow!("wrong atom name"))?
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
match object_format {
|
|
ObjectFormat::Serialized => {
|
|
let mut link_objects: Vec<PathBuf> = Vec::new();
|
|
|
|
let volume_object_path =
|
|
Self::write_volume_obj(volume_bytes, target, tempdir_path)?;
|
|
|
|
#[cfg(not(windows))]
|
|
let c_src_obj = working_dir.join("wasmer_main.o");
|
|
#[cfg(windows)]
|
|
let c_src_obj = working_dir.join("wasmer_main.obj");
|
|
|
|
for obj in std::fs::read_dir(working_dir.join("atoms"))? {
|
|
let path = obj?.path();
|
|
if !path.is_dir() {
|
|
link_objects.push(path.to_path_buf());
|
|
}
|
|
}
|
|
|
|
let c_code = Self::generate_pirita_wasmer_main_c(&atom_names, &entrypoint);
|
|
|
|
let c_src_path = working_dir.join("wasmer_main.c");
|
|
|
|
std::fs::write(&c_src_path, c_code.as_bytes())
|
|
.context("Failed to open C source code file")?;
|
|
|
|
// TODO: this branch is never hit because object format serialized +
|
|
// cross compilation doesn't work
|
|
if let Some(setup) = cross_compilation {
|
|
// zig treats .o files the same as .c files
|
|
link_objects.push(c_src_path);
|
|
|
|
self.compile_zig(
|
|
output_path,
|
|
&link_objects,
|
|
&[],
|
|
&setup,
|
|
&atom_names,
|
|
Some(&entrypoint),
|
|
Some(volume_object_path),
|
|
)?;
|
|
} else {
|
|
// compile with cc instead of zig
|
|
run_c_compile(c_src_path.as_path(), &c_src_obj, self.target_triple.clone())
|
|
.context("Failed to compile C source code")?;
|
|
|
|
link_objects.push(c_src_obj);
|
|
link_objects.push(volume_object_path);
|
|
|
|
LinkCode {
|
|
object_paths: link_objects,
|
|
output_path,
|
|
additional_libraries: self.libraries.clone(),
|
|
target: self.target_triple.clone(),
|
|
..Default::default()
|
|
}
|
|
.run()
|
|
.context("Failed to link objects together")?;
|
|
}
|
|
}
|
|
ObjectFormat::Symbols => {
|
|
let object_file_path = working_dir.join("atoms").join(&format!("{entrypoint}.o"));
|
|
let static_defs_file_path = working_dir
|
|
.join("atoms")
|
|
.join(&entrypoint)
|
|
.join("static_defs.h");
|
|
let volumes_obj_path = Self::write_volume_obj(volume_bytes, target, tempdir_path)?;
|
|
|
|
if let Some(setup) = cross_compilation.as_ref() {
|
|
self.compile_zig(
|
|
output_path,
|
|
&[object_file_path],
|
|
&[static_defs_file_path],
|
|
setup,
|
|
&atom_names,
|
|
Some(&entrypoint),
|
|
Some(volumes_obj_path),
|
|
)?;
|
|
} else {
|
|
self.link(
|
|
output_path,
|
|
object_file_path,
|
|
static_defs_file_path,
|
|
&atom_names,
|
|
Some(&entrypoint),
|
|
Some(volumes_obj_path),
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn normalize_atom_name(s: &str) -> String {
|
|
s.chars()
|
|
.filter_map(|c| {
|
|
if char::is_alphabetic(c) {
|
|
Some(c)
|
|
} else if c == '-' {
|
|
Some('_')
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn generate_pirita_wasmer_main_c_static(atom_names: &[String], atom_to_run: &str) -> String {
|
|
let mut c_code_to_instantiate = String::new();
|
|
let mut deallocate_module = String::new();
|
|
|
|
let atom_to_run = Self::normalize_atom_name(atom_to_run);
|
|
|
|
for atom_name in atom_names.iter() {
|
|
let atom_name = Self::normalize_atom_name(atom_name);
|
|
|
|
write!(
|
|
c_code_to_instantiate,
|
|
"
|
|
|
|
wasm_module_t *atom_{atom_name} = wasmer_object_module_new(store, \"{atom_name}\");
|
|
|
|
if (!atom_{atom_name}) {{
|
|
fprintf(stderr, \"Failed to create module from atom \\\"{atom_name}\\\"\\n\");
|
|
print_wasmer_error();
|
|
return -1;
|
|
}}
|
|
"
|
|
)
|
|
.unwrap();
|
|
write!(deallocate_module, "wasm_module_delete(atom_{atom_name});").unwrap();
|
|
}
|
|
|
|
write!(
|
|
c_code_to_instantiate,
|
|
"wasm_module_t *module = atom_{atom_to_run};"
|
|
)
|
|
.unwrap();
|
|
|
|
WASMER_STATIC_MAIN_C_SOURCE
|
|
.replace("#define WASI", "#define WASI\r\n#define WASI_PIRITA")
|
|
.replace("// INSTANTIATE_MODULES", &c_code_to_instantiate)
|
|
.replace("##atom-name##", &atom_to_run)
|
|
.replace("wasm_module_delete(module);", &deallocate_module)
|
|
}
|
|
|
|
#[cfg(feature = "webc_runner")]
|
|
fn generate_pirita_wasmer_main_c(atom_names: &[String], atom_to_run: &str) -> String {
|
|
let mut c_code_to_add = String::new();
|
|
let mut c_code_to_instantiate = String::new();
|
|
let mut deallocate_module = String::new();
|
|
|
|
for atom_name in atom_names.iter() {
|
|
let atom_name = Self::normalize_atom_name(atom_name);
|
|
let atom_name_uppercase = atom_name.to_uppercase();
|
|
|
|
c_code_to_add.push_str(&format!(
|
|
"
|
|
extern size_t {atom_name_uppercase}_LENGTH asm(\"{atom_name_uppercase}_LENGTH\");
|
|
extern char {atom_name_uppercase}_DATA asm(\"{atom_name_uppercase}_DATA\");
|
|
"
|
|
));
|
|
|
|
c_code_to_instantiate.push_str(&format!("
|
|
wasm_byte_vec_t atom_{atom_name}_byte_vec = {{
|
|
.size = {atom_name_uppercase}_LENGTH,
|
|
.data = &{atom_name_uppercase}_DATA,
|
|
}};
|
|
wasm_module_t *atom_{atom_name} = wasm_module_deserialize(store, &atom_{atom_name}_byte_vec);
|
|
|
|
if (!atom_{atom_name}) {{
|
|
fprintf(stderr, \"Failed to create module from atom \\\"{atom_name}\\\"\\n\");
|
|
print_wasmer_error();
|
|
return -1;
|
|
}}
|
|
"));
|
|
deallocate_module.push_str(&format!("wasm_module_delete(atom_{atom_name});"));
|
|
}
|
|
|
|
c_code_to_instantiate.push_str(&format!("wasm_module_t *module = atom_{atom_to_run};"));
|
|
|
|
WASMER_MAIN_C_SOURCE
|
|
.replace("#define WASI", "#define WASI\r\n#define WASI_PIRITA")
|
|
.replace("// DECLARE_MODULES", &c_code_to_add)
|
|
.replace("// INSTANTIATE_MODULES", &c_code_to_instantiate)
|
|
.replace("##atom-name##", atom_to_run)
|
|
.replace("wasm_module_delete(module);", &deallocate_module)
|
|
}
|
|
|
|
#[cfg(feature = "webc_runner")]
|
|
fn create_exe_pirita(
|
|
&self,
|
|
file: &WebCMmap,
|
|
target: Target,
|
|
cross_compilation: Option<CrossCompileSetup>,
|
|
working_dir: &Path,
|
|
output_path: PathBuf,
|
|
object_format: ObjectFormat,
|
|
) -> anyhow::Result<()> {
|
|
let _ = std::fs::create_dir_all(&working_dir);
|
|
let (store, _) = self.compiler.get_store_for_target(target.clone())?;
|
|
|
|
Self::create_objs_pirita(&store, file, &target, working_dir, object_format)?;
|
|
|
|
let volumes_obj = file.get_volumes_as_fileblock();
|
|
self.link_exe_from_dir(
|
|
volumes_obj.as_slice(),
|
|
&store,
|
|
&target,
|
|
cross_compilation,
|
|
working_dir,
|
|
output_path,
|
|
object_format,
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "static-artifact-create")]
|
|
fn link(
|
|
&self,
|
|
output_path: PathBuf,
|
|
object_path: PathBuf,
|
|
mut header_code_path: PathBuf,
|
|
pirita_atoms: &[String],
|
|
pirita_main_atom: Option<&str>,
|
|
pirita_volume_path: Option<PathBuf>,
|
|
) -> anyhow::Result<()> {
|
|
let tempdir = tempdir::TempDir::new("wasmer-static-compile")?;
|
|
let tempdir_path = tempdir.path();
|
|
|
|
let mut object_paths = vec![object_path, "main_obj.obj".into()];
|
|
if let Some(volume_obj) = pirita_volume_path.as_ref() {
|
|
object_paths.push(volume_obj.to_path_buf());
|
|
}
|
|
|
|
let linkcode = LinkCode {
|
|
object_paths,
|
|
output_path,
|
|
..Default::default()
|
|
};
|
|
let c_src_path = tempdir_path.join("wasmer_main.c");
|
|
let mut libwasmer_path = get_libwasmer_path()?
|
|
.canonicalize()
|
|
.context("Failed to find libwasmer")?;
|
|
|
|
let lib_filename = libwasmer_path
|
|
.file_name()
|
|
.unwrap()
|
|
.to_str()
|
|
.unwrap()
|
|
.to_string();
|
|
libwasmer_path.pop();
|
|
|
|
if let Some(entrypoint) = pirita_main_atom.as_ref() {
|
|
let c_code = Self::generate_pirita_wasmer_main_c_static(pirita_atoms, entrypoint);
|
|
std::fs::write(&c_src_path, c_code)?;
|
|
} else {
|
|
std::fs::write(&c_src_path, WASMER_STATIC_MAIN_C_SOURCE)?;
|
|
}
|
|
|
|
if !header_code_path.is_dir() {
|
|
header_code_path.pop();
|
|
}
|
|
|
|
if header_code_path.display().to_string().is_empty() {
|
|
header_code_path = std::env::current_dir()?;
|
|
}
|
|
|
|
let wasmer_include_dir = get_wasmer_include_directory()?;
|
|
let wasmer_h_path = wasmer_include_dir.join("wasmer.h");
|
|
if !wasmer_h_path.exists() {
|
|
return Err(anyhow::anyhow!(
|
|
"Could not find wasmer.h in {}",
|
|
wasmer_include_dir.display()
|
|
));
|
|
}
|
|
let wasm_h_path = wasmer_include_dir.join("wasm.h");
|
|
if !wasm_h_path.exists() {
|
|
return Err(anyhow::anyhow!(
|
|
"Could not find wasm.h in {}",
|
|
wasmer_include_dir.display()
|
|
));
|
|
}
|
|
std::fs::copy(wasmer_h_path, header_code_path.join("wasmer.h"))?;
|
|
std::fs::copy(wasm_h_path, header_code_path.join("wasm.h"))?;
|
|
|
|
/* Compile main function */
|
|
let compilation = {
|
|
Command::new("cc")
|
|
.arg("-c")
|
|
.arg(&c_src_path)
|
|
.arg(if linkcode.optimization_flag.is_empty() {
|
|
"-O2"
|
|
} else {
|
|
linkcode.optimization_flag.as_str()
|
|
})
|
|
.arg(&format!("-L{}", libwasmer_path.display()))
|
|
.arg(&format!("-l:{}", lib_filename))
|
|
//.arg("-lwasmer")
|
|
// Add libraries required per platform.
|
|
// We need userenv, sockets (Ws2_32), advapi32 for some system calls and bcrypt for random numbers.
|
|
//#[cfg(windows)]
|
|
// .arg("-luserenv")
|
|
// .arg("-lWs2_32")
|
|
// .arg("-ladvapi32")
|
|
// .arg("-lbcrypt")
|
|
// On unix we need dlopen-related symbols, libmath for a few things, and pthreads.
|
|
//#[cfg(not(windows))]
|
|
.arg("-ldl")
|
|
.arg("-lm")
|
|
.arg("-pthread")
|
|
.arg(&format!("-I{}", header_code_path.display()))
|
|
.arg("-v")
|
|
.arg("-o")
|
|
.arg("main_obj.obj")
|
|
.output()?
|
|
};
|
|
if !compilation.status.success() {
|
|
return Err(anyhow::anyhow!(String::from_utf8_lossy(
|
|
&compilation.stderr
|
|
)
|
|
.to_string()));
|
|
}
|
|
linkcode.run().context("Failed to link objects together")?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_normalize_atom_name() {
|
|
assert_eq!(
|
|
CreateExe::normalize_atom_name("atom-name-with-dash"),
|
|
"atom_name_with_dash".to_string()
|
|
);
|
|
}
|
|
|
|
fn triple_to_zig_triple(target_triple: &Triple) -> String {
|
|
let arch = match target_triple.architecture {
|
|
wasmer_types::Architecture::X86_64 => "x86_64".into(),
|
|
wasmer_types::Architecture::Aarch64(wasmer_types::Aarch64Architecture::Aarch64) => {
|
|
"aarch64".into()
|
|
}
|
|
v => v.to_string(),
|
|
};
|
|
let os = match target_triple.operating_system {
|
|
wasmer_types::OperatingSystem::Linux => "linux".into(),
|
|
wasmer_types::OperatingSystem::Darwin => "macos".into(),
|
|
wasmer_types::OperatingSystem::Windows => "windows".into(),
|
|
v => v.to_string(),
|
|
};
|
|
let env = match target_triple.environment {
|
|
wasmer_types::Environment::Musl => "musl",
|
|
wasmer_types::Environment::Gnu => "gnu",
|
|
wasmer_types::Environment::Msvc => "msvc",
|
|
_ => "none",
|
|
};
|
|
format!("{}-{}-{}", arch, os, env)
|
|
}
|
|
|
|
fn get_wasmer_dir() -> anyhow::Result<PathBuf> {
|
|
Ok(PathBuf::from(
|
|
env::var("WASMER_DIR")
|
|
.or_else(|e| {
|
|
option_env!("WASMER_INSTALL_PREFIX")
|
|
.map(str::to_string)
|
|
.ok_or(e)
|
|
})
|
|
.context("Trying to read env var `WASMER_DIR`")?,
|
|
))
|
|
}
|
|
|
|
fn get_wasmer_include_directory() -> anyhow::Result<PathBuf> {
|
|
let mut path = get_wasmer_dir()?;
|
|
if path.clone().join("wasmer.h").exists() {
|
|
return Ok(path);
|
|
}
|
|
path.push("include");
|
|
if !path.clone().join("wasmer.h").exists() {
|
|
println!(
|
|
"wasmer.h does not exist in {}, will probably default to the system path",
|
|
path.canonicalize().unwrap().display()
|
|
);
|
|
}
|
|
Ok(path)
|
|
}
|
|
|
|
/// path to the static libwasmer
|
|
fn get_libwasmer_path() -> anyhow::Result<PathBuf> {
|
|
let path = get_wasmer_dir()?;
|
|
|
|
// TODO: prefer headless Wasmer if/when it's a separate library.
|
|
#[cfg(not(windows))]
|
|
let libwasmer_static_name = "libwasmer.a";
|
|
#[cfg(windows)]
|
|
let libwasmer_static_name = "libwasmer.lib";
|
|
|
|
if path.exists() && path.join(libwasmer_static_name).exists() {
|
|
Ok(path.join(libwasmer_static_name))
|
|
} else {
|
|
Ok(path.join("lib").join(libwasmer_static_name))
|
|
}
|
|
}
|
|
|
|
/// path to library tarball cache dir
|
|
fn get_libwasmer_cache_path() -> anyhow::Result<PathBuf> {
|
|
let mut path = get_wasmer_dir()?;
|
|
path.push("cache");
|
|
let _ = std::fs::create_dir(&path);
|
|
Ok(path)
|
|
}
|
|
|
|
/// Compile the C code.
|
|
fn run_c_compile(
|
|
path_to_c_src: &Path,
|
|
output_name: &Path,
|
|
target: Option<Triple>,
|
|
) -> anyhow::Result<()> {
|
|
#[cfg(not(windows))]
|
|
let c_compiler = "cc";
|
|
// We must use a C++ compiler on Windows because wasm.h uses `static_assert`
|
|
// which isn't available in `clang` on Windows.
|
|
#[cfg(windows)]
|
|
let c_compiler = "clang++";
|
|
|
|
let mut command = Command::new(c_compiler);
|
|
let command = command
|
|
.arg("-Wall")
|
|
.arg("-O2")
|
|
.arg("-c")
|
|
.arg(path_to_c_src)
|
|
.arg("-I")
|
|
.arg(get_wasmer_include_directory()?);
|
|
|
|
let command = if let Some(target) = target {
|
|
command.arg("-target").arg(format!("{}", target))
|
|
} else {
|
|
command
|
|
};
|
|
|
|
let output = command.arg("-o").arg(output_name).output()?;
|
|
eprintln!(
|
|
"run_c_compile: stdout: {}\n\nstderr: {}",
|
|
std::str::from_utf8(&output.stdout)
|
|
.expect("stdout is not utf8! need to handle arbitrary bytes"),
|
|
std::str::from_utf8(&output.stderr)
|
|
.expect("stderr is not utf8! need to handle arbitrary bytes")
|
|
);
|
|
|
|
if !output.status.success() {
|
|
bail!(
|
|
"C code compile failed with: stdout: {}\n\nstderr: {}",
|
|
std::str::from_utf8(&output.stdout)
|
|
.expect("stdout is not utf8! need to handle arbitrary bytes"),
|
|
std::str::from_utf8(&output.stderr)
|
|
.expect("stderr is not utf8! need to handle arbitrary bytes")
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Data used to run a linking command for generated artifacts.
|
|
#[derive(Debug)]
|
|
struct LinkCode {
|
|
/// Path to the linker used to run the linking command.
|
|
linker_path: PathBuf,
|
|
/// String used as an optimization flag.
|
|
optimization_flag: String,
|
|
/// Paths of objects to link.
|
|
object_paths: Vec<PathBuf>,
|
|
/// Additional libraries to link against.
|
|
additional_libraries: Vec<String>,
|
|
/// Path to the output target.
|
|
output_path: PathBuf,
|
|
/// Path to the dir containing the static libwasmer library.
|
|
libwasmer_path: PathBuf,
|
|
/// The target to link the executable for.
|
|
target: Option<Triple>,
|
|
}
|
|
|
|
impl Default for LinkCode {
|
|
fn default() -> Self {
|
|
#[cfg(not(windows))]
|
|
let linker = "cc";
|
|
#[cfg(windows)]
|
|
let linker = "clang";
|
|
Self {
|
|
linker_path: PathBuf::from(linker),
|
|
optimization_flag: String::from("-O2"),
|
|
object_paths: vec![],
|
|
additional_libraries: vec![],
|
|
output_path: PathBuf::from("a.out"),
|
|
libwasmer_path: get_libwasmer_path().unwrap(),
|
|
target: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl LinkCode {
|
|
fn run(&self) -> anyhow::Result<()> {
|
|
let libwasmer_path = self
|
|
.libwasmer_path
|
|
.canonicalize()
|
|
.context("Failed to find libwasmer")?;
|
|
println!(
|
|
"Using path `{}` as libwasmer path.",
|
|
libwasmer_path.display()
|
|
);
|
|
let mut command = Command::new(&self.linker_path);
|
|
let command = command
|
|
.arg("-Wall")
|
|
.arg(&self.optimization_flag)
|
|
.args(
|
|
self.object_paths
|
|
.iter()
|
|
.map(|path| path.canonicalize().unwrap()),
|
|
)
|
|
.arg(&libwasmer_path);
|
|
let command = if let Some(target) = &self.target {
|
|
command.arg("-target").arg(format!("{}", target))
|
|
} else {
|
|
command
|
|
};
|
|
// Add libraries required per platform.
|
|
// We need userenv, sockets (Ws2_32), advapi32 for some system calls and bcrypt for random numbers.
|
|
#[cfg(windows)]
|
|
let command = command
|
|
.arg("-luserenv")
|
|
.arg("-lWs2_32")
|
|
.arg("-ladvapi32")
|
|
.arg("-lbcrypt");
|
|
// On unix we need dlopen-related symbols, libmath for a few things, and pthreads.
|
|
#[cfg(not(windows))]
|
|
let command = command.arg("-ldl").arg("-lm").arg("-pthread");
|
|
let link_against_extra_libs = self
|
|
.additional_libraries
|
|
.iter()
|
|
.map(|lib| format!("-l{}", lib));
|
|
let command = command.args(link_against_extra_libs);
|
|
let command = command.arg("-o").arg(&self.output_path);
|
|
let output = command.output()?;
|
|
|
|
if !output.status.success() {
|
|
bail!(
|
|
"linking failed with: stdout: {}\n\nstderr: {}",
|
|
std::str::from_utf8(&output.stdout)
|
|
.expect("stdout is not utf8! need to handle arbitrary bytes"),
|
|
std::str::from_utf8(&output.stderr)
|
|
.expect("stderr is not utf8! need to handle arbitrary bytes")
|
|
);
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
mod http_fetch {
|
|
use anyhow::{anyhow, Context, Result};
|
|
use http_req::{request::Request, response::StatusCode, uri::Uri};
|
|
use std::convert::TryFrom;
|
|
use target_lexicon::OperatingSystem;
|
|
|
|
pub fn get_latest_release() -> Result<serde_json::Value> {
|
|
let mut writer = Vec::new();
|
|
let uri = Uri::try_from("https://api.github.com/repos/wasmerio/wasmer/releases").unwrap();
|
|
|
|
// Increases rate-limiting in GitHub CI
|
|
let auth = std::env::var("GITHUB_TOKEN");
|
|
let mut response = Request::new(&uri);
|
|
|
|
if let Ok(token) = auth {
|
|
response.header("Authorization", &format!("Bearer {token}"));
|
|
}
|
|
|
|
let response = response
|
|
.header("User-Agent", "wasmerio")
|
|
.header("Accept", "application/vnd.github.v3+json")
|
|
.timeout(Some(std::time::Duration::new(30, 0)))
|
|
.send(&mut writer)
|
|
.map_err(anyhow::Error::new)
|
|
.context("Could not lookup wasmer repository on Github.")?;
|
|
|
|
if response.status_code() != StatusCode::new(200) {
|
|
#[cfg(feature = "debug")]
|
|
log::warn!(
|
|
"Warning: Github API replied with non-200 status code: {}. Response: {}",
|
|
response.status_code(),
|
|
String::from_utf8_lossy(&writer),
|
|
);
|
|
}
|
|
|
|
let v: std::result::Result<serde_json::Value, _> = serde_json::from_reader(&*writer);
|
|
let mut response = v.map_err(anyhow::Error::new)?;
|
|
|
|
if let Some(releases) = response.as_array_mut() {
|
|
releases.retain(|r| {
|
|
r["tag_name"].is_string() && !r["tag_name"].as_str().unwrap().is_empty()
|
|
});
|
|
releases.sort_by_cached_key(|r| r["tag_name"].as_str().unwrap_or_default().to_string());
|
|
if let Some(latest) = releases.pop() {
|
|
return Ok(latest);
|
|
}
|
|
}
|
|
|
|
Err(anyhow!(
|
|
"Could not get expected Github API response.\n\nReason: response format is not recognized:\n{response:#?}",
|
|
))
|
|
}
|
|
|
|
pub fn download_release(
|
|
mut release: serde_json::Value,
|
|
target_triple: wasmer::Triple,
|
|
) -> Result<std::path::PathBuf> {
|
|
let check_arch = |name: &str| -> bool {
|
|
match target_triple.architecture {
|
|
wasmer_types::Architecture::X86_64 => {
|
|
name.contains("x86_64") || name.contains("amd64")
|
|
}
|
|
wasmer_types::Architecture::Aarch64(wasmer_types::Aarch64Architecture::Aarch64) => {
|
|
name.contains("arm64") || name.contains("aarch64")
|
|
}
|
|
_ => false,
|
|
}
|
|
};
|
|
|
|
let check_vendor = |name: &str| -> bool {
|
|
match target_triple.vendor {
|
|
wasmer_types::Vendor::Apple => {
|
|
name.contains("apple") || name.contains("macos") || name.contains("darwin")
|
|
}
|
|
wasmer_types::Vendor::Pc => name.contains("windows"),
|
|
_ => true,
|
|
}
|
|
};
|
|
let check_os = |name: &str| -> bool {
|
|
match target_triple.operating_system {
|
|
wasmer_types::OperatingSystem::Darwin => {
|
|
name.contains("apple") || name.contains("darwin") || name.contains("macos")
|
|
}
|
|
wasmer_types::OperatingSystem::Windows => name.contains("windows"),
|
|
wasmer_types::OperatingSystem::Linux => name.contains("linux"),
|
|
_ => false,
|
|
}
|
|
};
|
|
let check_env = |name: &str| -> bool {
|
|
match target_triple.environment {
|
|
wasmer_types::Environment::Musl => name.contains("musl"),
|
|
_ => !name.contains("musl"),
|
|
}
|
|
};
|
|
|
|
// Test if file has been already downloaded
|
|
if let Ok(mut cache_path) = super::get_libwasmer_cache_path() {
|
|
let paths = std::fs::read_dir(&cache_path).and_then(|r| {
|
|
r.map(|res| res.map(|e| e.path()))
|
|
.collect::<Result<Vec<_>, std::io::Error>>()
|
|
});
|
|
|
|
if let Ok(mut entries) = paths {
|
|
entries.retain(|p| p.to_str().map(|p| p.ends_with(".tar.gz")).unwrap_or(false));
|
|
|
|
// create-exe on Windows is special: we use the windows-gnu64.tar.gz (GNU ABI)
|
|
// to link, not the windows-amd64.tar.gz (MSVC ABI)
|
|
if target_triple.operating_system == OperatingSystem::Windows {
|
|
entries.retain(|p| {
|
|
p.to_str()
|
|
.map(|p| p.contains("windows") && p.contains("gnu64"))
|
|
.unwrap_or(false)
|
|
});
|
|
} else {
|
|
entries.retain(|p| p.to_str().map(|p| check_arch(p)).unwrap_or(true));
|
|
entries.retain(|p| p.to_str().map(|p| check_vendor(p)).unwrap_or(true));
|
|
entries.retain(|p| p.to_str().map(|p| check_os(p)).unwrap_or(true));
|
|
entries.retain(|p| p.to_str().map(|p| check_env(p)).unwrap_or(true));
|
|
}
|
|
|
|
if !entries.is_empty() {
|
|
cache_path.push(&entries[0]);
|
|
if cache_path.exists() {
|
|
eprintln!(
|
|
"Using cached tarball to cache path `{}`.",
|
|
cache_path.display()
|
|
);
|
|
return Ok(cache_path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let assets = match release["assets"].as_array_mut() {
|
|
Some(s) => s,
|
|
None => {
|
|
return Err(anyhow!(
|
|
"GitHub API: no [assets] array in JSON response for latest releases"
|
|
));
|
|
}
|
|
};
|
|
|
|
// create-exe on Windows is special: we use the windows-gnu64.tar.gz (GNU ABI)
|
|
// to link, not the windows-amd64.tar.gz (MSVC ABI)
|
|
if target_triple.operating_system == OperatingSystem::Windows {
|
|
assets.retain(|a| {
|
|
if let Some(name) = a["name"].as_str() {
|
|
name.contains("windows") && name.contains("gnu64")
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
} else {
|
|
assets.retain(|a| {
|
|
if let Some(name) = a["name"].as_str() {
|
|
check_arch(name)
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
assets.retain(|a| {
|
|
if let Some(name) = a["name"].as_str() {
|
|
check_vendor(name)
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
|
|
assets.retain(|a| {
|
|
if let Some(name) = a["name"].as_str() {
|
|
check_os(name)
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
|
|
assets.retain(|a| {
|
|
if let Some(name) = a["name"].as_str() {
|
|
check_env(name)
|
|
} else {
|
|
false
|
|
}
|
|
});
|
|
}
|
|
|
|
if assets.len() != 1 {
|
|
return Err(anyhow!(
|
|
"GitHub API: more that one release selected for target {target_triple}: {assets:?}"
|
|
));
|
|
}
|
|
|
|
let browser_download_url = if let Some(url) = assets[0]["browser_download_url"].as_str() {
|
|
url.to_string()
|
|
} else {
|
|
return Err(anyhow!(
|
|
"Could not get download url from Github API response."
|
|
));
|
|
};
|
|
|
|
let filename = browser_download_url
|
|
.split('/')
|
|
.last()
|
|
.unwrap_or("output")
|
|
.to_string();
|
|
|
|
let download_tempdir = tempdir::TempDir::new("wasmer-download")?;
|
|
let download_path = download_tempdir.path().join(&filename);
|
|
|
|
let mut file = std::fs::File::create(&download_path)?;
|
|
#[cfg(feature = "debug")]
|
|
log::debug!(
|
|
"Downloading {} to {}",
|
|
browser_download_url,
|
|
download_path.display()
|
|
);
|
|
|
|
let mut response = reqwest::blocking::Client::builder()
|
|
.redirect(reqwest::redirect::Policy::limited(10))
|
|
.timeout(std::time::Duration::from_secs(10))
|
|
.build()
|
|
.map_err(anyhow::Error::new)
|
|
.context("Could not lookup wasmer artifact on Github.")?
|
|
.get(browser_download_url.as_str())
|
|
.send()
|
|
.map_err(anyhow::Error::new)
|
|
.context("Could not lookup wasmer artifact on Github.")?;
|
|
|
|
response
|
|
.copy_to(&mut file)
|
|
.map_err(|e| anyhow::anyhow!("{e}"))?;
|
|
|
|
match super::get_libwasmer_cache_path() {
|
|
Ok(mut cache_path) => {
|
|
cache_path.push(&filename);
|
|
if let Err(err) = std::fs::copy(&download_path, &cache_path) {
|
|
eprintln!(
|
|
"Could not store tarball to cache path `{}`: {}",
|
|
cache_path.display(),
|
|
err
|
|
);
|
|
Err(anyhow!(
|
|
"Could not copy from {} to {}",
|
|
download_path.display(),
|
|
cache_path.display()
|
|
))
|
|
} else {
|
|
eprintln!("Cached tarball to cache path `{}`.", cache_path.display());
|
|
Ok(cache_path)
|
|
}
|
|
}
|
|
Err(err) => {
|
|
eprintln!(
|
|
"Could not determine cache path for downloaded binaries.: {}",
|
|
err
|
|
);
|
|
Err(anyhow!("Could not determine libwasmer cache path"))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn untar(tarball: std::path::PathBuf, target: std::path::PathBuf) -> Result<Vec<String>> {
|
|
use walkdir::WalkDir;
|
|
|
|
wasmer_registry::try_unpack_targz(&tarball, &target, false)?;
|
|
|
|
Ok(WalkDir::new(&target)
|
|
.into_iter()
|
|
.filter_map(|e| e.ok())
|
|
.map(|entry| format!("{}", entry.path().display()))
|
|
.collect())
|
|
}
|
|
|
|
fn get_zig_exe_str() -> &'static str {
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
"zig.exe"
|
|
}
|
|
#[cfg(not(target_os = "windows"))]
|
|
{
|
|
"zig"
|
|
}
|
|
}
|
|
|
|
fn find_zig_binary(path: Option<PathBuf>) -> Result<PathBuf> {
|
|
use std::env::split_paths;
|
|
use std::ffi::OsStr;
|
|
#[cfg(unix)]
|
|
use std::os::unix::ffi::OsStrExt;
|
|
let path_var = std::env::var("PATH").unwrap_or_default();
|
|
#[cfg(unix)]
|
|
let system_path_var = std::process::Command::new("getconf")
|
|
.args(&["PATH"])
|
|
.output()
|
|
.map(|output| output.stdout)
|
|
.unwrap_or_default();
|
|
let retval = if let Some(p) = path {
|
|
if p.exists() {
|
|
p
|
|
} else {
|
|
return Err(anyhow!("Could not find `zig` binary in {}.", p.display()));
|
|
}
|
|
} else {
|
|
let mut retval = None;
|
|
for mut p in split_paths(&path_var).chain(split_paths(
|
|
#[cfg(unix)]
|
|
{
|
|
&OsStr::from_bytes(&system_path_var[..])
|
|
},
|
|
#[cfg(not(unix))]
|
|
{
|
|
OsStr::new("")
|
|
},
|
|
)) {
|
|
p.push(get_zig_exe_str());
|
|
if p.exists() {
|
|
retval = Some(p);
|
|
break;
|
|
}
|
|
}
|
|
retval.ok_or_else(|| anyhow!("Could not find `zig` binary in PATH."))?
|
|
};
|
|
|
|
let version = std::process::Command::new(&retval)
|
|
.arg("version")
|
|
.output()
|
|
.with_context(|| {
|
|
format!(
|
|
"Could not execute `zig` binary at path `{}`",
|
|
retval.display()
|
|
)
|
|
})?
|
|
.stdout;
|
|
let version_slice = if let Some(pos) = version
|
|
.iter()
|
|
.position(|c| !(c.is_ascii_digit() || (*c == b'.')))
|
|
{
|
|
&version[..pos]
|
|
} else {
|
|
&version[..]
|
|
};
|
|
|
|
if version_slice < b"0.10.0".as_ref() {
|
|
Err(anyhow!("`zig` binary in PATH (`{}`) is not a new enough version (`{}`): please use version `0.10.0` or newer.", retval.display(), String::from_utf8_lossy(version_slice)))
|
|
} else {
|
|
Ok(retval)
|
|
}
|
|
}
|