CLI: add --renderer argument (#4320)

* Add --renderer argument to the CLI

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Make `--platform native` work

Signed-off-by: Nico Burns <nico@nicoburns.com>

* Emit `krates` error when cargo metadata fails

* Reinstate default "desktop" feature for examples

* Remove commented 'no platform specified' error

Signed-off-by: Nico Burns <nico@nicoburns.com>

* use the target tripple and bundle format as the source of truth for the build

* resolve renderer and bundle format

* add shorthand flags

* add headings to the help for shorthands

* resolve the renderer from the target triple if it isn't passed

* make value after --fullstack optional

* convert desktop -> mobile when targeting mobile webview

* use the new shorthand flags in the playwright tests

* fix clippy

* merge fix

* cleanup merge conflicts

* fix clippy

* remove nit

* restore platform as a legacy argument

* improve help message

* vendor openssl on native, pin server_fn

* print build errors as warnings

* add --desktop arg compat

* adjust cli headings

* prefer self.is_wasm_or_wasi()

---------

Signed-off-by: Nico Burns <nico@nicoburns.com>
Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
authored and GitHub committed 2025-07-22 14:32:54 -07:00
1 parent 4848e714ae
commit b257e7e8ce
28 files changed
+998 -549

No files matched your search

Generated
+11 -11
View File
@@ -5664,6 +5664,7 @@ dependencies = [
"dioxus-native-dom", "dioxus-native-dom",
"futures-util", "futures-util",
"keyboard-types", "keyboard-types",
"openssl",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"tokio", "tokio",
"tracing", "tracing",
@@ -14276,9 +14277,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_qs" name = "serde_qs"
version = "0.14.0" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6" checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352"
dependencies = [ dependencies = [
"percent-encoding", "percent-encoding",
"serde", "serde",
@@ -14373,9 +14374,9 @@ dependencies = [
[[package]] [[package]]
name = "server_fn" name = "server_fn"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09b0f92b9d3a62c73f238ac21f7a09f15bad335a9d1651514d9da80d2eaf8d4c" checksum = "9c27fbd25ecc066481e383e2ed62ab2480e708aa3fe46cba36e95f58e61dfd04"
dependencies = [ dependencies = [
"axum 0.8.4", "axum 0.8.4",
"base64 0.22.1", "base64 0.22.1",
@@ -14390,7 +14391,6 @@ dependencies = [
"hyper 1.6.0", "hyper 1.6.0",
"inventory", "inventory",
"js-sys", "js-sys",
"once_cell",
"pin-project-lite", "pin-project-lite",
"reqwest 0.12.22", "reqwest 0.12.22",
"rustc_version", "rustc_version",
@@ -14403,7 +14403,7 @@ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.12",
"throw_error", "throw_error",
"tokio", "tokio",
"tokio-tungstenite 0.26.2", "tokio-tungstenite 0.27.0",
"tower 0.5.2", "tower 0.5.2",
"tower-layer", "tower-layer",
"url", "url",
@@ -14416,9 +14416,9 @@ dependencies = [
[[package]] [[package]]
name = "server_fn_macro" name = "server_fn_macro"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "341dd1087afe9f3e546c5979a4f0b6d55ac072e1201313f86e7fe364223835ac" checksum = "b9d530c872590473016016679c94e3bddf47372941fc924f687ffd11a1778a71"
dependencies = [ dependencies = [
"const_format", "const_format",
"convert_case 0.8.0", "convert_case 0.8.0",
@@ -14431,9 +14431,9 @@ dependencies = [
[[package]] [[package]]
name = "server_fn_macro_default" name = "server_fn_macro_default"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5ab934f581482a66da82f2b57b15390ad67c9ab85bd9a6c54bb65060fb1380" checksum = "ca7abc92ed696648275ed9ff171131a83d571af11748593dc2e6eb6a4e22a5b9"
dependencies = [ dependencies = [
"server_fn_macro", "server_fn_macro",
"syn 2.0.104", "syn 2.0.104",
@@ -16672,7 +16672,6 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"log", "log",
"rustls 0.23.29",
"tokio", "tokio",
"tungstenite 0.26.2", "tungstenite 0.26.2",
] ]
@@ -16685,6 +16684,7 @@ checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"log", "log",
"rustls 0.23.29",
"tokio", "tokio",
"tungstenite 0.27.0", "tungstenite 0.27.0",
] ]
+2 -2
View File
@@ -243,8 +243,8 @@ lru = "0.16.0"
async-trait = "0.1.88" async-trait = "0.1.88"
axum = { version = "0.8.4", default-features = false } axum = { version = "0.8.4", default-features = false }
axum-server = { version = "0.7.2", default-features = false } axum-server = { version = "0.7.2", default-features = false }
server_fn = { version = "0.8.2", default-features = false } server_fn = { version = "=0.8.3", default-features = false }
server_fn_macro = { version = "0.8.2" } server_fn_macro = { version = "=0.8.3" }
tower = "0.5.2" tower = "0.5.2"
http = "1.3.1" http = "1.3.1"
notify = { version = "8.1.0" } notify = { version = "8.1.0" }
+41 -41
View File
@@ -1,5 +1,5 @@
use crate::{ use crate::{
serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform, serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, BundleFormat,
ProgressRx, ProgressTx, Result, RustcArgs, StructuredOutput, ProgressRx, ProgressTx, Result, RustcArgs, StructuredOutput,
}; };
use anyhow::{bail, Context}; use anyhow::{bail, Context};
@@ -16,6 +16,7 @@ use std::{
process::Stdio, process::Stdio,
}; };
use subsecond_types::JumpTable; use subsecond_types::JumpTable;
use target_lexicon::Architecture;
use tokio::{ use tokio::{
io::{AsyncBufReadExt, BufReader, Lines}, io::{AsyncBufReadExt, BufReader, Lines},
process::{Child, ChildStderr, ChildStdout, Command}, process::{Child, ChildStderr, ChildStdout, Command},
@@ -189,7 +190,7 @@ impl AppBuilder {
match bundle { match bundle {
Ok(Ok(bundle)) => BuilderUpdate::BuildReady { bundle }, Ok(Ok(bundle)) => BuilderUpdate::BuildReady { bundle },
Ok(Err(err)) => BuilderUpdate::BuildFailed { err }, Ok(Err(err)) => BuilderUpdate::BuildFailed { err },
Err(err) => BuilderUpdate::BuildFailed { err: anyhow::anyhow!("Build panicked! {:#?}", err) }, Err(err) => BuilderUpdate::BuildFailed { err: anyhow::anyhow!("Build panicked! {err:#?}") },
} }
}, },
Some(Ok(Some(msg))) = OptionFuture::from(self.stdout.as_mut().map(|f| f.next_line())) => { Some(Ok(Some(msg))) = OptionFuture::from(self.stdout.as_mut().map(|f| f.next_line())) => {
@@ -300,7 +301,13 @@ impl AppBuilder {
// for all other platforms, we need to use the ASLR reference to know where to insert the patch. // for all other platforms, we need to use the ASLR reference to know where to insert the patch.
let aslr_reference = match self.aslr_reference { let aslr_reference = match self.aslr_reference {
Some(val) => val, Some(val) => val,
None if self.build.platform == Platform::Web => 0, None if matches!(
self.build.triple.architecture,
Architecture::Wasm32 | Architecture::Wasm64
) =>
{
0
}
None => { None => {
tracing::warn!( tracing::warn!(
"Ignoring hotpatch since there is no ASLR reference. Is the client connected?" "Ignoring hotpatch since there is no ASLR reference. Is the client connected?"
@@ -542,18 +549,18 @@ impl AppBuilder {
); );
// We try to use stdin/stdout to communicate with the app // We try to use stdin/stdout to communicate with the app
match self.build.platform { match self.build.bundle {
// Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead // Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead
// use use the websocket to communicate with it. I wish we could merge the concepts here, // use use the websocket to communicate with it. I wish we could merge the concepts here,
// like say, opening the socket as a subprocess, but alas, it's simpler to do that somewhere else. // like say, opening the socket as a subprocess, but alas, it's simpler to do that somewhere else.
Platform::Web => { BundleFormat::Web => {
// Only the first build we open the web app, after that the user knows it's running // Only the first build we open the web app, after that the user knows it's running
if open_browser { if open_browser {
self.open_web(open_address.unwrap_or(devserver_ip)); self.open_web(open_address.unwrap_or(devserver_ip));
} }
} }
Platform::Ios => { BundleFormat::Ios => {
if self.build.device { if self.build.device {
self.codesign_ios().await?; self.codesign_ios().await?;
self.open_ios_device().await? self.open_ios_device().await?
@@ -562,16 +569,15 @@ impl AppBuilder {
} }
} }
Platform::Android => { BundleFormat::Android => {
self.open_android_sim(false, devserver_ip, envs).await?; self.open_android_sim(false, devserver_ip, envs).await?;
} }
// These are all just basically running the main exe, but with slightly different resource dir paths // These are all just basically running the main exe, but with slightly different resource dir paths
Platform::Server BundleFormat::Server
| Platform::MacOS | BundleFormat::MacOS
| Platform::Windows | BundleFormat::Windows
| Platform::Linux | BundleFormat::Linux => self.open_with_main_exe(envs, args)?,
| Platform::Liveview => self.open_with_main_exe(envs, args)?,
}; };
self.builds_opened += 1; self.builds_opened += 1;
@@ -661,7 +667,7 @@ impl AppBuilder {
} }
// If the emulator is android, we need to copy the asset to the device with `adb push asset /data/local/tmp/dx/assets/filename.ext` // If the emulator is android, we need to copy the asset to the device with `adb push asset /data/local/tmp/dx/assets/filename.ext`
if self.build.platform == Platform::Android { if self.build.bundle == BundleFormat::Android {
let bundled_name = PathBuf::from(bundled.bundled_path()); let bundled_name = PathBuf::from(bundled.bundled_path());
_ = self.copy_file_to_android_tmp(&from, &bundled_name).await; _ = self.copy_file_to_android_tmp(&from, &bundled_name).await;
} }
@@ -684,7 +690,7 @@ impl AppBuilder {
let mut jump_table = crate::build::create_jump_table(&new, &triple, cache)?; let mut jump_table = crate::build::create_jump_table(&new, &triple, cache)?;
// If it's android, we need to copy the assets to the device and then change the location of the patch // If it's android, we need to copy the assets to the device and then change the location of the patch
if self.build.platform == Platform::Android { if self.build.bundle == BundleFormat::Android {
jump_table.lib = self jump_table.lib = self
.copy_file_to_android_tmp(&new, &(PathBuf::from(new.file_name().unwrap()))) .copy_file_to_android_tmp(&new, &(PathBuf::from(new.file_name().unwrap())))
.await?; .await?;
@@ -777,7 +783,7 @@ impl AppBuilder {
} }
// If the emulator is android, we need to copy the asset to the device with `adb push asset /data/local/tmp/dx/assets/filename.ext` // If the emulator is android, we need to copy the asset to the device with `adb push asset /data/local/tmp/dx/assets/filename.ext`
if self.build.platform == Platform::Android { if self.build.bundle == BundleFormat::Android {
_ = self _ = self
.copy_file_to_android_tmp(&changed_file, &bundled_name) .copy_file_to_android_tmp(&changed_file, &bundled_name)
.await; .await;
@@ -972,7 +978,7 @@ impl AppBuilder {
.await?; .await?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to install app: {:?}", output); bail!("Failed to install app: {output:?}");
} }
let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?) let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)
@@ -1007,7 +1013,7 @@ impl AppBuilder {
.await?; .await?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to launch app: {:?}", output); bail!("Failed to launch app: {output:?}");
} }
let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?) let json: Value = serde_json::from_str(&std::fs::read_to_string(tmpfile.path())?)
@@ -1032,7 +1038,7 @@ impl AppBuilder {
.await?; .await?;
if !output.status.success() { if !output.status.success() {
bail!("Failed to resume app: {:?}", output); bail!("Failed to resume app: {output:?}");
} }
Ok(()) Ok(())
@@ -1255,8 +1261,8 @@ We checked the folders:
let port = devserver_socket.port(); let port = devserver_socket.port();
if let Err(e) = Command::new(&adb) if let Err(e) = Command::new(&adb)
.arg("reverse") .arg("reverse")
.arg(format!("tcp:{}", port)) .arg(format!("tcp:{port}"))
.arg(format!("tcp:{}", port)) .arg(format!("tcp:{port}"))
.output() .output()
.await .await
{ {
@@ -1362,18 +1368,14 @@ We checked the folders:
let mut main_exe = self.build.main_exe(); let mut main_exe = self.build.main_exe();
// The requirement here is based on the platform, not necessarily our current architecture. // The requirement here is based on the platform, not necessarily our current architecture.
let requires_entropy = match self.build.platform { let requires_entropy = match self.build.bundle {
// When running "bundled", we don't need entropy // When running "bundled", we don't need entropy
Platform::Web => false, BundleFormat::Web | BundleFormat::MacOS | BundleFormat::Ios | BundleFormat::Android => {
Platform::MacOS => false, false
Platform::Ios => false, }
Platform::Android => false,
// But on platforms that aren't running as "bundled", we do. // But on platforms that aren't running as "bundled", we do.
Platform::Windows => true, BundleFormat::Windows | BundleFormat::Linux | BundleFormat::Server => true,
Platform::Linux => true,
Platform::Server => true,
Platform::Liveview => true,
}; };
if requires_entropy || crate::devcfg::should_force_entropy() { if requires_entropy || crate::devcfg::should_force_entropy() {
@@ -1443,24 +1445,22 @@ We checked the folders:
} }
pub(crate) async fn open_debugger(&mut self, server: &WebServer) -> Result<()> { pub(crate) async fn open_debugger(&mut self, server: &WebServer) -> Result<()> {
let url = match self.build.platform { let url = match self.build.bundle {
Platform::MacOS BundleFormat::MacOS
| Platform::Windows | BundleFormat::Windows
| Platform::Linux | BundleFormat::Linux
| Platform::Server | BundleFormat::Server => {
| Platform::Liveview => {
let Some(Some(pid)) = self.child.as_mut().map(|f| f.id()) else { let Some(Some(pid)) = self.child.as_mut().map(|f| f.id()) else {
tracing::warn!("No process to attach debugger to"); tracing::warn!("No process to attach debugger to");
return Ok(()); return Ok(());
}; };
format!( format!(
"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{}}}", "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}"
pid
) )
} }
Platform::Web => { BundleFormat::Web => {
// code --open-url "vscode://DioxusLabs.dioxus/debugger?uri=http://127.0.0.1:8080" // code --open-url "vscode://DioxusLabs.dioxus/debugger?uri=http://127.0.0.1:8080"
// todo - debugger could open to the *current* page afaik we don't have a way to have that info // todo - debugger could open to the *current* page afaik we don't have a way to have that info
let address = server.devserver_address(); let address = server.devserver_address();
@@ -1474,7 +1474,7 @@ We checked the folders:
format!("vscode://DioxusLabs.dioxus/debugger?uri={protocol}://{address}{base_path}") format!("vscode://DioxusLabs.dioxus/debugger?uri={protocol}://{address}{base_path}")
} }
Platform::Ios => { BundleFormat::Ios => {
let Some(pid) = self.pid else { let Some(pid) = self.pid else {
tracing::warn!("No process to attach debugger to"); tracing::warn!("No process to attach debugger to");
return Ok(()); return Ok(());
@@ -1513,7 +1513,7 @@ We checked the folders:
// (lldb) settings append target.exec-search-paths target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so // (lldb) settings append target.exec-search-paths target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so
// (lldb) process handle SIGSEGV --pass true --stop false --notify true (otherwise the java threads cause crash) // (lldb) process handle SIGSEGV --pass true --stop false --notify true (otherwise the java threads cause crash)
// //
Platform::Android => { BundleFormat::Android => {
// adb push ./sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/20/lib/linux/aarch64/lldb-server /tmp // adb push ./sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/20/lib/linux/aarch64/lldb-server /tmp
// adb shell "/tmp/lldb-server --server --listen ..." // adb shell "/tmp/lldb-server --server --listen ..."
// "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'connect','port': {}}}", // "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'connect','port': {}}}",
@@ -1601,7 +1601,7 @@ We checked the folders:
} }
}; };
tracing::info!("Opening debugger for [{}]: {url}", self.build.platform); tracing::info!("Opening debugger for [{}]: {url}", self.build.bundle);
_ = tokio::process::Command::new("code") _ = tokio::process::Command::new("code")
.arg("--open-url") .arg("--open-url")
+1 -1
View File
@@ -105,7 +105,7 @@ impl BuildContext {
} }
pub(crate) fn status_build_error(&self, line: String) { pub(crate) fn status_build_error(&self, line: String) {
tracing::debug!(dx_src = ?TraceSrc::Cargo, "{line}"); tracing::warn!(dx_src = ?TraceSrc::Cargo, "{line}");
} }
pub(crate) fn status_build_message(&self, line: String) { pub(crate) fn status_build_message(&self, line: String) {
+2 -2
View File
@@ -775,7 +775,7 @@ pub fn create_undefined_symbol_stub(
let mut defined_symbols = HashSet::new(); let mut defined_symbols = HashSet::new();
for path in sorted { for path in sorted {
let bytes = std::fs::read(path).with_context(|| format!("failed to read {:?}", path))?; let bytes = std::fs::read(path).with_context(|| format!("failed to read {path:?}"))?;
let file = File::parse(bytes.deref() as &[u8])?; let file = File::parse(bytes.deref() as &[u8])?;
for symbol in file.symbols() { for symbol in file.symbols() {
if symbol.is_undefined() { if symbol.is_undefined() {
@@ -853,7 +853,7 @@ pub fn create_undefined_symbol_stub(
if aslr_reference < aslr_ref_address { if aslr_reference < aslr_ref_address {
return Err(PatchError::InvalidModule( return Err(PatchError::InvalidModule(
format!( format!(
"ASLR reference is less than the main module's address - is there a `main`?. {:x} < {:x}", aslr_reference, aslr_ref_address ) "ASLR reference is less than the main module's address - is there a `main`?. {aslr_reference:x} < {aslr_ref_address:x}" )
)); ));
} }
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -108,11 +108,11 @@ fn refactor_file(
let out = dioxus_autofmt::apply_formats(&s, edits); let out = dioxus_autofmt::apply_formats(&s, edits);
if file == "-" { if file == "-" {
print!("{}", out); print!("{out}");
} else if let Err(e) = fs::write(&file, out) { } else if let Err(e) = fs::write(&file, out) {
tracing::error!("failed to write formatted content to file: {e}",); tracing::error!("failed to write formatted content to file: {e}",);
} else { } else {
println!("formatted {}", file); println!("formatted {file}");
} }
Ok(()) Ok(())
@@ -191,7 +191,7 @@ fn autoformat_project(
let files_formatted: usize = counts.into_iter().flatten().sum(); let files_formatted: usize = counts.into_iter().flatten().sum();
if files_formatted > 0 && check { if files_formatted > 0 && check {
bail!("{} files needed formatting", files_formatted); bail!("{files_formatted} files needed formatting");
} }
Ok(()) Ok(())
@@ -252,7 +252,7 @@ fn format_syn_error(err: syn::Error) -> Error {
let start = err.span().start(); let start = err.span().start();
let line = start.line; let line = start.line;
let column = start.column; let column = start.column;
anyhow::anyhow!("Syntax Error in line {} column {}:\n{}", line, column, err) anyhow::anyhow!("Syntax Error in line {line} column {column}:\n{err}")
} }
#[tokio::test] #[tokio::test]
+9 -5
View File
@@ -1,5 +1,5 @@
use crate::BuildMode;
use crate::{cli::*, AppBuilder, BuildRequest, Workspace}; use crate::{cli::*, AppBuilder, BuildRequest, Workspace};
use crate::{BuildMode, Platform};
use super::target::TargetArgs; use super::target::TargetArgs;
@@ -12,7 +12,11 @@ pub struct BuildArgs {
/// Enable fullstack mode [default: false] /// Enable fullstack mode [default: false]
/// ///
/// This is automatically detected from `dx serve` if the "fullstack" feature is enabled by default. /// This is automatically detected from `dx serve` if the "fullstack" feature is enabled by default.
#[clap(long)] #[arg(
long,
default_missing_value = "true",
num_args = 0..=1,
)]
pub(crate) fullstack: Option<bool>, pub(crate) fullstack: Option<bool>,
/// Pre-render all routes returned from the app's `/static_routes` endpoint [default: false] /// Pre-render all routes returned from the app's `/static_routes` endpoint [default: false]
@@ -48,7 +52,7 @@ impl BuildArgs {
// This involves modifying the BuildRequest to add the client features and server features // This involves modifying the BuildRequest to add the client features and server features
// only if we can properly detect that it's a fullstack build. Careful with this, since // only if we can properly detect that it's a fullstack build. Careful with this, since
// we didn't build BuildRequest to be generally mutable. // we didn't build BuildRequest to be generally mutable.
let default_server = client.enabled_platforms.contains(&Platform::Server); let default_server = client.enabled_renderers.contains(&crate::Renderer::Server);
// Make sure we set the fullstack platform so we actually build the fullstack variant // Make sure we set the fullstack platform so we actually build the fullstack variant
// Users need to enable "fullstack" in their default feature set. // Users need to enable "fullstack" in their default feature set.
@@ -119,8 +123,8 @@ impl CommandWithPlatformOverrides<BuildArgs> {
// Copy the main target from the client to the server // Copy the main target from the client to the server
let main_target = client.main_target.clone(); let main_target = client.main_target.clone();
let mut server_args = server_args.clone(); let mut server_args = server_args.clone();
// The platform in the server build is always set to Server // The renderer in the server build is always set to Server
server_args.platform = Some(Platform::Server); server_args.renderer.renderer = Some(crate::Renderer::Server);
server = server =
Some(BuildRequest::new(&server_args, Some(main_target), workspace.clone()).await?); Some(BuildRequest::new(&server_args, Some(main_target), workspace.clone()).await?);
} }
+12 -13
View File
@@ -1,4 +1,4 @@
use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, Platform}; use crate::{AppBuilder, BuildArgs, BuildMode, BuildRequest, BundleFormat};
use anyhow::{bail, Context}; use anyhow::{bail, Context};
use path_absolutize::Absolutize; use path_absolutize::Absolutize;
use std::collections::HashMap; use std::collections::HashMap;
@@ -55,7 +55,7 @@ impl Bundle {
} }
// If we're building for iOS, we need to bundle the iOS bundle // If we're building for iOS, we need to bundle the iOS bundle
if client.platform == Platform::Ios && self.package_types.is_none() { if client.bundle == BundleFormat::Ios && self.package_types.is_none() {
self.package_types = Some(vec![crate::PackageType::IosBundle]); self.package_types = Some(vec![crate::PackageType::IosBundle]);
} }
@@ -67,9 +67,9 @@ impl Bundle {
} }
// Create a list of bundles that we might need to copy // Create a list of bundles that we might need to copy
match client.platform { match client.bundle {
// By default, mac/win/linux work with tauri bundle // By default, mac/win/linux work with tauri bundle
Platform::MacOS | Platform::Linux | Platform::Windows => { BundleFormat::MacOS | BundleFormat::Linux | BundleFormat::Windows => {
tracing::info!("Running desktop bundler..."); tracing::info!("Running desktop bundler...");
for bundle in Self::bundle_desktop(&client, &self.package_types)? { for bundle in Self::bundle_desktop(&client, &self.package_types)? {
bundles.extend(bundle.bundle_paths); bundles.extend(bundle.bundle_paths);
@@ -77,15 +77,14 @@ impl Bundle {
} }
// Web/ios can just use their root_dir // Web/ios can just use their root_dir
Platform::Web => bundles.push(client.root_dir()), BundleFormat::Web => bundles.push(client.root_dir()),
Platform::Ios => { BundleFormat::Ios => {
tracing::warn!("iOS bundles are not currently codesigned! You will need to codesign the app before distributing."); tracing::warn!("iOS bundles are not currently codesigned! You will need to codesign the app before distributing.");
bundles.push(client.root_dir()) bundles.push(client.root_dir())
} }
Platform::Server => bundles.push(client.root_dir()), BundleFormat::Server => bundles.push(client.root_dir()),
Platform::Liveview => bundles.push(client.root_dir()),
Platform::Android => { BundleFormat::Android => {
let aab = client let aab = client
.android_gradle_bundle() .android_gradle_bundle()
.await .await
@@ -145,16 +144,16 @@ impl Bundle {
let krate = &build; let krate = &build;
let exe = build.main_exe(); let exe = build.main_exe();
_ = std::fs::remove_dir_all(krate.bundle_dir(build.platform)); _ = std::fs::remove_dir_all(krate.bundle_dir(build.bundle));
let package = krate.package(); let package = krate.package();
let mut name: PathBuf = krate.executable_name().into(); let mut name: PathBuf = krate.executable_name().into();
if cfg!(windows) { if cfg!(windows) {
name.set_extension("exe"); name.set_extension("exe");
} }
std::fs::create_dir_all(krate.bundle_dir(build.platform)) std::fs::create_dir_all(krate.bundle_dir(build.bundle))
.context("Failed to create bundle directory")?; .context("Failed to create bundle directory")?;
std::fs::copy(&exe, krate.bundle_dir(build.platform).join(&name)) std::fs::copy(&exe, krate.bundle_dir(build.bundle).join(&name))
.with_context(|| "Failed to copy the output executable into the bundle directory")?; .with_context(|| "Failed to copy the output executable into the bundle directory")?;
let binaries = vec![ let binaries = vec![
@@ -223,7 +222,7 @@ impl Bundle {
} }
let mut settings = SettingsBuilder::new() let mut settings = SettingsBuilder::new()
.project_out_directory(krate.bundle_dir(build.platform)) .project_out_directory(krate.bundle_dir(build.bundle))
.package_settings(PackageSettings { .package_settings(PackageSettings {
product_name: krate.bundled_app_name(), product_name: krate.bundled_app_name(),
version: package.version.to_string(), version: package.version.to_string(),
+1 -1
View File
@@ -119,7 +119,7 @@ async fn check_files_and_report(files_to_check: Vec<PathBuf>) -> Result<()> {
Ok(()) Ok(())
} }
1 => Err(anyhow!("1 issue found.")), 1 => Err(anyhow!("1 issue found.")),
_ => Err(anyhow!("{} issues found.", total_issues)), _ => Err(anyhow!("{total_issues} issues found.")),
} }
} }
+22 -20
View File
@@ -1,7 +1,7 @@
use super::*; use super::*;
use crate::{ use crate::{
serve::{AppServer, ServeUpdate, WebServer}, serve::{AppServer, ServeUpdate, WebServer},
BuilderUpdate, Platform, Result, BuilderUpdate, BundleFormat, Result,
}; };
use anyhow::bail; use anyhow::bail;
use dioxus_dx_wire_format::BuildStage; use dioxus_dx_wire_format::BuildStage;
@@ -43,7 +43,7 @@ impl RunArgs {
match msg { match msg {
ServeUpdate::BuilderUpdate { id, update } => { ServeUpdate::BuilderUpdate { id, update } => {
let platform = builder.get_build(id).unwrap().build.platform; let bundle_format = builder.get_build(id).unwrap().build.bundle;
// And then update the websocketed clients with the new build status in case they want it // And then update the websocketed clients with the new build status in case they want it
devserver.new_build_update(&update).await; devserver.new_build_update(&update).await;
@@ -59,7 +59,7 @@ impl RunArgs {
.await .await
.inspect_err(|e| tracing::error!("Failed to open app: {}", e)); .inspect_err(|e| tracing::error!("Failed to open app: {}", e));
if platform == Platform::Web { if bundle_format == BundleFormat::Web {
tracing::info!( tracing::info!(
"Serving app at http://{}:{}", "Serving app at http://{}:{}",
builder.devserver_bind_ip, builder.devserver_bind_ip,
@@ -69,7 +69,7 @@ impl RunArgs {
} }
BuilderUpdate::Progress { stage } => match stage { BuilderUpdate::Progress { stage } => match stage {
BuildStage::Initializing => { BuildStage::Initializing => {
tracing::info!("[{platform}] Initializing build") tracing::info!("[{bundle_format}] Initializing build")
} }
BuildStage::Starting { .. } => {} BuildStage::Starting { .. } => {}
BuildStage::InstallingTooling => {} BuildStage::InstallingTooling => {}
@@ -79,15 +79,15 @@ impl RunArgs {
krate, krate,
} => { } => {
tracing::debug!( tracing::debug!(
"[{platform}] ({current}/{total}) Compiling {krate} ", "[{bundle_format}] ({current}/{total}) Compiling {krate} ",
) )
} }
BuildStage::RunningBindgen => { BuildStage::RunningBindgen => {
tracing::info!("[{platform}] Running WASM bindgen") tracing::info!("[{bundle_format}] Running WASM bindgen")
} }
BuildStage::SplittingBundle => {} BuildStage::SplittingBundle => {}
BuildStage::OptimizingWasm => { BuildStage::OptimizingWasm => {
tracing::info!("[{platform}] Optimizing WASM with `wasm-opt`") tracing::info!("[{bundle_format}] Optimizing WASM with `wasm-opt`")
} }
BuildStage::Linking => tracing::info!("Linking app"), BuildStage::Linking => tracing::info!("Linking app"),
BuildStage::Hotpatching => {} BuildStage::Hotpatching => {}
@@ -96,49 +96,51 @@ impl RunArgs {
total, total,
path, path,
} => tracing::info!( } => tracing::info!(
"[{platform}] Copying asset {} ({current}/{total})", "[{bundle_format}] Copying asset {} ({current}/{total})",
path.display(), path.display(),
), ),
BuildStage::Bundling => tracing::info!("[{platform}] Bundling app"), BuildStage::Bundling => {
tracing::info!("[{bundle_format}] Bundling app")
}
BuildStage::RunningGradle => { BuildStage::RunningGradle => {
tracing::info!("[{platform}] Running Gradle") tracing::info!("[{bundle_format}] Running Gradle")
} }
BuildStage::Success => {} BuildStage::Success => {}
BuildStage::Restarting => {} BuildStage::Restarting => {}
BuildStage::CompressingAssets => {} BuildStage::CompressingAssets => {}
BuildStage::ExtractingAssets => {} BuildStage::ExtractingAssets => {}
BuildStage::Prerendering => { BuildStage::Prerendering => {
tracing::info!("[{platform}] Prerendering app") tracing::info!("[{bundle_format}] Prerendering app")
} }
BuildStage::Failed => { BuildStage::Failed => {
tracing::error!("[{platform}] Build failed"); tracing::error!("[{bundle_format}] Build failed");
bail!("Build failed for platform: {platform}"); bail!("Build failed for platform: {bundle_format}");
} }
BuildStage::Aborted => { BuildStage::Aborted => {
tracing::error!("[{platform}] Build aborted"); tracing::error!("[{bundle_format}] Build aborted");
bail!("Build aborted for platform: {platform}"); bail!("Build aborted for platform: {bundle_format}");
} }
_ => {} _ => {}
}, },
BuilderUpdate::CompilerMessage { message } => { BuilderUpdate::CompilerMessage { message } => {
print!("{}", message); print!("{message}");
} }
BuilderUpdate::BuildFailed { err } => { BuilderUpdate::BuildFailed { err } => {
tracing::error!("❌ Build failed: {}", err); tracing::error!("❌ Build failed: {}", err);
return Err(err); return Err(err);
} }
BuilderUpdate::StdoutReceived { msg } => { BuilderUpdate::StdoutReceived { msg } => {
tracing::info!("[{platform}] {msg}"); tracing::info!("[{bundle_format}] {msg}");
} }
BuilderUpdate::StderrReceived { msg } => { BuilderUpdate::StderrReceived { msg } => {
tracing::error!("[{platform}] {msg}"); tracing::error!("[{bundle_format}] {msg}");
} }
BuilderUpdate::ProcessExited { status } => { BuilderUpdate::ProcessExited { status } => {
if !status.success() { if !status.success() {
tracing::error!( tracing::error!(
"Application [{platform}] exited with error: {status}" "Application [{bundle_format}] exited with error: {status}"
); );
bail!("Application [{platform}] exited with error: {status}"); bail!("Application [{bundle_format}] exited with error: {status}");
} }
break; break;
+3 -3
View File
@@ -6,15 +6,15 @@ use std::{backtrace::Backtrace, panic::AssertUnwindSafe};
/// Serve the project /// Serve the project
/// ///
/// `dx serve` takes cargo args by default, except with a required `--platform` arg: /// `dx serve` takes cargo args by default with additional renderer args like `--web`, `--webview`, and `--native`:
/// ///
/// ```sh /// ```sh
/// dx serve --example blah --target blah --platform android /// dx serve --example blah --target blah --android
/// ``` /// ```
/// ///
/// A simple serve: /// A simple serve:
/// ```sh /// ```sh
/// dx serve --platform web /// dx serve --web
/// ``` /// ```
/// ///
/// As of dioxus 0.7, `dx serve` allows independent customization of the client and server builds, /// As of dioxus 0.7, `dx serve` allows independent customization of the client and server builds,
+22 -1
View File
@@ -1,5 +1,8 @@
use crate::cli::*; use crate::cli::*;
use crate::BundleFormat;
use crate::Platform; use crate::Platform;
use crate::RendererArg;
use crate::TargetAlias;
use target_lexicon::Triple; use target_lexicon::Triple;
const HELP_HEADING: &str = "Target Options"; const HELP_HEADING: &str = "Target Options";
@@ -7,7 +10,25 @@ const HELP_HEADING: &str = "Target Options";
/// A single target to build for /// A single target to build for
#[derive(Clone, Debug, Default, Deserialize, Parser)] #[derive(Clone, Debug, Default, Deserialize, Parser)]
pub(crate) struct TargetArgs { pub(crate) struct TargetArgs {
/// Build platform: support Web & Desktop [default: "default_platform"] /// The target alias to use for this build. Supports wasm, macos, windows, linux, ios, android, and host [default: "host"]
#[clap(flatten)]
pub(crate) target_alias: TargetAlias,
/// Build renderer: supports web, webview, native, server, and liveview
#[clap(flatten)]
pub(crate) renderer: RendererArg,
/// The bundle format to target for the build: supports web, macos, windows, linux, ios, android, and server
#[clap(long, value_enum, help_heading = HELP_HEADING)]
pub(crate) bundle: Option<BundleFormat>,
/// Build platform: supports Web, MacOS, Windows, Linux, iOS, Android, and Server
///
/// The platform implies a combination of the target alias, renderer, and bundle format flags.
///
/// You should generally prefer to use the `--web`, `--webview`, or `--native` flags to set the renderer
/// or the `--wasm`, `--macos`, `--windows`, `--linux`, `--ios`, or `--android` flags to set the target alias
/// instead of this flag. The renderer, target alias, and bundle format will be inferred if you only pass one.
#[clap(long, value_enum, help_heading = HELP_HEADING)] #[clap(long, value_enum, help_heading = HELP_HEADING)]
pub(crate) platform: Option<Platform>, pub(crate) platform: Option<Platform>,
+1 -1
View File
@@ -41,7 +41,7 @@ impl Translate {
// instead we should be printing as json (or maybe even a different format) if we're not interactive // instead we should be printing as json (or maybe even a different format) if we're not interactive
match self.output { match self.output {
Some(output) => std::fs::write(output, &html)?, Some(output) => std::fs::write(output, &html)?,
None => print!("{}", html), None => print!("{html}"),
} }
Ok(StructuredOutput::HtmlTranslate { html }) Ok(StructuredOutput::HtmlTranslate { html })
+1 -1
View File
@@ -341,7 +341,7 @@ impl FromStr for PackageType {
"appimage" => Ok(PackageType::AppImage), "appimage" => Ok(PackageType::AppImage),
"dmg" => Ok(PackageType::Dmg), "dmg" => Ok(PackageType::Dmg),
"updater" => Ok(PackageType::Updater), "updater" => Ok(PackageType::Updater),
_ => Err(format!("{} is not a valid package type", s)), _ => Err(format!("{s} is not a valid package type")),
} }
} }
} }
+11 -21
View File
@@ -14,10 +14,12 @@
//! 3. Build CLI layer for routing tracing logs to the TUI. //! 3. Build CLI layer for routing tracing logs to the TUI.
//! 4. Build fmt layer for non-interactive logging with a custom writer that prevents output during interactive mode. //! 4. Build fmt layer for non-interactive logging with a custom writer that prevents output during interactive mode.
use crate::{serve::ServeUpdate, Cli, Commands, Platform as TargetPlatform, Verbosity}; use crate::BundleFormat;
use crate::{serve::ServeUpdate, Cli, Commands, Verbosity};
use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel}; use cargo_metadata::diagnostic::{Diagnostic, DiagnosticLevel};
use clap::Parser; use clap::Parser;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use std::str::FromStr;
use std::sync::OnceLock; use std::sync::OnceLock;
use std::{ use std::{
collections::HashMap, collections::HashMap,
@@ -315,7 +317,7 @@ impl Visit for CollectVisitor {
let name = field.name(); let name = field.name();
let mut value_string = String::new(); let mut value_string = String::new();
write!(value_string, "{:?}", value).unwrap(); write!(value_string, "{value:?}").unwrap();
if name == "message" { if name == "message" {
self.message = value_string; self.message = value_string;
@@ -335,8 +337,8 @@ impl Visit for CollectVisitor {
fn format_field(field_name: &str, value: &dyn Debug) -> String { fn format_field(field_name: &str, value: &dyn Debug) -> String {
let mut out = String::new(); let mut out = String::new();
match field_name { match field_name {
"message" => write!(out, "{:?}", value), "message" => write!(out, "{value:?}"),
_ => write!(out, "{}={:?}", field_name, value), _ => write!(out, "{field_name}={value:?}"),
} }
.unwrap(); .unwrap();
@@ -391,7 +393,7 @@ impl TraceMsg {
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum TraceSrc { pub enum TraceSrc {
App(TargetPlatform), App(BundleFormat),
Dev, Dev,
Build, Build,
Bundle, Bundle,
@@ -412,12 +414,9 @@ impl From<String> for TraceSrc {
"dev" => Self::Dev, "dev" => Self::Dev,
"bld" => Self::Build, "bld" => Self::Build,
"cargo" => Self::Cargo, "cargo" => Self::Cargo,
"app" => Self::App(TargetPlatform::Web), other => BundleFormat::from_str(other)
"windows" => Self::App(TargetPlatform::Windows), .map(Self::App)
"macos" => Self::App(TargetPlatform::MacOS), .unwrap_or_else(|_| Self::Unknown),
"linux" => Self::App(TargetPlatform::Linux),
"server" => Self::App(TargetPlatform::Server),
_ => Self::Unknown,
} }
} }
} }
@@ -425,16 +424,7 @@ impl From<String> for TraceSrc {
impl Display for TraceSrc { impl Display for TraceSrc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::App(platform) => match platform { Self::App(bundle) => write!(f, "{bundle}"),
TargetPlatform::Web => write!(f, "web"),
TargetPlatform::MacOS => write!(f, "macos"),
TargetPlatform::Windows => write!(f, "windows"),
TargetPlatform::Linux => write!(f, "linux"),
TargetPlatform::Server => write!(f, "server"),
TargetPlatform::Ios => write!(f, "ios"),
TargetPlatform::Android => write!(f, "android"),
TargetPlatform::Liveview => write!(f, "liveview"),
},
Self::Dev => write!(f, "dev"), Self::Dev => write!(f, "dev"),
Self::Build => write!(f, "build"), Self::Build => write!(f, "build"),
Self::Cargo => write!(f, "cargo"), Self::Cargo => write!(f, "cargo"),
+507 -143
View File
@@ -1,6 +1,484 @@
use anyhow::{Context, Result};
use clap::{arg, ArgMatches, Args, FromArgMatches};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Display; use std::fmt::Display;
use std::str::FromStr; use std::str::FromStr;
use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
use crate::Workspace;
#[derive(
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
)]
#[non_exhaustive]
pub(crate) enum TargetAlias {
/// Targeting the WASM architecture
Wasm,
/// Targeting macos desktop
MacOS,
/// Targeting windows desktop
Windows,
/// Targeting linux desktop
Linux,
/// Targeting the ios platform
///
/// Can't work properly if you're not building from an Apple device.
Ios,
/// Targeting the android platform
Android,
/// Targeting the current target
Host,
/// An unknown target platform
#[default]
Unknown,
}
impl Args for TargetAlias {
fn augment_args(cmd: clap::Command) -> clap::Command {
const HELP_HEADING: &str = "Target Alias";
cmd.arg(arg!(--wasm "Target the wasm triple").help_heading(HELP_HEADING))
.arg(arg!(--macos "Target the macos triple").help_heading(HELP_HEADING))
.arg(arg!(--windows "Target the windows triple").help_heading(HELP_HEADING))
.arg(arg!(--linux "Target the linux triple").help_heading(HELP_HEADING))
.arg(arg!(--ios "Target the ios triple").help_heading(HELP_HEADING))
.arg(arg!(--android "Target the android triple").help_heading(HELP_HEADING))
.arg(arg!(--host "Target the host triple").help_heading(HELP_HEADING))
.arg(arg!(--desktop "Target the host triple").help_heading(HELP_HEADING))
.group(
clap::ArgGroup::new("target_alias")
.args([
"wasm", "macos", "windows", "linux", "ios", "android", "host", "host",
])
.multiple(false)
.required(false),
)
}
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
Self::augment_args(cmd)
}
}
impl FromArgMatches for TargetAlias {
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
if let Some(platform) = matches.get_one::<clap::Id>("target_alias") {
match platform.as_str() {
"wasm" => Ok(Self::Wasm),
"macos" => Ok(Self::MacOS),
"windows" => Ok(Self::Windows),
"linux" => Ok(Self::Linux),
"ios" => Ok(Self::Ios),
"android" => Ok(Self::Android),
"host" => Ok(Self::Host),
_ => Err(clap::Error::raw(
clap::error::ErrorKind::InvalidValue,
format!("Unknown target alias: {platform}"),
)),
}
} else {
Ok(Self::Unknown)
}
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), clap::Error> {
*self = Self::from_arg_matches(matches)?;
Ok(())
}
}
impl TargetAlias {
#[cfg(target_os = "macos")]
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::MacOS);
#[cfg(target_os = "windows")]
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Windows);
#[cfg(target_os = "linux")]
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Linux);
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
pub(crate) const TARGET_PLATFORM: Option<Self> = None;
pub(crate) async fn into_target(self, device: bool, workspace: &Workspace) -> Result<Triple> {
match self {
// Generally just use the host's triple for native executables unless specified otherwise
Self::MacOS | Self::Windows | Self::Linux | Self::Host | Self::Unknown => {
Ok(Triple::host())
}
// We currently assume unknown-unknown for web, but we might want to eventually
// support emscripten
Self::Wasm => Ok("wasm32-unknown-unknown".parse()?),
// For iOS we should prefer the actual architecture for the simulator, but in lieu of actually
// figuring that out, we'll assume aarch64 on m-series and x86_64 otherwise
Self::Ios => {
// use the host's architecture and sim if --device is passed
use target_lexicon::{Architecture, HOST};
let triple_str = match HOST.architecture {
Architecture::Aarch64(_) if device => "aarch64-apple-ios",
Architecture::Aarch64(_) => "aarch64-apple-ios-sim",
_ if device => "x86_64-apple-ios",
_ => "x86_64-apple-ios",
};
Ok(triple_str.parse()?)
}
// Same idea with android but we figure out the connected device using adb
Self::Android => Ok(workspace
.android_tools()?
.autodetect_android_device_triple()
.await),
}
}
pub(crate) fn or(self, other: Self) -> Self {
if self == Self::Unknown {
other
} else {
self
}
}
}
#[derive(
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
)]
pub(crate) struct RendererArg {
pub(crate) renderer: Option<Renderer>,
}
impl Args for RendererArg {
fn augment_args(cmd: clap::Command) -> clap::Command {
const HELP_HEADING: &str = "Renderer";
cmd.arg(arg!(--web "Enable the dioxus web renderer").help_heading(HELP_HEADING))
.arg(arg!(--webview "Enable the dioxus webview renderer").help_heading(HELP_HEADING))
.arg(arg!(--native "Enable the dioxus native renderer").help_heading(HELP_HEADING))
.arg(arg!(--server "Enable the dioxus server renderer").help_heading(HELP_HEADING))
.arg(arg!(--liveview "Enable the dioxus liveview renderer").help_heading(HELP_HEADING))
.group(
clap::ArgGroup::new("renderer")
.args(["web", "webview", "native", "server", "liveview"])
.multiple(false)
.required(false),
)
}
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
Self::augment_args(cmd)
}
}
impl FromArgMatches for RendererArg {
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
if let Some(renderer) = matches.get_one::<clap::Id>("renderer") {
match renderer.as_str() {
"web" => Ok(Self {
renderer: Some(Renderer::Web),
}),
"webview" => Ok(Self {
renderer: Some(Renderer::Webview),
}),
"native" => Ok(Self {
renderer: Some(Renderer::Native),
}),
"server" => Ok(Self {
renderer: Some(Renderer::Server),
}),
"liveview" => Ok(Self {
renderer: Some(Renderer::Liveview),
}),
_ => Err(clap::Error::raw(
clap::error::ErrorKind::InvalidValue,
format!("Unknown platform: {renderer}"),
)),
}
} else {
Ok(Self { renderer: None })
}
}
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), clap::Error> {
*self = Self::from_arg_matches(matches)?;
Ok(())
}
}
impl From<RendererArg> for Option<Renderer> {
fn from(val: RendererArg) -> Self {
val.renderer
}
}
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
#[non_exhaustive]
pub(crate) enum Renderer {
/// Targeting webview renderer
Webview,
/// Targeting native renderer
Native,
/// Targeting the server platform using Axum and Dioxus-Fullstack
///
/// This is implicitly passed if `fullstack` is enabled as a feature. Using this variant simply
/// means you're only building the server variant without the `.wasm` to serve.
Server,
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
Liveview,
/// Targeting the web renderer
Web,
}
impl Renderer {
/// Get the feature name for the platform in the dioxus crate
pub(crate) fn feature_name(&self, target: &Triple) -> &str {
match self {
Renderer::Webview => match (target.environment, target.operating_system) {
(Environment::Android, _) | (_, OperatingSystem::IOS(_)) => "mobile",
_ => "desktop",
},
Renderer::Native => "native",
Renderer::Server => "server",
Renderer::Liveview => "liveview",
Renderer::Web => "web",
}
}
pub(crate) fn autodetect_from_cargo_feature(feature: &str) -> Option<Self> {
match feature {
"web" => Some(Self::Web),
"desktop" | "mobile" => Some(Self::Webview),
"native" => Some(Self::Native),
"liveview" => Some(Self::Liveview),
"server" => Some(Self::Server),
_ => None,
}
}
pub(crate) fn default_platform(&self) -> TargetAlias {
match self {
Renderer::Webview | Renderer::Native | Renderer::Server | Renderer::Liveview => {
TargetAlias::TARGET_PLATFORM.unwrap()
}
Renderer::Web => TargetAlias::Wasm,
}
}
pub(crate) fn from_target(triple: &Triple) -> Self {
match triple.architecture {
// Assume any wasm32 or wasm64 target is a web target
Architecture::Wasm32 | Architecture::Wasm64 => Self::Web,
// Otherwise, assume webview for native targets
_ => Self::Webview,
}
}
}
#[derive(Debug)]
pub(crate) struct UnknownRendererError;
impl std::error::Error for UnknownRendererError {}
impl std::fmt::Display for UnknownRendererError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown renderer")
}
}
impl FromStr for Renderer {
type Err = UnknownRendererError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"webview" => Ok(Self::Webview),
"native" => Ok(Self::Native),
"server" => Ok(Self::Server),
"liveview" => Ok(Self::Liveview),
"web" => Ok(Self::Web),
_ => Err(UnknownRendererError),
}
}
}
impl Display for Renderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Renderer::Webview => "webview",
Renderer::Native => "native",
Renderer::Server => "server",
Renderer::Liveview => "liveview",
Renderer::Web => "web",
})
}
}
#[derive(
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
)]
#[non_exhaustive]
pub(crate) enum BundleFormat {
/// Targeting the web bundle structure
#[serde(rename = "web")]
#[default]
Web,
/// Targeting the macos desktop bundle structure
#[cfg_attr(target_os = "macos", serde(alias = "desktop"))]
#[serde(rename = "macos")]
MacOS,
/// Targeting the windows desktop bundle structure
#[cfg_attr(target_os = "windows", serde(alias = "desktop"))]
#[serde(rename = "windows")]
Windows,
/// Targeting the linux desktop bundle structure
#[cfg_attr(target_os = "linux", serde(alias = "desktop"))]
#[serde(rename = "linux")]
Linux,
/// Targeting the server bundle structure (a single binary placed next to the web build)
#[serde(rename = "server")]
Server,
/// Targeting the ios bundle structure
///
/// Can't work properly if you're not building from an Apple device.
#[serde(rename = "ios")]
Ios,
/// Targeting the android bundle structure
#[serde(rename = "android")]
Android,
}
#[derive(Debug)]
pub(crate) struct UnknownBundleFormatError;
impl std::error::Error for UnknownBundleFormatError {}
impl std::fmt::Display for UnknownBundleFormatError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown bundle format")
}
}
impl FromStr for BundleFormat {
type Err = UnknownBundleFormatError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"web" => Ok(Self::Web),
"macos" => Ok(Self::MacOS),
"windows" => Ok(Self::Windows),
"linux" => Ok(Self::Linux),
"server" => Ok(Self::Server),
"ios" => Ok(Self::Ios),
"android" => Ok(Self::Android),
_ => Err(UnknownBundleFormatError),
}
}
}
impl Display for BundleFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
BundleFormat::Web => "web",
BundleFormat::MacOS => "macos",
BundleFormat::Windows => "windows",
BundleFormat::Linux => "linux",
BundleFormat::Server => "server",
BundleFormat::Ios => "ios",
BundleFormat::Android => "android",
})
}
}
impl BundleFormat {
#[cfg(target_os = "macos")]
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::MacOS);
#[cfg(target_os = "windows")]
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Windows);
#[cfg(target_os = "linux")]
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Linux);
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
pub(crate) const TARGET_PLATFORM: Option<Self> = None;
/// Get the name of the folder we need to generate for this platform
///
/// Note that web and server share the same platform folder since we'll export the web folder as a bundle on its own
pub(crate) fn build_folder_name(&self) -> &'static str {
match self {
Self::Web => "web",
Self::Server => "web",
Self::Ios => "ios",
Self::Android => "android",
Self::Windows => "windows",
Self::Linux => "linux",
Self::MacOS => "macos",
}
}
pub(crate) fn profile_name(&self, release: bool) -> String {
let base_profile = match self {
Self::MacOS | Self::Windows | Self::Linux => "desktop",
Self::Web => "wasm",
Self::Ios => "ios",
Self::Android => "android",
Self::Server => "server",
};
let opt_level = if release { "release" } else { "dev" };
format!("{base_profile}-{opt_level}")
}
pub(crate) fn expected_name(&self) -> &'static str {
match self {
Self::Web => "Web",
Self::MacOS => "MacOS",
Self::Windows => "Windows",
Self::Linux => "Linux",
Self::Ios => "iOS",
Self::Android => "Android",
Self::Server => "Server",
}
}
pub(crate) fn from_target(target: &Triple, renderer: Option<Renderer>) -> Result<BundleFormat> {
match (
renderer,
target.architecture,
target.environment,
target.operating_system,
) {
// The server always uses the server bundle format
(Some(Renderer::Server), _, _, _) => Ok(BundleFormat::Server),
// The web renderer always uses the web bundle format
(Some(Renderer::Web), _, _, _) => Ok(BundleFormat::Web),
// Otherwise, guess it based on the target
// Assume any wasm32 or wasm64 target is a web target
(_, Architecture::Wasm32 | Architecture::Wasm64, _, _) => Ok(BundleFormat::Web),
// For native targets, we need to determine the bundle format based on the OS
(_, _, Environment::Android, _) => Ok(BundleFormat::Android),
(_, _, _, OperatingSystem::IOS(_)) => Ok(BundleFormat::Ios),
(_, _, _, OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_)) => {
Ok(BundleFormat::MacOS)
}
(_, _, _, OperatingSystem::Linux) => Ok(BundleFormat::Linux),
(_, _, _, OperatingSystem::Windows) => Ok(BundleFormat::Windows),
// If we don't recognize the target, default to desktop
_ => BundleFormat::TARGET_PLATFORM.context(
"failed to determine bundle format. Try setting the `--bundle` flag manually",
),
}
}
}
#[derive( #[derive(
Copy, Copy,
@@ -18,188 +496,74 @@ use std::str::FromStr;
)] )]
#[non_exhaustive] #[non_exhaustive]
pub(crate) enum Platform { pub(crate) enum Platform {
/// Targeting the web platform using WASM /// Alias for `--wasm --web --bundle-format web`
#[clap(name = "web")] #[clap(name = "web")]
#[serde(rename = "web")] #[serde(rename = "web")]
#[default] #[default]
Web, Web,
/// Targeting macos desktop /// Alias for `--macos --webview --bundle-format macos`
/// When running on macos, you can also use `--platform desktop` to build for the desktop
#[cfg_attr(target_os = "macos", clap(alias = "desktop"))] #[cfg_attr(target_os = "macos", clap(alias = "desktop"))]
#[clap(name = "macos")] #[clap(name = "macos")]
#[serde(rename = "macos")] #[serde(rename = "macos")]
MacOS, MacOS,
/// Targeting windows desktop /// Alias for `--windows --webview --bundle-format windows`
/// When running on windows, you can also use `--platform desktop` to build for the desktop
#[cfg_attr(target_os = "windows", clap(alias = "desktop"))] #[cfg_attr(target_os = "windows", clap(alias = "desktop"))]
#[clap(name = "windows")] #[clap(name = "windows")]
#[serde(rename = "windows")] #[serde(rename = "windows")]
Windows, Windows,
/// Targeting linux desktop /// Alias for `--linux --webview --bundle-format linux`
/// When running on linux, you can also use `--platform desktop` to build for the desktop
#[cfg_attr(target_os = "linux", clap(alias = "desktop"))] #[cfg_attr(target_os = "linux", clap(alias = "desktop"))]
#[clap(name = "linux")] #[clap(name = "linux")]
#[serde(rename = "linux")] #[serde(rename = "linux")]
Linux, Linux,
/// Targeting the ios platform /// Alias for `--ios --webview --bundle-format ios`
///
/// Can't work properly if you're not building from an Apple device.
#[clap(name = "ios")] #[clap(name = "ios")]
#[serde(rename = "ios")] #[serde(rename = "ios")]
Ios, Ios,
/// Targeting the android platform /// Alias for `--android --webview --bundle-format android`
#[clap(name = "android")] #[clap(name = "android")]
#[serde(rename = "android")] #[serde(rename = "android")]
Android, Android,
/// Targeting the server platform using Axum and Dioxus-Fullstack /// Alias for `--host --server --bundle-format server`
///
/// This is implicitly passed if `fullstack` is enabled as a feature. Using this variant simply
/// means you're only building the server variant without the `.wasm` to serve.
#[clap(name = "server")] #[clap(name = "server")]
#[serde(rename = "server")] #[serde(rename = "server")]
Server, Server,
/// Targeting the static generation platform using SSR and Dioxus-Fullstack /// Alias for `--host --liveview --bundle-format host`
#[clap(name = "liveview")] #[clap(name = "liveview")]
#[serde(rename = "liveview")] #[serde(rename = "liveview")]
Liveview, Liveview,
} }
/// An error that occurs when a platform is not recognized
pub(crate) struct UnknownPlatformError;
impl std::fmt::Display for UnknownPlatformError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unknown platform")
}
}
impl FromStr for Platform {
type Err = UnknownPlatformError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"web" => Ok(Self::Web),
"macos" => Ok(Self::MacOS),
"windows" => Ok(Self::Windows),
"linux" => Ok(Self::Linux),
"liveview" => Ok(Self::Liveview),
"server" => Ok(Self::Server),
"ios" => Ok(Self::Ios),
"android" => Ok(Self::Android),
_ => Err(UnknownPlatformError),
}
}
}
impl Display for Platform {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Platform::Web => "web",
Platform::MacOS => "macos",
Platform::Windows => "windows",
Platform::Linux => "linux",
Platform::Ios => "ios",
Platform::Android => "android",
Platform::Server => "server",
Platform::Liveview => "liveview",
})
}
}
impl Platform { impl Platform {
/// Get the feature name for the platform in the dioxus crate pub(crate) fn into_triple(self) -> (TargetAlias, Renderer, BundleFormat) {
pub(crate) fn feature_name(&self) -> &str {
match self { match self {
Platform::Web => "web", Platform::Web => (TargetAlias::Wasm, Renderer::Web, BundleFormat::Web),
Platform::MacOS => "desktop", Platform::MacOS => (TargetAlias::MacOS, Renderer::Webview, BundleFormat::MacOS),
Platform::Windows => "desktop", Platform::Windows => (
Platform::Linux => "desktop", TargetAlias::Windows,
Platform::Server => "server", Renderer::Webview,
Platform::Liveview => "liveview", BundleFormat::Windows,
Platform::Ios => "mobile", ),
Platform::Android => "mobile", Platform::Linux => (TargetAlias::Linux, Renderer::Webview, BundleFormat::Linux),
} Platform::Ios => (TargetAlias::Ios, Renderer::Webview, BundleFormat::Ios),
} Platform::Android => (
TargetAlias::Android,
/// Get the name of the folder we need to generate for this platform Renderer::Webview,
/// BundleFormat::Android,
/// Note that web and server share the same platform folder since we'll export the web folder as a bundle on its own ),
pub(crate) fn build_folder_name(&self) -> &'static str { Platform::Server => (TargetAlias::Host, Renderer::Server, BundleFormat::Server),
match self { Platform::Liveview => (
Platform::Web => "web", TargetAlias::Host,
Platform::Server => "web", Renderer::Liveview,
Platform::Liveview => "liveview", BundleFormat::TARGET_PLATFORM.unwrap(),
Platform::Ios => "ios", ),
Platform::Android => "android",
Platform::Windows => "windows",
Platform::Linux => "linux",
Platform::MacOS => "macos",
}
}
pub(crate) fn expected_name(&self) -> &'static str {
match self {
Platform::Web => "Web",
Platform::MacOS => "Desktop MacOS",
Platform::Windows => "Desktop Windows",
Platform::Linux => "Desktop Linux",
Platform::Ios => "Mobile iOS",
Platform::Android => "Mobile Android",
Platform::Server => "Server",
Platform::Liveview => "Liveview",
}
}
pub(crate) fn autodetect_from_cargo_feature(feature: &str) -> Option<Self> {
match feature {
"web" => Some(Platform::Web),
"desktop" | "native" => {
#[cfg(target_os = "macos")]
{
Some(Platform::MacOS)
}
#[cfg(target_os = "windows")]
{
Some(Platform::Windows)
}
#[cfg(target_os = "linux")]
{
Some(Platform::Linux)
}
// Possibly need a something for freebsd? Maybe default to Linux?
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
{
None
}
}
"mobile" => None,
"liveview" => Some(Platform::Liveview),
"server" => Some(Platform::Server),
_ => None,
}
}
pub(crate) fn profile_name(&self, release: bool) -> String {
let base_profile = match self {
Platform::MacOS | Platform::Windows | Platform::Linux => "desktop",
Platform::Web => "web",
Platform::Ios => "ios",
Platform::Android => "android",
Platform::Server => "server",
Platform::Liveview => "liveview",
};
if release {
format!("{}-release", base_profile)
} else {
format!("{}-dev", base_profile)
} }
} }
} }
+15 -12
View File
@@ -1,6 +1,7 @@
use crate::{ use crate::{
styles::{GLOW_STYLE, LINK_STYLE}, styles::{GLOW_STYLE, LINK_STYLE},
AppBuilder, BuildId, BuildMode, BuilderUpdate, Platform, Result, ServeArgs, TraceController, AppBuilder, BuildId, BuildMode, BuilderUpdate, BundleFormat, Result, ServeArgs,
TraceController,
}; };
mod ansi_buffer; mod ansi_buffer;
@@ -122,15 +123,15 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
// Received a message from the devtools server - currently we only use this for // Received a message from the devtools server - currently we only use this for
// logging, so we just forward it the tui // logging, so we just forward it the tui
ServeUpdate::WsMessage { msg, platform } => { ServeUpdate::WsMessage { msg, bundle } => {
screen.push_ws_message(platform, &msg); screen.push_ws_message(bundle, &msg);
} }
// Wait for logs from the build engine // Wait for logs from the build engine
// These will cause us to update the screen // These will cause us to update the screen
// We also can check the status of the builds here in case we have multiple ongoing builds // We also can check the status of the builds here in case we have multiple ongoing builds
ServeUpdate::BuilderUpdate { id, update } => { ServeUpdate::BuilderUpdate { id, update } => {
let platform = builder.get_build(id).unwrap().build.platform; let bundle_format = builder.get_build(id).unwrap().build.bundle;
// Queue any logs to be printed if need be // Queue any logs to be printed if need be
screen.new_build_update(&update); screen.new_build_update(&update);
@@ -147,14 +148,14 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
stage: BuildStage::Failed, stage: BuildStage::Failed,
} => { } => {
if exit_on_error { if exit_on_error {
bail!("Build failed for platform: {platform}"); bail!("Build failed for platform: {bundle_format}");
} }
} }
BuilderUpdate::Progress { BuilderUpdate::Progress {
stage: BuildStage::Aborted, stage: BuildStage::Aborted,
} => { } => {
if exit_on_error { if exit_on_error {
bail!("Build aborted for platform: {platform}"); bail!("Build aborted for platform: {bundle_format}");
} }
} }
BuilderUpdate::Progress { .. } => {} BuilderUpdate::Progress { .. } => {}
@@ -206,22 +207,24 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
} }
}, },
BuilderUpdate::StdoutReceived { msg } => { BuilderUpdate::StdoutReceived { msg } => {
screen.push_stdio(platform, msg, tracing::Level::INFO); screen.push_stdio(bundle_format, msg, tracing::Level::INFO);
} }
BuilderUpdate::StderrReceived { msg } => { BuilderUpdate::StderrReceived { msg } => {
screen.push_stdio(platform, msg, tracing::Level::ERROR); screen.push_stdio(bundle_format, msg, tracing::Level::ERROR);
} }
BuilderUpdate::ProcessExited { status } => { BuilderUpdate::ProcessExited { status } => {
if status.success() { if status.success() {
tracing::info!( tracing::info!(
r#"Application [{platform}] exited gracefully. r#"Application [{bundle_format}] exited gracefully.
• To restart the app, press `r` to rebuild or `o` to open • To restart the app, press `r` to rebuild or `o` to open
• To exit the server, press `ctrl+c`"# • To exit the server, press `ctrl+c`"#
); );
} else { } else {
tracing::error!("Application [{platform}] exited with error: {status}"); tracing::error!(
"Application [{bundle_format}] exited with error: {status}"
);
if exit_on_error { if exit_on_error {
bail!("Application [{platform}] exited with error: {status}"); bail!("Application [{bundle_format}] exited with error: {status}");
} }
} }
} }
@@ -241,7 +244,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
} }
ServeUpdate::OpenApp => match builder.use_hotpatch_engine { ServeUpdate::OpenApp => match builder.use_hotpatch_engine {
true if !matches!(builder.client.build.platform, Platform::Web) => { true if !matches!(builder.client.build.bundle, BundleFormat::Web) => {
tracing::warn!( tracing::warn!(
"Opening a native app with hotpatching enabled requires a full rebuild..." "Opening a native app with hotpatching enabled requires a full rebuild..."
); );
+10 -10
View File
@@ -1,6 +1,6 @@
use crate::{ use crate::{
serve::{ansi_buffer::ansi_string_to_line, ServeUpdate, WebServer}, serve::{ansi_buffer::ansi_string_to_line, ServeUpdate, WebServer},
BuildId, BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc, BuildId, BuildStage, BuilderUpdate, BundleFormat, TraceContent, TraceMsg, TraceSrc,
}; };
use cargo_metadata::diagnostic::Diagnostic; use cargo_metadata::diagnostic::Diagnostic;
use crossterm::{ use crossterm::{
@@ -316,12 +316,12 @@ impl Output {
/// Add a message from stderr to the logs /// Add a message from stderr to the logs
/// This will queue the stderr message as a TraceMsg and print it on the next render /// This will queue the stderr message as a TraceMsg and print it on the next render
/// We'll use the `App` TraceSrc for the msg, and whatever level is provided /// We'll use the `App` TraceSrc for the msg, and whatever level is provided
pub fn push_stdio(&mut self, platform: Platform, msg: String, level: Level) { pub fn push_stdio(&mut self, bundle: BundleFormat, msg: String, level: Level) {
self.push_log(TraceMsg::text(TraceSrc::App(platform), level, msg)); self.push_log(TraceMsg::text(TraceSrc::App(bundle), level, msg));
} }
/// Push a message from the websocket to the logs /// Push a message from the websocket to the logs
pub fn push_ws_message(&mut self, platform: Platform, message: &axum::extract::ws::Message) { pub fn push_ws_message(&mut self, bundle: BundleFormat, message: &axum::extract::ws::Message) {
use dioxus_devtools_types::ClientMsg; use dioxus_devtools_types::ClientMsg;
// We can only handle text messages from the websocket... // We can only handle text messages from the websocket...
@@ -336,7 +336,7 @@ impl Output {
let msg = match res { let msg = match res {
Ok(msg) => msg, Ok(msg) => msg,
Err(err) => { Err(err) => {
tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {} -> {:?}", platform, err, text.as_str()); tracing::error!(dx_src = ?TraceSrc::Dev, "Error parsing message from {}: {} -> {:?}", bundle, err, text.as_str());
return; return;
} }
}; };
@@ -358,7 +358,7 @@ impl Output {
}; };
// We don't care about logging the app's message so we directly push it instead of using tracing. // We don't care about logging the app's message so we directly push it instead of using tracing.
self.push_log(TraceMsg::text(TraceSrc::App(platform), level, content)); self.push_log(TraceMsg::text(TraceSrc::App(bundle), level, content));
} }
/// Change internal state based on the build engine's update /// Change internal state based on the build engine's update
@@ -641,7 +641,7 @@ impl Output {
frame.render_widget( frame.render_widget(
Paragraph::new(Line::from(vec![ Paragraph::new(Line::from(vec![
"Platform: ".gray(), "Platform: ".gray(),
client.build.platform.expected_name().yellow(), client.build.bundle.expected_name().yellow(),
if state.runner.is_fullstack() { if state.runner.is_fullstack() {
" + fullstack".yellow() " + fullstack".yellow()
} else { } else {
@@ -672,7 +672,7 @@ impl Output {
frame.render_widget_ref( frame.render_widget_ref(
Paragraph::new(Line::from(vec![ Paragraph::new(Line::from(vec![
if client.build.platform == Platform::Web { if client.build.bundle == BundleFormat::Web {
"Serving at: ".gray() "Serving at: ".gray()
} else { } else {
"Server at: ".gray() "Server at: ".gray()
@@ -755,7 +755,7 @@ impl Output {
); );
let server_address = match state.server.server_address() { let server_address = match state.server.server_address() {
Some(address) => format!("http://{}", address).yellow(), Some(address) => format!("http://{address}").yellow(),
None => "no address".dark_gray(), None => "no address".dark_gray(),
}; };
frame.render_widget( frame.render_widget(
@@ -770,7 +770,7 @@ impl Output {
frame.render_widget( frame.render_widget(
Paragraph::new(Line::from(vec![ Paragraph::new(Line::from(vec![
"Read the docs: ".gray(), "Read the docs: ".gray(),
"https://dioxuslabs.com/0.6/docs".blue(), "https://dioxuslabs.com/learn/0.7/".blue(),
])), ])),
links_list[0], links_list[0],
); );
+3 -4
View File
@@ -163,8 +163,7 @@ pub(crate) fn handle_proxy_error(e: Error) -> axum::http::Response<axum::body::B
axum::http::Response::builder() axum::http::Response::builder()
.status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
.body(axum::body::Body::from(format!( .body(axum::body::Body::from(format!(
"Proxy connection failed: {:#?}", "Proxy connection failed: {e:#?}"
e
))) )))
.unwrap() .unwrap()
} }
@@ -229,7 +228,7 @@ mod test {
let server_addr = setup_servers(config).await; let server_addr = setup_servers(config).await;
assert_eq!( assert_eq!(
reqwest::get(format!("http://{}/api", server_addr)) reqwest::get(format!("http://{server_addr}/api"))
.await .await
.unwrap() .unwrap()
.text() .text()
@@ -239,7 +238,7 @@ mod test {
); );
assert_eq!( assert_eq!(
reqwest::get(format!("http://{}/api/", server_addr)) reqwest::get(format!("http://{server_addr}/api/"))
.await .await
.unwrap() .unwrap()
.text() .text()
+11 -10
View File
@@ -1,7 +1,7 @@
use super::{AppBuilder, ServeUpdate, WebServer}; use super::{AppBuilder, ServeUpdate, WebServer};
use crate::{ use crate::{
platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode, platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,
BuildTargets, BuilderUpdate, HotpatchModuleCache, Platform, Result, ServeArgs, TailwindCli, BuildTargets, BuilderUpdate, BundleFormat, HotpatchModuleCache, Result, ServeArgs, TailwindCli,
TraceSrc, Workspace, TraceSrc, Workspace,
}; };
use anyhow::{bail, Context}; use anyhow::{bail, Context};
@@ -151,8 +151,8 @@ impl AppServer {
// All servers will end up behind us (the devserver) but on a different port // All servers will end up behind us (the devserver) but on a different port
// This is so we can serve a loading screen as well as devtools without anything particularly fancy // This is so we can serve a loading screen as well as devtools without anything particularly fancy
let fullstack = server.is_some(); let fullstack = server.is_some();
let should_proxy_port = match client.platform { let should_proxy_port = match client.bundle {
Platform::Server => true, BundleFormat::Server => true,
_ => fullstack && !ssg, _ => fullstack && !ssg,
}; };
@@ -168,7 +168,7 @@ impl AppServer {
// Only start Tailwind watcher for client builds that serve assets (not server builds or fullstack mode) // Only start Tailwind watcher for client builds that serve assets (not server builds or fullstack mode)
// In fullstack mode, the client build's prebuild() handles Tailwind generation to avoid race conditions // In fullstack mode, the client build's prebuild() handles Tailwind generation to avoid race conditions
let tw_watcher = if client.build.platform != Platform::Server && !fullstack { let tw_watcher = if client.build.bundle != BundleFormat::Server && !fullstack {
TailwindCli::serve( TailwindCli::serve(
client.build.package_manifest_dir(), client.build.package_manifest_dir(),
client.build.config.application.tailwind_input.clone(), client.build.config.application.tailwind_input.clone(),
@@ -536,7 +536,8 @@ impl AppServer {
use crate::styles::NOTE_STYLE; use crate::styles::NOTE_STYLE;
tracing::info!(dx_src = ?TraceSrc::Dev, "Hotreloading: {NOTE_STYLE}{}{NOTE_STYLE:#}", file); tracing::info!(dx_src = ?TraceSrc::Dev, "Hotreloading: {NOTE_STYLE}{}{NOTE_STYLE:#}", file);
if !server.has_hotreload_sockets() && self.client.build.platform != Platform::Web { if !server.has_hotreload_sockets() && self.client.build.bundle != BundleFormat::Web
{
tracing::warn!("No clients to hotreload - try reloading the app!"); tracing::warn!("No clients to hotreload - try reloading the app!");
} }
@@ -554,8 +555,8 @@ impl AppServer {
devserver: &mut WebServer, devserver: &mut WebServer,
) -> Result<()> { ) -> Result<()> {
// Make sure to save artifacts regardless of if we're opening the app or not // Make sure to save artifacts regardless of if we're opening the app or not
match artifacts.platform { match artifacts.bundle {
Platform::Server => { BundleFormat::Server => {
if let Some(server) = self.server.as_mut() { if let Some(server) = self.server.as_mut() {
server.artifacts = Some(artifacts.clone()); server.artifacts = Some(artifacts.clone());
} }
@@ -661,7 +662,7 @@ impl AppServer {
// If the client is running on Android, we need to remove the port forwarding // If the client is running on Android, we need to remove the port forwarding
// todo: use the android tools "adb" // todo: use the android tools "adb"
if matches!(self.client.build.platform, Platform::Android) { if matches!(self.client.build.bundle, BundleFormat::Android) {
if let Err(err) = Command::new(&self.workspace.android_tools()?.adb) if let Err(err) = Command::new(&self.workspace.android_tools()?.adb)
.arg("reverse") .arg("reverse")
.arg("--remove") .arg("--remove")
@@ -786,7 +787,7 @@ impl AppServer {
BuildId::CLIENT => { BuildId::CLIENT => {
// multiple tabs on web can cause this to be called incorrectly, and it doesn't // multiple tabs on web can cause this to be called incorrectly, and it doesn't
// make any sense anyways // make any sense anyways
if self.client.build.platform != Platform::Web { if self.client.build.bundle != BundleFormat::Web {
if let Some(aslr_reference) = aslr_reference { if let Some(aslr_reference) = aslr_reference {
self.client.aslr_reference = Some(aslr_reference); self.client.aslr_reference = Some(aslr_reference);
} }
@@ -804,7 +805,7 @@ impl AppServer {
} }
// Assign the runtime asset dir to the runner // Assign the runtime asset dir to the runner
if self.client.build.platform == Platform::Ios { if self.client.build.bundle == BundleFormat::Ios {
// xcrun simctl get_app_container booted com.dioxuslabs // xcrun simctl get_app_container booted com.dioxuslabs
let res = Command::new("xcrun") let res = Command::new("xcrun")
.arg("simctl") .arg("simctl")
+10 -11
View File
@@ -1,5 +1,5 @@
use crate::{ use crate::{
config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, Platform, config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, BundleFormat,
Result, TraceSrc, Result, TraceSrc,
}; };
use anyhow::{bail, Context}; use anyhow::{bail, Context};
@@ -62,7 +62,7 @@ pub(crate) struct WebServer {
new_build_status_sockets: UnboundedReceiver<ConnectedWsClient>, new_build_status_sockets: UnboundedReceiver<ConnectedWsClient>,
build_status: SharedStatus, build_status: SharedStatus,
application_name: String, application_name: String,
platform: Platform, bundle: BundleFormat,
} }
pub(crate) struct ConnectedWsClient { pub(crate) struct ConnectedWsClient {
@@ -128,7 +128,7 @@ impl WebServer {
new_hot_reload_sockets: hot_reload_sockets_rx, new_hot_reload_sockets: hot_reload_sockets_rx,
new_build_status_sockets: build_status_sockets_rx, new_build_status_sockets: build_status_sockets_rx,
application_name: runner.app_name().to_string(), application_name: runner.app_name().to_string(),
platform: runner.client.build.platform, bundle: runner.client.build.bundle,
}) })
} }
@@ -163,7 +163,7 @@ impl WebServer {
drop(new_message); drop(new_message);
// Update the socket with project info and current build status // Update the socket with project info and current build status
let project_info = SharedStatus::new(Status::ClientInit { application_name: self.application_name.clone(), platform: self.platform }); let project_info = SharedStatus::new(Status::ClientInit { application_name: self.application_name.clone(), bundle: self.bundle });
if project_info.send_to(&mut new_socket.socket).await.is_ok() { if project_info.send_to(&mut new_socket.socket).await.is_ok() {
_ = self.build_status.send_to(&mut new_socket.socket).await; _ = self.build_status.send_to(&mut new_socket.socket).await;
self.build_status_sockets.push(new_socket); self.build_status_sockets.push(new_socket);
@@ -175,7 +175,7 @@ impl WebServer {
} }
Some((idx, message)) = new_message.next() => { Some((idx, message)) = new_message.next() => {
match message { match message {
Some(Ok(msg)) => return ServeUpdate::WsMessage { msg, platform: Platform::Web }, Some(Ok(msg)) => return ServeUpdate::WsMessage { msg, bundle: BundleFormat::Web },
_ => { _ => {
drop(new_message); drop(new_message);
_ = self.hot_reload_sockets.remove(idx); _ = self.hot_reload_sockets.remove(idx);
@@ -375,8 +375,8 @@ impl WebServer {
} }
pub fn server_address(&self) -> Option<SocketAddr> { pub fn server_address(&self) -> Option<SocketAddr> {
match self.platform { match self.bundle {
Platform::Web | Platform::Server => Some(self.devserver_address()), BundleFormat::Web | BundleFormat::Server => Some(self.devserver_address()),
_ => self.proxied_server_address(), _ => self.proxied_server_address(),
} }
} }
@@ -461,8 +461,7 @@ fn build_devserver_router(
Response::builder() Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR) .status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(format!( .body(Body::from(format!(
"Backend connection failed. The backend is likely still starting up. Please try again in a few seconds. Error: {:#?}", "Backend connection failed. The backend is likely still starting up. Please try again in a few seconds. Error: {error:#?}"
error
))) )))
.unwrap() .unwrap()
}, },
@@ -577,7 +576,7 @@ fn build_serve_dir(runner: &AppServer) -> axum::routing::MethodRouter {
.handle_error(|error: Infallible| async move { .handle_error(|error: Infallible| async move {
( (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled internal error: {}", error), format!("Unhandled internal error: {error}"),
) )
}) })
} }
@@ -730,7 +729,7 @@ struct SharedStatus(Arc<RwLock<Status>>);
enum Status { enum Status {
ClientInit { ClientInit {
application_name: String, application_name: String,
platform: Platform, bundle: BundleFormat,
}, },
Building { Building {
progress: f64, progress: f64,
+2 -2
View File
@@ -1,4 +1,4 @@
use crate::{BuildId, BuilderUpdate, Error, Platform, TraceMsg}; use crate::{BuildId, BuilderUpdate, BundleFormat, Error, TraceMsg};
use axum::extract::ws::Message as WsMessage; use axum::extract::ws::Message as WsMessage;
use std::path::PathBuf; use std::path::PathBuf;
@@ -13,7 +13,7 @@ pub(crate) enum ServeUpdate {
pid: Option<u32>, pid: Option<u32>,
}, },
WsMessage { WsMessage {
platform: Platform, bundle: BundleFormat,
msg: WsMessage, msg: WsMessage,
}, },
+1 -1
View File
@@ -220,7 +220,7 @@ impl TailwindCli {
_ => return None, _ => return None,
}; };
Some(format!("tailwindcss-{}-{}", platform, arch)) Some(format!("tailwindcss-{platform}-{arch}"))
} }
fn install_dir(&self) -> Result<PathBuf> { fn install_dir(&self) -> Result<PathBuf> {
+2 -1
View File
@@ -11,6 +11,7 @@
</div> </div>
--- ---
Dioxus is a framework for building cross-platform apps in Rust. With one codebase, you can build web, desktop, and mobile apps with fullstack server functions. Dioxus is designed to be easy to learn for developers familiar with web technologies like HTML, CSS, and JavaScript. Dioxus is a framework for building cross-platform apps in Rust. With one codebase, you can build web, desktop, and mobile apps with fullstack server functions. Dioxus is designed to be easy to learn for developers familiar with web technologies like HTML, CSS, and JavaScript.
<div align="center"> <div align="center">
@@ -42,7 +43,7 @@ cargo binstall dioxus-cli
dx new my-app && cd my-app dx new my-app && cd my-app
# and then serve! # and then serve!
dx serve --platform desktop dx serve --desktop
``` ```
## Your first app ## Your first app
+5 -1
View File
@@ -10,13 +10,14 @@ homepage = "https://dioxuslabs.com/learn/0.6/getting_started"
keywords = ["dom", "ui", "gui", "react"] keywords = ["dom", "ui", "gui", "react"]
[features] [features]
default = ["accessibility", "hot-reload", "tracing", "net", "svg", "system-fonts"] default = ["accessibility", "hot-reload", "tracing", "net", "svg", "system-fonts", "openssl-vendored-android"]
svg = ["blitz-dom/svg", "blitz-paint/svg"] svg = ["blitz-dom/svg", "blitz-paint/svg"]
net = ["dep:tokio", "dep:blitz-net"] net = ["dep:tokio", "dep:blitz-net"]
accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"] accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"]
tracing = ["dep:tracing", "blitz-shell/tracing", "blitz-dom/tracing"] tracing = ["dep:tracing", "blitz-shell/tracing", "blitz-dom/tracing"]
hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-devtools"] hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-devtools"]
system-fonts = ["blitz-dom/system_fonts"] system-fonts = ["blitz-dom/system_fonts"]
openssl-vendored-android = ["openssl"]
autofocus = [] autofocus = []
[dependencies] [dependencies]
@@ -51,6 +52,9 @@ tracing = { workspace = true, optional = true }
rustc-hash = { workspace = true } rustc-hash = { workspace = true }
futures-util = { workspace = true } futures-util = { workspace = true }
[target.'cfg(target_os = "android")'.dependencies]
openssl = { version = "0.10", features = ["vendored"], optional = true }
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
@@ -18,8 +18,8 @@ Run the following command in the root of your project to start developing with t
dx serve dx serve
``` ```
To run for a different platform, use the `--platform platform` flag. E.g. To run for a different platform, use the `--webview`, `--native`, or `--web` flags. E.g.
```bash
dx serve --platform desktop
```
```bash
dx serve --webview
```
+11 -11
View File
@@ -87,7 +87,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "web"), cwd: path.join(process.cwd(), "web"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 9990', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 9990',
port: 9990, port: 9990,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -96,7 +96,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "web-routing"), cwd: path.join(process.cwd(), "web-routing"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 2020', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 2020',
port: 2020, port: 2020,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -105,7 +105,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "web-hash-routing"), cwd: path.join(process.cwd(), "web-hash-routing"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 2021', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 2021',
port: 2021, port: 2021,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -114,7 +114,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "fullstack"), cwd: path.join(process.cwd(), "fullstack"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 3333', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 3333',
port: 3333, port: 3333,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -123,7 +123,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "fullstack-mounted"), cwd: path.join(process.cwd(), "fullstack-mounted"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 7777', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 7777',
port: 7777, port: 7777,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -132,7 +132,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "fullstack-routing"), cwd: path.join(process.cwd(), "fullstack-routing"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 8888', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 8888',
port: 8888, port: 8888,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -141,7 +141,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "suspense-carousel"), cwd: path.join(process.cwd(), "suspense-carousel"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 4040', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 4040',
port: 4040, port: 4040,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -150,7 +150,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "nested-suspense"), cwd: path.join(process.cwd(), "nested-suspense"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --platform web --addr "127.0.0.1" --port 5050', 'cargo run --package dioxus-cli --release -- run --verbose --force-sequential --web --addr "127.0.0.1" --port 5050',
port: 5050, port: 5050,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -159,7 +159,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "nested-suspense"), cwd: path.join(process.cwd(), "nested-suspense"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --bin nested-suspense-ssg --force-sequential --platform web --ssg --addr "127.0.0.1" --port 6060', 'cargo run --package dioxus-cli --release -- run --verbose --bin nested-suspense-ssg --force-sequential --web --ssg --addr "127.0.0.1" --port 6060',
port: 6060, port: 6060,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -178,7 +178,7 @@ module.exports = defineConfig({
{ {
cwd: path.join(process.cwd(), "wasm-split-harness"), cwd: path.join(process.cwd(), "wasm-split-harness"),
command: command:
'cargo run --package dioxus-cli --release -- run --verbose --bin wasm-split-harness --platform web --addr "127.0.0.1" --port 8001 --wasm-split --profile wasm-split-release', 'cargo run --package dioxus-cli --release -- run --verbose --bin wasm-split-harness --web --addr "127.0.0.1" --port 8001 --wasm-split --profile wasm-split-release',
port: 8001, port: 8001,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
@@ -204,7 +204,7 @@ module.exports = defineConfig({
}, },
{ {
command: command:
'rm -rf ./web-hot-patch-temp && cp -r ./web-hot-patch ./web-hot-patch-temp && cd web-hot-patch-temp && cargo run --manifest-path ../../cli/Cargo.toml --release -- serve --verbose --force-sequential --platform web --addr "127.0.0.1" --port 9980 --hot-patch --exit-on-error', 'rm -rf ./web-hot-patch-temp && cp -r ./web-hot-patch ./web-hot-patch-temp && cd web-hot-patch-temp && cargo run --manifest-path ../../cli/Cargo.toml --release -- serve --verbose --force-sequential --web --addr "127.0.0.1" --port 9980 --hot-patch --exit-on-error',
port: 9980, port: 9980,
timeout: 50 * 60 * 1000, timeout: 50 * 60 * 1000,
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,