Skip to content

Commit

Permalink
Add JS runtime option infra and redirect-stdout-to-stderr option (#739)
Browse files Browse the repository at this point in the history
* Add JS runtime option infra and redirect-stdout-to-stderr option

* Update fuel targets

* Revert to old builder interface except for config
  • Loading branch information
jeffcharles authored Aug 29, 2024
1 parent 9c11cec commit da6adb4
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 45 deletions.
16 changes: 11 additions & 5 deletions crates/cli/src/codegen/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::codegen::{CodeGen, CodeGenType, DynamicGenerator, StaticGenerator};
use anyhow::{bail, Result};
use javy_config::Config;
use std::path::PathBuf;

/// Options for using WIT in the code generation process.
Expand Down Expand Up @@ -79,18 +80,23 @@ impl CodeGenBuilder {
}

/// Build a [`CodeGenerator`].
pub fn build<T>(self) -> Result<Box<dyn CodeGen>>
pub fn build<T>(self, js_runtime_config: Config) -> Result<Box<dyn CodeGen>>
where
T: CodeGen,
{
match T::classify() {
CodeGenType::Static => self.build_static(),
CodeGenType::Dynamic => self.build_dynamic(),
CodeGenType::Static => self.build_static(js_runtime_config),
CodeGenType::Dynamic => {
if js_runtime_config != Config::all() {
bail!("Cannot set JS runtime options when building a dynamic module")
}
self.build_dynamic()
}
}
}

fn build_static(self) -> Result<Box<dyn CodeGen>> {
let mut static_gen = Box::new(StaticGenerator::new());
fn build_static(self, js_runtime_config: Config) -> Result<Box<dyn CodeGen>> {
let mut static_gen = Box::new(StaticGenerator::new(js_runtime_config));

static_gen.source_compression = self.source_compression;
static_gen.wit_opts = self.wit_opts;
Expand Down
12 changes: 8 additions & 4 deletions crates/cli/src/codegen/static.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,20 @@ pub(crate) struct StaticGenerator {
function_exports: Exports,
/// WIT options for code generation.
pub wit_opts: WitOptions,
/// JS runtime options for code generation.
pub js_runtime_config: Config,
}

impl StaticGenerator {
/// Creates a new [`StaticGenerator`].
pub fn new() -> Self {
pub fn new(js_runtime_config: Config) -> Self {
let engine = include_bytes!(concat!(env!("OUT_DIR"), "/engine.wasm"));
Self {
engine,
source_compression: Default::default(),
function_exports: Default::default(),
wit_opts: Default::default(),
js_runtime_config,
}
}
}
Expand Down Expand Up @@ -71,9 +74,10 @@ impl CodeGen for StaticGenerator {
.unwrap()
.set_stdin(Box::new(ReadPipe::from(js.as_bytes())));

WASI.get_mut()
.unwrap()
.push_env("JS_RUNTIME_CONFIG", &Config::all().bits().to_string())?;
WASI.get_mut().unwrap().push_env(
"JS_RUNTIME_CONFIG",
&self.js_runtime_config.bits().to_string(),
)?;
};

let wasm = Wizer::new()
Expand Down
104 changes: 95 additions & 9 deletions crates/cli/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use anyhow::{anyhow, bail};
use clap::{Parser, Subcommand};
use javy_config::Config;
use std::{path::PathBuf, str::FromStr};

use crate::codegen::WitOptions;
Expand Down Expand Up @@ -91,6 +92,16 @@ pub struct BuildCommandOpts {
)]
/// Codegen options.
pub codegen: Vec<CodegenOption>,

#[arg(
short = 'J',
long = "js",
long_help = "Available JS runtime options:
-J redirect-stdout-to-stderr[=y|n] -- Redirects console.log to stderr.
"
)]
/// JS runtime options.
pub js_runtime: Vec<JsRuntimeOption>,
}

#[derive(Debug, Parser)]
Expand All @@ -110,6 +121,16 @@ pub struct CodegenOptionGroup {
pub source_compression: bool,
}

impl Default for CodegenOptionGroup {
fn default() -> Self {
Self {
dynamic: false,
wit: WitOptions::default(),
source_compression: true,
}
}
}

