Skip to content

Commit

Permalink
refactor!: Renamed SpawnConfig and RunConfig to SpawnOptions an…
Browse files Browse the repository at this point in the history
…d `RunOptions`.

feat: Users are now able to mount directories using either a existing `Directory` or a `DirectoryInit` which will be used to instantiate a new `Directory`

Release-As: 0.3.0
  • Loading branch information
Michael-F-Bryan committed Nov 27, 2023
1 parent 105b3ce commit e43ea8c
Show file tree
Hide file tree
Showing 7 changed files with 383 additions and 345 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
- name: Build
run: npm run build
- name: Generate Docs
run: npm run doc
run: npm run docs
- name: Upload API Docs
uses: JamesIves/github-pages-deploy-action@v4.4.0
if: github.ref == 'refs/heads/main'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"build:dev": "wasm-pack build --dev --target=web --weak-refs --no-pack && rollup -c --environment BUILD:development",
"dev": "rollup -c -w",
"test": "web-test-runner --node-resolve --esbuild-target auto --config ./web-dev-server.config.mjs",
"doc": "typedoc",
"docs": "typedoc",
"doc:watch": "typedoc --watch",
"clean": "rimraf dist coverage pkg target"
},
Expand Down
258 changes: 113 additions & 145 deletions src/facade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use web_sys::{ReadableStream, WritableStream};
use crate::{
instance::ExitCondition,
utils::{Error, GlobalScope},
Instance, RunConfig, Runtime,
Instance, Runtime, SpawnOptions,
};

