Skip to content

Commit 7f42bc6

Browse files
rvolosatovsbongjunj
authored andcommitted
feat(wasip3): implement wasi:cli (bytecodealliance#11257)
* feat(wasi): introduce common CLI context Signed-off-by: Roman Volosatovs <[email protected]> * chore(wasi): implement traits for boxed values Signed-off-by: Roman Volosatovs <[email protected]> * chore(wasi): implement `Default` for common WASI builder Signed-off-by: Roman Volosatovs <[email protected]> * feat(wasip3): implement `wasi:cli` Signed-off-by: Roman Volosatovs <[email protected]> * refactor: require streams to be `Send` Signed-off-by: Roman Volosatovs <[email protected]> * refactor: avoid typing `WasiCli` in task Signed-off-by: Roman Volosatovs <[email protected]> * refactor: remove `Unpin` bound from stream I/O Signed-off-by: Roman Volosatovs <[email protected]> * refactor: remove `ResourceView` Signed-off-by: Roman Volosatovs <[email protected]> * chore: update `serve` to new WASI `isatty` API Signed-off-by: Roman Volosatovs <[email protected]> * chore: adapt to stream API changes Signed-off-by: Roman Volosatovs <[email protected]> * refactor: avoid `**` syntax Signed-off-by: Roman Volosatovs <[email protected]> * refactor(wasip3): remove `Impl` wrappers Signed-off-by: Roman Volosatovs <[email protected]> * refactor(wasip3): use shorthand closure syntax Signed-off-by: Roman Volosatovs <[email protected]> * chore: account for different env on different targets prtest:full Signed-off-by: Roman Volosatovs <[email protected]> --------- Signed-off-by: Roman Volosatovs <[email protected]>
1 parent 1cbab34 commit 7f42bc6

File tree

22 files changed

+928
-223
lines changed

22 files changed

+928
-223
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use test_programs::p3::wasi::cli::{
2+
environment, stderr, stdin, stdout, terminal_stderr, terminal_stdin, terminal_stdout,
3+
};
4+
use test_programs::p3::wit_stream;
5+
use wit_bindgen::StreamResult;
6+
7+
struct Component;
8+
9+
test_programs::p3::export!(Component);
10+
11+
impl test_programs::p3::exports::wasi::cli::run::Guest for Component {
12+
async fn run() -> Result<(), ()> {
13+
assert_eq!(environment::get_arguments(), ["p3_cli.component", "."]);
14+
assert_ne!(environment::get_environment(), []);
15+
assert_eq!(environment::initial_cwd(), None);
16+
17+
assert!(terminal_stdin::get_terminal_stdin().is_none());
18+
assert!(terminal_stdout::get_terminal_stdout().is_none());
19+
assert!(terminal_stderr::get_terminal_stderr().is_none());
20+
21+
let mut stdin = stdin::get_stdin();
22+
assert!(stdin.next().await.is_none());
23+
24+
let (mut stdout_tx, stdout_rx) = wit_stream::new();
25+
stdout::set_stdout(stdout_rx);
26+
let (res, buf) = stdout_tx.write(b"hello stdout\n".into()).await;
27+
assert_eq!(res, StreamResult::Complete(13));
28+
assert_eq!(buf.into_vec(), []);
29+
30+
let (mut stderr_tx, stderr_rx) = wit_stream::new();
31+
stderr::set_stderr(stderr_rx);
32+
let (res, buf) = stderr_tx.write(b"hello stderr\n".into()).await;
33+
assert_eq!(res, StreamResult::Complete(13));
34+
assert_eq!(buf.into_vec(), []);
35+
36+
Ok(())
37+
}
38+
}
39+
40+
fn main() {}

crates/wasi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ preview1 = [
6161
]
6262
p3 = [
6363
"wasmtime/component-model-async",
64+
"wasmtime/component-model-async-bytes",
6465
]
6566

6667
[[test]]

crates/wasi/src/cli.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use std::rc::Rc;
2+
use std::sync::Arc;
3+
4+
#[derive(Default)]
5+
pub struct WasiCliCtx<I, O> {
6+
pub environment: Vec<(String, String)>,
7+
pub arguments: Vec<String>,
8+
pub initial_cwd: Option<String>,
9+
pub stdin: I,
10+
pub stdout: O,
11+
pub stderr: O,
12+
}
13+
14+
pub trait IsTerminal {
15+
/// Returns whether this stream is backed by a TTY.
16+
fn is_terminal(&self) -> bool;
17+
}
18+
19+
impl<T: ?Sized + IsTerminal> IsTerminal for &T {
20+
fn is_terminal(&self) -> bool {
21+
T::is_terminal(self)
22+
}
23+
}
24+
25+
impl<T: ?Sized + IsTerminal> IsTerminal for &mut T {
26+
fn is_terminal(&self) -> bool {
27+
T::is_terminal(self)
28+
}
29+
}
30+
31+
impl<T: ?Sized + IsTerminal> IsTerminal for Box<T> {
32+
fn is_terminal(&self) -> bool {
33+
T::is_terminal(self)
34+
}
35+
}
36+
37+
impl<T: ?Sized + IsTerminal> IsTerminal for Rc<T> {
38+
fn is_terminal(&self) -> bool {
39+
T::is_terminal(self)
40+
}
41+
}
42+
43+
impl<T: ?Sized + IsTerminal> IsTerminal for Arc<T> {
44+
fn is_terminal(&self) -> bool {
45+
T::is_terminal(self)
46+
}
47+
}
48+
49+
impl IsTerminal for tokio::io::Empty {
50+
fn is_terminal(&self) -> bool {
51+
false
52+
}
53+
}
54+
55+
impl IsTerminal for std::io::Empty {
56+
fn is_terminal(&self) -> bool {
57+
false
58+
}
59+
}
60+
61+
impl IsTerminal for tokio::io::Stdin {
62+
fn is_terminal(&self) -> bool {
63+
std::io::stdin().is_terminal()
64+
}
65+
}
66+
67+
impl IsTerminal for std::io::Stdin {
68+
fn is_terminal(&self) -> bool {
69+
std::io::IsTerminal::is_terminal(self)
70+
}
71+
}
72+
73+
impl IsTerminal for tokio::io::Stdout {
74+
fn is_terminal(&self) -> bool {
75+
std::io::stdout().is_terminal()
76+
}
77+
}
78+
79+
impl IsTerminal for std::io::Stdout {
80+
fn is_terminal(&self) -> bool {
81+
std::io::IsTerminal::is_terminal(self)
82+
}
83+
}
84+
85+
impl IsTerminal for tokio::io::Stderr {
86+
fn is_terminal(&self) -> bool {
87+
std::io::stderr().is_terminal()
88+
}
89+
}
90+
91+
impl IsTerminal for std::io::Stderr {
92+
fn is_terminal(&self) -> bool {
93+
std::io::IsTerminal::is_terminal(self)
94+
}
95+
}

crates/wasi/src/clocks.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,26 @@ use cap_std::time::{Duration, Instant, SystemClock};
22
use cap_std::{AmbientAuthority, ambient_authority};
33
use cap_time_ext::{MonotonicClockExt as _, SystemClockExt as _};
44

5-
#[repr(transparent)]
6-
pub struct WasiClocksImpl<T>(pub T);
7-
85
impl<T: WasiClocksView> WasiClocksView for &mut T {
9-
fn clocks(&mut self) -> &WasiClocksCtx {
10-
(**self).clocks()
6+
fn clocks(&mut self) -> &mut WasiClocksCtx {
7+
T::clocks(self)
118
}
129
}
1310

14-
impl<T: WasiClocksView> WasiClocksView for WasiClocksImpl<T> {
15-
fn clocks(&mut self) -> &WasiClocksCtx {
16-
self.0.clocks()
11+
impl<T: WasiClocksView> WasiClocksView for Box<T> {
12+
fn clocks(&mut self) -> &mut WasiClocksCtx {
13+
T::clocks(self)
1714
}
1815
}
1916

2017
impl WasiClocksView for WasiClocksCtx {
21-
fn clocks(&mut self) -> &WasiClocksCtx {
18+
fn clocks(&mut self) -> &mut WasiClocksCtx {
2219
self
2320
}
2421
}
2522

2623
pub trait WasiClocksView: Send {
27-
fn clocks(&mut self) -> &WasiClocksCtx;
24+
fn clocks(&mut self) -> &mut WasiClocksCtx;
2825
}
2926

3027
pub struct WasiClocksCtx {

crates/wasi/src/ctx.rs

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::cli::WasiCliCtx;
12
use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};
23
use crate::net::{SocketAddrCheck, SocketAddrUse};
34
use crate::random::WasiRandomCtx;
@@ -16,17 +17,17 @@ use std::sync::Arc;
1617
/// [p2::WasiCtxBuilder](crate::p2::WasiCtxBuilder)
1718
///
1819
/// [`Store`]: wasmtime::Store
19-
pub(crate) struct WasiCtxBuilder {
20-
pub(crate) env: Vec<(String, String)>,
21-
pub(crate) args: Vec<String>,
20+
#[derive(Default)]
21+
pub(crate) struct WasiCtxBuilder<I, O> {
2222
pub(crate) random: WasiRandomCtx,
2323
pub(crate) clocks: WasiClocksCtx,
24+
pub(crate) cli: WasiCliCtx<I, O>,
2425
pub(crate) socket_addr_check: SocketAddrCheck,
2526
pub(crate) allowed_network_uses: AllowedNetworkUses,
2627
pub(crate) allow_blocking_current_thread: bool,
2728
}
2829

29-
impl WasiCtxBuilder {
30+
impl<I, O> WasiCtxBuilder<I, O> {
3031
/// Creates a builder for a new context with default parameters set.
3132
///
3233
/// The current defaults are:
@@ -44,20 +45,45 @@ impl WasiCtxBuilder {
4445
///
4546
/// These defaults can all be updated via the various builder configuration
4647
/// methods below.
47-
pub(crate) fn new() -> Self {
48+
pub(crate) fn new(stdin: I, stdout: O, stderr: O) -> Self {
4849
let random = WasiRandomCtx::default();
4950
let clocks = WasiClocksCtx::default();
51+
let cli = WasiCliCtx {
52+
environment: Vec::default(),
53+
arguments: Vec::default(),
54+
initial_cwd: None,
55+
stdin,
56+
stdout,
57+
stderr,
58+
};
5059
Self {
51-
env: Vec::new(),
52-
args: Vec::new(),
5360
random,
5461
clocks,
62+
cli,
5563
socket_addr_check: SocketAddrCheck::default(),
5664
allowed_network_uses: AllowedNetworkUses::default(),
5765
allow_blocking_current_thread: false,
5866
}
5967
}
6068

69+
/// Provides a custom implementation of stdin to use.
70+
pub fn stdin(&mut self, stdin: I) -> &mut Self {
71+
self.cli.stdin = stdin;
72+
self
73+
}
74+
75+
/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout.
76+
pub fn stdout(&mut self, stdout: O) -> &mut Self {
77+
self.cli.stdout = stdout;
78+
self
79+
}
80+
81+
/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr.
82+
pub fn stderr(&mut self, stderr: O) -> &mut Self {
83+
self.cli.stderr = stderr;
84+
self
85+
}
86+
6187
/// Configures whether or not blocking operations made through this
6288
/// `WasiCtx` are allowed to block the current thread.
6389
///
@@ -97,7 +123,7 @@ impl WasiCtxBuilder {
97123
/// At this time environment variables are not deduplicated and if the same
98124
/// key is set twice then the guest will see two entries for the same key.
99125
pub fn envs(&mut self, env: &[(impl AsRef<str>, impl AsRef<str>)]) -> &mut Self {
100-
self.env.extend(
126+
self.cli.environment.extend(
101127
env.iter()
102128
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned())),
103129
);
@@ -109,7 +135,8 @@ impl WasiCtxBuilder {
109135
/// At this time environment variables are not deduplicated and if the same
110136
/// key is set twice then the guest will see two entries for the same key.
111137
pub fn env(&mut self, k: impl AsRef<str>, v: impl AsRef<str>) -> &mut Self {
112-
self.env
138+
self.cli
139+
.environment
113140
.push((k.as_ref().to_owned(), v.as_ref().to_owned()));
114141
self
115142
}
@@ -125,13 +152,15 @@ impl WasiCtxBuilder {
125152

126153
/// Appends a list of arguments to the argument array to pass to wasm.
127154
pub fn args(&mut self, args: &[impl AsRef<str>]) -> &mut Self {
128-
self.args.extend(args.iter().map(|a| a.as_ref().to_owned()));
155+
self.cli
156+
.arguments
157+
.extend(args.iter().map(|a| a.as_ref().to_owned()));
129158
self
130159
}
131160

132161
/// Appends a single argument to get passed to wasm.
133162
pub fn arg(&mut self, arg: impl AsRef<str>) -> &mut Self {
134-
self.args.push(arg.as_ref().to_owned());
163+
self.cli.arguments.push(arg.as_ref().to_owned());
135164
self
136165
}
137166

crates/wasi/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//!
1313
//! For WASIp3, see [`p3`]. WASIp3 support is experimental, unstable and incomplete.
1414
15+
pub mod cli;
1516
pub mod clocks;
1617
mod ctx;
1718
mod error;

crates/wasi/src/p2/ctx.rs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::cli::WasiCliCtx;
12
use crate::clocks::{HostMonotonicClock, HostWallClock, WasiClocksCtx};
23
use crate::ctx::AllowedNetworkUses;
34
use crate::net::{SocketAddrCheck, SocketAddrUse};
@@ -36,10 +37,7 @@ use std::pin::Pin;
3637
///
3738
/// [`Store`]: wasmtime::Store
3839
pub struct WasiCtxBuilder {
39-
common: crate::WasiCtxBuilder,
40-
stdin: Box<dyn StdinStream>,
41-
stdout: Box<dyn StdoutStream>,
42-
stderr: Box<dyn StdoutStream>,
40+
common: crate::WasiCtxBuilder<Box<dyn StdinStream>, Box<dyn StdoutStream>>,
4341
preopens: Vec<(Dir, String)>,
4442
built: bool,
4543
}
@@ -64,10 +62,11 @@ impl WasiCtxBuilder {
6462
/// methods below.
6563
pub fn new() -> Self {
6664
Self {
67-
common: crate::WasiCtxBuilder::new(),
68-
stdin: Box::new(pipe::ClosedInputStream),
69-
stdout: Box::new(pipe::SinkOutputStream),
70-
stderr: Box::new(pipe::SinkOutputStream),
65+
common: crate::WasiCtxBuilder::new(
66+
Box::new(pipe::ClosedInputStream),
67+
Box::new(pipe::SinkOutputStream),
68+
Box::new(pipe::SinkOutputStream),
69+
),
7170
preopens: Vec::new(),
7271
built: false,
7372
}
@@ -88,19 +87,19 @@ impl WasiCtxBuilder {
8887
/// Note that inheriting the process's stdin can also be done through
8988
/// [`inherit_stdin`](WasiCtxBuilder::inherit_stdin).
9089
pub fn stdin(&mut self, stdin: impl StdinStream + 'static) -> &mut Self {
91-
self.stdin = Box::new(stdin);
90+
self.common.stdin(Box::new(stdin));
9291
self
9392
}
9493

9594
/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stdout.
9695
pub fn stdout(&mut self, stdout: impl StdoutStream + 'static) -> &mut Self {
97-
self.stdout = Box::new(stdout);
96+
self.common.stdout(Box::new(stdout));
9897
self
9998
}
10099

101100
/// Same as [`stdin`](WasiCtxBuilder::stdin), but for stderr.
102101
pub fn stderr(&mut self, stderr: impl StdoutStream + 'static) -> &mut Self {
103-
self.stderr = Box::new(stderr);
102+
self.common.stderr(Box::new(stderr));
104103
self
105104
}
106105

@@ -436,8 +435,6 @@ impl WasiCtxBuilder {
436435
let Self {
437436
common:
438437
crate::WasiCtxBuilder {
439-
env,
440-
args,
441438
random:
442439
WasiRandomCtx {
443440
random,
@@ -449,13 +446,19 @@ impl WasiCtxBuilder {
449446
wall_clock,
450447
monotonic_clock,
451448
},
449+
cli:
450+
WasiCliCtx {
451+
environment: env,
452+
arguments: args,
453+
initial_cwd: _,
454+
stdin,
455+
stdout,
456+
stderr,
457+
},
452458
socket_addr_check,
453459
allowed_network_uses,
454460
allow_blocking_current_thread,
455461
},
456-
stdin,
457-
stdout,
458-
stderr,
459462
preopens,
460463
built: _,
461464
} = mem::replace(self, Self::new());

0 commit comments

Comments
 (0)