#[derive(Clone, Debug)]
pub enum CodegenOption {
/// Creates a smaller module that requires a dynamically linked QuickJS provider Wasm
Expand Down Expand Up @@ -163,24 +184,89 @@ impl TryFrom<Vec<CodegenOption>> for CodegenOptionGroup {
type Error = anyhow::Error;

fn try_from(value: Vec<CodegenOption>) -> Result<Self, Self::Error> {
let mut dynamic = false;
let mut options = Self::default();
let mut wit = None;
let mut wit_world = None;
let mut source_compression = true;

for option in value {
match option {
CodegenOption::Dynamic(enabled) => dynamic = enabled,
CodegenOption::Dynamic(enabled) => options.dynamic = enabled,
CodegenOption::Wit(path) => wit = Some(path),
CodegenOption::WitWorld(world) => wit_world = Some(world),
CodegenOption::SourceCompression(enabled) => source_compression = enabled,
CodegenOption::SourceCompression(enabled) => options.source_compression = enabled,
}
}

Ok(Self {
dynamic,
wit: WitOptions::from_tuple((wit, wit_world))?,
source_compression,
})
options.wit = WitOptions::from_tuple((wit, wit_world))?;
Ok(options)
}
}

#[derive(Debug, PartialEq)]
pub struct JsRuntimeOptionGroup {
/// Whether to redirect console.log to stderr.
pub redirect_stdout_to_stderr: bool,
}

impl Default for JsRuntimeOptionGroup {
fn default() -> Self {
Self {
redirect_stdout_to_stderr: true,
}
}
}

#[derive(Clone, Debug)]
pub enum JsRuntimeOption {
/// Whether to redirect console.log to stderr.
RedirectStdoutToStderr(bool),
}

impl FromStr for JsRuntimeOption {
type Err = anyhow::Error;

fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut parts = s.splitn(2, '=');
let key = parts
.next()
.ok_or_else(|| anyhow!("Invalid JS runtime key"))?;
let value = parts.next();
let option = match key {
"redirect-stdout-to-stderr" => Self::RedirectStdoutToStderr(match value {
None => true,
Some("y") => true,
Some("n") => false,
_ => bail!("Invalid value for redirect-stdout-to-stderr"),
}),
_ => bail!("Invalid JS runtime key"),
};
Ok(option)
}
}

impl From<Vec<JsRuntimeOption>> for JsRuntimeOptionGroup {
fn from(value: Vec<JsRuntimeOption>) -> Self {
let mut group = Self::default();

for option in value {
match option {
JsRuntimeOption::RedirectStdoutToStderr(enabled) => {
group.redirect_stdout_to_stderr = enabled;
}
}
}

group
}
}

impl From<JsRuntimeOptionGroup> for Config {
fn from(value: JsRuntimeOptionGroup) -> Self {
let mut config = Self::all();
config.set(
Config::REDIRECT_STDOUT_TO_STDERR,
value.redirect_stdout_to_stderr,
);
config
}
}
17 changes: 10 additions & 7 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ mod commands;
mod js;
mod wit;

use crate::codegen::{DynamicGenerator, StaticGenerator, WitOptions};
use crate::codegen::WitOptions;
use crate::commands::{Cli, Command, EmitProviderCommandOpts};
use anyhow::Result;
use clap::Parser;
use codegen::CodeGenBuilder;
use commands::CodegenOptionGroup;
use codegen::{CodeGenBuilder, DynamicGenerator, StaticGenerator};
use commands::{CodegenOptionGroup, JsRuntimeOptionGroup};
use javy_config::Config;
use js::JS;
use std::fs;
use std::fs::File;
Expand Down Expand Up @@ -43,10 +44,11 @@ fn main() -> Result<()> {
.source_compression(!opts.no_source_compression)
.provider_version("2");

let config = Config::all();
let mut gen = if opts.dynamic {
builder.build::<DynamicGenerator>()?
builder.build::<DynamicGenerator>(config)?
} else {
builder.build::<StaticGenerator>()?
builder.build::<StaticGenerator>(config)?
};

let wasm = gen.generate(&js)?;
Expand All @@ -63,10 +65,11 @@ fn main() -> Result<()> {
.source_compression(codegen.source_compression)
.provider_version("2");

let js_runtime_options: JsRuntimeOptionGroup = opts.js_runtime.clone().into();
let mut gen = if codegen.dynamic {
builder.build::<DynamicGenerator>()?
builder.build::<DynamicGenerator>(js_runtime_options.into())?
} else {
builder.build::<StaticGenerator>()?
builder.build::<StaticGenerator>(js_runtime_options.into())?
};

let wasm = gen.generate(&js)?;
Expand Down
18 changes: 17 additions & 1 deletion crates/cli/tests/dynamic_linking_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Result;
use javy_runner::Builder;
use javy_runner::{Builder, JavyCommand};
use std::path::{Path, PathBuf};
use std::str;

Expand Down Expand Up @@ -152,6 +152,22 @@ fn test_producers_section_present() -> Result<()> {
})
}

