#![allow(clippy::disallowed_methods, reason = "tooling is exempt")] use std::io::Write; use std::path::Path; use std::process::Command; use anyhow::{Context as _, Result, bail}; use clap::Parser; #[derive(Parser)] pub struct WebExamplesArgs { #[arg(long)] pub release: bool, #[arg(long, default_value = "8080")] pub port: u16, #[arg(long)] pub no_serve: bool, } fn check_program(binary: &str, install_hint: &str) -> Result<()> { match Command::new(binary).arg("--version").output() { Ok(output) if output.status.success() => Ok(()), _ => bail!("`{binary}` not found. Install with: {install_hint}"), } } fn discover_examples() -> Result> { let examples_dir = Path::new("crates/gpui/examples"); let mut names = Vec::new(); for entry in std::fs::read_dir(examples_dir).context("failed to read crates/gpui/examples")? { let path = entry?.path(); if path.extension().and_then(|e| e.to_str()) == Some("rs") { if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) { names.push(stem.to_string()); } } } if names.is_empty() { bail!("no examples found in crates/gpui/examples"); } names.sort(); Ok(names) } pub fn run_web_examples(args: WebExamplesArgs) -> Result<()> { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); let profile = if args.release { "release" } else { "debug" }; let out_dir = "target/web-examples"; check_program("wasm-bindgen", "cargo install wasm-bindgen-cli")?; let examples = discover_examples()?; eprintln!( "Building {} example(s) for wasm32-unknown-unknown ({profile})...\n", examples.len() ); std::fs::create_dir_all(out_dir).context("failed to create output directory")?; eprintln!("Building all examples..."); let mut cmd = Command::new(&cargo); cmd.args([ "build", "--target", "wasm32-unknown-unknown", "-p", "gpui", "--keep-going", ]); // 🙈 cmd.env("RUSTC_BOOTSTRAP", "1"); for name in &examples { cmd.args(["--example", name]); } if args.release { cmd.arg("--release"); } let _ = cmd.status().context("failed to run cargo build")?; // Run wasm-bindgen on each .wasm that was produced. let mut succeeded: Vec = Vec::new(); let mut failed: Vec = Vec::new(); for name in &examples { let wasm_path = format!("target/wasm32-unknown-unknown/{profile}/examples/{name}.wasm"); if !Path::new(&wasm_path).exists() { eprintln!("[{name}] SKIPPED (build failed)"); failed.push(name.clone()); continue; } eprintln!("[{name}] Running wasm-bindgen..."); let example_dir = format!("{out_dir}/{name}"); std::fs::create_dir_all(&example_dir) .with_context(|| format!("failed to create {example_dir}"))?; let status = Command::new("wasm-bindgen") .args([ &wasm_path, "--target", "web", "--no-typescript", "--out-dir", &example_dir, "--out-name", name, ]) // 🙈 .env("RUSTC_BOOTSTRAP", "1") .status() .context("failed to run wasm-bindgen")?; if !status.success() { eprintln!("[{name}] SKIPPED (wasm-bindgen failed)"); failed.push(name.clone()); continue; } // Write per-example index.html. let html_path = format!("{example_dir}/index.html"); std::fs::File::create(&html_path) .and_then(|mut file| file.write_all(make_example_html(name).as_bytes())) .with_context(|| format!("failed to write {html_path}"))?; eprintln!("[{name}] OK"); succeeded.push(name.clone()); } if succeeded.is_empty() { bail!("all {} examples failed to build", examples.len()); } let example_names: Vec<&str> = succeeded.iter().map(|s| s.as_str()).collect(); let index_path = format!("{out_dir}/index.html"); std::fs::File::create(&index_path) .and_then(|mut file| file.write_all(make_gallery_html(&example_names).as_bytes())) .context("failed to write index.html")?; if args.no_serve { return Ok(()); } // Serve with COEP/COOP headers required for WebGPU / SharedArrayBuffer. eprintln!("Serving on http://127.0.0.1:{}...", args.port); let server_script = format!( r#" import http.server class Handler(http.server.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): super().__init__(*args, directory="{out_dir}", **kwargs) def end_headers(self): self.send_header("Cross-Origin-Embedder-Policy", "require-corp") self.send_header("Cross-Origin-Opener-Policy", "same-origin") super().end_headers() http.server.HTTPServer(("127.0.0.1", {port}), Handler).serve_forever() "#, port = args.port, ); let status = Command::new("python3") .args(["-c", &server_script]) .status() .context("failed to run python3 http server (is python3 installed?)")?; if !status.success() { bail!("python3 http server exited with: {status}"); } Ok(()) } fn make_example_html(name: &str) -> String { format!( r#" GPUI Web: {name}
Loading {name}…
"# ) } fn make_gallery_html(examples: &[&str]) -> String { let mut buttons = String::new(); for name in examples { buttons.push_str(&format!( " \n" )); } let first = examples.first().copied().unwrap_or("hello_web"); format!( r##" GPUI Web Examples
"##, count = examples.len(), ) }