Skip to content

Commit

Permalink
Add -C flag to build command (#734)
Browse files Browse the repository at this point in the history
* Add `-C` flag to build command

* no-source-compression -> source-compression

* Use enum instead of bool for command in tests

* Use WitOptions in CodegenOptionsGroup

* Make the linter happy
  • Loading branch information
jeffcharles committed Aug 26, 2024
1 parent 2627a5e commit 9641533
Show file tree
Hide file tree
Showing 6 changed files with 610 additions and 325 deletions.
126 changes: 115 additions & 11 deletions crates/cli/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use anyhow::{anyhow, bail};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::{path::PathBuf, str::FromStr};

use crate::codegen::WitOptions;

#[derive(Debug, Parser)]
#[command(
Expand Down Expand Up @@ -27,24 +30,17 @@ pub enum Command {
///
/// Use the `build` command instead.
#[command(arg_required_else_help = true)]
Compile(CompileAndBuildCommandOpts),
Compile(CompileCommandOpts),
/// Generates WebAssembly from a JavaScript source.
#[command(arg_required_else_help = true)]
Build(CompileAndBuildCommandOpts),
Build(BuildCommandOpts),
/// Emits the provider binary that is required to run dynamically
/// linked WebAssembly modules.
EmitProvider(EmitProviderCommandOpts),
}

impl Command {
/// Returns true if it is [`Command::Compile`].
pub fn is_compile(&self) -> bool {
matches!(self, Command::Compile(_))
}
}

#[derive(Debug, Parser)]
pub struct CompileAndBuildCommandOpts {
pub struct CompileCommandOpts {
#[arg(value_name = "INPUT", required = true)]
/// Path of the JavaScript input file.
pub input: PathBuf,
Expand Down Expand Up @@ -73,9 +69,117 @@ pub struct CompileAndBuildCommandOpts {
pub no_source_compression: bool,
}

#[derive(Debug, Parser)]
pub struct BuildCommandOpts {
#[arg(value_name = "INPUT", required = true)]
/// Path of the JavaScript input file.
pub input: PathBuf,

#[arg(short, default_value = "index.wasm")]
/// Desired path of the WebAssembly output file.
pub output: PathBuf,

#[arg(
short = 'C',
long_help = "Available codegen options:
-C dynamic[=y|n] -- Creates a smaller module that requires a dynamically linked QuickJS provider Wasm module to execute (see `emit-provider` command).
-C wit=path -- Optional path to WIT file describing exported functions. Only supports function exports with no arguments and no return values.
-C wit-world=val -- Optional WIT world name for WIT file. Must be specified if WIT is file path is specified.
-C source-compression[=y|n] -- Enable source code compression, which generates smaller WebAssembly files at the cost of increased compile time. Defaults to enabled.
"
)]
/// Codegen options.
pub codegen: Vec<CodegenOption>,
}

#[derive(Debug, Parser)]
pub struct EmitProviderCommandOpts {
#[structopt(short, long)]
/// Output path for the provider binary (default is stdout).
pub out: Option<PathBuf>,
}

pub struct CodegenOptionGroup {
/// Creates a smaller module that requires a dynamically linked QuickJS provider Wasm
/// module to execute (see `emit-provider` command).
pub dynamic: bool,
/// The WIT options.
pub wit: WitOptions,
/// Enable source code compression, which generates smaller WebAssembly files at the cost of increased compile time. Defaults to enabled.
pub source_compression: bool,
}

#[derive(Clone, Debug)]
pub enum CodegenOption {
/// Creates a smaller module that requires a dynamically linked QuickJS provider Wasm
/// module to execute (see `emit-provider` command).
Dynamic(bool),
/// Optional path to WIT file describing exported functions.
/// Only supports function exports with no arguments and no return values.
Wit(PathBuf),
/// Optional path to WIT file describing exported functions.
/// Only supports function exports with no arguments and no return values.
WitWorld(String),
/// Enable source code compression, which generates smaller WebAssembly files at the cost of increased compile time. Defaults to enabled.
SourceCompression(bool),
}

impl FromStr for CodegenOption {
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 codegen key"))?;
let value = parts.next();
let option = match key {
"dynamic" => Self::Dynamic(match value {
None => true,
Some("y") => true,
Some("n") => false,
_ => bail!("Invalid value for dynamic"),
}),
"wit" => Self::Wit(PathBuf::from(
value.ok_or_else(|| anyhow!("Must provide value for wit"))?,
)),
"wit-world" => Self::WitWorld(
value
.ok_or_else(|| anyhow!("Must provide value for wit-world"))?
.to_string(),
),
"source-compression" => Self::SourceCompression(match value {
None => true,
Some("y") => true,
Some("n") => false,
_ => bail!("Invalid value for source-compression"),
}),
_ => bail!("Invalid codegen key"),
};
Ok(option)
}
}

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 wit = None;
let mut wit_world = None;
let mut source_compression = true;

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

Ok(Self {
dynamic,
wit: WitOptions::from_tuple((wit, wit_world))?,
source_compression,
})
}
}
31 changes: 25 additions & 6 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::commands::{Cli, Command, EmitProviderCommandOpts};
use anyhow::Result;
use clap::Parser;
use codegen::CodeGenBuilder;
use commands::CodegenOptionGroup;
use js::JS;
use std::fs;
use std::fs::File;
Expand All @@ -19,10 +20,9 @@ fn main() -> Result<()> {

match &args.command {
Command::EmitProvider(opts) => emit_provider(opts),
c @ Command::Compile(opts) | c @ Command::Build(opts) => {
if c.is_compile() {
eprintln!(
r#"
Command::Compile(opts) => {
eprintln!(
r#"
The `compile` command will be deprecated in the next major
release of the CLI (v4.0.0)
Expand All @@ -31,8 +31,7 @@ fn main() -> Result<()> {
Use the `build` command instead.
"#
);
}
);

let js = JS::from_file(&opts.input)?;
let mut builder = CodeGenBuilder::new();
Expand All @@ -52,6 +51,26 @@ fn main() -> Result<()> {

let wasm = gen.generate(&js)?;

fs::write(&opts.output, wasm)?;
Ok(())
}
Command::Build(opts) => {
let js = JS::from_file(&opts.input)?;
let codegen: CodegenOptionGroup = opts.codegen.clone().try_into()?;
let mut builder = CodeGenBuilder::new();
builder
.wit_opts(codegen.wit)
.source_compression(codegen.source_compression)
.provider_version("2");

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

let wasm = gen.generate(&js)?;

fs::write(&opts.output, wasm)?;
Ok(())
}
Expand Down
11 changes: 11 additions & 0 deletions crates/cli/tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use anyhow::Result;
use javy_runner::{Builder, JavyCommand};

pub fn run_with_compile_and_build<F>(test: F) -> Result<()>
where
F: Fn(&mut Builder) -> Result<()>,
{
test(Builder::default().command(JavyCommand::Compile))?;
test(Builder::default().command(JavyCommand::Build))?;
Ok(())
}
Loading

0 comments on commit 9641533

Please sign in to comment.