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

+41 -41
View File
@@ -1,5 +1,5 @@
use crate::{
serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform,
serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, BundleFormat,
ProgressRx, ProgressTx, Result, RustcArgs, StructuredOutput,
};
use anyhow::{bail, Context};
@@ -16,6 +16,7 @@ use std::{
process::Stdio,
};
use subsecond_types::JumpTable;
use target_lexicon::Architecture;
use tokio::{
io::{AsyncBufReadExt, BufReader, Lines},
process::{Child, ChildStderr, ChildStdout, Command},
@@ -189,7 +190,7 @@ impl AppBuilder {
match bundle {
Ok(Ok(bundle)) => BuilderUpdate::BuildReady { bundle },
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())) => {
@@ -300,7 +301,13 @@ impl AppBuilder {
// 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 {
Some(val) => val,
None if self.build.platform == Platform::Web => 0,
None if matches!(
self.build.triple.architecture,
Architecture::Wasm32 | Architecture::Wasm64
) =>
{
0
}
None => {
tracing::warn!(
"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
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
// 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.
Platform::Web => {
BundleFormat::Web => {
// Only the first build we open the web app, after that the user knows it's running
if open_browser {
self.open_web(open_address.unwrap_or(devserver_ip));
}
}
Platform::Ios => {
BundleFormat::Ios => {
if self.build.device {
self.codesign_ios().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?;
}
// These are all just basically running the main exe, but with slightly different resource dir paths
Platform::Server
| Platform::MacOS
| Platform::Windows
| Platform::Linux
| Platform::Liveview => self.open_with_main_exe(envs, args)?,
BundleFormat::Server
| BundleFormat::MacOS
| BundleFormat::Windows
| BundleFormat::Linux => self.open_with_main_exe(envs, args)?,
};
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 self.build.platform == Platform::Android {
if self.build.bundle == BundleFormat::Android {
let bundled_name = PathBuf::from(bundled.bundled_path());
_ = 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)?;
// 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
.copy_file_to_android_tmp(&new, &(PathBuf::from(new.file_name().unwrap())))
.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 self.build.platform == Platform::Android {
if self.build.bundle == BundleFormat::Android {
_ = self
.copy_file_to_android_tmp(&changed_file, &bundled_name)
.await;
@@ -972,7 +978,7 @@ impl AppBuilder {
.await?;
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())?)
@@ -1007,7 +1013,7 @@ impl AppBuilder {
.await?;
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())?)
@@ -1032,7 +1038,7 @@ impl AppBuilder {
.await?;
if !output.status.success() {
bail!("Failed to resume app: {:?}", output);
bail!("Failed to resume app: {output:?}");
}
Ok(())
@@ -1255,8 +1261,8 @@ We checked the folders:
let port = devserver_socket.port();
if let Err(e) = Command::new(&adb)
.arg("reverse")
.arg(format!("tcp:{}", port))
.arg(format!("tcp:{}", port))
.arg(format!("tcp:{port}"))
.arg(format!("tcp:{port}"))
.output()
.await
{
@@ -1362,18 +1368,14 @@ We checked the folders:
let mut main_exe = self.build.main_exe();
// 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
Platform::Web => false,
Platform::MacOS => false,
Platform::Ios => false,
Platform::Android => false,
BundleFormat::Web | BundleFormat::MacOS | BundleFormat::Ios | BundleFormat::Android => {
false
}
// But on platforms that aren't running as "bundled", we do.
Platform::Windows => true,
Platform::Linux => true,
Platform::Server => true,
Platform::Liveview => true,
BundleFormat::Windows | BundleFormat::Linux | BundleFormat::Server => true,
};
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<()> {
let url = match self.build.platform {
Platform::MacOS
| Platform::Windows
| Platform::Linux
| Platform::Server
| Platform::Liveview => {
let url = match self.build.bundle {
BundleFormat::MacOS
| BundleFormat::Windows
| BundleFormat::Linux
| BundleFormat::Server => {
let Some(Some(pid)) = self.child.as_mut().map(|f| f.id()) else {
tracing::warn!("No process to attach debugger to");
return Ok(());
};
format!(
"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{}}}",
pid
"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}"
)
}
Platform::Web => {
BundleFormat::Web => {
// 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
let address = server.devserver_address();
@@ -1474,7 +1474,7 @@ We checked the folders:
format!("vscode://DioxusLabs.dioxus/debugger?uri={protocol}://{address}{base_path}")
}
Platform::Ios => {
BundleFormat::Ios => {
let Some(pid) = self.pid else {
tracing::warn!("No process to attach debugger to");
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) 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 shell "/tmp/lldb-server --server --listen ..."
// "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")
.arg("--open-url")
+1 -1
View File
@@ -105,7 +105,7 @@ impl BuildContext {
}
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) {
+2 -2
View File
@@ -775,7 +775,7 @@ pub fn create_undefined_symbol_stub(
let mut defined_symbols = HashSet::new();
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])?;
for symbol in file.symbols() {
if symbol.is_undefined() {
@@ -853,7 +853,7 @@ pub fn create_undefined_symbol_stub(
if aslr_reference < aslr_ref_address {
return Err(PatchError::InvalidModule(
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);
if file == "-" {
print!("{}", out);
print!("{out}");
} else if let Err(e) = fs::write(&file, out) {
tracing::error!("failed to write formatted content to file: {e}",);
} else {
println!("formatted {}", file);
println!("formatted {file}");
}
Ok(())
@@ -191,7 +191,7 @@ fn autoformat_project(
let files_formatted: usize = counts.into_iter().flatten().sum();
if files_formatted > 0 && check {
bail!("{} files needed formatting", files_formatted);
bail!("{files_formatted} files needed formatting");
}
Ok(())
@@ -252,7 +252,7 @@ fn format_syn_error(err: syn::Error) -> Error {
let start = err.span().start();
let line = start.line;
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]
+9 -5
View File
@@ -1,5 +1,5 @@
use crate::BuildMode;
use crate::{cli::*, AppBuilder, BuildRequest, Workspace};
use crate::{BuildMode, Platform};
use super::target::TargetArgs;
@@ -12,7 +12,11 @@ pub struct BuildArgs {
/// Enable fullstack mode [default: false]
///
/// 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>,
/// 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
// 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.
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
// 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
let main_target = client.main_target.clone();
let mut server_args = server_args.clone();
// The platform in the server build is always set to Server
server_args.platform = Some(Platform::Server);
// The renderer in the server build is always set to Server
server_args.renderer.renderer = Some(crate::Renderer::Server);
server =
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 path_absolutize::Absolutize;
use std::collections::HashMap;
@@ -55,7 +55,7 @@ impl 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]);
}
@@ -67,9 +67,9 @@ impl Bundle {
}
// 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
Platform::MacOS | Platform::Linux | Platform::Windows => {
BundleFormat::MacOS | BundleFormat::Linux | BundleFormat::Windows => {
tracing::info!("Running desktop bundler...");
for bundle in Self::bundle_desktop(&client, &self.package_types)? {
bundles.extend(bundle.bundle_paths);
@@ -77,15 +77,14 @@ impl Bundle {
}
// Web/ios can just use their root_dir
Platform::Web => bundles.push(client.root_dir()),
Platform::Ios => {
BundleFormat::Web => bundles.push(client.root_dir()),
BundleFormat::Ios => {
tracing::warn!("iOS bundles are not currently codesigned! You will need to codesign the app before distributing.");
bundles.push(client.root_dir())
}
Platform::Server => bundles.push(client.root_dir()),
Platform::Liveview => bundles.push(client.root_dir()),
BundleFormat::Server => bundles.push(client.root_dir()),
Platform::Android => {
BundleFormat::Android => {
let aab = client
.android_gradle_bundle()
.await
@@ -145,16 +144,16 @@ impl Bundle {
let krate = &build;
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 mut name: PathBuf = krate.executable_name().into();
if cfg!(windows) {
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")?;
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")?;
let binaries = vec![
@@ -223,7 +222,7 @@ impl Bundle {
}
let mut settings = SettingsBuilder::new()
.project_out_directory(krate.bundle_dir(build.platform))
.project_out_directory(krate.bundle_dir(build.bundle))
.package_settings(PackageSettings {
product_name: krate.bundled_app_name(),
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(())
}
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 crate::{
serve::{AppServer, ServeUpdate, WebServer},
BuilderUpdate, Platform, Result,
BuilderUpdate, BundleFormat, Result,
};
use anyhow::bail;
use dioxus_dx_wire_format::BuildStage;
@@ -43,7 +43,7 @@ impl RunArgs {
match msg {
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
devserver.new_build_update(&update).await;
@@ -59,7 +59,7 @@ impl RunArgs {
.await
.inspect_err(|e| tracing::error!("Failed to open app: {}", e));
if platform == Platform::Web {
if bundle_format == BundleFormat::Web {
tracing::info!(
"Serving app at http://{}:{}",
builder.devserver_bind_ip,
@@ -69,7 +69,7 @@ impl RunArgs {
}
BuilderUpdate::Progress { stage } => match stage {
BuildStage::Initializing => {
tracing::info!("[{platform}] Initializing build")
tracing::info!("[{bundle_format}] Initializing build")
}
BuildStage::Starting { .. } => {}
BuildStage::InstallingTooling => {}
@@ -79,15 +79,15 @@ impl RunArgs {
krate,
} => {
tracing::debug!(
"[{platform}] ({current}/{total}) Compiling {krate} ",
"[{bundle_format}] ({current}/{total}) Compiling {krate} ",
)
}
BuildStage::RunningBindgen => {
tracing::info!("[{platform}] Running WASM bindgen")
tracing::info!("[{bundle_format}] Running WASM bindgen")
}
BuildStage::SplittingBundle => {}
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::Hotpatching => {}
@@ -96,49 +96,51 @@ impl RunArgs {
total,
path,
} => tracing::info!(
"[{platform}] Copying asset {} ({current}/{total})",
"[{bundle_format}] Copying asset {} ({current}/{total})",
path.display(),
),
BuildStage::Bundling => tracing::info!("[{platform}] Bundling app"),
BuildStage::Bundling => {
tracing::info!("[{bundle_format}] Bundling app")
}
BuildStage::RunningGradle => {
tracing::info!("[{platform}] Running Gradle")
tracing::info!("[{bundle_format}] Running Gradle")
}
BuildStage::Success => {}
BuildStage::Restarting => {}
BuildStage::CompressingAssets => {}
BuildStage::ExtractingAssets => {}
BuildStage::Prerendering => {
tracing::info!("[{platform}] Prerendering app")
tracing::info!("[{bundle_format}] Prerendering app")
}
BuildStage::Failed => {
tracing::error!("[{platform}] Build failed");
bail!("Build failed for platform: {platform}");
tracing::error!("[{bundle_format}] Build failed");
bail!("Build failed for platform: {bundle_format}");
}
BuildStage::Aborted => {
tracing::error!("[{platform}] Build aborted");
bail!("Build aborted for platform: {platform}");
tracing::error!("[{bundle_format}] Build aborted");
bail!("Build aborted for platform: {bundle_format}");
}
_ => {}
},
BuilderUpdate::CompilerMessage { message } => {
print!("{}", message);
print!("{message}");
}
BuilderUpdate::BuildFailed { err } => {
tracing::error!("❌ Build failed: {}", err);
return Err(err);
}
BuilderUpdate::StdoutReceived { msg } => {
tracing::info!("[{platform}] {msg}");
tracing::info!("[{bundle_format}] {msg}");
}
BuilderUpdate::StderrReceived { msg } => {
tracing::error!("[{platform}] {msg}");
tracing::error!("[{bundle_format}] {msg}");
}
BuilderUpdate::ProcessExited { status } => {
if !status.success() {
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;
+3 -3
View File
@@ -6,15 +6,15 @@ use std::{backtrace::Backtrace, panic::AssertUnwindSafe};
/// 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
/// dx serve --example blah --target blah --platform android
/// dx serve --example blah --target blah --android
/// ```
///
/// A simple serve:
/// ```sh
/// dx serve --platform web
/// dx serve --web
/// ```
///
/// 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::BundleFormat;
use crate::Platform;
use crate::RendererArg;
use crate::TargetAlias;
use target_lexicon::Triple;
const HELP_HEADING: &str = "Target Options";
@@ -7,7 +10,25 @@ const HELP_HEADING: &str = "Target Options";
/// A single target to build for
#[derive(Clone, Debug, Default, Deserialize, Parser)]
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)]
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
match self.output {
Some(output) => std::fs::write(output, &html)?,
None => print!("{}", html),
None => print!("{html}"),
}
Ok(StructuredOutput::HtmlTranslate { html })
+1 -1
View File
@@ -341,7 +341,7 @@ impl FromStr for PackageType {
"appimage" => Ok(PackageType::AppImage),
"dmg" => Ok(PackageType::Dmg),
"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.
//! 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 clap::Parser;
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use std::str::FromStr;
use std::sync::OnceLock;
use std::{
collections::HashMap,
@@ -315,7 +317,7 @@ impl Visit for CollectVisitor {
let name = field.name();
let mut value_string = String::new();
write!(value_string, "{:?}", value).unwrap();
write!(value_string, "{value:?}").unwrap();
if name == "message" {
self.message = value_string;
@@ -335,8 +337,8 @@ impl Visit for CollectVisitor {
fn format_field(field_name: &str, value: &dyn Debug) -> String {
let mut out = String::new();
match field_name {
"message" => write!(out, "{:?}", value),
_ => write!(out, "{}={:?}", field_name, value),
"message" => write!(out, "{value:?}"),
_ => write!(out, "{field_name}={value:?}"),
}
.unwrap();
@@ -391,7 +393,7 @@ impl TraceMsg {
#[derive(Clone, PartialEq)]
pub enum TraceSrc {
App(TargetPlatform),
App(BundleFormat),
Dev,
Build,
Bundle,
@@ -412,12 +414,9 @@ impl From<String> for TraceSrc {
"dev" => Self::Dev,
"bld" => Self::Build,
"cargo" => Self::Cargo,
"app" => Self::App(TargetPlatform::Web),
"windows" => Self::App(TargetPlatform::Windows),
"macos" => Self::App(TargetPlatform::MacOS),
"linux" => Self::App(TargetPlatform::Linux),
"server" => Self::App(TargetPlatform::Server),
_ => Self::Unknown,
other => BundleFormat::from_str(other)
.map(Self::App)
.unwrap_or_else(|_| Self::Unknown),
}
}
}
@@ -425,16 +424,7 @@ impl From<String> for TraceSrc {
impl Display for TraceSrc {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::App(platform) => match platform {
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::App(bundle) => write!(f, "{bundle}"),
Self::Dev => write!(f, "dev"),
Self::Build => write!(f, "build"),
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 std::fmt::Display;
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(
Copy,
@@ -18,188 +496,74 @@ use std::str::FromStr;
)]
#[non_exhaustive]
pub(crate) enum Platform {
/// Targeting the web platform using WASM
/// Alias for `--wasm --web --bundle-format web`
#[clap(name = "web")]
#[serde(rename = "web")]
#[default]
Web,
/// Targeting macos desktop
/// When running on macos, you can also use `--platform desktop` to build for the desktop
/// Alias for `--macos --webview --bundle-format macos`
#[cfg_attr(target_os = "macos", clap(alias = "desktop"))]
#[clap(name = "macos")]
#[serde(rename = "macos")]
MacOS,
/// Targeting windows desktop
/// When running on windows, you can also use `--platform desktop` to build for the desktop
/// Alias for `--windows --webview --bundle-format windows`
#[cfg_attr(target_os = "windows", clap(alias = "desktop"))]
#[clap(name = "windows")]
#[serde(rename = "windows")]
Windows,
/// Targeting linux desktop
/// When running on linux, you can also use `--platform desktop` to build for the desktop
/// Alias for `--linux --webview --bundle-format linux`
#[cfg_attr(target_os = "linux", clap(alias = "desktop"))]
#[clap(name = "linux")]
#[serde(rename = "linux")]
Linux,
/// Targeting the ios platform
///
/// Can't work properly if you're not building from an Apple device.
/// Alias for `--ios --webview --bundle-format ios`
#[clap(name = "ios")]
#[serde(rename = "ios")]
Ios,
/// Targeting the android platform
/// Alias for `--android --webview --bundle-format android`
#[clap(name = "android")]
#[serde(rename = "android")]
Android,
/// 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.
/// Alias for `--host --server --bundle-format server`
#[clap(name = "server")]
#[serde(rename = "server")]
Server,
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
/// Alias for `--host --liveview --bundle-format host`
#[clap(name = "liveview")]
#[serde(rename = "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 {
/// Get the feature name for the platform in the dioxus crate
pub(crate) fn feature_name(&self) -> &str {
pub(crate) fn into_triple(self) -> (TargetAlias, Renderer, BundleFormat) {
match self {
Platform::Web => "web",
Platform::MacOS => "desktop",
Platform::Windows => "desktop",
Platform::Linux => "desktop",
Platform::Server => "server",
Platform::Liveview => "liveview",
Platform::Ios => "mobile",
Platform::Android => "mobile",
}
}
/// 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 {
Platform::Web => "web",
Platform::Server => "web",
Platform::Liveview => "liveview",
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)
Platform::Web => (TargetAlias::Wasm, Renderer::Web, BundleFormat::Web),
Platform::MacOS => (TargetAlias::MacOS, Renderer::Webview, BundleFormat::MacOS),
Platform::Windows => (
TargetAlias::Windows,
Renderer::Webview,
BundleFormat::Windows,
),
Platform::Linux => (TargetAlias::Linux, Renderer::Webview, BundleFormat::Linux),
Platform::Ios => (TargetAlias::Ios, Renderer::Webview, BundleFormat::Ios),
Platform::Android => (
TargetAlias::Android,
Renderer::Webview,
BundleFormat::Android,
),
Platform::Server => (TargetAlias::Host, Renderer::Server, BundleFormat::Server),
Platform::Liveview => (
TargetAlias::Host,
Renderer::Liveview,
BundleFormat::TARGET_PLATFORM.unwrap(),
),
}
}
}
+15 -12
View File
@@ -1,6 +1,7 @@
use crate::{
styles::{GLOW_STYLE, LINK_STYLE},
AppBuilder, BuildId, BuildMode, BuilderUpdate, Platform, Result, ServeArgs, TraceController,
AppBuilder, BuildId, BuildMode, BuilderUpdate, BundleFormat, Result, ServeArgs,
TraceController,
};
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
// logging, so we just forward it the tui
ServeUpdate::WsMessage { msg, platform } => {
screen.push_ws_message(platform, &msg);
ServeUpdate::WsMessage { msg, bundle } => {
screen.push_ws_message(bundle, &msg);
}
// Wait for logs from the build engine
// 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
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
screen.new_build_update(&update);
@@ -147,14 +148,14 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
stage: BuildStage::Failed,
} => {
if exit_on_error {
bail!("Build failed for platform: {platform}");
bail!("Build failed for platform: {bundle_format}");
}
}
BuilderUpdate::Progress {
stage: BuildStage::Aborted,
} => {
if exit_on_error {
bail!("Build aborted for platform: {platform}");
bail!("Build aborted for platform: {bundle_format}");
}
}
BuilderUpdate::Progress { .. } => {}
@@ -206,22 +207,24 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
}
},
BuilderUpdate::StdoutReceived { msg } => {
screen.push_stdio(platform, msg, tracing::Level::INFO);
screen.push_stdio(bundle_format, msg, tracing::Level::INFO);
}
BuilderUpdate::StderrReceived { msg } => {
screen.push_stdio(platform, msg, tracing::Level::ERROR);
screen.push_stdio(bundle_format, msg, tracing::Level::ERROR);
}
BuilderUpdate::ProcessExited { status } => {
if status.success() {
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 exit the server, press `ctrl+c`"#
);
} else {
tracing::error!("Application [{platform}] exited with error: {status}");
tracing::error!(
"Application [{bundle_format}] exited with error: {status}"
);
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 {
true if !matches!(builder.client.build.platform, Platform::Web) => {
true if !matches!(builder.client.build.bundle, BundleFormat::Web) => {
tracing::warn!(
"Opening a native app with hotpatching enabled requires a full rebuild..."
);
+10 -10
View File
@@ -1,6 +1,6 @@
use crate::{
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 crossterm::{
@@ -316,12 +316,12 @@ impl Output {
/// Add a message from stderr to the logs
/// 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
pub fn push_stdio(&mut self, platform: Platform, msg: String, level: Level) {
self.push_log(TraceMsg::text(TraceSrc::App(platform), level, msg));
pub fn push_stdio(&mut self, bundle: BundleFormat, msg: String, level: Level) {
self.push_log(TraceMsg::text(TraceSrc::App(bundle), level, msg));
}
/// 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;
// We can only handle text messages from the websocket...
@@ -336,7 +336,7 @@ impl Output {
let msg = match res {
Ok(msg) => msg,
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;
}
};
@@ -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.
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
@@ -641,7 +641,7 @@ impl Output {
frame.render_widget(
Paragraph::new(Line::from(vec![
"Platform: ".gray(),
client.build.platform.expected_name().yellow(),
client.build.bundle.expected_name().yellow(),
if state.runner.is_fullstack() {
" + fullstack".yellow()
} else {
@@ -672,7 +672,7 @@ impl Output {
frame.render_widget_ref(
Paragraph::new(Line::from(vec![
if client.build.platform == Platform::Web {
if client.build.bundle == BundleFormat::Web {
"Serving at: ".gray()
} else {
"Server at: ".gray()
@@ -755,7 +755,7 @@ impl Output {
);
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(),
};
frame.render_widget(
@@ -770,7 +770,7 @@ impl Output {
frame.render_widget(
Paragraph::new(Line::from(vec![
"Read the docs: ".gray(),
"https://dioxuslabs.com/0.6/docs".blue(),
"https://dioxuslabs.com/learn/0.7/".blue(),
])),
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()
.status(axum::http::StatusCode::INTERNAL_SERVER_ERROR)
.body(axum::body::Body::from(format!(
"Proxy connection failed: {:#?}",
e
"Proxy connection failed: {e:#?}"
)))
.unwrap()
}
@@ -229,7 +228,7 @@ mod test {
let server_addr = setup_servers(config).await;
assert_eq!(
reqwest::get(format!("http://{}/api", server_addr))
reqwest::get(format!("http://{server_addr}/api"))
.await
.unwrap()
.text()
@@ -239,7 +238,7 @@ mod test {
);
assert_eq!(
reqwest::get(format!("http://{}/api/", server_addr))
reqwest::get(format!("http://{server_addr}/api/"))
.await
.unwrap()
.text()
+11 -10
View File
@@ -1,7 +1,7 @@
use super::{AppBuilder, ServeUpdate, WebServer};
use crate::{
platform_override::CommandWithPlatformOverrides, BuildArtifacts, BuildId, BuildMode,
BuildTargets, BuilderUpdate, HotpatchModuleCache, Platform, Result, ServeArgs, TailwindCli,
BuildTargets, BuilderUpdate, BundleFormat, HotpatchModuleCache, Result, ServeArgs, TailwindCli,
TraceSrc, Workspace,
};
use anyhow::{bail, Context};
@@ -151,8 +151,8 @@ impl AppServer {
// 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
let fullstack = server.is_some();
let should_proxy_port = match client.platform {
Platform::Server => true,
let should_proxy_port = match client.bundle {
BundleFormat::Server => true,
_ => fullstack && !ssg,
};
@@ -168,7 +168,7 @@ impl AppServer {
// 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
let tw_watcher = if client.build.platform != Platform::Server && !fullstack {
let tw_watcher = if client.build.bundle != BundleFormat::Server && !fullstack {
TailwindCli::serve(
client.build.package_manifest_dir(),
client.build.config.application.tailwind_input.clone(),
@@ -536,7 +536,8 @@ impl AppServer {
use crate::styles::NOTE_STYLE;
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!");
}
@@ -554,8 +555,8 @@ impl AppServer {
devserver: &mut WebServer,
) -> Result<()> {
// Make sure to save artifacts regardless of if we're opening the app or not
match artifacts.platform {
Platform::Server => {
match artifacts.bundle {
BundleFormat::Server => {
if let Some(server) = self.server.as_mut() {
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
// 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)
.arg("reverse")
.arg("--remove")
@@ -786,7 +787,7 @@ impl AppServer {
BuildId::CLIENT => {
// multiple tabs on web can cause this to be called incorrectly, and it doesn't
// 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 {
self.client.aslr_reference = Some(aslr_reference);
}
@@ -804,7 +805,7 @@ impl AppServer {
}
// 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
let res = Command::new("xcrun")
.arg("simctl")
+10 -11
View File
@@ -1,5 +1,5 @@
use crate::{
config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, Platform,
config::WebHttpsConfig, serve::ServeUpdate, BuildId, BuildStage, BuilderUpdate, BundleFormat,
Result, TraceSrc,
};
use anyhow::{bail, Context};
@@ -62,7 +62,7 @@ pub(crate) struct WebServer {
new_build_status_sockets: UnboundedReceiver<ConnectedWsClient>,
build_status: SharedStatus,
application_name: String,
platform: Platform,
bundle: BundleFormat,
}
pub(crate) struct ConnectedWsClient {
@@ -128,7 +128,7 @@ impl WebServer {
new_hot_reload_sockets: hot_reload_sockets_rx,
new_build_status_sockets: build_status_sockets_rx,
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);
// 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() {
_ = self.build_status.send_to(&mut new_socket.socket).await;
self.build_status_sockets.push(new_socket);
@@ -175,7 +175,7 @@ impl WebServer {
}
Some((idx, message)) = new_message.next() => {
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);
_ = self.hot_reload_sockets.remove(idx);
@@ -375,8 +375,8 @@ impl WebServer {
}
pub fn server_address(&self) -> Option<SocketAddr> {
match self.platform {
Platform::Web | Platform::Server => Some(self.devserver_address()),
match self.bundle {
BundleFormat::Web | BundleFormat::Server => Some(self.devserver_address()),
_ => self.proxied_server_address(),
}
}
@@ -461,8 +461,7 @@ fn build_devserver_router(
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from(format!(
"Backend connection failed. The backend is likely still starting up. Please try again in a few seconds. Error: {:#?}",
error
"Backend connection failed. The backend is likely still starting up. Please try again in a few seconds. Error: {error:#?}"
)))
.unwrap()
},
@@ -577,7 +576,7 @@ fn build_serve_dir(runner: &AppServer) -> axum::routing::MethodRouter {
.handle_error(|error: Infallible| async move {
(
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 {
ClientInit {
application_name: String,
platform: Platform,
bundle: BundleFormat,
},
Building {
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 std::path::PathBuf;
@@ -13,7 +13,7 @@ pub(crate) enum ServeUpdate {
pid: Option<u32>,
},
WsMessage {
platform: Platform,
bundle: BundleFormat,
msg: WsMessage,
},
+1 -1
View File
@@ -220,7 +220,7 @@ impl TailwindCli {
_ => return None,
};
Some(format!("tailwindcss-{}-{}", platform, arch))
Some(format!("tailwindcss-{platform}-{arch}"))
}
fn install_dir(&self) -> Result<PathBuf> {