Skip to content

Commit

Permalink
Merge pull request #347 from wasmerio/api-tweaks
Browse files Browse the repository at this point in the history
[SDK-3] Finalise the public API
  • Loading branch information
Michael Bryan authored Nov 30, 2023
2 parents 8a00445 + 0746e5c commit cc26237
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 16
registry-url: 'https://registry.npmjs.org'
registry-url: "https://registry.npmjs.org"
- name: Setup Rust
uses: dsherret/rust-toolchain-file@v1
- name: Install wasm-pack
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.md
node_modules/
dist/
docs/
23 changes: 21 additions & 2 deletions src/fs/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use js_sys::Reflect;
use tracing::Instrument;
use virtual_fs::{AsyncReadExt, AsyncWriteExt, FileSystem, FileType};
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
use wasmer_wasix::runtime::task_manager::InlineWaker;

use crate::{utils::Error, StringOrBytes};

Expand Down Expand Up @@ -282,17 +283,35 @@ fn in_memory_filesystem(record: &js_sys::Object) -> Result<virtual_fs::mem_fs::F
create_dir_all(&fs, parent)?;
}

fs.insert_ro_file(&path, contents.into())
.with_context(|| format!("Unable to write to \"{}\"", path.display()))?;
tracing::trace!(
path=%path.display(),
file.length=contents.len(),
"Adding file to directory",
);
InlineWaker::block_on(async {
let mut f = fs
.new_open_options()
.write(true)
.create_new(true)
.open(&path)?;
f.write_all(&contents).await?;
f.flush().await
})
.with_context(|| format!("Unable to write to \"{}\"", path.display()))?;
}

Ok(fs)
}

#[tracing::instrument(level = "trace", skip(fs))]
fn create_dir_all(fs: &dyn FileSystem, path: &Path) -> Result<(), anyhow::Error> {
let ancestors: Vec<&Path> = path.ancestors().collect();

for ancestor in ancestors.into_iter().rev() {
if fs.read_dir(ancestor).is_ok() {
continue;
}

fs.create_dir(ancestor).with_context(|| {
format!("Unable to create the \"{}\" directory", ancestor.display())
})?;
Expand Down
32 changes: 31 additions & 1 deletion src/instance.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use futures::{channel::oneshot::Receiver, Stream, StreamExt, TryFutureExt};
use js_sys::Uint8Array;
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue};
use wasmer_wasix::WasiRuntimeError;

use crate::utils::Error;
Expand Down Expand Up @@ -152,16 +152,42 @@ impl From<Output> for JsOutput {
&JsValue::from_str("stdout"),
&Uint8Array::from(stdout.as_slice()),
);
js_sys::Object::define_property(
&output,
&JsValue::from_str("stdoutUtf8"),
&lazily_decoded_string_property(stdout),
);
let _ = js_sys::Reflect::set(
&output,
&JsValue::from_str("stderr"),
&Uint8Array::from(stderr.as_slice()),
);
js_sys::Object::define_property(
&output,
&JsValue::from_str("stderrUtf8"),
&lazily_decoded_string_property(stderr),
);

output.unchecked_into()
}
}

fn lazily_decoded_string_property(binary: Vec<u8>) -> js_sys::Object {
let contents: once_cell::unsync::Lazy<js_sys::JsString, _> =
once_cell::unsync::Lazy::new(move || {
let utf8 = String::from_utf8_lossy(&binary);
js_sys::JsString::from(utf8.as_ref())
});

let property = js_sys::Object::new();
let getter: Closure<dyn Fn() -> js_sys::JsString> =
Closure::new(move || js_sys::JsString::clone(&contents));
js_sys::Reflect::set(&property, &JsValue::from("get"), &getter.into_js_value()).unwrap();
js_sys::Reflect::set(&property, &JsValue::from("enumerable"), &JsValue::TRUE).unwrap();

property
}

