Browse Source

Use Manganis Linker System (#2561)

* feat: new manganis linker

* revision: fmt, clippy, cleanup

* revision: link subcommand

* fix: async runtime

* revision: remove working dir arg

* revision: switch dep source

* fix: matrix, playwright

* fix: fullstack manganis
Miles Murgaw 1 year ago
parent
commit
2ef406982e

+ 4 - 4
.github/workflows/main.yml

@@ -222,7 +222,7 @@ jobs:
           - {
               target: x86_64-pc-windows-msvc,
               os: windows-latest,
-              toolchain: "1.75.0",
+              toolchain: "1.76.0",
               cross: false,
               command: "test",
               args: "--all --tests",
@@ -230,7 +230,7 @@ jobs:
           - {
               target: x86_64-apple-darwin,
               os: macos-latest,
-              toolchain: "1.75.0",
+              toolchain: "1.76.0",
               cross: false,
               command: "test",
               args: "--all --tests",
@@ -238,7 +238,7 @@ jobs:
           - {
               target: aarch64-apple-ios,
               os: macos-latest,
-              toolchain: "1.75.0",
+              toolchain: "1.76.0",
               cross: false,
               command: "build",
               args: "--package dioxus-mobile",
@@ -246,7 +246,7 @@ jobs:
           - {
               target: aarch64-linux-android,
               os: ubuntu-latest,
-              toolchain: "1.75.0",
+              toolchain: "1.76.0",
               cross: true,
               command: "build",
               args: "--package dioxus-mobile",

+ 66 - 7
Cargo.lock

@@ -906,7 +906,7 @@ dependencies = [
  "cfg-if",
  "libc",
  "miniz_oxide",
- "object",
+ "object 0.32.2",
  "rustc-demangle",
 ]
 
@@ -1936,7 +1936,7 @@ dependencies = [
  "cssparser-macros",
  "dtoa-short",
  "itoa 1.0.11",
- "phf 0.11.2",
+ "phf 0.8.0",
  "smallvec",
 ]
 
@@ -5918,8 +5918,7 @@ dependencies = [
 [[package]]
 name = "manganis-cli-support"
 version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d502b0db0ef8c880fecff174903815efedfacbcd5ea9a8bef96752f1e0d7eb6"
+source = "git+https://github.com/DogeDark/dioxus-collect-assets/#6b6077150c854af6497d8b96d3a070635ab9b6c4"
 dependencies = [
  "anyhow",
  "cargo-lock",
@@ -5928,8 +5927,9 @@ dependencies = [
  "image 0.25.1",
  "imagequant",
  "lightningcss",
- "manganis-common",
+ "manganis-common 0.2.4 (git+https://github.com/DogeDark/dioxus-collect-assets/)",
  "mozjpeg",
+ "object 0.36.0",
  "petgraph",
  "png",
  "railwind",
@@ -5938,6 +5938,7 @@ dependencies = [
  "reqwest 0.11.27",
  "rustc-hash",
  "serde",
+ "serde_json",
  "toml 0.7.8",
  "tracing",
  "url",
@@ -5960,13 +5961,29 @@ dependencies = [
  "url",
 ]
 
+[[package]]
+name = "manganis-common"
+version = "0.2.4"
+source = "git+https://github.com/DogeDark/dioxus-collect-assets/#6b6077150c854af6497d8b96d3a070635ab9b6c4"
+dependencies = [
+ "anyhow",
+ "base64 0.21.7",
+ "home",
+ "infer 0.11.0",
+ "reqwest",
+ "serde",
+ "toml 0.7.8",
+ "tracing",
+ "url",
+]
+
 [[package]]
 name = "manganis-macro"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "704a0123ac90fa630b21a04fde56c29dfd5a7665c5e8f3639567989daa2d29d1"
 dependencies = [
- "manganis-common",
+ "manganis-common 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2",
  "quote",
  "syn 2.0.60",
@@ -6554,6 +6571,18 @@ dependencies = [
  "memchr",
 ]
 
+[[package]]
+name = "object"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434"
+dependencies = [
+ "flate2",
+ "memchr",
+ "ruzstd",
+ "wasmparser 0.208.1",
+]
+
 [[package]]
 name = "once_cell"
 version = "1.19.0"
@@ -8117,6 +8146,17 @@ version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
 
+[[package]]
+name = "ruzstd"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5174a470eeb535a721ae9fdd6e291c2411a906b96592182d05217591d5c5cf7b"
+dependencies = [
+ "byteorder",
+ "derive_more",
+ "twox-hash",
+]
+
 [[package]]
 name = "ryu"
 version = "1.0.17"
@@ -9899,6 +9939,16 @@ dependencies = [
  "utf-8",
 ]
 
+[[package]]
+name = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
 [[package]]
 name = "typenum"
 version = "1.17.0"
@@ -10132,7 +10182,7 @@ dependencies = [
  "log",
  "walrus-macro",
  "wasm-encoder",
- "wasmparser",
+ "wasmparser 0.80.2",
 ]
 
 [[package]]
@@ -10419,6 +10469,15 @@ version = "0.80.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "449167e2832691a1bff24cde28d2804e90e09586a448c8e76984792c44334a6b"
 
+[[package]]
+name = "wasmparser"
+version = "0.208.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd921789c9dcc495f589cb37d200155dee65b4a4beeb853323b5e24e0a5f9c58"
+dependencies = [
+ "bitflags 2.5.0",
+]
+
 [[package]]
 name = "web-sys"
 version = "0.3.69"

+ 1 - 1
Cargo.toml

@@ -98,7 +98,7 @@ wasm-bindgen-futures = "0.4.42"
 html_parser = "0.7.0"
 thiserror = "1.0.40"
 prettyplease = { version = "0.2.16", features = ["verbatim"] }
-manganis-cli-support = { version = "0.2.1", features = ["html"] }
+manganis-cli-support = { git = "https://github.com/DogeDark/dioxus-collect-assets/", features = ["html"] }
 manganis = { version = "0.2.1" }
 interprocess = { version = "1.2.2", package = "interprocess-docfix" }
 

+ 11 - 6
packages/cli/src/assets.rs

@@ -1,4 +1,5 @@
 use brotli::enc::BrotliEncoderParams;
+use std::fs;
 use std::path::Path;
 use std::{ffi::OsString, path::PathBuf};
 use walkdir::WalkDir;
@@ -10,12 +11,16 @@ use dioxus_cli_config::CrateConfig;
 use dioxus_cli_config::Platform;
 use manganis_cli_support::{AssetManifest, AssetManifestExt};
 
-pub fn asset_manifest(bin: Option<&str>, crate_config: &CrateConfig) -> AssetManifest {
-    AssetManifest::load_from_path(
-        bin,
-        crate_config.crate_dir.join("Cargo.toml"),
-        crate_config.workspace_dir.join("Cargo.lock"),
-    )
+/// The temp file name for passing manganis json from linker to current exec.
+pub const MG_JSON_OUT: &str = "mg-out";
+
+pub fn asset_manifest(config: &CrateConfig) -> AssetManifest {
+    let file_path = config.out_dir().join(MG_JSON_OUT);
+    let read = fs::read_to_string(&file_path).unwrap();
+    _ = fs::remove_file(file_path);
+    let json: Vec<String> = serde_json::from_str(&read).unwrap();
+
+    AssetManifest::load(json)
 }
 
 /// Create a head file that contains all of the imports for assets that the user project uses

+ 89 - 43
packages/cli/src/builder.rs

@@ -4,6 +4,7 @@ use crate::{
         AssetConfigDropGuard,
     },
     error::{Error, Result},
+    link::LinkCommand,
     tools::Tool,
 };
 use anyhow::Context;
@@ -83,13 +84,12 @@ pub fn build_web(
     let CrateConfig {
         crate_dir,
         target_dir,
-        executable,
         dioxus_config,
         ..
     } = config;
     let out_dir = config.out_dir();
 
-    let _guard = AssetConfigDropGuard::new();
+    let _asset_guard = AssetConfigDropGuard::new();
     let _manganis_support = ManganisSupportGuard::default();
 
     // start to build the assets
@@ -114,54 +114,76 @@ pub fn build_web(
         }
     }
 
-    let cmd = subprocess::Exec::cmd("cargo")
+    let mut cargo_args = vec!["--target".to_string(), "wasm32-unknown-unknown".to_string()];
+
+    let mut cmd = subprocess::Exec::cmd("cargo")
         .set_rust_flags(rust_flags)
         .env("CARGO_TARGET_DIR", target_dir)
         .cwd(crate_dir)
         .arg("build")
-        .arg("--target")
-        .arg("wasm32-unknown-unknown")
         .arg("--message-format=json-render-diagnostics");
 
     // TODO: make the initial variable mutable to simplify all the expressions
     // below. Look inside the `build_desktop()` as an example.
-    let cmd = if config.release {
-        cmd.arg("--release")
-    } else {
-        cmd
-    };
-    let cmd = if config.verbose {
-        cmd.arg("--verbose")
+    if config.release {
+        cargo_args.push("--release".to_string());
+    }
+    if config.verbose {
+        cargo_args.push("--verbose".to_string());
     } else {
-        cmd.arg("--quiet")
-    };
+        cargo_args.push("--quiet".to_string());
+    }
 
-    let cmd = if config.custom_profile.is_some() {
+    if config.custom_profile.is_some() {
         let custom_profile = config.custom_profile.as_ref().unwrap();
-        cmd.arg("--profile").arg(custom_profile)
-    } else {
-        cmd
-    };
+        cargo_args.push("--profile".to_string());
+        cargo_args.push(custom_profile.to_string());
+    }
 
-    let cmd = if config.features.is_some() {
+    if config.features.is_some() {
         let features_str = config.features.as_ref().unwrap().join(" ");
-        cmd.arg("--features").arg(features_str)
-    } else {
-        cmd
-    };
+        cargo_args.push("--features".to_string());
+        cargo_args.push(features_str);
+    }
 
-    let cmd = cmd.args(&config.cargo_args);
+    if let Some(target) = &config.target {
+        cargo_args.push("--target".to_string());
+        cargo_args.push(target.clone());
+    }
 
-    let cmd = match executable {
-        ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
-        ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
-        ExecutableType::Example(name) => cmd.arg("--example").arg(name),
+    cargo_args.append(&mut config.cargo_args.clone());
+
+    match &config.executable {
+        ExecutableType::Binary(name) => {
+            cargo_args.push("--bin".to_string());
+            cargo_args.push(name.to_string());
+        }
+        ExecutableType::Lib(name) => {
+            cargo_args.push("--lib".to_string());
+            cargo_args.push(name.to_string());
+        }
+        ExecutableType::Example(name) => {
+            cargo_args.push("--example".to_string());
+            cargo_args.push(name.to_string());
+        }
     };
 
+    cmd = cmd.args(&cargo_args);
     let CargoBuildResult {
         warnings,
         output_location,
     } = prettier_build(cmd)?;
+
+    // Start Manganis linker intercept.
+    let linker_args = vec![format!("{}", config.out_dir().display())];
+
+    manganis_cli_support::start_linker_intercept(
+        &LinkCommand::command_name(),
+        cargo_args,
+        Some(linker_args),
+    )
+    .unwrap();
+
     let output_location = output_location.context("No output location found")?;
 
     // [2] Establish the output directory structure
@@ -277,7 +299,7 @@ pub fn build_web(
 
     let assets = if !skip_assets {
         tracing::info!("Processing assets");
-        let assets = asset_manifest(executable.executable(), config);
+        let assets = asset_manifest(config);
         process_assets(config, &assets)?;
         Some(assets)
     } else {
@@ -337,7 +359,9 @@ pub fn build_desktop(
     build_assets(config)?;
     let _guard = dioxus_cli_config::__private::save_config(config);
     let _manganis_support = ManganisSupportGuard::default();
-    let _guard = AssetConfigDropGuard::new();
+    let _asset_guard = AssetConfigDropGuard::new();
+
+    let mut cargo_args = Vec::new();
 
     let mut cmd = subprocess::Exec::cmd("cargo")
         .set_rust_flags(rust_flags)
@@ -347,38 +371,60 @@ pub fn build_desktop(
         .arg("--message-format=json-render-diagnostics");
 
     if config.release {
-        cmd = cmd.arg("--release");
+        cargo_args.push("--release".to_string());
     }
     if config.verbose {
-        cmd = cmd.arg("--verbose");
+        cargo_args.push("--verbose".to_string());
     } else {
-        cmd = cmd.arg("--quiet");
+        cargo_args.push("--quiet".to_string());
     }
 
     if config.custom_profile.is_some() {
         let custom_profile = config.custom_profile.as_ref().unwrap();
-        cmd = cmd.arg("--profile").arg(custom_profile);
+        cargo_args.push("--profile".to_string());
+        cargo_args.push(custom_profile.to_string());
     }
 
     if config.features.is_some() {
         let features_str = config.features.as_ref().unwrap().join(" ");
-        cmd = cmd.arg("--features").arg(features_str);
+        cargo_args.push("--features".to_string());
+        cargo_args.push(features_str);
     }
 
     if let Some(target) = &config.target {
-        cmd = cmd.arg("--target").arg(target);
+        cargo_args.push("--target".to_string());
+        cargo_args.push(target.clone());
     }
 
-    cmd = cmd.args(&config.cargo_args);
+    cargo_args.append(&mut config.cargo_args.clone());
 
-    let cmd = match &config.executable {
-        ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
-        ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
-        ExecutableType::Example(name) => cmd.arg("--example").arg(name),
+    match &config.executable {
+        ExecutableType::Binary(name) => {
+            cargo_args.push("--bin".to_string());
+            cargo_args.push(name.to_string());
+        }
+        ExecutableType::Lib(name) => {
+            cargo_args.push("--lib".to_string());
+            cargo_args.push(name.to_string());
+        }
+        ExecutableType::Example(name) => {
+            cargo_args.push("--example".to_string());
+            cargo_args.push(name.to_string());
+        }
     };
 
+    cmd = cmd.args(&cargo_args);
     let warning_messages = prettier_build(cmd)?;
 
+    // Start Manganis linker intercept.
+    let linker_args = vec![format!("{}", config.out_dir().display())];
+
+    manganis_cli_support::start_linker_intercept(
+        &LinkCommand::command_name(),
+        cargo_args,
+        Some(linker_args),
+    )?;
+
     let file_name: String = config.executable.executable().unwrap().to_string();
 
     let target_file = if cfg!(windows) {
@@ -399,7 +445,7 @@ pub fn build_desktop(
 
     let assets = if !skip_assets {
         tracing::info!("Processing assets");
-        let assets = asset_manifest(config.executable.executable(), config);
+        let assets = asset_manifest(config);
         // Collect assets
         process_assets(config, &assets)?;
         // Create the __assets_head.html file for bundling

+ 17 - 13
packages/cli/src/cli/check.rs

@@ -16,22 +16,26 @@ pub struct Check {
 
 impl Check {
     // Todo: check the entire crate
-    pub async fn check(self) -> Result<()> {
-        match self.file {
-            // Default to checking the project
-            None => {
-                if let Err(e) = check_project_and_report().await {
-                    eprintln!("error checking project: {}", e);
-                    exit(1);
+    pub fn check(self) -> Result<()> {
+        let rt = tokio::runtime::Runtime::new().unwrap();
+
+        rt.block_on(async move {
+            match self.file {
+                // Default to checking the project
+                None => {
+                    if let Err(e) = check_project_and_report().await {
+                        eprintln!("error checking project: {}", e);
+                        exit(1);
+                    }
                 }
-            }
-            Some(file) => {
-                if let Err(e) = check_file_and_report(file).await {
-                    eprintln!("failed to check file: {}", e);
-                    exit(1);
+                Some(file) => {
+                    if let Err(e) = check_file_and_report(file).await {
+                        eprintln!("failed to check file: {}", e);
+                        exit(1);
+                    }
                 }
             }
-        }
+        });
 
         Ok(())
     }

+ 39 - 0
packages/cli/src/cli/link.rs

@@ -0,0 +1,39 @@
+use crate::{assets, error::Result};
+use clap::Parser;
+use std::{fs, path::PathBuf};
+
+#[derive(Clone, Debug, Parser)]
+#[clap(name = "link", hide = true)]
+pub struct LinkCommand {
+    // Allow us to accept any argument after `dx link`
+    #[clap(trailing_var_arg = true, allow_hyphen_values = true)]
+    pub args: Vec<String>,
+}
+
+impl LinkCommand {
+    pub fn link(self) -> Result<()> {
+        let Some((link_args, object_files)) = manganis_cli_support::linker_intercept(self.args)
+        else {
+            tracing::warn!("Invalid linker arguments.");
+            return Ok(());
+        };
+
+        // Parse object files, deserialize JSON, & create a file to propogate JSON.
+        let json = manganis_cli_support::get_json_from_object_files(object_files);
+        let parsed = serde_json::to_string(&json).unwrap();
+
+        let out_dir = PathBuf::from(link_args.first().unwrap());
+        fs::create_dir_all(&out_dir).unwrap();
+
+        let path = out_dir.join(assets::MG_JSON_OUT);
+        fs::write(path, parsed).unwrap();
+
+        Ok(())
+    }
+
+    /// We need to pass the subcommand name to Manganis so this
+    /// helps centralize where we set the subcommand "name".
+    pub fn command_name() -> String {
+        "link".to_string()
+    }
+}

+ 6 - 0
packages/cli/src/cli/mod.rs

@@ -7,6 +7,7 @@ pub mod clean;
 pub mod config;
 pub mod create;
 pub mod init;
+pub mod link;
 pub mod plugin;
 pub mod serve;
 pub mod translate;
@@ -85,6 +86,10 @@ pub enum Commands {
     #[cfg(feature = "plugin")]
     #[clap(subcommand)]
     Plugin(plugin::Plugin),
+
+    /// Handles parsing of linker arguments for linker-based systems
+    /// such as Manganis and binary patching.
+    Link(link::LinkCommand),
 }
 
 impl Display for Commands {
@@ -100,6 +105,7 @@ impl Display for Commands {
             Commands::Autoformat(_) => write!(f, "fmt"),
             Commands::Check(_) => write!(f, "check"),
             Commands::Bundle(_) => write!(f, "bundle"),
+            Commands::Link(_) => write!(f, "link"),
 
             #[cfg(feature = "plugin")]
             Commands::Plugin(_) => write!(f, "plugin"),

+ 1 - 1
packages/cli/src/cli/plugin.rs

@@ -15,7 +15,7 @@ pub enum Plugin {
 }
 
 impl Plugin {
-    pub async fn plugin(self) -> Result<()> {
+    pub fn plugin(self) -> Result<()> {
         match self {
             Plugin::List {} => {
                 for item in crate::plugin::PluginManager::plugin_list() {

+ 4 - 4
packages/cli/src/cli/serve.rs

@@ -14,7 +14,7 @@ pub struct Serve {
 }
 
 impl Serve {
-    pub async fn serve(self, bin: Option<PathBuf>) -> Result<()> {
+    pub fn serve(self, bin: Option<PathBuf>) -> Result<()> {
         let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?;
         let serve_cfg = self.serve.clone();
 
@@ -65,10 +65,10 @@ impl Serve {
         // start the develop server
         use server::{desktop, fullstack, web};
         match platform {
-            Platform::Web => web::startup(crate_config.clone(), &serve_cfg).await?,
-            Platform::Desktop => desktop::startup(crate_config.clone(), &serve_cfg).await?,
+            Platform::Web => web::startup(crate_config.clone(), &serve_cfg)?,
+            Platform::Desktop => desktop::startup(crate_config.clone(), &serve_cfg)?,
             Platform::Fullstack | Platform::StaticGeneration => {
-                fullstack::startup(crate_config.clone(), &serve_cfg).await?
+                fullstack::startup(crate_config.clone(), &serve_cfg)?
             }
             _ => unreachable!(),
         }

+ 1 - 1
packages/cli/src/lib.rs

@@ -2,7 +2,7 @@
 #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
 #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
 
-mod assets;
+pub mod assets;
 pub mod builder;
 pub mod server;
 pub mod tools;

+ 7 - 11
packages/cli/src/main.rs

@@ -10,8 +10,7 @@ use Commands::*;
 
 const LOG_ENV: &str = "DIOXUS_LOG";
 
-#[tokio::main]
-async fn main() -> anyhow::Result<()> {
+fn main() -> anyhow::Result<()> {
     let args = Cli::parse();
 
     // If {LOG_ENV} is set, default to env, otherwise filter to cli
@@ -40,19 +39,17 @@ async fn main() -> anyhow::Result<()> {
             .context(error_wrapper("Configuring new project failed")),
 
         #[cfg(feature = "plugin")]
-        Plugin(opts) => opts
-            .plugin()
-            .await
-            .context(error_wrapper("Error with plugin")),
+        Plugin(opts) => opts.plugin().context(error_wrapper("Error with plugin")),
 
         Autoformat(opts) => opts
             .autoformat()
             .context(error_wrapper("Error autoformatting RSX")),
 
-        Check(opts) => opts
-            .check()
-            .await
-            .context(error_wrapper("Error checking RSX")),
+        Check(opts) => opts.check().context(error_wrapper("Error checking RSX")),
+
+        Link(opts) => opts
+            .link()
+            .context(error_wrapper("Error with linker passthrough")),
 
         action => {
             let bin = get_bin(args.bin)?;
@@ -81,7 +78,6 @@ async fn main() -> anyhow::Result<()> {
 
                 Serve(opts) => opts
                     .serve(Some(bin.clone()))
-                    .await
                     .context(error_wrapper("Serving project failed")),
 
                 Bundle(opts) => opts

+ 44 - 42
packages/cli/src/server/desktop/mod.rs

@@ -23,11 +23,11 @@ use crate::plugin::PluginManager;
 
 use super::HotReloadState;
 
-pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
-    startup_with_platform::<DesktopPlatform>(config, serve).await
+pub fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
+    startup_with_platform::<DesktopPlatform>(config, serve)
 }
 
-pub(crate) async fn startup_with_platform<P: Platform + Send + 'static>(
+pub(crate) fn startup_with_platform<P: Platform + Send + 'static>(
     config: CrateConfig,
     serve_cfg: &ConfigOptsServe,
 ) -> Result<()> {
@@ -54,7 +54,7 @@ pub(crate) async fn startup_with_platform<P: Platform + Send + 'static>(
         file_map,
     };
 
-    serve::<P>(config, serve_cfg, hot_reload_state).await?;
+    serve::<P>(config, serve_cfg, hot_reload_state)?;
 
     Ok(())
 }
@@ -70,53 +70,55 @@ fn set_ctrl_c(config: &CrateConfig) {
 }
 
 /// Start the server without hot reload
-async fn serve<P: Platform + Send + 'static>(
+fn serve<P: Platform + Send + 'static>(
     config: CrateConfig,
     serve: &ConfigOptsServe,
     hot_reload_state: HotReloadState,
 ) -> Result<()> {
-    let hot_reload: tokio::task::JoinHandle<Result<()>> = tokio::spawn({
-        let hot_reload_state = hot_reload_state.clone();
-        async move {
-            match hot_reload_state.file_map.clone() {
-                Some(file_map) => {
-                    // The open interprocess sockets
-                    start_desktop_hot_reload(hot_reload_state, file_map).await?;
-                }
-                None => {
-                    std::future::pending::<()>().await;
-                }
-            }
-            Ok(())
-        }
-    });
-
     let platform = RwLock::new(P::start(&config, serve, Vec::new())?);
 
-    tracing::info!("🚀 Starting development server...");
-
-    // We got to own watcher so that it exists for the duration of serve
-    // Otherwise full reload won't work.
-    let _watcher = setup_file_watcher(
-        {
-            let config = config.clone();
-            let serve = serve.clone();
-            move || {
-                platform
-                    .write()
-                    .unwrap()
-                    .rebuild(&config, &serve, Vec::new())
+    let rt = tokio::runtime::Runtime::new().unwrap();
+    rt.block_on(async move {
+        let hot_reload: tokio::task::JoinHandle<Result<()>> = tokio::spawn({
+            let hot_reload_state = hot_reload_state.clone();
+            async move {
+                match hot_reload_state.file_map.clone() {
+                    Some(file_map) => {
+                        // The open interprocess sockets
+                        start_desktop_hot_reload(hot_reload_state, file_map).await?;
+                    }
+                    None => {
+                        std::future::pending::<()>().await;
+                    }
+                }
+                Ok(())
             }
-        },
-        &config,
-        None,
-        hot_reload_state,
-    )
-    .await?;
+        });
 
-    hot_reload.await.unwrap()?;
+        tracing::info!("🚀 Starting development server...");
 
-    Ok(())
+        // We got to own watcher so that it exists for the duration of serve
+        // Otherwise full reload won't work.
+        let _watcher = setup_file_watcher(
+            {
+                let config = config.clone();
+                let serve = serve.clone();
+                move || {
+                    platform
+                        .write()
+                        .unwrap()
+                        .rebuild(&config, &serve, Vec::new())
+                }
+            },
+            &config,
+            None,
+            hot_reload_state,
+        )
+        .await?;
+
+        hot_reload.await.unwrap()?;
+        Ok(())
+    })
 }
 
 async fn start_desktop_hot_reload(

+ 2 - 2
packages/cli/src/server/fullstack/mod.rs

@@ -36,8 +36,8 @@ pub fn server_rust_flags(build: &ConfigOptsBuild) -> String {
     rust_flags(build, SERVER_RUST_FLAGS)
 }
 
-pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
-    desktop::startup_with_platform::<FullstackPlatform>(config, serve).await
+pub fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> {
+    desktop::startup_with_platform::<FullstackPlatform>(config, serve)
 }
 
 fn start_web_build_thread(

+ 42 - 39
packages/cli/src/server/web/mod.rs

@@ -22,7 +22,7 @@ use server::*;
 
 use super::HotReloadState;
 
-pub async fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result<()> {
+pub fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result<()> {
     set_ctrlc_handler(&config);
 
     let ip = serve_cfg
@@ -33,11 +33,11 @@ pub async fn startup(config: CrateConfig, serve_cfg: &ConfigOptsServe) -> Result
 
     let hot_reload_state = build_hotreload_filemap(&config);
 
-    serve(ip, config, hot_reload_state, serve_cfg).await
+    serve(ip, config, hot_reload_state, serve_cfg)
 }
 
 /// Start the server without hot reload
-pub async fn serve(
+pub fn serve(
     ip: IpAddr,
     config: CrateConfig,
     hot_reload_state: HotReloadState,
@@ -55,42 +55,45 @@ pub async fn serve(
 
     tracing::info!("🚀 Starting development server...");
 
-    // We got to own watcher so that it exists for the duration of serve
-    // Otherwise full reload won't work.
-    let _watcher = setup_file_watcher(
-        {
-            let config = config.clone();
-            let hot_reload_state = hot_reload_state.clone();
-            move || build(&config, &hot_reload_state, skip_assets)
-        },
-        &config,
-        Some(WebServerInfo { ip, port }),
-        hot_reload_state.clone(),
-    )
-    .await?;
-
-    // HTTPS
-    // Before console info so it can stop if mkcert isn't installed or fails
-    let rustls_config = get_rustls(&config).await?;
-
-    // Print serve info
-    print_console_info(
-        &config,
-        PrettierOptions {
-            changed: vec![],
-            warnings: first_build_result.warnings,
-            elapsed_time: first_build_result.elapsed_time,
-        },
-        Some(WebServerInfo { ip, port }),
-    );
-
-    // Router
-    let router = setup_router(config.clone(), hot_reload_state).await?;
-
-    // Start server
-    start_server(ip, port, router, opts.open, rustls_config, &config).await?;
-
-    Ok(())
+    let rt = tokio::runtime::Runtime::new().unwrap();
+    rt.block_on(async move {
+        // We got to own watcher so that it exists for the duration of serve
+        // Otherwise full reload won't work.
+        let _watcher = setup_file_watcher(
+            {
+                let config = config.clone();
+                let hot_reload_state = hot_reload_state.clone();
+                move || build(&config, &hot_reload_state, skip_assets)
+            },
+            &config,
+            Some(WebServerInfo { ip, port }),
+            hot_reload_state.clone(),
+        )
+        .await?;
+
+        // HTTPS
+        // Before console info so it can stop if mkcert isn't installed or fails
+        let rustls_config = get_rustls(&config).await?;
+
+        // Print serve info
+        print_console_info(
+            &config,
+            PrettierOptions {
+                changed: vec![],
+                warnings: first_build_result.warnings,
+                elapsed_time: first_build_result.elapsed_time,
+            },
+            Some(WebServerInfo { ip, port }),
+        );
+
+        // Router
+        let router = setup_router(config.clone(), hot_reload_state).await?;
+
+        // Start server
+        start_server(ip, port, router, opts.open, rustls_config, &config).await?;
+
+        Ok(())
+    })
 }
 
 /// Starts dx serve with no hot reload