#[test]
fn test_using_runtime_flag_with_dynamic_triggers_error() -> Result<()> {
let build_result = Builder::default()
.root(root())
.bin(BIN)
.input("console.js")
.preload("javy_quickjs_provider_v2".into(), provider_module_path())
.command(JavyCommand::Build)
.redirect_stdout_to_stderr(false)
.build();
assert!(build_result.is_err_and(|e| e
.to_string()
.contains("Error: Cannot set JS runtime options when building a dynamic module")));
Ok(())
}

#[test]
// Temporarily ignore given that Javy.JSON is disabled by default.
#[ignore]
Expand Down
69 changes: 53 additions & 16 deletions crates/cli/tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::Result;
use javy_runner::{Runner, RunnerError};
use javy_runner::{Builder, Runner, RunnerError};
use std::path::PathBuf;
use std::str;

Expand Down Expand Up @@ -95,22 +95,59 @@ fn test_encoding() -> Result<()> {
}

#[test]
fn test_logging() -> Result<()> {
run_with_compile_and_build(|builder| {
let mut runner = builder
.root(sample_scripts())
.bin(BIN)
.input("logging.js")
.build()?;
fn test_logging_with_compile() -> Result<()> {
let mut runner = Builder::default()
.root(sample_scripts())
.bin(BIN)
.input("logging.js")
.command(javy_runner::JavyCommand::Compile)
.build()?;

let (output, logs, fuel_consumed) = run(&mut runner, &[]);
assert!(output.is_empty());
assert_eq!(
"hello world from console.log\nhello world from console.error\n",
logs.as_str(),
);
assert_fuel_consumed_within_threshold(37309, fuel_consumed);
Ok(())
}

let (_output, logs, fuel_consumed) = run(&mut runner, &[]);
assert_eq!(
"hello world from console.log\nhello world from console.error\n",
logs.as_str(),
);
assert_fuel_consumed_within_threshold(34169, fuel_consumed);
Ok(())
})
#[test]
fn test_logging_without_redirect() -> Result<()> {
let mut runner = Builder::default()
.root(sample_scripts())
.bin(BIN)
.input("logging.js")
.command(javy_runner::JavyCommand::Build)
.redirect_stdout_to_stderr(false)
.build()?;

let (output, logs, fuel_consumed) = run(&mut runner, &[]);
assert_eq!(b"hello world from console.log\n".to_vec(), output);
assert_eq!("hello world from console.error\n", logs.as_str());
assert_fuel_consumed_within_threshold(37485, fuel_consumed);
Ok(())
}

#[test]
fn test_logging_with_redirect() -> Result<()> {
let mut runner = Builder::default()
.root(sample_scripts())
.bin(BIN)
.input("logging.js")
.command(javy_runner::JavyCommand::Build)
.redirect_stdout_to_stderr(true)
.build()?;

let (output, logs, fuel_consumed) = run(&mut runner, &[]);
assert!(output.is_empty());
assert_eq!(
"hello world from console.log\nhello world from console.error\n",
logs.as_str(),
);
assert_fuel_consumed_within_threshold(37309, fuel_consumed);
Ok(())
}

#[test]
Expand Down
Loading

0 comments on commit da6adb4

Please sign in to comment.