From b257e7e8cec00c1b9f3f2017fc342f72aef31e87 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Tue, 22 Jul 2025 22:32:54 +0100 Subject: [PATCH] CLI: add `--renderer` argument (#4320) * Add --renderer argument to the CLI Signed-off-by: Nico Burns * Make `--platform native` work Signed-off-by: Nico Burns * Emit `krates` error when cargo metadata fails * Reinstate default "desktop" feature for examples * Remove commented 'no platform specified' error Signed-off-by: Nico Burns * 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 Co-authored-by: Jonathan Kelley Co-authored-by: Evan Almloff --- Cargo.lock | 22 +- Cargo.toml | 4 +- packages/cli/src/build/builder.rs | 82 +-- packages/cli/src/build/context.rs | 2 +- packages/cli/src/build/patch.rs | 4 +- packages/cli/src/build/request.rs | 486 +++++++------ packages/cli/src/cli/autoformat.rs | 8 +- packages/cli/src/cli/build.rs | 14 +- packages/cli/src/cli/bundle.rs | 25 +- packages/cli/src/cli/check.rs | 2 +- packages/cli/src/cli/run.rs | 42 +- packages/cli/src/cli/serve.rs | 6 +- packages/cli/src/cli/target.rs | 23 +- packages/cli/src/cli/translate.rs | 2 +- packages/cli/src/config/bundle.rs | 2 +- packages/cli/src/logging.rs | 32 +- packages/cli/src/platform.rs | 650 ++++++++++++++---- packages/cli/src/serve/mod.rs | 27 +- packages/cli/src/serve/output.rs | 20 +- packages/cli/src/serve/proxy.rs | 7 +- packages/cli/src/serve/runner.rs | 21 +- packages/cli/src/serve/server.rs | 21 +- packages/cli/src/serve/update.rs | 4 +- packages/cli/src/tailwind.rs | 2 +- packages/dioxus/README.md | 3 +- packages/native/Cargo.toml | 6 +- .../barebones-template/README.md | 8 +- .../playwright-tests/playwright.config.js | 22 +- 28 files changed, 998 insertions(+), 549 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 326e6190f..e3a584ab3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5664,6 +5664,7 @@ dependencies = [ "dioxus-native-dom", "futures-util", "keyboard-types", + "openssl", "rustc-hash 2.1.1", "tokio", "tracing", @@ -14276,9 +14277,9 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6" +checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", @@ -14373,9 +14374,9 @@ dependencies = [ [[package]] name = "server_fn" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b0f92b9d3a62c73f238ac21f7a09f15bad335a9d1651514d9da80d2eaf8d4c" +checksum = "9c27fbd25ecc066481e383e2ed62ab2480e708aa3fe46cba36e95f58e61dfd04" dependencies = [ "axum 0.8.4", "base64 0.22.1", @@ -14390,7 +14391,6 @@ dependencies = [ "hyper 1.6.0", "inventory", "js-sys", - "once_cell", "pin-project-lite", "reqwest 0.12.22", "rustc_version", @@ -14403,7 +14403,7 @@ dependencies = [ "thiserror 2.0.12", "throw_error", "tokio", - "tokio-tungstenite 0.26.2", + "tokio-tungstenite 0.27.0", "tower 0.5.2", "tower-layer", "url", @@ -14416,9 +14416,9 @@ dependencies = [ [[package]] name = "server_fn_macro" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "341dd1087afe9f3e546c5979a4f0b6d55ac072e1201313f86e7fe364223835ac" +checksum = "b9d530c872590473016016679c94e3bddf47372941fc924f687ffd11a1778a71" dependencies = [ "const_format", "convert_case 0.8.0", @@ -14431,9 +14431,9 @@ dependencies = [ [[package]] name = "server_fn_macro_default" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ab934f581482a66da82f2b57b15390ad67c9ab85bd9a6c54bb65060fb1380" +checksum = "ca7abc92ed696648275ed9ff171131a83d571af11748593dc2e6eb6a4e22a5b9" dependencies = [ "server_fn_macro", "syn 2.0.104", @@ -16672,7 +16672,6 @@ checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" dependencies = [ "futures-util", "log", - "rustls 0.23.29", "tokio", "tungstenite 0.26.2", ] @@ -16685,6 +16684,7 @@ checksum = "489a59b6730eda1b0171fcfda8b121f4bee2b35cba8645ca35c5f7ba3eb736c1" dependencies = [ "futures-util", "log", + "rustls 0.23.29", "tokio", "tungstenite 0.27.0", ] diff --git a/Cargo.toml b/Cargo.toml index 750403f7c..dd384da61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -243,8 +243,8 @@ lru = "0.16.0" async-trait = "0.1.88" axum = { version = "0.8.4", default-features = false } axum-server = { version = "0.7.2", default-features = false } -server_fn = { version = "0.8.2", default-features = false } -server_fn_macro = { version = "0.8.2" } +server_fn = { version = "=0.8.3", default-features = false } +server_fn_macro = { version = "=0.8.3" } tower = "0.5.2" http = "1.3.1" notify = { version = "8.1.0" } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 8b00b24c9..3cf385acc 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -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") diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index db99ac81d..c0ab88edc 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -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) { diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 6fdc7cc32..ed38220e0 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -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}" ) )); } diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index b398fed12..130b4cda4 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -316,9 +316,9 @@ //! - xbuild: use crate::{ - AndroidTools, BuildContext, DioxusConfig, Error, LinkAction, LinkerFlavor, Platform, Result, - RustcArgs, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig, Workspace, - DX_RUSTC_WRAPPER_ENV_VAR, + AndroidTools, BuildContext, BundleFormat, DioxusConfig, Error, LinkAction, LinkerFlavor, + Renderer, Result, RustcArgs, TargetAlias, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig, + Workspace, DX_RUSTC_WRAPPER_ENV_VAR, }; use anyhow::{bail, Context}; use cargo_metadata::diagnostic::Diagnostic; @@ -344,7 +344,7 @@ use std::{ }, time::{SystemTime, UNIX_EPOCH}, }; -use target_lexicon::{OperatingSystem, Triple}; +use target_lexicon::{Environment, OperatingSystem, Triple}; use tempfile::{NamedTempFile, TempDir}; use tokio::{io::AsyncBufReadExt, process::Command}; use uuid::Uuid; @@ -371,8 +371,8 @@ pub(crate) struct BuildRequest { pub(crate) crate_target: krates::cm::Target, pub(crate) profile: String, pub(crate) release: bool, - pub(crate) platform: Platform, - pub(crate) enabled_platforms: Vec, + pub(crate) bundle: BundleFormat, + pub(crate) enabled_renderers: Vec, pub(crate) triple: Triple, pub(crate) device: bool, pub(crate) package: String, @@ -441,7 +441,7 @@ pub enum BuildMode { /// The patch cache is only populated on fat builds and then used for thin builds (see `BuildMode::Thin`). #[derive(Clone, Debug)] pub struct BuildArtifacts { - pub(crate) platform: Platform, + pub(crate) bundle: BundleFormat, pub(crate) exe: PathBuf, pub(crate) direct_rustc: RustcArgs, pub(crate) time_start: SystemTime, @@ -538,10 +538,10 @@ impl BuildRequest { filtered_packages.join(", ")}; if let Some(example) = &args.example { let examples = target_of_kind(&TargetKind::Example); - format!("Failed to find example {example}. \nAvailable examples are:\n{}", examples) + format!("Failed to find example {example}. \nAvailable examples are:\n{examples}") } else if let Some(bin) = &args.bin { let binaries = target_of_kind(&TargetKind::Bin); - format!("Failed to find binary {bin}. \nAvailable binaries are:\n{}", binaries) + format!("Failed to find binary {bin}. \nAvailable binaries are:\n{binaries}") } else { format!("Failed to find target {target_name}. \nIt looks like you are trying to build dioxus in a library crate. \ You either need to run dx from inside a binary crate or build a specific example with the `--example` flag. \ @@ -552,8 +552,8 @@ impl BuildRequest { // The crate might enable multiple platforms or no platforms at // We collect all the platforms it enables first and then select based on the --platform arg - let enabled_platforms = - Self::enabled_cargo_toml_platforms(main_package, args.no_default_features); + let enabled_renderers = + Self::enabled_cargo_toml_renderers(main_package, args.no_default_features); let using_dioxus_explicitly = main_package .dependencies .iter() @@ -562,31 +562,108 @@ impl BuildRequest { let mut features = args.features.clone(); let mut no_default_features = args.no_default_features; - let platform: Platform = match args.platform { - Some(platform) => match enabled_platforms.len() { - 0 => platform, + // Apply the platform alias if present + let mut target_alias = args.target_alias; + let mut renderer = args.renderer; + let mut bundle = args.bundle; + if let Some(platform) = args.platform { + let (default_target, default_renderer, default_bundle) = platform.into_triple(); + target_alias = target_alias.or(default_target); + renderer.renderer = Some(renderer.renderer.unwrap_or(default_renderer)); + bundle = Some(bundle.unwrap_or(default_bundle)); + } + + let mut renderer: Option = match renderer.into() { + Some(renderer) => match enabled_renderers.len() { + 0 => Some(renderer), // The user passed --platform XYZ but already has `default = ["ABC"]` in their Cargo.toml or dioxus = { features = ["abc"] } // We want to strip out the default platform and use the one they passed, setting no-default-features _ => { - features.extend(Self::platformless_features(main_package)); + features.extend(Self::rendererless_features(main_package)); no_default_features = true; - platform + Some(renderer) } }, - None if !using_dioxus_explicitly => Platform::autodetect_from_cargo_feature("desktop").unwrap(), - None => match enabled_platforms.len() { - 0 => bail!("No platform specified and no platform marked as default in Cargo.toml. Try specifying a platform with `--platform`"), - 1 => enabled_platforms[0], + None if !using_dioxus_explicitly => None, + None => match enabled_renderers.as_slice() { + [] => None, // Wait until we resolve everything else first then we will resolve it from the triple + [(renderer, feature)] => { + let targeting_mobile = match (&args.target, target_alias) { + (_, TargetAlias::Android | TargetAlias::Ios) => true, + (Some(target), _) => { + matches!( + (target.environment, target.operating_system), + (Environment::Android, OperatingSystem::IOS(_)) + ) + } + _ => false, + }; + // The renderer usually maps directly to a feature flag, but the mobile crate is an exception. + // If we see the `desktop` renderer in the default features, but the user is targeting mobile, + // we should remove the default `desktop` feature, but still target webview. The feature selection + // logic below will handle adding back in the `mobile` feature flag + if targeting_mobile && feature == "desktop" { + features.extend(Self::rendererless_features(main_package)); + no_default_features = true; + } + Some(*renderer) + } _ => { - bail!("Multiple platforms enabled in Cargo.toml. Please specify a platform with `--platform` or set a default platform in Cargo.toml") + bail!( + "Multiple platforms enabled in Cargo.toml. Please specify a platform with `--web`, `--webview`, or `--native` or set a default platform in Cargo.toml" + ); } }, }; + // Just grab the renderers from the enabled renderers and discard the feature names + let enabled_renderers = enabled_renderers + .into_iter() + .map(|(renderer, _)| renderer) + .collect::>(); + + // We usually use the simulator unless --device is passed *or* a device is detected by probing. + // For now, though, since we don't have probing, it just defaults to false + // Tools like xcrun/adb can detect devices + let device = args.device; + + // Resolve the target alias and renderer into a concrete target alias. + // If the user didn't pass a platform, but we have a renderer, get the default platform for that renderer + if let (TargetAlias::Unknown, Some(renderer)) = (target_alias, renderer) { + target_alias = renderer.default_platform(); + }; + + // We want a real triple to build with, so we'll autodetect it if it's not provided + // The triple ends up being a source of truth for us later hence all this work to figure it out + let triple = match (args.target.clone(), target_alias) { + // If there is an explicit target, use it + (Some(target), _) => target, + // If there is a platform, use it to determine the target triple + (None, platform) => platform.into_target(device, &workspace).await?, + }; + + // If the user didn't pass a renderer, and we didn't find a good default renderer, but they are using dioxus explicitly, + // then we can try to guess the renderer based on the target triple. + if renderer.is_none() && using_dioxus_explicitly { + renderer = Some(Renderer::from_target(&triple)); + } + + // Resolve the bundle format based on the combination of the target triple and renderer + let bundle = match bundle { + // If there is an explicit bundle format, use it + Some(bundle) => bundle, + // Otherwise guess a bundle format based on the target triple and renderer + None => BundleFormat::from_target(&triple, renderer)?, + }; + // Add any features required to turn on the client - if using_dioxus_explicitly { - features.push(Self::feature_for_platform(main_package, platform)); + if let Some(renderer) = renderer { + features.push(Self::feature_for_platform_and_renderer( + main_package, + &triple, + renderer, + )); } // Set the profile of the build if it's not already set @@ -594,7 +671,7 @@ impl BuildRequest { // We might want to move some of these profiles into dioxus.toml and make them "virtual". let profile = match args.profile.clone() { Some(profile) => profile, - None => platform.profile_name(args.release), + None => bundle.profile_name(args.release), }; // Warn if the user is trying to build with strip and using manganis @@ -616,50 +693,6 @@ impl BuildRequest { .clone() .unwrap_or_else(|| main_package.name.clone()); - // We usually use the simulator unless --device is passed *or* a device is detected by probing. - // For now, though, since we don't have probing, it just defaults to false - // Tools like xcrun/adb can detect devices - let device = args.device; - - // We want a real triple to build with, so we'll autodetect it if it's not provided - // The triple ends up being a source of truth for us later hence all this work to figure it out - let triple = match args.target.clone() { - Some(target) => target, - None => match platform { - // Generally just use the host's triple for native executables unless specified otherwise - Platform::MacOS - | Platform::Windows - | Platform::Linux - | Platform::Server - | Platform::Liveview => target_lexicon::HOST, - - // We currently assume unknown-unknown for web, but we might want to eventually - // support emscripten - Platform::Web => "wasm32-unknown-unknown".parse().unwrap(), - - // 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 - Platform::Ios => { - // use the host's architecture and sim if --device is passed - use target_lexicon::{Architecture, HOST}; - match HOST.architecture { - Architecture::Aarch64(_) if device => "aarch64-apple-ios".parse().unwrap(), - Architecture::Aarch64(_) => "aarch64-apple-ios-sim".parse().unwrap(), - _ if device => "x86_64-apple-ios".parse().unwrap(), - _ => "x86_64-apple-ios".parse().unwrap(), - } - } - - // Same idea with android but we figure out the connected device using adb - Platform::Android => { - workspace - .android_tools()? - .autodetect_android_device_triple() - .await - } - }, - }; - // Somethings we override are also present in the user's config. // If we can't get them by introspecting cargo, then we need to get them from the config // @@ -670,7 +703,7 @@ impl BuildRequest { let mut custom_linker = cargo_config.linker(triple.to_string()).ok().flatten(); let mut rustflags = cargo_config2::Flags::default(); - if matches!(platform, Platform::Android) { + if matches!(bundle, BundleFormat::Android) { rustflags.flags.extend([ "-Clink-arg=-landroid".to_string(), "-Clink-arg=-llog".to_string(), @@ -704,7 +737,7 @@ impl BuildRequest { } // If no custom linker is set, then android falls back to us as the linker - if custom_linker.is_none() && platform == Platform::Android { + if custom_linker.is_none() && bundle == BundleFormat::Android { custom_linker = Some(workspace.android_tools()?.android_cc(&triple)); } @@ -740,6 +773,12 @@ impl BuildRequest { let extra_cargo_args = shell_words::split(&args.cargo_args.clone().unwrap_or_default()) .context("Failed to parse cargo args")?; + tracing::debug!( + r#"Target Info: + • features: {features:?} + • triple: {triple} + • bundle format: {bundle:?}"# + ); tracing::debug!( r#"Log Files: • link_args_file: {}, @@ -747,8 +786,7 @@ impl BuildRequest { • rustc_wrapper_args_file: {}, • session_cache_dir: {} • linker: {:?} - • target_dir: {:?} - "#, + • target_dir: {:?}"#, link_args_file.path().display(), link_err_file.path().display(), rustc_wrapper_args_file.path().display(), @@ -758,8 +796,8 @@ impl BuildRequest { ); Ok(Self { - platform, features, + bundle, no_default_features, crate_package, crate_target, @@ -768,7 +806,7 @@ impl BuildRequest { device, workspace, config, - enabled_platforms, + enabled_renderers, target_dir, custom_linker, link_args_file, @@ -866,7 +904,7 @@ impl BuildRequest { ctx.status_starting_build(crate_count); let mut cmd = self.build_command(ctx)?; - tracing::debug!(dx_src = ?TraceSrc::Build, "Executing cargo for {} using {}", self.platform, self.triple); + tracing::debug!(dx_src = ?TraceSrc::Build, "Executing cargo for {} using {}", self.bundle, self.triple); let mut child = cmd .stdout(Stdio::piped()) @@ -1003,7 +1041,7 @@ impl BuildRequest { let assets = self.collect_assets(&exe, ctx)?; let time_end = SystemTime::now(); let mode = ctx.mode.clone(); - let platform = self.platform; + let bundle = self.bundle; let depinfo = RustcDepInfo::from_file(&exe.with_extension("d")).unwrap_or_default(); tracing::debug!( @@ -1014,7 +1052,7 @@ impl BuildRequest { Ok(BuildArtifacts { time_end, - platform, + bundle, exe, direct_rustc, time_start, @@ -1051,7 +1089,7 @@ impl BuildRequest { exe: &Path, assets: &mut AssetManifest, ) -> Result<()> { - match self.platform { + match self.bundle { // Run wasm-bindgen on the wasm binary and set its output to be in the bundle folder // Also run wasm-opt on the wasm binary, and sets the index.html since that's also the "executable". // @@ -1075,7 +1113,7 @@ impl BuildRequest { // assets/ // logo.png // ``` - Platform::Web => { + BundleFormat::Web => { self.bundle_web(ctx, exe, assets).await?; } @@ -1092,13 +1130,12 @@ impl BuildRequest { // // These are all super simple, just copy the exe into the folder // eventually, perhaps, maybe strip + encrypt the exe? - Platform::Android - | Platform::MacOS - | Platform::Windows - | Platform::Linux - | Platform::Ios - | Platform::Liveview - | Platform::Server => { + BundleFormat::Android + | BundleFormat::MacOS + | BundleFormat::Windows + | BundleFormat::Linux + | BundleFormat::Ios + | BundleFormat::Server => { std::fs::create_dir_all(self.exe_dir())?; std::fs::copy(exe, self.main_exe())?; } @@ -1143,7 +1180,7 @@ impl BuildRequest { // On android, the c++_shared flag means we need to copy the libc++_shared.so precompiled // library to the jniLibs folder - if arg.contains("-lc++_shared") && self.platform == Platform::Android { + if arg.contains("-lc++_shared") && self.bundle == BundleFormat::Android { std::fs::copy( self.workspace.android_tools()?.libcpp_shared(&self.triple), framework_dir.join("libc++_shared.so"), @@ -1161,7 +1198,7 @@ impl BuildRequest { self.root_dir().join("Contents").join("Frameworks") } OperatingSystem::IOS(_) => self.root_dir().join("Frameworks"), - OperatingSystem::Linux if self.platform == Platform::Android => self + OperatingSystem::Linux if self.bundle == BundleFormat::Android => self .root_dir() .join("app") .join("src") @@ -1178,7 +1215,7 @@ impl BuildRequest { /// Should be the same on all platforms - just copy over the assets from the manifest into the output directory async fn write_assets(&self, ctx: &BuildContext, assets: &AssetManifest) -> Result<()> { // Server doesn't need assets - web will provide them - if self.platform == Platform::Server { + if self.bundle == BundleFormat::Server { return Ok(()); } @@ -1390,7 +1427,7 @@ impl BuildRequest { // Requiring the ASLR offset here is necessary but unfortunately might be flakey in practice. // Android apps can take a long time to open, and a hot patch might've been issued in the interim, // making this hotpatch a failure. - if self.platform != Platform::Web { + if !self.is_wasm_or_wasi() { let stub_bytes = crate::build::create_undefined_symbol_stub( cache, &object_files, @@ -1931,7 +1968,7 @@ impl BuildRequest { } // We want to go through wasm-ld directly, so we need to remove the -flavor flag - if self.platform == Platform::Web { + if self.is_wasm_or_wasi() { let flavor_idx = args.iter().position(|arg| *arg == "-flavor").unwrap(); args.remove(flavor_idx + 1); args.remove(flavor_idx); @@ -2128,7 +2165,7 @@ impl BuildRequest { ); cmd.arg(format!("-Clinker={}", Workspace::path_to_dx()?.display())); - if self.platform == Platform::Web { + if self.is_wasm_or_wasi() { cmd.arg("-Crelocation-model=pic"); } @@ -2251,7 +2288,7 @@ impl BuildRequest { // The bundle splitter needs relocation data to create a call-graph. // This will automatically be erased by wasm-opt during the optimization step. - if self.platform == Platform::Web && self.wasm_split { + if self.bundle == BundleFormat::Web && self.wasm_split { cargo_args.push("-Clink-args=--emit-relocs".to_string()); } @@ -2267,7 +2304,7 @@ impl BuildRequest { // for debuggability, we need to make sure android studio can properly understand our build // https://stackoverflow.com/questions/68481401/debugging-a-prebuilt-shared-library-in-android-studio - if self.platform == Platform::Android { + if self.bundle == BundleFormat::Android { cargo_args.push("-Clink-arg=-Wl,--build-id=sha1".to_string()); } @@ -2351,8 +2388,10 @@ impl BuildRequest { // https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features/#disabling-on-by-default-webassembly-proposals // // It's fine that these exist in the base module but not in the patch. - if self.platform == Platform::Web - || self.triple.operating_system == OperatingSystem::Wasi + if matches!( + self.triple.architecture, + target_lexicon::Architecture::Wasm32 | target_lexicon::Architecture::Wasm64 + ) || self.triple.operating_system == OperatingSystem::Wasi { cargo_args.push("-Ctarget-cpu=mvp".into()); cargo_args.push("-Clink-arg=--no-gc-sections".into()); @@ -2373,7 +2412,7 @@ impl BuildRequest { let mut env_vars = vec![]; // Make sure to set all the crazy android flags. Cross-compiling is hard, man. - if self.platform == Platform::Android { + if self.bundle == BundleFormat::Android { env_vars.extend(self.android_env_vars()?); }; @@ -2391,9 +2430,7 @@ impl BuildRequest { // Disable reference types on wasm when using hotpatching // https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features/#disabling-on-by-default-webassembly-proposals - if self.platform == Platform::Web - && matches!(ctx.mode, BuildMode::Thin { .. } | BuildMode::Fat) - { + if self.is_wasm_or_wasi() && matches!(ctx.mode, BuildMode::Thin { .. } | BuildMode::Fat) { rust_flags.flags.push("-Ctarget-cpu=mvp".to_string()); } @@ -2751,43 +2788,41 @@ impl BuildRequest { pub(crate) fn root_dir(&self) -> PathBuf { let platform_dir = self.platform_dir(); - match self.platform { - Platform::Web => platform_dir.join("public"), - Platform::Server => platform_dir.clone(), // ends up *next* to the public folder + match self.bundle { + BundleFormat::Web => platform_dir.join("public"), + BundleFormat::Server => platform_dir.clone(), // ends up *next* to the public folder // These might not actually need to be called `.app` but it does let us run these with `open` - Platform::MacOS => platform_dir.join(format!("{}.app", self.bundled_app_name())), - Platform::Ios => platform_dir.join(format!("{}.app", self.bundled_app_name())), + BundleFormat::MacOS => platform_dir.join(format!("{}.app", self.bundled_app_name())), + BundleFormat::Ios => platform_dir.join(format!("{}.app", self.bundled_app_name())), // in theory, these all could end up directly in the root dir - Platform::Android => platform_dir.join("app"), // .apk (after bundling) - Platform::Linux => platform_dir.join("app"), // .appimage (after bundling) - Platform::Windows => platform_dir.join("app"), // .exe (after bundling) - Platform::Liveview => platform_dir.join("app"), // .exe (after bundling) + BundleFormat::Android => platform_dir.join("app"), // .apk (after bundling) + BundleFormat::Linux => platform_dir.join("app"), // .appimage (after bundling) + BundleFormat::Windows => platform_dir.join("app"), // .exe (after bundling) } } fn platform_dir(&self) -> PathBuf { - self.build_dir(self.platform, self.release) + self.build_dir(self.bundle, self.release) } fn platform_exe_name(&self) -> String { - match self.platform { - Platform::MacOS => self.executable_name().to_string(), - Platform::Ios => self.executable_name().to_string(), - Platform::Server => self.executable_name().to_string(), - Platform::Liveview => self.executable_name().to_string(), - Platform::Windows => format!("{}.exe", self.executable_name()), + match self.bundle { + BundleFormat::MacOS => self.executable_name().to_string(), + BundleFormat::Ios => self.executable_name().to_string(), + BundleFormat::Server => self.executable_name().to_string(), + BundleFormat::Windows => format!("{}.exe", self.executable_name()), // from the apk spec, the root exe is a shared library // we include the user's rust code as a shared library with a fixed namespace - Platform::Android => "libdioxusmain.so".to_string(), + BundleFormat::Android => "libdioxusmain.so".to_string(), // this will be wrong, I think, but not important? - Platform::Web => format!("{}_bg.wasm", self.executable_name()), + BundleFormat::Web => format!("{}_bg.wasm", self.executable_name()), // todo: maybe this should be called AppRun? - Platform::Linux => self.executable_name().to_string(), + BundleFormat::Linux => self.executable_name().to_string(), } } @@ -3050,22 +3085,22 @@ impl BuildRequest { /// target/dx/build/app/web/ /// target/dx/build/app/web/public/ /// target/dx/build/app/web/server.exe - pub(crate) fn build_dir(&self, platform: Platform, release: bool) -> PathBuf { + pub(crate) fn build_dir(&self, bundle: BundleFormat, release: bool) -> PathBuf { self.internal_out_dir() .join(&self.main_target) .join(if release { "release" } else { "debug" }) - .join(platform.build_folder_name()) + .join(bundle.build_folder_name()) } /// target/dx/bundle/app/ /// target/dx/bundle/app/blah.app /// target/dx/bundle/app/blah.exe /// target/dx/bundle/app/public/ - pub(crate) fn bundle_dir(&self, platform: Platform) -> PathBuf { + pub(crate) fn bundle_dir(&self, bundle: BundleFormat) -> PathBuf { self.internal_out_dir() .join(&self.main_target) .join("bundle") - .join(platform.build_folder_name()) + .join(bundle.build_folder_name()) } /// Get the workspace directory for the crate @@ -3103,37 +3138,54 @@ impl BuildRequest { } /// Get the features required to build for the given platform - fn feature_for_platform(package: &krates::cm::Package, platform: Platform) -> String { + fn feature_for_platform_and_renderer( + package: &krates::cm::Package, + triple: &Triple, + renderer: Renderer, + ) -> String { // Try to find the feature that activates the dioxus feature for the given platform - let dioxus_feature = platform.feature_name(); + let dioxus_feature = renderer.feature_name(triple); let res = package.features.iter().find_map(|(key, features)| { // if the feature is just the name of the platform, we use that if key == dioxus_feature { + tracing::debug!("Found feature {key} for renderer {renderer}"); return Some(key.clone()); } - // Otherwise look for the feature that starts with dioxus/ or dioxus?/ and matches the platform + // Otherwise look for the feature that starts with dioxus/ or dioxus?/ and matches just the single platform + // we are looking for. + let mut dioxus_renderers_enabled = Vec::new(); for feature in features { if let Some((_, after_dioxus)) = feature.split_once("dioxus") { if let Some(dioxus_feature_enabled) = after_dioxus.trim_start_matches('?').strip_prefix('/') { - // If that enables the feature we are looking for, return that feature - if dioxus_feature_enabled == dioxus_feature { - return Some(key.clone()); + if Renderer::autodetect_from_cargo_feature(dioxus_feature_enabled).is_some() + { + dioxus_renderers_enabled.push(dioxus_feature_enabled.to_string()); } } } } + // If there is exactly one renderer enabled by this feature, we can use it + if let [feature_name] = dioxus_renderers_enabled.as_slice() { + if feature_name == dioxus_feature { + tracing::debug!( + "Found feature {key} for renderer {renderer} which enables dioxus/{renderer}" + ); + return Some(key.clone()); + } + } + None }); res.unwrap_or_else(|| { - let fallback = format!("dioxus/{}", platform.feature_name()) ; + let fallback = format!("dioxus/{dioxus_feature}"); tracing::debug!( - "Could not find explicit feature for platform {platform}, passing `fallback` instead" + "Could not find explicit feature for renderer {renderer}, passing `fallback` instead" ); fallback }) @@ -3180,11 +3232,11 @@ impl BuildRequest { /// Return the platforms that are enabled for the package /// /// Ideally only one platform is enabled but we need to be able to - pub(crate) fn enabled_cargo_toml_platforms( + pub(crate) fn enabled_cargo_toml_renderers( package: &krates::cm::Package, no_default_features: bool, - ) -> Vec { - let mut platforms = vec![]; + ) -> Vec<(Renderer, String)> { + let mut renderers = vec![]; // Attempt to discover the platform directly from the dioxus dependency // @@ -3193,8 +3245,8 @@ impl BuildRequest { // if let Some(dxs) = package.dependencies.iter().find(|dep| dep.name == "dioxus") { for f in dxs.features.iter() { - if let Some(platform) = Platform::autodetect_from_cargo_feature(f) { - platforms.push(platform); + if let Some(renderer) = Renderer::autodetect_from_cargo_feature(f) { + renderers.push((renderer, f.clone())); } } } @@ -3210,21 +3262,22 @@ impl BuildRequest { // default = ["web"] // web = ["dioxus/web"] if no_default_features { - return platforms; + return renderers; } let Some(default) = package.features.get("default") else { - return platforms; + return renderers; }; // we only trace features 1 level deep.. + // TODO: trace all enabled features, not just default features for feature in default.iter() { // If the user directly specified a platform we can just use that. if feature.starts_with("dioxus/") { let dx_feature = feature.trim_start_matches("dioxus/"); - let auto = Platform::autodetect_from_cargo_feature(dx_feature); + let auto = Renderer::autodetect_from_cargo_feature(dx_feature); if let Some(auto) = auto { - platforms.push(auto); + renderers.push((auto, dx_feature.to_string())); } } @@ -3234,23 +3287,23 @@ impl BuildRequest { for feature in internal_feature { if feature.starts_with("dioxus/") { let dx_feature = feature.trim_start_matches("dioxus/"); - let auto = Platform::autodetect_from_cargo_feature(dx_feature); + let auto = Renderer::autodetect_from_cargo_feature(dx_feature); if let Some(auto) = auto { - platforms.push(auto); + renderers.push((auto, dx_feature.to_string())); } } } } } - platforms.sort(); - platforms.dedup(); + renderers.sort(); + renderers.dedup(); - platforms + renderers } /// Gather the features that are enabled for the package - fn platformless_features(package: &krates::cm::Package) -> Vec { + fn rendererless_features(package: &krates::cm::Package) -> Vec { let Some(default) = package.features.get("default") else { return Vec::new(); }; @@ -3263,7 +3316,10 @@ impl BuildRequest { // Don't keep features that point to a platform via dioxus/blah if feature.starts_with("dioxus/") { let dx_feature = feature.trim_start_matches("dioxus/"); - if Platform::autodetect_from_cargo_feature(dx_feature).is_some() { + if Renderer::autodetect_from_cargo_feature(dx_feature).is_some() { + tracing::debug!( + "Dropping feature {feature} since it points to a platform renderer" + ); continue 'top; } } @@ -3273,7 +3329,10 @@ impl BuildRequest { for feature in internal_feature { if feature.starts_with("dioxus/") { let dx_feature = feature.trim_start_matches("dioxus/"); - if Platform::autodetect_from_cargo_feature(dx_feature).is_some() { + if Renderer::autodetect_from_cargo_feature(dx_feature).is_some() { + tracing::debug!( + "Dropping feature {feature} since it points to a platform renderer transitively" + ); continue 'top; } } @@ -3319,6 +3378,13 @@ impl BuildRequest { self.exe_dir().join(self.platform_exe_name()) } + fn is_wasm_or_wasi(&self) -> bool { + matches!( + self.triple.architecture, + target_lexicon::Architecture::Wasm32 | target_lexicon::Architecture::Wasm64 + ) || self.triple.operating_system == target_lexicon::OperatingSystem::Wasi + } + /// Does the app specify: /// /// - Dioxus with "fullstack" enabled? (access to serverfns, etc) @@ -3385,36 +3451,35 @@ impl BuildRequest { /// todo(jon): use handlebars templates instead of these prebaked templates async fn write_metadata(&self) -> Result<()> { // write the Info.plist file - match self.platform { - Platform::MacOS => { + match self.bundle { + BundleFormat::MacOS => { let dest = self.root_dir().join("Contents").join("Info.plist"); - let plist = self.info_plist_contents(self.platform)?; + let plist = self.info_plist_contents(self.bundle)?; std::fs::write(dest, plist)?; } - Platform::Ios => { + BundleFormat::Ios => { let dest = self.root_dir().join("Info.plist"); - let plist = self.info_plist_contents(self.platform)?; + let plist = self.info_plist_contents(self.bundle)?; std::fs::write(dest, plist)?; } // AndroidManifest.xml // er.... maybe even all the kotlin/java/gradle stuff? - Platform::Android => {} + BundleFormat::Android => {} // Probably some custom format or a plist file (haha) // When we do the proper bundle, we'll need to do something with wix templates, I think? - Platform::Windows => {} + BundleFormat::Windows => {} // eventually we'll create the .appimage file, I guess? - Platform::Linux => {} + BundleFormat::Linux => {} // These are served as folders, not appimages, so we don't need to do anything special (I think?) // Eventually maybe write some secrets/.env files for the server? // We could also distribute them as a deb/rpm for linux and msi for windows - Platform::Web => {} - Platform::Server => {} - Platform::Liveview => {} + BundleFormat::Web => {} + BundleFormat::Server => {} } Ok(()) @@ -3422,8 +3487,8 @@ impl BuildRequest { /// Run the optimizers, obfuscators, minimizers, signers, etc async fn optimize(&self, ctx: &BuildContext) -> Result<()> { - match self.platform { - Platform::Web => { + match self.bundle { + BundleFormat::Web => { // Compress the asset dir // If pre-compressing is enabled, we can pre_compress the wasm-bindgen output let pre_compress = self.should_pre_compress_web_assets(self.release); @@ -3438,13 +3503,12 @@ impl BuildRequest { .unwrap()?; } } - Platform::MacOS => {} - Platform::Windows => {} - Platform::Linux => {} - Platform::Ios => {} - Platform::Android => {} - Platform::Server => {} - Platform::Liveview => {} + BundleFormat::MacOS + | BundleFormat::Windows + | BundleFormat::Linux + | BundleFormat::Ios + | BundleFormat::Android + | BundleFormat::Server => {} } Ok(()) @@ -3458,7 +3522,7 @@ impl BuildRequest { /// Check if the wasm output should be bundled to an asset type app. fn should_bundle_to_asset(&self) -> bool { - self.release && !self.wasm_split && self.platform == Platform::Web + self.release && !self.wasm_split && self.bundle == BundleFormat::Web } /// Bundle the web app @@ -3586,7 +3650,7 @@ impl BuildRequest { .as_ref() .context("generated bindgen module has no name?")?; - let path = bindgen_outdir.join(format!("module_{}_{}.wasm", idx, comp_name)); + let path = bindgen_outdir.join(format!("module_{idx}_{comp_name}.wasm")); wasm_opt::write_wasm(&module.bytes, &path, &wasm_opt_options).await?; let hash_id = module @@ -3756,7 +3820,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ } } - fn info_plist_contents(&self, platform: Platform) -> Result { + fn info_plist_contents(&self, bundle: BundleFormat) -> Result { #[derive(Serialize)] pub struct InfoPlistData { pub display_name: String, @@ -3767,13 +3831,13 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ // Attempt to use the user's manually specified let _app = &self.config.application; - match platform { - Platform::MacOS => { + match bundle { + BundleFormat::MacOS => { if let Some(macos_info_plist) = _app.macos_info_plist.as_deref() { return Ok(std::fs::read_to_string(macos_info_plist)?); } } - Platform::Ios => { + BundleFormat::Ios => { if let Some(macos_info_plist) = _app.ios_info_plist.as_deref() { return Ok(std::fs::read_to_string(macos_info_plist)?); } @@ -3781,8 +3845,8 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ _ => {} } - match platform { - Platform::MacOS => handlebars::Handlebars::new() + match bundle { + BundleFormat::MacOS => handlebars::Handlebars::new() .render_template( include_str!("../../assets/macos/mac.plist.hbs"), &InfoPlistData { @@ -3793,7 +3857,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ }, ) .map_err(|e| e.into()), - Platform::Ios => handlebars::Handlebars::new() + BundleFormat::Ios => handlebars::Handlebars::new() .render_template( include_str!("../../assets/ios/ios.plist.hbs"), &InfoPlistData { @@ -3812,7 +3876,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ /// /// This might include codesigning, zipping, creating an appimage, etc async fn assemble(&self, ctx: &BuildContext) -> Result<()> { - if let Platform::Android = self.platform { + if self.bundle == BundleFormat::Android { ctx.status_running_gradle(); // When the build mode is set to release and there is an Android signature configuration, use assembleRelease @@ -3918,7 +3982,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ static INITIALIZED: OnceLock> = OnceLock::new(); let success = INITIALIZED.get_or_init(|| { - if self.platform != Platform::Server { + if self.bundle != BundleFormat::Server { _ = remove_dir_all(self.exe_dir()); } @@ -3941,7 +4005,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ // we could download the templates from somewhere (github?) but after having banged my head against // cargo-mobile2 for ages, I give up with that. We're literally just going to hardcode the templates // by writing them here. - if let Platform::Android = self.platform { + if self.bundle == BundleFormat::Android { self.build_android_app_dir()?; } @@ -3956,14 +4020,14 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ } pub(crate) fn asset_dir(&self) -> PathBuf { - match self.platform { - Platform::MacOS => self + match self.bundle { + BundleFormat::MacOS => self .root_dir() .join("Contents") .join("Resources") .join("assets"), - Platform::Android => self + BundleFormat::Android => self .root_dir() .join("app") .join("src") @@ -3971,12 +4035,11 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ .join("assets"), // everyone else is soooo normal, just app/assets :) - Platform::Web - | Platform::Ios - | Platform::Windows - | Platform::Linux - | Platform::Server - | Platform::Liveview => self.root_dir().join("assets"), + BundleFormat::Web + | BundleFormat::Ios + | BundleFormat::Windows + | BundleFormat::Linux + | BundleFormat::Server => self.root_dir().join("assets"), } } @@ -3991,12 +4054,12 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ /// /// todo(jon): investigate if we need to put .wasm in `wasm`. It kinda leaks implementation details, which ideally we don't want to do. fn exe_dir(&self) -> PathBuf { - match self.platform { - Platform::MacOS => self.root_dir().join("Contents").join("MacOS"), - Platform::Web => self.root_dir().join("wasm"), + match self.bundle { + BundleFormat::MacOS => self.root_dir().join("Contents").join("MacOS"), + BundleFormat::Web => self.root_dir().join("wasm"), // Android has a whole build structure to it - Platform::Android => self + BundleFormat::Android => self .root_dir() .join("app") .join("src") @@ -4005,11 +4068,10 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ .join(AndroidTools::android_jnilib(&self.triple)), // these are all the same, I think? - Platform::Windows - | Platform::Linux - | Platform::Ios - | Platform::Server - | Platform::Liveview => self.root_dir(), + BundleFormat::Windows + | BundleFormat::Linux + | BundleFormat::Ios + | BundleFormat::Server => self.root_dir(), } } @@ -4050,12 +4112,12 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ pub(crate) async fn verify_tooling(&self, ctx: &BuildContext) -> Result<()> { ctx.status_installing_tooling(); - match self.platform { - Platform::Web => self.verify_web_tooling().await?, - Platform::Ios => self.verify_ios_tooling().await?, - Platform::Android => self.verify_android_tooling().await?, - Platform::Linux => self.verify_linux_tooling().await?, - Platform::MacOS | Platform::Windows | Platform::Server | Platform::Liveview => {} + match self.bundle { + BundleFormat::Web => self.verify_web_tooling().await?, + BundleFormat::Ios => self.verify_ios_tooling().await?, + BundleFormat::Android => self.verify_android_tooling().await?, + BundleFormat::Linux => self.verify_linux_tooling().await?, + BundleFormat::MacOS | BundleFormat::Windows | BundleFormat::Server => {} } Ok(()) @@ -4199,8 +4261,8 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ } async fn create_patch_cache(&self, exe: &Path) -> Result { - let exe = match self.platform { - Platform::Web => self.wasm_bindgen_wasm_output_file(), + let exe = match self.bundle { + BundleFormat::Web => self.wasm_bindgen_wasm_output_file(), _ => exe.to_path_buf(), }; @@ -4405,7 +4467,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ self.base_path .as_deref() .or(self.config.web.app.base_path.as_deref()) - .filter(|_| matches!(self.platform, Platform::Web | Platform::Server)) + .filter(|_| matches!(self.bundle, BundleFormat::Web | BundleFormat::Server)) } /// Get the normalized base path for the application with `/` trimmed from both ends. If the base path is not set, this will return `.`. @@ -4433,13 +4495,13 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ return Ok(()); } - match self.platform { + match self.bundle { // Boot an iOS simulator if one is not already running. // // We always choose the most recently opened simulator based on the xcrun list. // Note that simulators can be running but the simulator app itself is not open. // Calling `open::that` is always fine, even on running apps, since apps are singletons. - Platform::Ios => { + BundleFormat::Ios => { #[derive(Deserialize, Debug)] struct XcrunListJson { // "com.apple.CoreSimulator.SimRuntime.iOS-18-4": [{}, {}, {}] @@ -4504,7 +4566,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ open::that_detached(path_to_sim)?; } - Platform::Android => { + BundleFormat::Android => { let tools = self.workspace.android_tools()?; tokio::spawn(async move { let emulator = tools.emulator(); @@ -4597,7 +4659,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ // The default dioxus experience is to lightly optimize the web build, both in debug and release // Note that typically in release builds, you would strip debuginfo, but we actually choose to do // that with wasm-opt tooling instead. - if matches!(self.platform, Platform::Web) { + if matches!(self.bundle, BundleFormat::Web) { match self.release { true => args.push(r#"profile.web.opt-level="s""#.to_string()), false => args.push(r#"profile.web.opt-level="1""#.to_string()), @@ -4611,7 +4673,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ } async fn prebuild(&self) -> Result<()> { - if self.platform == Platform::Server { + if self.bundle == BundleFormat::Server { return Ok(()); } diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index cf98726e6..85a51173f 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -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] diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index cb14926ff..096426601 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -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, /// 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 { // 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?); } diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index 397821c04..81064cb72 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -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(), diff --git a/packages/cli/src/cli/check.rs b/packages/cli/src/cli/check.rs index 942e759e6..bd1c2f501 100644 --- a/packages/cli/src/cli/check.rs +++ b/packages/cli/src/cli/check.rs @@ -119,7 +119,7 @@ async fn check_files_and_report(files_to_check: Vec) -> Result<()> { Ok(()) } 1 => Err(anyhow!("1 issue found.")), - _ => Err(anyhow!("{} issues found.", total_issues)), + _ => Err(anyhow!("{total_issues} issues found.")), } } diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index eac2846bc..525ce4caf 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -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; diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index a608089e8..646f68fa1 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -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, diff --git a/packages/cli/src/cli/target.rs b/packages/cli/src/cli/target.rs index c4c98ccf5..f20c7faf2 100644 --- a/packages/cli/src/cli/target.rs +++ b/packages/cli/src/cli/target.rs @@ -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, + + /// 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, diff --git a/packages/cli/src/cli/translate.rs b/packages/cli/src/cli/translate.rs index bea96f929..ce365ba25 100644 --- a/packages/cli/src/cli/translate.rs +++ b/packages/cli/src/cli/translate.rs @@ -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 }) diff --git a/packages/cli/src/config/bundle.rs b/packages/cli/src/config/bundle.rs index 071c0d6a6..21f19833b 100644 --- a/packages/cli/src/config/bundle.rs +++ b/packages/cli/src/config/bundle.rs @@ -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")), } } } diff --git a/packages/cli/src/logging.rs b/packages/cli/src/logging.rs index f4630db19..37c204bdd 100644 --- a/packages/cli/src/logging.rs +++ b/packages/cli/src/logging.rs @@ -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 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 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"), diff --git a/packages/cli/src/platform.rs b/packages/cli/src/platform.rs index 986c30614..d9aa092b3 100644 --- a/packages/cli/src/platform.rs +++ b/packages/cli/src/platform.rs @@ -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 { + if let Some(platform) = matches.get_one::("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 = Some(Self::MacOS); + #[cfg(target_os = "windows")] + pub(crate) const TARGET_PLATFORM: Option = Some(Self::Windows); + #[cfg(target_os = "linux")] + pub(crate) const TARGET_PLATFORM: Option = Some(Self::Linux); + #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] + pub(crate) const TARGET_PLATFORM: Option = None; + + pub(crate) async fn into_target(self, device: bool, workspace: &Workspace) -> Result { + 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, +} + +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 { + if let Some(renderer) = matches.get_one::("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 for Option { + 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 { + 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 { + 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 { + 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 = Some(Self::MacOS); + #[cfg(target_os = "windows")] + pub(crate) const TARGET_PLATFORM: Option = Some(Self::Windows); + #[cfg(target_os = "linux")] + pub(crate) const TARGET_PLATFORM: Option = Some(Self::Linux); + #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))] + pub(crate) const TARGET_PLATFORM: Option = 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) -> Result { + 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 { - 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 { - 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(), + ), } } } diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 32afa3a99..d8886a478 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -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..." ); diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 74e27ab82..372e6dc1e 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -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], ); diff --git a/packages/cli/src/serve/proxy.rs b/packages/cli/src/serve/proxy.rs index 4765984a8..c52bd86d4 100644 --- a/packages/cli/src/serve/proxy.rs +++ b/packages/cli/src/serve/proxy.rs @@ -163,8 +163,7 @@ pub(crate) fn handle_proxy_error(e: Error) -> axum::http::Response 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") diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 308646c3c..6993ef90b 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -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, 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 { - 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>); enum Status { ClientInit { application_name: String, - platform: Platform, + bundle: BundleFormat, }, Building { progress: f64, diff --git a/packages/cli/src/serve/update.rs b/packages/cli/src/serve/update.rs index fa8e98c4f..63b2e810a 100644 --- a/packages/cli/src/serve/update.rs +++ b/packages/cli/src/serve/update.rs @@ -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, }, WsMessage { - platform: Platform, + bundle: BundleFormat, msg: WsMessage, }, diff --git a/packages/cli/src/tailwind.rs b/packages/cli/src/tailwind.rs index 4877da5ab..9848a22dd 100644 --- a/packages/cli/src/tailwind.rs +++ b/packages/cli/src/tailwind.rs @@ -220,7 +220,7 @@ impl TailwindCli { _ => return None, }; - Some(format!("tailwindcss-{}-{}", platform, arch)) + Some(format!("tailwindcss-{platform}-{arch}")) } fn install_dir(&self) -> Result { diff --git a/packages/dioxus/README.md b/packages/dioxus/README.md index 1592a999f..8563ad49d 100644 --- a/packages/dioxus/README.md +++ b/packages/dioxus/README.md @@ -11,6 +11,7 @@ --- + 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.
@@ -42,7 +43,7 @@ cargo binstall dioxus-cli dx new my-app && cd my-app # and then serve! -dx serve --platform desktop +dx serve --desktop ``` ## Your first app diff --git a/packages/native/Cargo.toml b/packages/native/Cargo.toml index a02b0936e..95fb2ed7a 100644 --- a/packages/native/Cargo.toml +++ b/packages/native/Cargo.toml @@ -10,13 +10,14 @@ homepage = "https://dioxuslabs.com/learn/0.6/getting_started" keywords = ["dom", "ui", "gui", "react"] [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"] net = ["dep:tokio", "dep:blitz-net"] accessibility = ["blitz-shell/accessibility", "blitz-dom/accessibility"] tracing = ["dep:tracing", "blitz-shell/tracing", "blitz-dom/tracing"] hot-reload = ["dep:dioxus-cli-config", "dep:dioxus-devtools"] system-fonts = ["blitz-dom/system_fonts"] +openssl-vendored-android = ["openssl"] autofocus = [] [dependencies] @@ -51,6 +52,9 @@ tracing = { workspace = true, optional = true } rustc-hash = { workspace = true } futures-util = { workspace = true } +[target.'cfg(target_os = "android")'.dependencies] +openssl = { version = "0.10", features = ["vendored"], optional = true } + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] diff --git a/packages/playwright-tests/barebones-template/README.md b/packages/playwright-tests/barebones-template/README.md index 1e629b630..f6c278c96 100644 --- a/packages/playwright-tests/barebones-template/README.md +++ b/packages/playwright-tests/barebones-template/README.md @@ -18,8 +18,8 @@ Run the following command in the root of your project to start developing with t dx serve ``` -To run for a different platform, use the `--platform platform` flag. E.g. -```bash -dx serve --platform desktop -``` +To run for a different platform, use the `--webview`, `--native`, or `--web` flags. E.g. +```bash +dx serve --webview +``` diff --git a/packages/playwright-tests/playwright.config.js b/packages/playwright-tests/playwright.config.js index 1c6074261..0078fdca4 100644 --- a/packages/playwright-tests/playwright.config.js +++ b/packages/playwright-tests/playwright.config.js @@ -87,7 +87,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "web"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -96,7 +96,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "web-routing"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -105,7 +105,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "web-hash-routing"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -114,7 +114,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "fullstack"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -123,7 +123,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "fullstack-mounted"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -132,7 +132,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "fullstack-routing"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -141,7 +141,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "suspense-carousel"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -150,7 +150,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "nested-suspense"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -159,7 +159,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "nested-suspense"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -178,7 +178,7 @@ module.exports = defineConfig({ { cwd: path.join(process.cwd(), "wasm-split-harness"), 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI, @@ -204,7 +204,7 @@ module.exports = defineConfig({ }, { 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, timeout: 50 * 60 * 1000, reuseExistingServer: !process.env.CI,