Skip to content

Commit

Permalink
Make runner-host interface dynamic (#633)
Browse files Browse the repository at this point in the history
This is needed for #278 and similar to #632.
  • Loading branch information
ia0 authored Oct 7, 2024
1 parent 909ec3c commit cc73b7e
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 99 deletions.
3 changes: 1 addition & 2 deletions crates/runner-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ wasefire-one-of = { path = "../one-of" }
wasefire-protocol-tokio = { path = "../protocol-tokio", features = ["device"] }
wasefire-protocol-usb = { path = "../protocol-usb", features = ["device", "std"] }
wasefire-store = { path = "../store", features = ["std"] }
web-server = { path = "crates/web-server", optional = true }
web-server = { path = "crates/web-server" }

[dependencies.wasefire-scheduler]
path = "../scheduler"
Expand All @@ -51,7 +51,6 @@ features = [
]

[features]
web = ["dep:web-server"]
# Exactly one is enabled by xtask.
debug = [
"wasefire-logger/log",
Expand Down
14 changes: 5 additions & 9 deletions crates/runner-host/crates/web-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::net::IpAddr;
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::Duration;

use anyhow::{Context, Result};
use anyhow::Result;
use futures_util::{SinkExt, StreamExt};
use tokio::sync::{mpsc, oneshot};
use warp::ws::{Message, WebSocket};
Expand All @@ -35,11 +35,7 @@ pub struct Client {
}

impl Client {
pub async fn new(url: &str, events: mpsc::Sender<Event>) -> Result<Self> {
let (addr, port) = url.split_once(':').context("URL is not <addr>:<port>")?;
let addr: IpAddr = addr.parse().context("parsing <addr> in URL")?;
let port: u16 = port.parse().context("parsing <port> in URL")?;

pub async fn new(addr: SocketAddr, events: mpsc::Sender<Event>) -> Result<Self> {
let (sender, mut receiver) = oneshot::channel();
let client = Arc::new(Mutex::new(Some((sender, events))));

Expand All @@ -50,8 +46,8 @@ impl Client {
.map(|ws: warp::ws::Ws, client| ws.on_upgrade(|socket| handle(socket, client)));
let routes = warp::get().and(static_files.or(ws));

tokio::spawn(async move { warp::serve(routes).run((addr, port)).await });
let url = format!("http://{addr}:{port}/");
tokio::spawn(async move { warp::serve(routes).run(addr).await });
let url = format!("http://{addr}/");

// Wait 2 seconds for a client to connect, otherwise open a browser. The client is supposed
// to connect every second. This should ensure that at most one client is open.
Expand Down
3 changes: 1 addition & 2 deletions crates/runner-host/src/board.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ pub struct State {
pub protocol: platform::protocol::State,
pub usb: usb::State,
pub storage: Option<FileStorage>,
#[cfg(feature = "web")]
pub web: web_server::Client,
pub web: Option<web_server::Client>,
}

pub enum Board {}
Expand Down
8 changes: 5 additions & 3 deletions crates/runner-host/src/board/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@
use std::sync::OnceLock;
use std::time::Instant;

use wasefire_board_api as board;
use {wasefire_board_api as board, wasefire_logger as log};

pub enum Impl {}

impl board::debug::Api for Impl {
const MAX_TIME: u64 = u64::MAX;

#[cfg(feature = "web")]
fn println(line: &str) {
let time = Self::time();
let message = format!("{}.{:06}: {}", time / 1000000, time % 1000000, line);
crate::with_state(|state| state.web.println(message))
crate::with_state(|state| match &state.web {
Some(x) => x.println(message),
None => log::println!("{message}"),
})
}

fn time() -> u64 {
Expand Down
10 changes: 5 additions & 5 deletions crates/runner-host/src/board/led.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ impl Api for Impl {

fn set(id: Id<Self>, on: bool) -> Result<(), Error> {
assert_eq!(*id, 0);
#[cfg(not(feature = "web"))]
println!("Led is {}", if on { "on" } else { "off" });
with_state(|state| {
#[cfg(feature = "web")]
state.web.set_led(on);
state.led = on
match &state.web {
Some(x) => x.set_led(on),
None => println!("Led is {}", if on { "on" } else { "off" }),
}
state.led = on;
});
Ok(())
}
Expand Down
112 changes: 57 additions & 55 deletions crates/runner-host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#![feature(never_type)]
#![feature(try_blocks)]

use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Mutex;

Expand Down Expand Up @@ -56,13 +57,13 @@ struct Flags {
#[arg(long, default_value = "usb")]
protocol: Protocol,

/// Address to bind to when --protocol=tcp (ignored otherwise).
/// Socket address to bind to when --protocol=tcp (ignored otherwise).
#[arg(long, default_value = "127.0.0.1:3457")]
tcp_addr: std::net::SocketAddr,
tcp_addr: SocketAddr,

/// Socket path to bind to when --protocol=unix (ignored otherwise).
#[arg(long, default_value = "/tmp/wasefire")]
unix_path: std::path::PathBuf,
unix_path: PathBuf,

/// The VID:PID to use for the USB device.
///
Expand All @@ -75,32 +76,31 @@ struct Flags {
#[arg(long)]
usb_serial: bool,

#[cfg(feature = "web")]
#[command(flatten)]
web_options: WebOptions,
}
/// User interface to interact with the board.
#[arg(long, default_value = "stdio")]
interface: Interface,

#[derive(Clone, clap::ValueEnum)]
enum Protocol {
Tcp,
Unix,
Usb,
/// Socket address to bind to when --interface=web (ignored otherwise).
#[arg(long, default_value = "127.0.0.1:5000")]
web_addr: SocketAddr,
}

#[test]
fn flags() {
<Flags as clap::CommandFactory>::command().debug_assert();
}

#[derive(clap::Args)]
struct WebOptions {
/// Host to start the webserver.
#[clap(long, default_value = "127.0.0.1")]
web_host: String,
#[derive(Clone, clap::ValueEnum)]
enum Protocol {
Tcp,
Unix,
Usb,
}

/// Port to start the webserver.
#[clap(long, default_value = "5000")]
web_port: u16,
#[derive(Clone, clap::ValueEnum)]
enum Interface {
Stdio,
Web,
}

#[tokio::main]
Expand Down Expand Up @@ -128,24 +128,25 @@ async fn main() -> Result<()> {
board::applet::init(flags.flash_dir.join("applet.bin")).await;
let (sender, receiver) = channel(10);
*RECEIVER.lock().unwrap() = Some(receiver);
#[cfg(feature = "web")]
let web = {
let (sender, mut receiver) = channel(10);
tokio::spawn(async move {
while let Some(event) = receiver.recv().await {
match event {
web_server::Event::Exit => cleanup::shutdown(0),
web_server::Event::Button { pressed } => {
with_state(|state| board::button::event(state, Some(pressed)));
let web = match flags.interface {
Interface::Stdio => None,
Interface::Web => {
let (sender, mut receiver) = channel(10);
tokio::spawn(async move {
while let Some(event) = receiver.recv().await {
match event {
web_server::Event::Exit => cleanup::shutdown(0),
web_server::Event::Button { pressed } => {
with_state(|state| board::button::event(state, Some(pressed)));
}
}
}
}
});
let mut trunk = tokio::process::Command::new("../../scripts/wrapper.sh");
trunk.args(["trunk", "build", "--release", "crates/web-client/index.html"]);
wasefire_cli_tools::cmd::execute(&mut trunk).await?;
let url = format!("{}:{}", flags.web_options.web_host, flags.web_options.web_port);
web_server::Client::new(&url, sender).await?
});
let mut trunk = tokio::process::Command::new("../../scripts/wrapper.sh");
trunk.args(["trunk", "build", "--release", "crates/web-client/index.html"]);
wasefire_cli_tools::cmd::execute(&mut trunk).await?;
Some(web_server::Client::new(flags.web_addr, sender).await?)
}
};
let push = {
use wasefire_board_api::platform::protocol::Event;
Expand Down Expand Up @@ -176,29 +177,30 @@ async fn main() -> Result<()> {
protocol,
usb,
storage,
#[cfg(feature = "web")]
web,
});
board::uart::Uarts::init();
board::usb::init()?;
#[cfg(not(feature = "web"))]
tokio::task::spawn_blocking(|| {
use std::io::BufRead;
// The tokio::io::Stdin documentation recommends to use blocking IO in a dedicated thread.
// Note that because of this, the runtime may not exit until the user press enter.
for line in std::io::stdin().lock().lines() {
let pressed = match line.unwrap().as_str() {
"button" => None,
"press" => Some(true),
"release" => Some(false),
x => {
println!("Unrecognized command: {x}");
continue;
}
};
with_state(|state| board::button::event(state, pressed));
}
});
if matches!(flags.interface, Interface::Stdio) {
tokio::task::spawn_blocking(|| {
use std::io::BufRead;
// The tokio::io::Stdin documentation recommends to use blocking IO in a dedicated
// thread. Note that because of this, the runtime may not exit until the
// user press enter.
for line in std::io::stdin().lock().lines() {
let pressed = match line.unwrap().as_str() {
"button" => None,
"press" => Some(true),
"release" => Some(false),
x => {
println!("Unrecognized command: {x}");
continue;
}
};
with_state(|state| board::button::event(state, pressed));
}
});
}
println!("Board initialized. Starting scheduler.");
// Not sure why Rust doesn't figure out this can't return (maybe async).
let _: ! = tokio::task::spawn_blocking(|| Scheduler::<board::Board>::run()).await?;
Expand Down
1 change: 0 additions & 1 deletion crates/runner-host/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,5 @@ ensure_applet
test_helper

cargo test --bin=runner-host --features=wasm,debug
cargo check --bin=runner-host --features=wasm,debug,web
cargo check --bin=runner-host --features=wasm,release
cargo check --bin=runner-host --target=i686-unknown-linux-gnu --features=native,release
22 changes: 0 additions & 22 deletions crates/xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,18 +211,6 @@ struct RunnerOptions {
#[clap(long)]
log: Option<String>,

/// Creates a web interface for the host runner.
#[clap(long)]
web: bool,

/// Host to start the webserver.
#[clap(long)]
web_host: Option<String>,

/// Port to start the webserver.
#[clap(long)]
web_port: Option<u16>,

/// Measures bloat after building.
// TODO: Make this a subcommand taking additional options for cargo bloat.
#[clap(long)]
Expand Down Expand Up @@ -483,10 +471,6 @@ impl RunnerOptions {
if let Some(log) = &self.log {
cargo.env(self.log_env(), log);
}
let web = self.web || self.web_host.is_some() || self.web_port.is_some();
if self.name == "host" && web {
features.push("web".to_string());
}
if self.stack_sizes.is_some() {
rustflags.push("-Z emit-stack-sizes".to_string());
rustflags.push("-C link-arg=-Tstack-sizes.x".to_string());
Expand Down Expand Up @@ -517,12 +501,6 @@ impl RunnerOptions {
}
}
cargo.arg("--");
if let Some(host) = &self.web_host {
cargo.arg(format!("--web-host={host}"));
}
if let Some(port) = &self.web_port {
cargo.arg(format!("--web-port={port}"));
}
if std::env::var_os("CODESPACES").is_some() {
log::warn!("Assuming runner --arg=--protocol=unix when running in a codespace.");
cargo.arg("--protocol=unix");
Expand Down

0 comments on commit cc73b7e

Please sign in to comment.