Skip to content

Commit

Permalink
Allow workspace to be built on wasm (#3840)
Browse files Browse the repository at this point in the history
  • Loading branch information
cwfitzgerald authored Jun 6, 2023
1 parent 3f0aed7 commit 4aff9b6
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 151 deletions.
4 changes: 3 additions & 1 deletion cts_runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ description = "CTS runner for wgpu"
license.workspace = true
publish = false

[dependencies]
# We make all dependencies conditional on not being wasm,
# so the whole workspace can built as wasm.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
deno_console.workspace = true
deno_core.workspace = true
deno_url.workspace = true
Expand Down
303 changes: 156 additions & 147 deletions cts_runner/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,168 +1,177 @@
use std::{
env, fmt,
io::{Read, Write},
rc::Rc,
};

use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::resolve_url_or_path;
use deno_core::serde_json::json;
use deno_core::v8;
use deno_core::JsRuntime;
use deno_core::RuntimeOptions;
use deno_core::ZeroCopyBuf;
use deno_web::BlobStore;
use termcolor::Ansi;
use termcolor::Color::Red;
use termcolor::ColorSpec;
use termcolor::WriteColor;

#[tokio::main(flavor = "current_thread")]
async fn main() {
unwrap_or_exit(run().await)
}

async fn run() -> Result<(), AnyError> {
let mut args_iter = env::args();
let _ = args_iter.next();
let url = args_iter
.next()
.ok_or_else(|| anyhow!("missing specifier in first command line argument"))?;
let specifier = resolve_url_or_path(&url)?;

let options = RuntimeOptions {
module_loader: Some(Rc::new(deno_core::FsModuleLoader)),
get_error_class_fn: Some(&get_error_class_name),
extensions: vec![
deno_webidl::init_esm(),
deno_console::init_esm(),
deno_url::init_ops_and_esm(),
deno_web::init_ops_and_esm::<Permissions>(BlobStore::default(), None),
deno_webgpu::init_ops_and_esm(true),
extension(),
],
..Default::default()
#[cfg(not(target_arch = "wasm32"))]
mod native {
use std::{
env, fmt,
io::{Read, Write},
rc::Rc,
};
let mut isolate = JsRuntime::new(options);
let args = args_iter.collect::<Vec<String>>();
let cfg = json!({"args": args, "cwd": env::current_dir().unwrap().to_string_lossy() });

{
let context = isolate.global_context();
let scope = &mut isolate.handle_scope();
let context_local = v8::Local::new(scope, context);
let global_obj = context_local.global(scope);
let bootstrap_str = v8::String::new(scope, "bootstrap").unwrap();
let bootstrap_fn = global_obj.get(scope, bootstrap_str.into()).unwrap();
let bootstrap_fn = v8::Local::<v8::Function>::try_from(bootstrap_fn).unwrap();

let options_v8 = deno_core::serde_v8::to_v8(scope, cfg).unwrap();
let bootstrap_fn = v8::Local::new(scope, bootstrap_fn);
let undefined = v8::undefined(scope);
bootstrap_fn
.call(scope, undefined.into(), &[options_v8])
.unwrap();
}

isolate.op_state().borrow_mut().put(Permissions {});
use deno_core::anyhow::anyhow;
use deno_core::error::AnyError;
use deno_core::op;
use deno_core::resolve_url_or_path;
use deno_core::serde_json::json;
use deno_core::v8;
use deno_core::JsRuntime;
use deno_core::RuntimeOptions;
use deno_core::ZeroCopyBuf;
use deno_web::BlobStore;
use termcolor::Ansi;
use termcolor::Color::Red;
use termcolor::ColorSpec;
use termcolor::WriteColor;

pub async fn run() -> Result<(), AnyError> {
let mut args_iter = env::args();
let _ = args_iter.next();
let url = args_iter
.next()
.ok_or_else(|| anyhow!("missing specifier in first command line argument"))?;
let specifier = resolve_url_or_path(&url)?;

let options = RuntimeOptions {
module_loader: Some(Rc::new(deno_core::FsModuleLoader)),
get_error_class_fn: Some(&get_error_class_name),
extensions: vec![
deno_webidl::init_esm(),
deno_console::init_esm(),
deno_url::init_ops_and_esm(),
deno_web::init_ops_and_esm::<Permissions>(BlobStore::default(), None),
deno_webgpu::init_ops_and_esm(true),
extension(),
],
..Default::default()
};
let mut isolate = JsRuntime::new(options);
let args = args_iter.collect::<Vec<String>>();
let cfg = json!({"args": args, "cwd": env::current_dir().unwrap().to_string_lossy() });

{
let context = isolate.global_context();
let scope = &mut isolate.handle_scope();
let context_local = v8::Local::new(scope, context);
let global_obj = context_local.global(scope);
let bootstrap_str = v8::String::new(scope, "bootstrap").unwrap();
let bootstrap_fn = global_obj.get(scope, bootstrap_str.into()).unwrap();
let bootstrap_fn = v8::Local::<v8::Function>::try_from(bootstrap_fn).unwrap();

let options_v8 = deno_core::serde_v8::to_v8(scope, cfg).unwrap();
let bootstrap_fn = v8::Local::new(scope, bootstrap_fn);
let undefined = v8::undefined(scope);
bootstrap_fn
.call(scope, undefined.into(), &[options_v8])
.unwrap();
}

let mod_id = isolate.load_main_module(&specifier, None).await?;
let mod_rx = isolate.mod_evaluate(mod_id);
isolate.op_state().borrow_mut().put(Permissions {});

let rx = tokio::spawn(async move {
match mod_rx.await {
Ok(err @ Err(_)) => err,
_ => Ok(()),
}
});
let mod_id = isolate.load_main_module(&specifier, None).await?;
let mod_rx = isolate.mod_evaluate(mod_id);

isolate.run_event_loop(false).await?;
rx.await.unwrap()?;
let rx = tokio::spawn(async move {
match mod_rx.await {
Ok(err @ Err(_)) => err,
_ => Ok(()),
}
});

Ok(())
}
isolate.run_event_loop(false).await?;
rx.await.unwrap()?;

fn extension() -> deno_core::Extension {
deno_core::Extension::builder(env!("CARGO_PKG_NAME"))
.ops(vec![
op_exit::decl(),
op_read_file_sync::decl(),
op_write_file_sync::decl(),
])
.esm(deno_core::include_js_files!("bootstrap.js",))
.build()
}
Ok(())
}

#[op]
fn op_exit(code: i32) -> Result<(), AnyError> {
std::process::exit(code)
}
fn extension() -> deno_core::Extension {
deno_core::Extension::builder(env!("CARGO_PKG_NAME"))
.ops(vec![
op_exit::decl(),
op_read_file_sync::decl(),
op_write_file_sync::decl(),
])
.esm(deno_core::include_js_files!("bootstrap.js",))
.build()
}

#[op]
fn op_read_file_sync(path: String) -> Result<ZeroCopyBuf, AnyError> {
let path = std::path::Path::new(&path);
let mut file = std::fs::File::open(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(ZeroCopyBuf::from(buf))
}
#[op]
fn op_exit(code: i32) -> Result<(), AnyError> {
std::process::exit(code)
}

#[op]
fn op_write_file_sync(path: String, buf: ZeroCopyBuf) -> Result<(), AnyError> {
let path = std::path::Path::new(&path);
let mut file = std::fs::File::create(path)?;
file.write_all(&buf)?;
Ok(())
}
#[op]
fn op_read_file_sync(path: String) -> Result<ZeroCopyBuf, AnyError> {
let path = std::path::Path::new(&path);
let mut file = std::fs::File::open(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(ZeroCopyBuf::from(buf))
}

fn get_error_class_name(e: &AnyError) -> &'static str {
deno_core::error::get_custom_error_class(e)
.or_else(|| deno_webgpu::error::get_error_class_name(e))
.unwrap_or_else(|| {
panic!(
"Error '{}' contains boxed error of unsupported type:{}",
e,
e.chain()
.map(|e| format!("\n {:?}", e))
.collect::<String>()
);
})
}
#[op]
fn op_write_file_sync(path: String, buf: ZeroCopyBuf) -> Result<(), AnyError> {
let path = std::path::Path::new(&path);
let mut file = std::fs::File::create(path)?;
file.write_all(&buf)?;
Ok(())
}

fn get_error_class_name(e: &AnyError) -> &'static str {
deno_core::error::get_custom_error_class(e)
.or_else(|| deno_webgpu::error::get_error_class_name(e))
.unwrap_or_else(|| {
panic!(
"Error '{}' contains boxed error of unsupported type:{}",
e,
e.chain()
.map(|e| format!("\n {:?}", e))
.collect::<String>()
);
})
}

fn unwrap_or_exit<T>(result: Result<T, AnyError>) -> T {
match result {
Ok(value) => value,
Err(error) => {
eprintln!("{}: {:?}", red_bold("error"), error);
std::process::exit(1);
pub fn unwrap_or_exit<T>(result: Result<T, AnyError>) -> T {
match result {
Ok(value) => value,
Err(error) => {
eprintln!("{}: {:?}", red_bold("error"), error);
std::process::exit(1);
}
}
}
}

fn style<S: AsRef<str>>(s: S, colorspec: ColorSpec) -> impl fmt::Display {
let mut v = Vec::new();
let mut ansi_writer = Ansi::new(&mut v);
ansi_writer.set_color(&colorspec).unwrap();
ansi_writer.write_all(s.as_ref().as_bytes()).unwrap();
ansi_writer.reset().unwrap();
String::from_utf8_lossy(&v).into_owned()
}
fn style<S: AsRef<str>>(s: S, colorspec: ColorSpec) -> impl fmt::Display {
let mut v = Vec::new();
let mut ansi_writer = Ansi::new(&mut v);
ansi_writer.set_color(&colorspec).unwrap();
ansi_writer.write_all(s.as_ref().as_bytes()).unwrap();
ansi_writer.reset().unwrap();
String::from_utf8_lossy(&v).into_owned()
}

fn red_bold<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Red)).set_bold(true);
style(s, style_spec)
}
fn red_bold<S: AsRef<str>>(s: S) -> impl fmt::Display {
let mut style_spec = ColorSpec::new();
style_spec.set_fg(Some(Red)).set_bold(true);
style(s, style_spec)
}

// NOP permissions
struct Permissions;

// NOP permissions
struct Permissions;
impl deno_web::TimersPermission for Permissions {
fn allow_hrtime(&mut self) -> bool {
false
}

impl deno_web::TimersPermission for Permissions {
fn allow_hrtime(&mut self) -> bool {
false
fn check_unstable(&self, _state: &deno_core::OpState, _api_name: &'static str) {}
}
}

#[cfg(not(target_arch = "wasm32"))]
#[tokio::main(flavor = "current_thread")]
async fn main() {
native::unwrap_or_exit(native::run().await)
}

fn check_unstable(&self, _state: &deno_core::OpState, _api_name: &'static str) {}
#[cfg(target_arch = "wasm32")]
fn main() {
panic!("This is a native only module. It can't be run on the web!");
}
6 changes: 4 additions & 2 deletions deno_webgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ path = "lib.rs"
[features]
surface = ["wgpu-core/raw-window-handle", "dep:raw-window-handle"]

[dependencies]
# We make all dependencies conditional on not being wasm,
# so the whole workspace can built as wasm.
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
deno_core.workspace = true
serde = { workspace = true, features = ["derive"] }
tokio = { workspace = true, features = ["full"] }
wgpu-types = { workspace = true, features = ["trace", "replay", "serde"] }
raw-window-handle = { workspace = true, optional = true }

[dependencies.wgpu-core]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.wgpu-core]
workspace = true
features = ["trace", "replay", "serde", "strict_asserts", "wgsl", "gles"]

Expand Down
2 changes: 1 addition & 1 deletion deno_webgpu/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

#![cfg(not(target_arch = "wasm32"))]
#![warn(unsafe_op_in_unsafe_fn)]

use deno_core::error::AnyError;
Expand Down

0 comments on commit 4aff9b6

Please sign in to comment.