#[wasm_bindgen(typescript_custom_section)]
const OUTPUT_TYPE_DEFINITION: &'static str = r#"
export type Output = {
Expand All @@ -171,8 +197,12 @@ export type Output = {
ok: boolean;
/* The contents of the program's stdout stream. */
stdout: Uint8Array;
/* The program's stdout stream, decoded as UTF-8. */
readonly stdoutUtf8: string;
/* The contents of the program's stderr stream. */
stderr: Uint8Array;
/* The program's stderr stream, decoded as UTF-8. */
readonly stderrUtf8: string;
}
"#;

Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub use crate::{
js_runtime::{JsRuntime, RuntimeOptions},
logging::initialize_logger,
options::{RunOptions, SpawnOptions},
run::run,
run::run_wasix,
utils::StringOrBytes,
wasmer::Wasmer,
};
Expand Down
9 changes: 6 additions & 3 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,13 @@ impl CommonOptions {

let value = if let Ok(dir) = Directory::try_from(value) {
dir
} else {
// looks like we need to call the constructor ourselves
let init: &DirectoryInit = value.dyn_ref().unwrap();
} else if value.is_object() {
// looks like we were given parameters for initializing a
// Directory and need to call the constructor ourselves
let init: &DirectoryInit = value.unchecked_ref();
Directory::new(Some(init.clone()))?
} else {
unreachable!();
};
mounted_directories.push((key, value));
}
Expand Down
17 changes: 15 additions & 2 deletions src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,21 @@ use crate::{instance::ExitCondition, utils::Error, Instance, RunOptions};
const DEFAULT_PROGRAM_NAME: &str = "wasm";

/// Run a WASIX program.
#[wasm_bindgen]
pub async fn run(wasm_module: WasmModule, config: RunOptions) -> Result<Instance, Error> {
///
/// # WASI Compatibility
///
/// The WASIX standard is a superset of [WASI preview 1][preview-1], so programs
/// compiled to WASI will run without any problems.
///
/// [WASI Preview 2][preview-2] is a backwards incompatible rewrite of WASI
/// to use the experimental [Component Model Proposal][component-model]. That
/// means programs compiled for WASI Preview 2 will fail to load.
///
/// [preview-1]: https://github.com/WebAssembly/WASI/blob/main/legacy/README.md
/// [preview-2]: https://github.com/WebAssembly/WASI/blob/main/preview2/README.md
/// [component-model]: https://github.com/WebAssembly/component-model
#[wasm_bindgen(js_name = "runWasix")]
pub async fn run_wasix(wasm_module: WasmModule, config: RunOptions) -> Result<Instance, Error> {
let _span = tracing::debug_span!("run").entered();

let runtime = config.runtime().resolve()?.into_inner();
Expand Down
14 changes: 7 additions & 7 deletions src/wasmer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ impl Wasmer {
Wasmer::from_registry(specifier, runtime).await
}

/// Load a package from a `*.webc` file.
#[wasm_bindgen(js_name = "fromWebc")]
pub async fn js_from_webc(
webc: Uint8Array,
/// Load a package from a package file.
#[wasm_bindgen(js_name = "fromFile")]
pub async fn js_from_file(
binary: Uint8Array,
runtime: Option<OptionalRuntime>,
) -> Result<Wasmer, Error> {
Wasmer::from_webc(webc.to_vec(), runtime).await
Wasmer::from_file(binary.to_vec(), runtime).await
}
}

Expand All @@ -87,9 +87,9 @@ impl Wasmer {
}

#[tracing::instrument(skip(runtime))]
async fn from_webc(webc: Vec<u8>, runtime: Option<OptionalRuntime>) -> Result<Self, Error> {
async fn from_file(binary: Vec<u8>, runtime: Option<OptionalRuntime>) -> Result<Self, Error> {
let runtime = runtime.unwrap_or_default().resolve()?.into_inner();
let container = webc::Container::from_bytes(webc)?;
let container = webc::Container::from_bytes(binary)?;
let pkg = BinaryPackage::from_webc(&container, &*runtime).await?;

Wasmer::from_package(pkg, runtime)
Expand Down
10 changes: 10 additions & 0 deletions tests/directory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,14 @@ describe("In-Memory Directory", function () {
{ name: "tmp", type: "dir" },
]);
});

it("can be created with DirectoryInit", async () => {
const dir = new Directory({
"/file.txt": "file",
"/another/nested/file.txt": "another",
});

expect(await dir.readTextFile("/file.txt")).to.equal("file");
expect(await dir.readTextFile("/another/nested/file.txt")).to.equal("another");
});
});
39 changes: 33 additions & 6 deletions tests/run.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect } from "@esm-bundle/chai";
import {
Runtime,
run,
runWasix,
wat2wasm,
Wasmer,
init,
Expand Down Expand Up @@ -32,7 +32,7 @@ describe("run", function () {
const wasm = wat2wasm(noop);
const module = await WebAssembly.compile(wasm);

const instance = await run(module, { program: "noop", runtime });
const instance = await runWasix(module, { program: "noop", runtime });
const output = await instance.wait();

expect(output.ok).to.be.true;
Expand All @@ -44,7 +44,7 @@ describe("run", function () {
const quickjs = pkg.commands["quickjs"].binary();
const module = await WebAssembly.compile(quickjs);

const instance = await run(module, {
const instance = await runWasix(module, {
program: "quickjs",
args: ["--eval", "console.log('Hello, World!')"],
runtime,
Expand All @@ -64,7 +64,7 @@ describe("run", function () {
const quickjs = pkg.commands["quickjs"].binary();
const module = await WebAssembly.compile(quickjs);

const instance = await run(module, {
const instance = await runWasix(module, {
program: "quickjs",
args: [
"--std",
Expand All @@ -91,7 +91,7 @@ describe("run", function () {
const quickjs = pkg.commands["quickjs"].binary();
const module = await WebAssembly.compile(quickjs);

const instance = await run(module, {
const instance = await runWasix(module, {
program: "quickjs",
args: [
"--std",
Expand All @@ -111,6 +111,33 @@ describe("run", function () {
expect(decoder.decode(output.stderr)).to.be.empty;
});

it("can read files mounted using DirectoryInit", async () => {
const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3");
const quickjs = pkg.commands["quickjs"].binary();
const module = await WebAssembly.compile(quickjs);

const instance = await runWasix(module, {
program: "quickjs",
args: [
"--std",
"--eval",
`console.log(std.open('/tmp/file.txt', "r").readAsString())`,
],
runtime,
mount: {
"/tmp": {
"file.txt": "Hello, World!"
},
},
});
const output = await instance.wait();

expect(output.ok).to.be.true;
expect(output.code).to.equal(0);
expect(decoder.decode(output.stdout)).to.equal("Hello, World!\n");
expect(decoder.decode(output.stderr)).to.be.empty;
});

it("can write files", async () => {
const dir = new Directory();
const pkg = await Wasmer.fromRegistry("saghul/quickjs@0.0.3");
Expand All @@ -122,7 +149,7 @@ describe("run", function () {
f.close();
`;

const instance = await run(module, {
const instance = await runWasix(module, {
program: "quickjs",
args: ["--std", "--eval", script],
runtime,
Expand Down

0 comments on commit cc26237

Please sign in to comment.