Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rust workers pipeline #167

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/common.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Common functionality and types.

use std::ffi::OsStr;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};

use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use async_process::{Command, Stdio};
use async_std::fs;
use async_std::task::spawn_blocking;

Expand Down Expand Up @@ -78,3 +80,22 @@ pub fn strip_prefix(target: &Path) -> &Path {
Err(_) => target,
}
}

/// Run a global command with the given arguments and make sure it completes successfully. If it
/// fails an error is returned.
#[tracing::instrument(level = "trace", skip(name, args))]
pub async fn run_command(name: &str, args: &[impl AsRef<OsStr>]) -> Result<()> {
let status = Command::new(name)
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.with_context(|| format!("error spawning {} call", name))?
.status()
.await
.with_context(|| format!("error during {} call", name))?;
if !status.success() {
bail!("{} call returned a bad status", name);
}
Ok(())
}
6 changes: 6 additions & 0 deletions src/config/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ pub struct ConfigOptsBuild {
/// The public URL from which assets are to be served [default: /]
#[structopt(long, parse(from_str=parse_public_url))]
pub public_url: Option<String>,
/// Output Module Workers [default: false]
#[structopt(long)]
#[serde(default)]
pub module_workers: Option<bool>,
}

/// Config options for the watch system.
Expand Down Expand Up @@ -157,6 +161,7 @@ impl ConfigOpts {
release: cli.release,
dist: cli.dist,
public_url: cli.public_url,
module_workers: cli.module_workers,
};
let cfg_build = ConfigOpts {
build: Some(opts),
Expand Down Expand Up @@ -305,6 +310,7 @@ impl ConfigOpts {
g.target = g.target.or(l.target);
g.dist = g.dist.or(l.dist);
g.public_url = g.public_url.or(l.public_url);
g.module_workers = g.module_workers.or(l.module_workers);
// NOTE: this can not be disabled in the cascade.
if l.release {
g.release = true
Expand Down
3 changes: 3 additions & 0 deletions src/config/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub struct RtcBuild {
pub final_dist: PathBuf,
/// The directory used to stage build artifacts during an active build.
pub staging_dist: PathBuf,
/// Output ESM Workers
pub module_workers: bool,
}

impl RtcBuild {
Expand Down Expand Up @@ -56,6 +58,7 @@ impl RtcBuild {
staging_dist,
final_dist,
public_url: opts.public_url.unwrap_or_else(|| "/".into()),
module_workers: opts.module_workers.unwrap_or(false),
})
}
}
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod config;
mod pipelines;
mod proxy;
mod serve;
mod wasm_opt;
mod watch;

use std::path::PathBuf;
Expand Down
7 changes: 7 additions & 0 deletions src/pipelines/html.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ impl HtmlPipeline {
assets.push(TrunkLink::RustApp(app));
}

// Only one worker for now
let rust_worker_nodes = target_html.select(r#"link[data-trunk][rel="rust-worker"]"#).length();
ensure!(
rust_worker_nodes <= 1,
r#"only one <link data-trunk rel="rust-worker" .../> may be specified"#
);

// Spawn all asset pipelines.
let mut pipelines: AssetPipelineHandles = FuturesUnordered::new();
pipelines.extend(assets.into_iter().map(|asset| asset.spawn()));
Expand Down
5 changes: 1 addition & 4 deletions src/pipelines/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,7 @@ impl AssetFile {
Some(file_stem) => file_stem.to_owned(),
None => bail!("asset has no file name stem {:?}", &path),
};
let ext = match path.extension() {
Some(ext) => Some(ext.to_owned().to_string_lossy().to_string()),
None => None,
};
let ext = path.extension().map(|ext| ext.to_owned().to_string_lossy().to_string());
Ok(Self {
path: path.into(),
file_name,
Expand Down
119 changes: 3 additions & 116 deletions src/pipelines/rust_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use std::iter::Iterator;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::{ffi::OsStr, str::FromStr};

use anyhow::{anyhow, bail, Context, Result};
use async_process::{Command, Stdio};
Expand All @@ -14,8 +13,9 @@ use nipper::Document;

use super::{LinkAttrs, TrunkLinkPipelineOutput};
use super::{ATTR_HREF, SNIPPETS_DIR};
use crate::common::{copy_dir_recursive, path_exists};
use crate::common::{copy_dir_recursive, path_exists, run_command};
use crate::config::{CargoMetadata, RtcBuild};
use crate::wasm_opt::{wasm_opt_build, WasmOptLevel};

/// A Rust application pipeline.
pub struct RustApp {
Expand Down Expand Up @@ -108,7 +108,7 @@ impl RustApp {
async fn build(mut self) -> Result<TrunkLinkPipelineOutput> {
let (wasm, hashed_name) = self.cargo_build().await?;
let output = self.wasm_bindgen_build(wasm.as_ref(), &hashed_name).await?;
self.wasm_opt_build(&output.wasm_output).await?;
wasm_opt_build(self.cfg, self.manifest, self.wasm_opt, &output.wasm_output).await?;
Ok(TrunkLinkPipelineOutput::RustApp(output))
}

Expand Down Expand Up @@ -245,38 +245,6 @@ impl RustApp {
wasm_output: hashed_wasm_name,
})
}

#[tracing::instrument(level = "trace", skip(self, hashed_name))]
async fn wasm_opt_build(&self, hashed_name: &str) -> Result<()> {
// If opt level is off, we skip calling wasm-opt as it wouldn't have any effect.
if self.wasm_opt == WasmOptLevel::Off {
return Ok(());
}

// Ensure our output dir is in place.
tracing::info!("calling wasm-opt");
let mode_segment = if self.cfg.release { "release" } else { "debug" };
let output = self.manifest.metadata.target_directory.join("wasm-opt").join(mode_segment);
fs::create_dir_all(&output).await.context("error creating wasm-opt output dir")?;

// Build up args for calling wasm-opt.
let output = output.join(hashed_name);
let arg_output = format!("--output={}", output.display());
let arg_opt_level = format!("-O{}", self.wasm_opt.as_ref());
let target_wasm = self.cfg.staging_dist.join(hashed_name).to_string_lossy().to_string();
let args = vec![&arg_output, &arg_opt_level, &target_wasm];

// Invoke wasm-opt.
run_command("wasm-opt", &args).await?;

// Copy the generated WASM file to the dist dir.
tracing::info!("copying generated wasm-opt artifacts");
fs::copy(output, self.cfg.staging_dist.join(hashed_name))
.await
.context("error copying wasm file to dist dir")?;

Ok(())
}
}

/// The output of a cargo build pipeline.
Expand Down Expand Up @@ -318,84 +286,3 @@ impl RustAppOutput {
Ok(())
}
}

/// Run a global command with the given arguments and make sure it completes successfully. If it
/// fails an error is returned.
#[tracing::instrument(level = "trace", skip(name, args))]
async fn run_command(name: &str, args: &[impl AsRef<OsStr>]) -> Result<()> {
let status = Command::new(name)
.args(args)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.with_context(|| format!("error spawning {} call", name))?
.status()
.await
.with_context(|| format!("error during {} call", name))?;
if !status.success() {
bail!("{} call returned a bad status", name);
}
Ok(())
}

/// Different optimization levels that can be configured with `wasm-opt`.
#[derive(PartialEq, Eq)]
enum WasmOptLevel {
/// Default optimization passes.
Default,
/// No optimization passes, skipping the wasp-opt step.
Off,
/// Run quick & useful optimizations. useful for iteration testing.
One,
/// Most optimizations, generally gets most performance.
Two,
/// Spend potentially a lot of time optimizing.
Three,
/// Also flatten the IR, which can take a lot more time and memory, but is useful on more nested
/// / complex / less-optimized input.
Four,
/// Default optimizations, focus on code size.
S,
/// Default optimizations, super-focusing on code size.
Z,
}

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

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"" => Self::Default,
"0" => Self::Off,
"1" => Self::One,
"2" => Self::Two,
"3" => Self::Three,
"4" => Self::Four,
"s" | "S" => Self::S,
"z" | "Z" => Self::Z,
_ => bail!("unknown wasm-opt level `{}`", s),
})
}
}

impl AsRef<str> for WasmOptLevel {
fn as_ref(&self) -> &str {
match self {
Self::Default => "",
Self::Off => "0",
Self::One => "1",
Self::Two => "2",
Self::Three => "3",
Self::Four => "4",
Self::S => "s",
Self::Z => "z",
}
}
}

impl Default for WasmOptLevel {
fn default() -> Self {
// Current default is off until automatic download of wasm-opt is implemented.
Self::Off
}
}
Loading