/// The entrypoint to the Wasmer SDK.
Expand Down Expand Up @@ -54,7 +54,7 @@ impl Wasmer {
pub async fn js_spawn(
&self,
app_id: String,
config: Option<SpawnConfig>,
config: Option<SpawnOptions>,
) -> Result<Instance, Error> {
self.spawn(app_id, config).await
}
Expand All @@ -66,7 +66,7 @@ impl Wasmer {

impl Wasmer {
#[tracing::instrument(skip_all)]
async fn spawn(&self, app_id: String, config: Option<SpawnConfig>) -> Result<Instance, Error> {
async fn spawn(&self, app_id: String, config: Option<SpawnOptions>) -> Result<Instance, Error> {
let specifier: PackageSpecifier = app_id.parse()?;
let config = config.unwrap_or_default();

Expand All @@ -84,7 +84,7 @@ impl Wasmer {
let tasks = Arc::clone(runtime.task_manager());

let mut runner = WasiRunner::new();
let (stdin, stdout, stderr) = config.configure_runner(&mut runner, &runtime).await?;
let (stdin, stdout, stderr) = configure_runner(&config, &mut runner, &runtime).await?;

tracing::debug!(%specifier, %command_name, "Starting the WASI runner");

Expand All @@ -106,139 +106,125 @@ impl Wasmer {
}
}

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "SpawnConfig", extends = RunConfig)]
#[derive(Default)]
pub type SpawnConfig;

#[wasm_bindgen(method, getter)]
fn command(this: &SpawnConfig) -> JsValue;
#[wasm_bindgen(method, getter)]
fn uses(this: &SpawnConfig) -> Option<js_sys::Array>;
}

impl SpawnConfig {
pub(crate) async fn configure_runner(
&self,
runner: &mut WasiRunner,
runtime: &Runtime,
) -> Result<
(
Option<web_sys::WritableStream>,
web_sys::ReadableStream,
web_sys::ReadableStream,
),
Error,
> {
let args = self.parse_args()?;
runner.set_args(args);

let env = self.parse_env()?;
runner.set_envs(env);

for (dest, dir) in self.mounted_directories()? {
runner.mount(dest, Arc::new(dir));
}

if let Some(uses) = self.uses() {
let uses = crate::utils::js_string_array(uses)?;
let packages = load_injected_packages(uses, runtime).await?;
runner.add_injected_packages(packages);
}

let (stderr_pipe, stderr_stream) = crate::streams::output_pipe();
runner.set_stderr(Box::new(stderr_pipe));

let options = runtime.tty_options().clone();
match self.setup_tty(options) {
TerminalMode::Interactive {
stdin_pipe,
stdout_pipe,
stdout_stream,
stdin_stream,
} => {
tracing::debug!("Setting up interactive TTY");
runner.set_stdin(Box::new(stdin_pipe));
runner.set_stdout(Box::new(stdout_pipe));
runtime.set_connected_to_tty(true);
Ok((Some(stdin_stream), stdout_stream, stderr_stream))
}
TerminalMode::NonInteractive { stdin } => {
tracing::debug!("Setting up non-interactive TTY");
let (stdout_pipe, stdout_stream) = crate::streams::output_pipe();
runner.set_stdin(Box::new(stdin));
runner.set_stdout(Box::new(stdout_pipe));

// HACK: Make sure we don't report stdin as interactive. This
// doesn't belong here because now it'll affect every other
// instance sharing the same runtime... In theory, every
// instance should get its own TTY state, but that's an issue
// for wasmer-wasix to work out.
runtime.set_connected_to_tty(false);

Ok((None, stdout_stream, stderr_stream))
}
}
pub(crate) async fn configure_runner(
options: &SpawnOptions,
runner: &mut WasiRunner,
runtime: &Runtime,
) -> Result<
(
Option<web_sys::WritableStream>,
web_sys::ReadableStream,
web_sys::ReadableStream,
),
Error,
> {
let args = options.parse_args()?;
runner.set_args(args);

let env = options.parse_env()?;
runner.set_envs(env);

for (dest, dir) in options.mounted_directories()? {
runner.mount(dest, Arc::new(dir));
}

fn setup_tty(&self, options: TtyOptions) -> TerminalMode {
// Handle the simple (non-interactive) case first.
if let Some(stdin) = self.read_stdin() {
return TerminalMode::NonInteractive {
stdin: virtual_fs::StaticFile::new(stdin.into()),
};
}

let (stdout_pipe, stdout_stream) = crate::streams::output_pipe();

// Note: Because this is an interactive session, we want to intercept
// stdin and let the TTY modify it.
//
// To do that, we manually copy data from the user's pipe into the TTY,
// then the TTY modifies those bytes and writes them to the pipe we gave
// to the runtime.
//
// To avoid confusing the pipes and how stdin data gets moved around,
// here's a diagram:
//
// --------------------------------- -------------------- ----------------------------
// | stdin_stream (user) u_stdin_rx | --copy--> | (tty) u_stdin_tx | --pipe-> | stdin_pipe (runtime) ... |
// --------------------------------- -------------------- ----------------------------
let (u_stdin_rx, stdin_stream) = crate::streams::input_pipe();
let (u_stdin_tx, stdin_pipe) = Pipe::channel();

let tty = Tty::new(
Box::new(u_stdin_tx),
Box::new(stdout_pipe.clone()),
GlobalScope::current().is_mobile(),
options,
);

// Because the TTY is manually copying between pipes, we need to make
// sure the stdin pipe passed to the runtime is closed when the user
// closes their end.
let cleanup = {
let stdin_pipe = stdin_pipe.clone();
move || {
tracing::debug!("Closing stdin");
stdin_pipe.close();
}
};
if let Some(uses) = options.uses() {
let uses = crate::utils::js_string_array(uses)?;
let packages = load_injected_packages(uses, runtime).await?;
runner.add_injected_packages(packages);
}

// Use the JS event loop to drive our manual user->tty copy
wasm_bindgen_futures::spawn_local(
copy_stdin_to_tty(u_stdin_rx, tty, cleanup)
.in_current_span()
.instrument(tracing::debug_span!("tty")),
);
let (stderr_pipe, stderr_stream) = crate::streams::output_pipe();
runner.set_stderr(Box::new(stderr_pipe));

let tty_options = runtime.tty_options().clone();
match setup_tty(options, tty_options) {
TerminalMode::Interactive {
stdin_pipe,
stdout_pipe,
stdout_stream,
stdin_stream,
} => {
tracing::debug!("Setting up interactive TTY");
runner.set_stdin(Box::new(stdin_pipe));
runner.set_stdout(Box::new(stdout_pipe));
runtime.set_connected_to_tty(true);
Ok((Some(stdin_stream), stdout_stream, stderr_stream))
}
TerminalMode::NonInteractive { stdin } => {
tracing::debug!("Setting up non-interactive TTY");
let (stdout_pipe, stdout_stream) = crate::streams::output_pipe();
runner.set_stdin(Box::new(stdin));
runner.set_stdout(Box::new(stdout_pipe));

// HACK: Make sure we don't report stdin as interactive. This
// doesn't belong here because now it'll affect every other
// instance sharing the same runtime... In theory, every
// instance should get its own TTY state, but that's an issue
// for wasmer-wasix to work out.
runtime.set_connected_to_tty(false);

Ok((None, stdout_stream, stderr_stream))
}
}
}

fn setup_tty(options: &SpawnOptions, tty_options: TtyOptions) -> TerminalMode {
// Handle the simple (non-interactive) case first.
if let Some(stdin) = options.read_stdin() {
return TerminalMode::NonInteractive {
stdin: virtual_fs::StaticFile::new(stdin.into()),
};
}

let (stdout_pipe, stdout_stream) = crate::streams::output_pipe();

// Note: Because this is an interactive session, we want to intercept
// stdin and let the TTY modify it.
//
// To do that, we manually copy data from the user's pipe into the TTY,
// then the TTY modifies those bytes and writes them to the pipe we gave
// to the runtime.
//
// To avoid confusing the pipes and how stdin data gets moved around,
// here's a diagram:
//
// --------------------------------- -------------------- ----------------------------
// | stdin_stream (user) u_stdin_rx | --copy--> | (tty) u_stdin_tx | --pipe-> | stdin_pipe (runtime) ... |
// --------------------------------- -------------------- ----------------------------
let (u_stdin_rx, stdin_stream) = crate::streams::input_pipe();
let (u_stdin_tx, stdin_pipe) = Pipe::channel();

let tty = Tty::new(
Box::new(u_stdin_tx),
Box::new(stdout_pipe.clone()),
GlobalScope::current().is_mobile(),
tty_options,
);

// Because the TTY is manually copying between pipes, we need to make
// sure the stdin pipe passed to the runtime is closed when the user
// closes their end.
let cleanup = {
let stdin_pipe = stdin_pipe.clone();
move || {
tracing::debug!("Closing stdin");
stdin_pipe.close();
}
};

// Use the JS event loop to drive our manual user->tty copy
wasm_bindgen_futures::spawn_local(
copy_stdin_to_tty(u_stdin_rx, tty, cleanup)
.in_current_span()
.instrument(tracing::debug_span!("tty")),
);

TerminalMode::Interactive {
stdin_pipe,
stdout_pipe,
stdout_stream,
stdin_stream,
}
}

Expand Down Expand Up @@ -325,24 +311,6 @@ async fn load_package(pkg: &str, runtime: &Runtime) -> Result<BinaryPackage, Err
Ok(pkg)
}

#[wasm_bindgen(typescript_custom_section)]
const SPAWN_CONFIG_TYPE_DEFINITION: &'static str = r#"
/**
* Configuration used when starting a WASI program.
*/
export type SpawnConfig = RunConfig & {
/**
* The name of the command to be run (uses the package's entrypoint if not
* defined).
*/
command?: string;
/**
* Packages that should also be loaded into the WASIX environment.
*/
uses?: string[];
}
"#;

#[wasm_bindgen(typescript_custom_section)]
const WASMER_CONFIG_TYPE_DEFINITION: &'static str = r#"
/**
Expand Down
14 changes: 12 additions & 2 deletions src/fs/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub struct Directory(Arc<dyn FileSystem>);

#[wasm_bindgen]
impl Directory {
/// Create a temporary [`Directory`] that holds its contents in memory.
/// Create a new {@link Directory}.
#[wasm_bindgen(constructor)]
pub fn new(init: Option<DirectoryInit>) -> Result<Directory, Error> {
match init {
Expand Down Expand Up @@ -201,9 +201,19 @@ extern "C" {
pub type ListOfStrings;
}

#[wasm_bindgen(typescript_custom_section)]
const DIRECTORY_INIT_TYPE_DEF: &'static str = r#"
/**
* A mapping from file paths to their contents that can be used to initialize
* a {@link Directory}.
*/
export type DirectoryInit = Record<string, string | Uint8Array>;
"#;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "Record<string, string|Uint8Array>", extends = js_sys::Object)]
#[wasm_bindgen(typescript_type = "DirectoryInit", extends = js_sys::Object)]
#[derive(Debug, Clone, PartialEq)]
pub type DirectoryInit;
}

Expand Down
6 changes: 4 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod fs;
mod instance;
mod logging;
mod net;
mod options;
mod package_loader;
mod run;
mod runtime;
Expand All @@ -21,11 +22,12 @@ use std::sync::Mutex;

pub use crate::{
container::{Container, Manifest, Volume},
facade::{SpawnConfig, Wasmer, WasmerConfig},
facade::{Wasmer, WasmerConfig},
fs::{Directory, DirectoryInit},
instance::{Instance, JsOutput},
logging::initialize_logger,
run::{run, RunConfig},
options::{RunOptions, SpawnOptions},
run::run,
runtime::Runtime,
utils::StringOrBytes,
};
Expand Down
Loading

0 comments on commit e43ea8c

Please sign in to comment.