codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)
overhaul dioxus fullstack using a new implementation of server_fn This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -320,7 +320,10 @@ pub(crate) async fn extract_assets_from_file(path: impl AsRef<Path>) -> Result<A
|
||||
|
||||
if let Some((_, bundled_asset)) = const_serialize::deserialize_const!(BundledAsset, buffer)
|
||||
{
|
||||
tracing::debug!("Found asset at offset {offset}: {:?}", bundled_asset);
|
||||
tracing::debug!(
|
||||
"Found asset at offset {offset}: {:?}",
|
||||
bundled_asset.absolute_source_path()
|
||||
);
|
||||
assets.push(bundled_asset);
|
||||
} else {
|
||||
tracing::warn!("Found an asset at offset {offset} that could not be deserialized. This may be caused by a mismatch between your dioxus and dioxus-cli versions.");
|
||||
|
||||
@@ -715,7 +715,7 @@ impl AppBuilder {
|
||||
|
||||
let changed_file = changed_files.first().unwrap();
|
||||
tracing::info!(
|
||||
"Hot-patching: {NOTE_STYLE}{}{NOTE_STYLE:#} took {GLOW_STYLE}{:?}ms{GLOW_STYLE:#}",
|
||||
"Hot-patching: {NOTE_STYLE}{}{NOTE_STYLE:#} took {GLOW_STYLE}{:?}ms{GLOW_STYLE:#} ({})",
|
||||
changed_file
|
||||
.display()
|
||||
.to_string()
|
||||
@@ -723,7 +723,8 @@ impl AppBuilder {
|
||||
SystemTime::now()
|
||||
.duration_since(res.time_start)
|
||||
.unwrap()
|
||||
.as_millis()
|
||||
.as_millis(),
|
||||
triple
|
||||
);
|
||||
|
||||
self.patches.push(jump_table.clone());
|
||||
|
||||
@@ -1191,6 +1191,8 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result<Vec<u8>> {
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let mut exported = HashSet::new();
|
||||
|
||||
// Wasm-bindgen will synthesize imports to satisfy its external calls. This facilitates things
|
||||
// like inline-js, snippets, and literally the `#[wasm_bindgen]` macro. All calls to JS are
|
||||
// just `extern "wbg"` blocks!
|
||||
@@ -1227,9 +1229,10 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result<Vec<u8>> {
|
||||
|
||||
let new_func_id = module.funcs.add_local(builder.local_func(locals));
|
||||
|
||||
module
|
||||
.exports
|
||||
.add(&format!("__saved_wbg_{}", import.name), new_func_id);
|
||||
let saved_name = format!("__saved_wbg_{}", import.name);
|
||||
if exported.insert(saved_name.clone()) {
|
||||
module.exports.add(&saved_name, new_func_id);
|
||||
}
|
||||
|
||||
make_indirect.push(new_func_id);
|
||||
}
|
||||
@@ -1254,9 +1257,10 @@ pub fn prepare_wasm_base_module(bytes: &[u8]) -> Result<Vec<u8>> {
|
||||
//
|
||||
// https://github.com/rustwasm/wasm-bindgen/blob/c35cc9369d5e0dc418986f7811a0dd702fb33ef9/crates/cli-support/src/wit/mod.rs#L1505
|
||||
if name.starts_with("__wbindgen") {
|
||||
module
|
||||
.exports
|
||||
.add(&format!("__saved_wbg_{name}"), func.id());
|
||||
let saved_name = format!("__saved_wbg_{}", name);
|
||||
if exported.insert(saved_name.clone()) {
|
||||
module.exports.add(&saved_name, func.id());
|
||||
}
|
||||
}
|
||||
|
||||
// This is basically `--export-all` but designed to work around wasm-bindgen not properly gc-ing
|
||||
|
||||
+297
-107
@@ -321,7 +321,7 @@
|
||||
|
||||
use crate::{
|
||||
AndroidTools, BuildContext, BundleFormat, DioxusConfig, Error, LinkAction, LinkerFlavor,
|
||||
Renderer, Result, RustcArgs, TargetAlias, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig,
|
||||
Platform, Renderer, Result, RustcArgs, TargetArgs, TraceSrc, WasmBindgen, WasmOptConfig,
|
||||
Workspace, DX_RUSTC_WRAPPER_ENV_VAR,
|
||||
};
|
||||
use anyhow::{bail, Context};
|
||||
@@ -348,7 +348,7 @@ use std::{
|
||||
},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use target_lexicon::{Architecture, Environment, OperatingSystem, Triple};
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||
use tempfile::TempDir;
|
||||
use tokio::{io::AsyncBufReadExt, process::Command};
|
||||
use uuid::Uuid;
|
||||
@@ -549,105 +549,257 @@ impl BuildRequest {
|
||||
})?
|
||||
.clone();
|
||||
|
||||
// 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_renderers =
|
||||
Self::enabled_cargo_toml_renderers(main_package, args.no_default_features);
|
||||
let using_dioxus_explicitly = main_package
|
||||
.dependencies
|
||||
.iter()
|
||||
.any(|dep| dep.name == "dioxus");
|
||||
|
||||
let mut features = args.features.clone();
|
||||
let mut no_default_features = args.no_default_features;
|
||||
|
||||
// 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<Renderer> = 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::rendererless_features(main_package));
|
||||
no_default_features = true;
|
||||
Some(renderer)
|
||||
}
|
||||
},
|
||||
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 `--web`, `--webview`, or `--native` or set a default platform in Cargo.toml"
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// 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.clone();
|
||||
|
||||
// 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();
|
||||
let using_dioxus_explicitly = main_package
|
||||
.dependencies
|
||||
.iter()
|
||||
.any(|dep| dep.name == "dioxus");
|
||||
|
||||
/*
|
||||
Determine which features, triple, profile, etc to pass to the build.
|
||||
|
||||
Most of the time, users should use `dx serve --<platform>` where the platform name directly
|
||||
corresponds to the feature in their cargo.toml. So,
|
||||
- `dx serve --web` will enable the `web` feature
|
||||
- `dx serve --mobile` will enable the `mobile` feature
|
||||
- `dx serve --desktop` will enable the `desktop` feature
|
||||
|
||||
In this case, we set default-features to false and then add back the default features that
|
||||
aren't renderers, and then add the feature for the given renderer (ie web/desktop/mobile).
|
||||
We call this "no-default-features-stripped."
|
||||
|
||||
There are a few cases where the user doesn't need to pass a platform.
|
||||
- they selected one via `dioxus = { features = ["web"] }`
|
||||
- they have a single platform in their default features `default = ["web"]`
|
||||
- there is only a single non-server renderer as a feature `web = ["dioxus/web"], server = ["dioxus/server"]`
|
||||
- they compose the super triple via triple + bundleformat + features
|
||||
|
||||
Note that we only use the names of the features to correspond with the platform.
|
||||
Platforms are "super triples", meaning they contain information about
|
||||
- bundle format
|
||||
- target triple
|
||||
- how to serve
|
||||
- enabled features
|
||||
|
||||
By default, the --platform presets correspond to:
|
||||
- web: bundle(web), triple(wasm32), serve(http-serve), features("web")
|
||||
- desktop: alias to mac/win/linux
|
||||
- mac: bundle(mac), triple(host), serve(appbundle-open), features("desktop")
|
||||
- windows: bundle(exefolder), triple(host), serve(run-exe), features("desktop")
|
||||
- linux: bundle(appimage), triple(host), serve(run-exe), features("desktop")
|
||||
- ios: bundle(ios), triple(arm64-apple-ios), serve(ios-simulator/xcrun), features("mobile")
|
||||
- android: bundle(android), triple(arm64-apple-ios), serve(android-emulator/adb), features("mobile")
|
||||
- server: bundle(server), triple(host), serve(run-exe), features("server") (and disables the client)
|
||||
- liveview: bundle(liveview), triple(host), serve(run-exe), features("liveview")
|
||||
- unknown: <auto or default to desktop>
|
||||
|
||||
Fullstack usage is inferred from the presence of the fullstack feature or --fullstack.
|
||||
*/
|
||||
let mut features = args.features.clone();
|
||||
let mut no_default_features = args.no_default_features;
|
||||
let mut triple = args.target.clone();
|
||||
let mut renderer = args.renderer;
|
||||
let mut bundle_format = args.bundle;
|
||||
let mut platform = args.platform;
|
||||
|
||||
// the crate might be selecting renderers but the user also passes a renderer. this is weird
|
||||
// ie dioxus = { features = ["web"] } but also --platform desktop
|
||||
// anyways, we collect it here in the event we need it if platform is not specified.
|
||||
let dioxus_direct_renderer = Self::renderer_enabled_by_dioxus_dependency(main_package);
|
||||
let known_features_as_renderers = Self::features_that_enable_renderers(main_package);
|
||||
|
||||
// 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_renderers = if no_default_features {
|
||||
vec![]
|
||||
} else {
|
||||
Self::enabled_cargo_toml_default_features_renderers(main_package)
|
||||
};
|
||||
|
||||
// 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.is_some(), &workspace).await?,
|
||||
};
|
||||
// Try the easy autodetects.
|
||||
// - if the user has `dioxus = { features = ["web"] }`
|
||||
// - if the `default =["web"]` or `default = ["dioxus/web"]`
|
||||
// - if there's only one non-server platform ie `web = ["dioxus/web"], server = ["dioxus/server"]`
|
||||
// Only do this if we're explicitly using dioxus
|
||||
if matches!(platform, Platform::Unknown) && using_dioxus_explicitly {
|
||||
let auto = dioxus_direct_renderer
|
||||
.or_else(|| {
|
||||
if enabled_renderers.len() == 1 {
|
||||
Some(enabled_renderers[0].clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.or_else(|| {
|
||||
let non_server_features = known_features_as_renderers
|
||||
.iter()
|
||||
.filter(|f| f.1.as_str() != "server")
|
||||
.collect::<Vec<_>>();
|
||||
if non_server_features.len() == 1 {
|
||||
Some(non_server_features[0].clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// 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));
|
||||
if let Some((direct, feature)) = auto {
|
||||
match direct {
|
||||
_ if feature == "mobile" || feature == "dioxus/mobile" => {
|
||||
bail!(
|
||||
"Could not autodetect mobile platform. Use --ios or --android instead."
|
||||
);
|
||||
}
|
||||
Renderer::Webview | Renderer::Native => {
|
||||
if cfg!(target_os = "macos") {
|
||||
platform = Platform::MacOS;
|
||||
} else if cfg!(target_os = "linux") {
|
||||
platform = Platform::Linux;
|
||||
} else if cfg!(target_os = "windows") {
|
||||
platform = Platform::Windows;
|
||||
}
|
||||
}
|
||||
Renderer::Server => platform = Platform::Server,
|
||||
Renderer::Liveview => platform = Platform::Liveview,
|
||||
Renderer::Web => platform = Platform::Web,
|
||||
}
|
||||
renderer = renderer.or(Some(direct));
|
||||
}
|
||||
}
|
||||
|
||||
// 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)?,
|
||||
// Set the super triple from the platform if it's provided.
|
||||
// Otherwise, we attempt to guess it from the rest of their inputs.
|
||||
match platform {
|
||||
Platform::Unknown => {}
|
||||
|
||||
Platform::Web => {
|
||||
if main_package.features.contains_key("web") && renderer.is_none() {
|
||||
features.push("web".into());
|
||||
}
|
||||
renderer = renderer.or(Some(Renderer::Web));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::Web));
|
||||
triple = triple.or(Some("wasm32-unknown-unknown".parse()?));
|
||||
no_default_features = true;
|
||||
}
|
||||
Platform::MacOS => {
|
||||
if main_package.features.contains_key("desktop") && renderer.is_none() {
|
||||
features.push("desktop".into());
|
||||
}
|
||||
renderer = renderer.or(Some(Renderer::Webview));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::MacOS));
|
||||
triple = triple.or(Some(Triple::host()));
|
||||
no_default_features = true;
|
||||
}
|
||||
Platform::Windows => {
|
||||
if main_package.features.contains_key("desktop") && renderer.is_none() {
|
||||
features.push("desktop".into());
|
||||
}
|
||||
renderer = renderer.or(Some(Renderer::Webview));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::Windows));
|
||||
triple = triple.or(Some(Triple::host()));
|
||||
no_default_features = true;
|
||||
}
|
||||
Platform::Linux => {
|
||||
if main_package.features.contains_key("desktop") && renderer.is_none() {
|
||||
features.push("desktop".into());
|
||||
}
|
||||
renderer = renderer.or(Some(Renderer::Webview));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::Linux));
|
||||
triple = triple.or(Some(Triple::host()));
|
||||
no_default_features = true;
|
||||
}
|
||||
Platform::Ios => {
|
||||
if main_package.features.contains_key("mobile") && renderer.is_none() {
|
||||
features.push("mobile".into());
|
||||
}
|
||||
renderer = renderer.or(Some(Renderer::Webview));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::Ios));
|
||||
no_default_features = true;
|
||||
match device.is_some() {
|
||||
// If targeting device, we want to build for the device which is always aarch64
|
||||
true => triple = triple.or(Some("aarch64-apple-ios".parse()?)),
|
||||
|
||||
// If the host is aarch64, we assume the user wants to build for iOS simulator
|
||||
false if matches!(Triple::host().architecture, Architecture::Aarch64(_)) => {
|
||||
triple = triple.or(Some("aarch64-apple-ios-sim".parse()?))
|
||||
}
|
||||
|
||||
// Otherwise, it's the x86_64 simulator, which is just x86_64-apple-ios
|
||||
_ => triple = triple.or(Some("x86_64-apple-ios".parse()?)),
|
||||
}
|
||||
}
|
||||
Platform::Android => {
|
||||
if main_package.features.contains_key("mobile") && renderer.is_none() {
|
||||
features.push("mobile".into());
|
||||
}
|
||||
|
||||
renderer = renderer.or(Some(Renderer::Webview));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::Android));
|
||||
no_default_features = true;
|
||||
|
||||
// maybe probe adb?
|
||||
if let Some(_device_name) = device.as_ref() {
|
||||
if triple.is_none() {
|
||||
triple = Some(
|
||||
crate::get_android_tools()
|
||||
.context("Failed to get android tools")?
|
||||
.autodetect_android_device_triple()
|
||||
.await,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
triple = triple.or(Some({
|
||||
match Triple::host().architecture {
|
||||
Architecture::X86_32(_) => "i686-linux-android".parse()?,
|
||||
Architecture::X86_64 => "x86_64-linux-android".parse()?,
|
||||
Architecture::Aarch64(_) => "aarch64-linux-android".parse()?,
|
||||
_ => "aarch64-linux-android".parse()?,
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
Platform::Server => {
|
||||
if main_package.features.contains_key("server") && renderer.is_none() {
|
||||
features.push("server".into());
|
||||
}
|
||||
renderer = renderer.or(Some(Renderer::Server));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::Server));
|
||||
triple = triple.or(Some(Triple::host()));
|
||||
no_default_features = true;
|
||||
}
|
||||
Platform::Liveview => {
|
||||
if main_package.features.contains_key("liveview") && renderer.is_none() {
|
||||
features.push("liveview".into());
|
||||
}
|
||||
renderer = renderer.or(Some(Renderer::Liveview));
|
||||
bundle_format = bundle_format.or(Some(BundleFormat::Server));
|
||||
triple = triple.or(Some(Triple::host()));
|
||||
no_default_features = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If no default features are enabled, we need to add the rendererless features
|
||||
if no_default_features {
|
||||
features.extend(Self::rendererless_features(main_package));
|
||||
features.dedup();
|
||||
features.sort();
|
||||
}
|
||||
|
||||
// The triple will be the triple passed or the host if using dioxus.
|
||||
let triple = if using_dioxus_explicitly {
|
||||
triple.context("Could not automatically detect target triple")?
|
||||
} else {
|
||||
triple.unwrap_or(Triple::host())
|
||||
};
|
||||
|
||||
// The bundle format will be the bundle format passed or the host.
|
||||
let bundle = if using_dioxus_explicitly {
|
||||
bundle_format.context("Could not automatically detect bundle format")?
|
||||
} else {
|
||||
bundle_format.unwrap_or(BundleFormat::host())
|
||||
};
|
||||
|
||||
// Add any features required to turn on the client
|
||||
@@ -657,6 +809,7 @@ impl BuildRequest {
|
||||
&triple,
|
||||
renderer,
|
||||
));
|
||||
features.dedup();
|
||||
}
|
||||
|
||||
// Set the profile of the build if it's not already set
|
||||
@@ -714,6 +867,14 @@ impl BuildRequest {
|
||||
]);
|
||||
}
|
||||
|
||||
// automatically set the getrandom backend for web builds if the user requested it
|
||||
if matches!(bundle, BundleFormat::Web) && args.wasm_js_cfg {
|
||||
rustflags.flags.extend(
|
||||
cargo_config2::Flags::from_space_separated(r#"--cfg getrandom_backend="wasm_js""#)
|
||||
.flags,
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure to take into account the RUSTFLAGS env var and the CARGO_TARGET_<triple>_RUSTFLAGS
|
||||
for env in [
|
||||
"RUSTFLAGS".to_string(),
|
||||
@@ -1032,7 +1193,11 @@ impl BuildRequest {
|
||||
// since that is a really bad user experience.
|
||||
Message::BuildFinished(finished) => {
|
||||
if !finished.success {
|
||||
bail!("cargo build finished with errors.")
|
||||
bail!(
|
||||
"cargo build finished with errors for target: {} [{}]",
|
||||
self.main_target,
|
||||
self.triple
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -1190,7 +1355,6 @@ impl BuildRequest {
|
||||
|
||||
async fn write_frameworks(&self, _ctx: &BuildContext, direct_rustc: &RustcArgs) -> Result<()> {
|
||||
let framework_dir = self.frameworks_folder();
|
||||
_ = std::fs::create_dir_all(&framework_dir);
|
||||
|
||||
// We have some prebuilt stuff that needs to be copied into the framework dir
|
||||
let openssl_dir = AndroidTools::openssl_lib_dir(&self.triple);
|
||||
@@ -1208,6 +1372,8 @@ impl BuildRequest {
|
||||
|
||||
tracing::debug!("Copying framework from {from:?} to {to:?}");
|
||||
|
||||
_ = std::fs::create_dir_all(&framework_dir);
|
||||
|
||||
// in dev and on normal oses, we want to symlink the file
|
||||
// otherwise, just copy it (since in release you want to distribute the framework)
|
||||
if cfg!(any(windows, unix)) && !self.release {
|
||||
@@ -1225,6 +1391,11 @@ impl BuildRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// Always create the framework dir for android
|
||||
if self.bundle == BundleFormat::Android {
|
||||
_ = std::fs::create_dir_all(&framework_dir);
|
||||
}
|
||||
|
||||
// On android, the c++_shared flag means we need to copy the libc++_shared.so precompiled
|
||||
// library to the jniLibs folder
|
||||
if self.bundle == BundleFormat::Android && arg.contains("-lc++_shared") {
|
||||
@@ -3352,13 +3523,9 @@ 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_renderers(
|
||||
pub(crate) fn renderer_enabled_by_dioxus_dependency(
|
||||
package: &krates::cm::Package,
|
||||
no_default_features: bool,
|
||||
) -> Vec<(Renderer, String)> {
|
||||
) -> Option<(Renderer, String)> {
|
||||
let mut renderers = vec![];
|
||||
|
||||
// Attempt to discover the platform directly from the dioxus dependency
|
||||
@@ -3367,13 +3534,40 @@ impl BuildRequest {
|
||||
// dioxus = { features = ["web"] }
|
||||
//
|
||||
if let Some(dxs) = package.dependencies.iter().find(|dep| dep.name == "dioxus") {
|
||||
for f in dxs.features.iter() {
|
||||
if let Some(renderer) = Renderer::autodetect_from_cargo_feature(f) {
|
||||
renderers.push((renderer, f.clone()));
|
||||
for feature in dxs.features.iter() {
|
||||
if let Some(renderer) = Renderer::autodetect_from_cargo_feature(feature) {
|
||||
renderers.push((renderer, format!("dioxus/{}", feature)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if renderers.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(renderers[0].clone())
|
||||
}
|
||||
|
||||
pub(crate) fn features_that_enable_renderers(
|
||||
package: &krates::cm::Package,
|
||||
) -> Vec<(Renderer, String)> {
|
||||
package
|
||||
.features
|
||||
.keys()
|
||||
.filter_map(|key| {
|
||||
Renderer::autodetect_from_cargo_feature(key).map(|v| (v, key.to_string()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Return the platforms that are enabled for the package only from the default features
|
||||
///
|
||||
/// Ideally only one platform is enabled but we need to be able to
|
||||
pub(crate) fn enabled_cargo_toml_default_features_renderers(
|
||||
package: &krates::cm::Package,
|
||||
) -> Vec<(Renderer, String)> {
|
||||
let mut renderers = vec![];
|
||||
|
||||
// Start searching through the default features
|
||||
//
|
||||
// [features]
|
||||
@@ -3384,10 +3578,6 @@ impl BuildRequest {
|
||||
// [features]
|
||||
// default = ["web"]
|
||||
// web = ["dioxus/web"]
|
||||
if no_default_features {
|
||||
return renderers;
|
||||
}
|
||||
|
||||
let Some(default) = package.features.get("default") else {
|
||||
return renderers;
|
||||
};
|
||||
@@ -4396,7 +4586,7 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{
|
||||
///
|
||||
/// This might stop working if/when cargo stabilizes contents-based fingerprinting.
|
||||
fn bust_fingerprint(&self, ctx: &BuildContext) -> Result<()> {
|
||||
if matches!(ctx.mode, BuildMode::Fat | BuildMode::Base { run: true }) {
|
||||
if matches!(ctx.mode, BuildMode::Fat) {
|
||||
// `dx` compiles everything with `--target` which ends up with a structure like:
|
||||
// target/<triple>/<profile>/.fingerprint/<package_name>-<hash>
|
||||
//
|
||||
|
||||
@@ -3,8 +3,7 @@ use anyhow::Context;
|
||||
use itertools::Itertools;
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use target_lexicon::{
|
||||
Aarch64Architecture, Architecture, ArmArchitecture, Environment, OperatingSystem, Triple,
|
||||
X86_32Architecture,
|
||||
Aarch64Architecture, Architecture, ArmArchitecture, Triple, X86_32Architecture,
|
||||
};
|
||||
use tokio::process::Command;
|
||||
|
||||
@@ -236,9 +235,6 @@ impl AndroidTools {
|
||||
// - We try to match the architecture unless otherwise specified. This is because
|
||||
// emulators that match the host arch are usually faster.
|
||||
let mut triple = "aarch64-linux-android".parse::<Triple>().unwrap();
|
||||
triple.operating_system = OperatingSystem::Linux;
|
||||
triple.environment = Environment::Android;
|
||||
triple.architecture = target_lexicon::HOST.architecture;
|
||||
|
||||
// TODO: Wire this up with --device flag. (add `-s serial`` flag before `shell` arg)
|
||||
let output = Command::new(&self.adb)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use dioxus_dx_wire_format::StructuredBuildArtifacts;
|
||||
|
||||
use crate::{
|
||||
cli::*, Anonymized, AppBuilder, BuildArtifacts, BuildMode, BuildRequest, TargetArgs, Workspace,
|
||||
cli::*, Anonymized, AppBuilder, BuildArtifacts, BuildMode, BuildRequest, BundleFormat,
|
||||
TargetArgs, Workspace,
|
||||
};
|
||||
|
||||
/// Build the Rust Dioxus app and all of its assets.
|
||||
@@ -116,14 +117,22 @@ impl CommandWithPlatformOverrides<BuildArgs> {
|
||||
Some(server_args) => {
|
||||
// Make sure we set the client target here so @server knows to place its output into the @client target directory.
|
||||
server_args.build_arguments.client_target = Some(client.main_target.clone());
|
||||
|
||||
// We don't override anything except the bundle format since @server usually implies a server output
|
||||
server_args.build_arguments.bundle = server_args
|
||||
.build_arguments
|
||||
.bundle
|
||||
.or(Some(BundleFormat::Server));
|
||||
|
||||
server = Some(
|
||||
BuildRequest::new(&server_args.build_arguments, workspace.clone()).await?,
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let mut args = self.shared.build_arguments.clone();
|
||||
args.platform = Some(crate::Platform::Server);
|
||||
args.renderer.renderer = Some(crate::Renderer::Server);
|
||||
args.platform = crate::Platform::Server;
|
||||
args.renderer = Some(crate::Renderer::Server);
|
||||
args.bundle = Some(crate::BundleFormat::Server);
|
||||
args.target = Some(target_lexicon::Triple::host());
|
||||
server = Some(BuildRequest::new(&args, workspace.clone()).await?);
|
||||
}
|
||||
|
||||
@@ -190,8 +190,8 @@ impl LinkAction {
|
||||
target_lexicon::BinaryFormat::Macho => object::BinaryFormat::MachO,
|
||||
target_lexicon::BinaryFormat::Wasm => object::BinaryFormat::Wasm,
|
||||
target_lexicon::BinaryFormat::Xcoff => object::BinaryFormat::Xcoff,
|
||||
target_lexicon::BinaryFormat::Unknown => todo!(),
|
||||
_ => todo!("Binary format not supported"),
|
||||
target_lexicon::BinaryFormat::Unknown => unimplemented!(),
|
||||
_ => unimplemented!("Binary format not supported"),
|
||||
};
|
||||
|
||||
let arch = match triple.architecture {
|
||||
@@ -202,13 +202,13 @@ impl LinkAction {
|
||||
target_lexicon::Architecture::Aarch64(_) => object::Architecture::Aarch64,
|
||||
target_lexicon::Architecture::LoongArch64 => object::Architecture::LoongArch64,
|
||||
target_lexicon::Architecture::Unknown => object::Architecture::Unknown,
|
||||
_ => todo!("Architecture not supported"),
|
||||
_ => unimplemented!("Architecture not supported"),
|
||||
};
|
||||
|
||||
let endian = match triple.endianness() {
|
||||
Ok(target_lexicon::Endianness::Little) => object::Endianness::Little,
|
||||
Ok(target_lexicon::Endianness::Big) => object::Endianness::Big,
|
||||
Err(_) => todo!("Endianness not supported"),
|
||||
Err(_) => unimplemented!("Endianness not supported"),
|
||||
};
|
||||
|
||||
let bytes = object::write::Object::new(format, arch, endian)
|
||||
|
||||
@@ -102,7 +102,9 @@ impl RunArgs {
|
||||
path,
|
||||
} => tracing::info!(
|
||||
"[{bundle_format}] Copying asset {} ({current}/{total})",
|
||||
path.display(),
|
||||
path.file_name()
|
||||
.map(|f| f.to_string_lossy())
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
BuildStage::Bundling => {
|
||||
tracing::info!("[{bundle_format}] Bundling app")
|
||||
|
||||
@@ -61,7 +61,7 @@ pub(crate) struct ServeArgs {
|
||||
pub(crate) hot_patch: bool,
|
||||
|
||||
/// Watch the filesystem for changes and trigger a rebuild [default: true]
|
||||
#[clap(long, default_missing_value = "true")]
|
||||
#[clap(long, default_missing_value = "true", num_args=0..=1)]
|
||||
pub(crate) watch: Option<bool>,
|
||||
|
||||
/// Exit the CLI after running into an error. This is mainly used to test hot patching internally
|
||||
@@ -92,10 +92,6 @@ impl ServeArgs {
|
||||
///
|
||||
/// We also set up proper panic handling since the TUI has a tendency to corrupt the terminal.
|
||||
pub(crate) async fn serve(self, tracer: &TraceController) -> Result<StructuredOutput> {
|
||||
if std::env::var("RUST_BACKTRACE").is_err() {
|
||||
std::env::set_var("RUST_BACKTRACE", "1");
|
||||
}
|
||||
|
||||
// Redirect all logging the cli logger - if there's any pending after a panic, we flush it
|
||||
let is_interactive_tty = self.is_interactive_tty();
|
||||
if is_interactive_tty {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::cli::*;
|
||||
use crate::BundleFormat;
|
||||
use crate::Platform;
|
||||
use crate::RendererArg;
|
||||
use crate::TargetAlias;
|
||||
use crate::{cli::*, Renderer};
|
||||
// use crate::RendererArg;
|
||||
// use crate::PlatformAlias;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
const HELP_HEADING: &str = "Target Options";
|
||||
@@ -10,18 +10,6 @@ const HELP_HEADING: &str = "Target Options";
|
||||
/// A single target to build for
|
||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||
pub(crate) struct TargetArgs {
|
||||
/// The target alias to use for this build. Supports wasm, macos, windows, linux, ios, android, and host [default: "host"]
|
||||
#[clap(flatten)]
|
||||
pub(crate) target_alias: TargetAlias,
|
||||
|
||||
/// Build renderer: supports web, webview, native, server, and liveview
|
||||
#[clap(flatten)]
|
||||
pub(crate) renderer: RendererArg,
|
||||
|
||||
/// The bundle format to target for the build: supports web, macos, windows, linux, ios, android, and server
|
||||
#[clap(long, value_enum, help_heading = HELP_HEADING)]
|
||||
pub(crate) bundle: Option<BundleFormat>,
|
||||
|
||||
/// Build platform: supports Web, MacOS, Windows, Linux, iOS, Android, and Server
|
||||
///
|
||||
/// The platform implies a combination of the target alias, renderer, and bundle format flags.
|
||||
@@ -29,8 +17,16 @@ pub(crate) struct TargetArgs {
|
||||
/// 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(flatten)]
|
||||
pub(crate) platform: Platform,
|
||||
|
||||
/// Which renderer to use? By default, this is usually inferred from the platform.
|
||||
#[clap(long, value_enum, help_heading = HELP_HEADING)]
|
||||
pub(crate) platform: Option<Platform>,
|
||||
pub(crate) renderer: Option<Renderer>,
|
||||
|
||||
/// The bundle format to target for the build: supports web, macos, windows, linux, ios, android, and server
|
||||
#[clap(long, value_enum, help_heading = HELP_HEADING)]
|
||||
pub(crate) bundle: Option<BundleFormat>,
|
||||
|
||||
/// Build in release mode [default: false]
|
||||
#[clap(long, short, help_heading = HELP_HEADING)]
|
||||
@@ -112,7 +108,7 @@ pub(crate) struct TargetArgs {
|
||||
/// simulator. If the device name is passed, we will upload to that device instead.
|
||||
///
|
||||
/// This performs a search among devices, and fuzzy matches might be found.
|
||||
#[arg(long, default_missing_value=None, num_args=0..=1, help_heading = HELP_HEADING)]
|
||||
#[arg(long, default_missing_value=Some("".into()), num_args=0..=1)]
|
||||
pub(crate) device: Option<String>,
|
||||
|
||||
/// The base path the build will fetch assets relative to. This will override the
|
||||
@@ -153,12 +149,15 @@ pub(crate) struct TargetArgs {
|
||||
/// when merging `@client and @server` targets together.
|
||||
#[clap(long, help_heading = HELP_HEADING)]
|
||||
pub(crate) client_target: Option<String>,
|
||||
|
||||
/// Automatically pass `--features=js_cfg` when building for wasm targets. This is enabled by default.
|
||||
#[clap(long, default_value_t = true, help_heading = HELP_HEADING, num_args = 0..=1)]
|
||||
pub(crate) wasm_js_cfg: bool,
|
||||
}
|
||||
|
||||
impl Anonymized for TargetArgs {
|
||||
fn anonymized(&self) -> Value {
|
||||
json! {{
|
||||
"target_alias": self.target_alias,
|
||||
"renderer": self.renderer,
|
||||
"bundle": self.bundle,
|
||||
"platform": self.platform,
|
||||
|
||||
@@ -18,6 +18,7 @@ mod rustcwrapper;
|
||||
mod serve;
|
||||
mod settings;
|
||||
mod tailwind;
|
||||
mod test_harnesses;
|
||||
mod wasm_bindgen;
|
||||
mod wasm_opt;
|
||||
mod workspace;
|
||||
|
||||
+119
-334
@@ -1,82 +1,106 @@
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::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;
|
||||
use target_lexicon::{Environment, OperatingSystem, Triple};
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub(crate) enum TargetAlias {
|
||||
/// Targeting the WASM architecture
|
||||
Wasm,
|
||||
pub(crate) enum Platform {
|
||||
/// Alias for `--target wasm32-unknown-unknown --renderer websys --bundle-format web`
|
||||
#[serde(rename = "web")]
|
||||
Web,
|
||||
|
||||
/// Targeting macos desktop
|
||||
/// Alias for `--target <host> --renderer webview --bundle-format macos`
|
||||
#[serde(rename = "macos")]
|
||||
MacOS,
|
||||
|
||||
/// Targeting windows desktop
|
||||
/// Alias for `--target <host> --renderer webview --bundle-format windows`
|
||||
#[serde(rename = "windows")]
|
||||
Windows,
|
||||
|
||||
/// Targeting linux desktop
|
||||
/// Alias for `--target <host> --renderer webview --bundle-format linux`
|
||||
#[serde(rename = "linux")]
|
||||
Linux,
|
||||
|
||||
/// Targeting the ios platform
|
||||
///
|
||||
/// Can't work properly if you're not building from an Apple device.
|
||||
/// Alias for `--target <aarch64-apple-ios/sim> --renderer webview --bundle-format ios`
|
||||
#[serde(rename = "ios")]
|
||||
Ios,
|
||||
|
||||
/// Targeting the android platform
|
||||
/// Alias for `--target <device-triple> --renderer webview --bundle-format android`
|
||||
#[serde(rename = "android")]
|
||||
Android,
|
||||
|
||||
/// Targeting the current target
|
||||
Host,
|
||||
/// Alias for `--target <host> --renderer ssr --bundle-format server`
|
||||
#[serde(rename = "server")]
|
||||
Server,
|
||||
|
||||
/// An unknown target platform
|
||||
/// Alias for `--target <host> --renderer liveview --bundle-format server`
|
||||
#[serde(rename = "liveview")]
|
||||
Liveview,
|
||||
|
||||
/// No platform was specified, so the CLI is free to choose the best one.
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Args for TargetAlias {
|
||||
impl Args for Platform {
|
||||
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
|
||||
Self::augment_args(cmd)
|
||||
}
|
||||
|
||||
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))
|
||||
const HELP_HEADING: &str = "Platform";
|
||||
cmd.arg(arg!(--web "Target a web app").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--desktop "Target a desktop app").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--macos "Target a macos desktop app").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--windows "Target a windows desktop app").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--linux "Target a linux desktop app").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--ios "Target an ios app").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--android "Target an android app").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--server "Target a server build").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--liveview "Target a liveview build").help_heading(HELP_HEADING))
|
||||
.group(
|
||||
clap::ArgGroup::new("target_alias")
|
||||
.args([
|
||||
"wasm", "macos", "windows", "linux", "ios", "android", "host", "host",
|
||||
"web", "desktop", "macos", "windows", "linux", "ios", "android", "server",
|
||||
"liveview",
|
||||
])
|
||||
.multiple(false)
|
||||
.required(false),
|
||||
)
|
||||
}
|
||||
|
||||
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
|
||||
Self::augment_args(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArgMatches for TargetAlias {
|
||||
impl FromArgMatches for Platform {
|
||||
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
|
||||
if let Some(platform) = matches.get_one::<clap::Id>("target_alias") {
|
||||
match platform.as_str() {
|
||||
"wasm" => Ok(Self::Wasm),
|
||||
"web" => Ok(Self::Web),
|
||||
"desktop" => {
|
||||
if cfg!(target_os = "macos") {
|
||||
Ok(Self::MacOS)
|
||||
} else if cfg!(target_os = "windows") {
|
||||
Ok(Self::Windows)
|
||||
} else if cfg!(unix) {
|
||||
Ok(Self::Linux)
|
||||
} else {
|
||||
Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
"Desktop alias is not supported on this platform",
|
||||
))
|
||||
}
|
||||
}
|
||||
"macos" => Ok(Self::MacOS),
|
||||
"windows" => Ok(Self::Windows),
|
||||
"linux" => Ok(Self::Linux),
|
||||
"ios" => Ok(Self::Ios),
|
||||
"android" => Ok(Self::Android),
|
||||
"host" => Ok(Self::Host),
|
||||
"liveview" => Ok(Self::Liveview),
|
||||
"server" => Ok(Self::Server),
|
||||
_ => Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Unknown target alias: {platform}"),
|
||||
@@ -86,134 +110,26 @@ impl FromArgMatches for TargetAlias {
|
||||
Ok(Self::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), clap::Error> {
|
||||
*self = Self::from_arg_matches(matches)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TargetAlias {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::MacOS);
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Windows);
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Linux);
|
||||
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = None;
|
||||
|
||||
pub(crate) async fn into_target(self, device: bool, workspace: &Workspace) -> Result<Triple> {
|
||||
match self {
|
||||
// Generally just use the host's triple for native executables unless specified otherwise
|
||||
Self::MacOS | Self::Windows | Self::Linux | Self::Host | Self::Unknown => {
|
||||
Ok(Triple::host())
|
||||
}
|
||||
|
||||
// We currently assume unknown-unknown for web, but we might want to eventually
|
||||
// support emscripten
|
||||
Self::Wasm => Ok("wasm32-unknown-unknown".parse()?),
|
||||
|
||||
// For iOS we should prefer the actual architecture for the simulator, but in lieu of actually
|
||||
// figuring that out, we'll assume aarch64 on m-series and x86_64 otherwise
|
||||
Self::Ios => {
|
||||
// use the host's architecture and sim if --device is passed
|
||||
use target_lexicon::{Architecture, HOST};
|
||||
let triple_str = match HOST.architecture {
|
||||
Architecture::Aarch64(_) if device => "aarch64-apple-ios",
|
||||
Architecture::Aarch64(_) => "aarch64-apple-ios-sim",
|
||||
_ if device => "x86_64-apple-ios",
|
||||
_ => "x86_64-apple-ios",
|
||||
};
|
||||
Ok(triple_str.parse()?)
|
||||
}
|
||||
|
||||
// Same idea with android but we figure out the connected device using adb
|
||||
Self::Android => Ok(workspace
|
||||
.android_tools()?
|
||||
.autodetect_android_device_triple()
|
||||
.await),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn or(self, other: Self) -> Self {
|
||||
if self == Self::Unknown {
|
||||
other
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug, Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Debug,
|
||||
clap::ValueEnum,
|
||||
)]
|
||||
pub(crate) struct RendererArg {
|
||||
pub(crate) renderer: Option<Renderer>,
|
||||
}
|
||||
|
||||
impl Args for RendererArg {
|
||||
fn augment_args(cmd: clap::Command) -> clap::Command {
|
||||
const HELP_HEADING: &str = "Renderer";
|
||||
cmd.arg(arg!(--web "Enable the dioxus web renderer").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--webview "Enable the dioxus webview renderer").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--native "Enable the dioxus native renderer").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--server "Enable the dioxus server renderer").help_heading(HELP_HEADING))
|
||||
.arg(arg!(--liveview "Enable the dioxus liveview renderer").help_heading(HELP_HEADING))
|
||||
.group(
|
||||
clap::ArgGroup::new("renderer")
|
||||
.args(["web", "webview", "native", "server", "liveview"])
|
||||
.multiple(false)
|
||||
.required(false),
|
||||
)
|
||||
}
|
||||
|
||||
fn augment_args_for_update(cmd: clap::Command) -> clap::Command {
|
||||
Self::augment_args(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromArgMatches for RendererArg {
|
||||
fn from_arg_matches(matches: &ArgMatches) -> Result<Self, clap::Error> {
|
||||
if let Some(renderer) = matches.get_one::<clap::Id>("renderer") {
|
||||
match renderer.as_str() {
|
||||
"web" => Ok(Self {
|
||||
renderer: Some(Renderer::Web),
|
||||
}),
|
||||
"webview" => Ok(Self {
|
||||
renderer: Some(Renderer::Webview),
|
||||
}),
|
||||
"native" => Ok(Self {
|
||||
renderer: Some(Renderer::Native),
|
||||
}),
|
||||
"server" => Ok(Self {
|
||||
renderer: Some(Renderer::Server),
|
||||
}),
|
||||
"liveview" => Ok(Self {
|
||||
renderer: Some(Renderer::Liveview),
|
||||
}),
|
||||
_ => Err(clap::Error::raw(
|
||||
clap::error::ErrorKind::InvalidValue,
|
||||
format!("Unknown platform: {renderer}"),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Ok(Self { renderer: None })
|
||||
}
|
||||
}
|
||||
|
||||
fn update_from_arg_matches(&mut self, matches: &ArgMatches) -> Result<(), clap::Error> {
|
||||
*self = Self::from_arg_matches(matches)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RendererArg> for Option<Renderer> {
|
||||
fn from(val: RendererArg) -> Self {
|
||||
val.renderer
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub(crate) enum Renderer {
|
||||
/// Targeting webview renderer
|
||||
@@ -231,7 +147,7 @@ pub(crate) enum Renderer {
|
||||
/// Targeting the static generation platform using SSR and Dioxus-Fullstack
|
||||
Liveview,
|
||||
|
||||
/// Targeting the web renderer
|
||||
/// Targeting the web-sys renderer
|
||||
Web,
|
||||
}
|
||||
|
||||
@@ -260,24 +176,6 @@ impl Renderer {
|
||||
_ => 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)]
|
||||
@@ -357,57 +255,19 @@ pub(crate) enum BundleFormat {
|
||||
Android,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnknownBundleFormatError;
|
||||
|
||||
impl std::error::Error for UnknownBundleFormatError {}
|
||||
|
||||
impl std::fmt::Display for UnknownBundleFormatError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Unknown bundle format")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BundleFormat {
|
||||
type Err = UnknownBundleFormatError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"web" => Ok(Self::Web),
|
||||
"macos" => Ok(Self::MacOS),
|
||||
"windows" => Ok(Self::Windows),
|
||||
"linux" => Ok(Self::Linux),
|
||||
"server" => Ok(Self::Server),
|
||||
"ios" => Ok(Self::Ios),
|
||||
"android" => Ok(Self::Android),
|
||||
_ => Err(UnknownBundleFormatError),
|
||||
impl BundleFormat {
|
||||
/// The native "desktop" host app format.
|
||||
pub(crate) fn host() -> Self {
|
||||
if cfg!(target_os = "macos") {
|
||||
Self::MacOS
|
||||
} else if cfg!(target_os = "windows") {
|
||||
Self::Windows
|
||||
} else if cfg!(target_os = "linux") {
|
||||
Self::Linux
|
||||
} else {
|
||||
Self::Web
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BundleFormat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
BundleFormat::Web => "web",
|
||||
BundleFormat::MacOS => "macos",
|
||||
BundleFormat::Windows => "windows",
|
||||
BundleFormat::Linux => "linux",
|
||||
BundleFormat::Server => "server",
|
||||
BundleFormat::Ios => "ios",
|
||||
BundleFormat::Android => "android",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BundleFormat {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::MacOS);
|
||||
#[cfg(target_os = "windows")]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Windows);
|
||||
#[cfg(target_os = "linux")]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = Some(Self::Linux);
|
||||
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
|
||||
pub(crate) const TARGET_PLATFORM: Option<Self> = None;
|
||||
|
||||
/// Get the name of the folder we need to generate for this platform
|
||||
///
|
||||
@@ -449,121 +309,46 @@ impl BundleFormat {
|
||||
Self::Server => "Server",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_target(target: &Triple, renderer: Option<Renderer>) -> Result<BundleFormat> {
|
||||
match (
|
||||
renderer,
|
||||
target.architecture,
|
||||
target.environment,
|
||||
target.operating_system,
|
||||
) {
|
||||
// The server always uses the server bundle format
|
||||
(Some(Renderer::Server), _, _, _) => Ok(BundleFormat::Server),
|
||||
// The web renderer always uses the web bundle format
|
||||
(Some(Renderer::Web), _, _, _) => Ok(BundleFormat::Web),
|
||||
// Otherwise, guess it based on the target
|
||||
// Assume any wasm32 or wasm64 target is a web target
|
||||
(_, Architecture::Wasm32 | Architecture::Wasm64, _, _) => Ok(BundleFormat::Web),
|
||||
// For native targets, we need to determine the bundle format based on the OS
|
||||
(_, _, Environment::Android, _) => Ok(BundleFormat::Android),
|
||||
(_, _, _, OperatingSystem::IOS(_)) => Ok(BundleFormat::Ios),
|
||||
(_, _, _, OperatingSystem::MacOSX(_) | OperatingSystem::Darwin(_)) => {
|
||||
Ok(BundleFormat::MacOS)
|
||||
}
|
||||
(_, _, _, OperatingSystem::Linux) => Ok(BundleFormat::Linux),
|
||||
(_, _, _, OperatingSystem::Windows) => Ok(BundleFormat::Windows),
|
||||
// If we don't recognize the target, default to desktop
|
||||
_ => BundleFormat::TARGET_PLATFORM.context(
|
||||
"failed to determine bundle format. Try setting the `--bundle` flag manually",
|
||||
),
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct UnknownBundleFormatError;
|
||||
|
||||
impl std::error::Error for UnknownBundleFormatError {}
|
||||
|
||||
impl std::fmt::Display for UnknownBundleFormatError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Unknown bundle format")
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for BundleFormat {
|
||||
type Err = UnknownBundleFormatError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"web" => Ok(Self::Web),
|
||||
"macos" => Ok(Self::MacOS),
|
||||
"windows" => Ok(Self::Windows),
|
||||
"linux" => Ok(Self::Linux),
|
||||
"server" => Ok(Self::Server),
|
||||
"ios" => Ok(Self::Ios),
|
||||
"android" => Ok(Self::Android),
|
||||
_ => Err(UnknownBundleFormatError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Debug,
|
||||
Default,
|
||||
clap::ValueEnum,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub(crate) enum Platform {
|
||||
/// Alias for `--wasm --web --bundle-format web`
|
||||
#[clap(name = "web")]
|
||||
#[serde(rename = "web")]
|
||||
#[default]
|
||||
Web,
|
||||
|
||||
/// Alias for `--macos --webview --bundle-format macos`
|
||||
#[cfg_attr(target_os = "macos", clap(alias = "desktop"))]
|
||||
#[clap(name = "macos")]
|
||||
#[serde(rename = "macos")]
|
||||
MacOS,
|
||||
|
||||
/// Alias for `--windows --webview --bundle-format windows`
|
||||
#[cfg_attr(target_os = "windows", clap(alias = "desktop"))]
|
||||
#[clap(name = "windows")]
|
||||
#[serde(rename = "windows")]
|
||||
Windows,
|
||||
|
||||
/// Alias for `--linux --webview --bundle-format linux`
|
||||
#[cfg_attr(target_os = "linux", clap(alias = "desktop"))]
|
||||
#[clap(name = "linux")]
|
||||
#[serde(rename = "linux")]
|
||||
Linux,
|
||||
|
||||
/// Alias for `--ios --webview --bundle-format ios`
|
||||
#[clap(name = "ios")]
|
||||
#[serde(rename = "ios")]
|
||||
Ios,
|
||||
|
||||
/// Alias for `--android --webview --bundle-format android`
|
||||
#[clap(name = "android")]
|
||||
#[serde(rename = "android")]
|
||||
Android,
|
||||
|
||||
/// Alias for `--host --server --bundle-format server`
|
||||
#[clap(name = "server")]
|
||||
#[serde(rename = "server")]
|
||||
Server,
|
||||
|
||||
/// Alias for `--host --liveview --bundle-format host`
|
||||
#[clap(name = "liveview")]
|
||||
#[serde(rename = "liveview")]
|
||||
Liveview,
|
||||
}
|
||||
|
||||
impl Platform {
|
||||
pub(crate) fn into_triple(self) -> (TargetAlias, Renderer, BundleFormat) {
|
||||
match self {
|
||||
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(),
|
||||
),
|
||||
}
|
||||
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",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,13 @@ pub(crate) async fn proxy_websocket(
|
||||
.await
|
||||
.map_err(IntoResponse::into_response)?;
|
||||
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Proxying websocket connection {req:?}");
|
||||
tracing::trace!(dx_src = ?TraceSrc::Dev, "Proxying websocket connection {req:?}");
|
||||
let proxied_request = into_proxied_request(req, backend_url).map_err(handle_proxy_error)?;
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Connection proxied to {proxied_uri}", proxied_uri = proxied_request.uri());
|
||||
tracing::trace!(dx_src = ?TraceSrc::Dev, "Connection proxied to {proxied_uri}", proxied_uri = proxied_request.uri());
|
||||
|
||||
Ok(ws.on_upgrade(move |client_ws| async move {
|
||||
match handle_ws_connection(client_ws, proxied_request).await {
|
||||
Ok(()) => tracing::info!(dx_src = ?TraceSrc::Dev, "Websocket connection closed"),
|
||||
Ok(()) => tracing::trace!(dx_src = ?TraceSrc::Dev, "Websocket connection closed"),
|
||||
Err(e) => {
|
||||
tracing::error!(dx_src = ?TraceSrc::Dev, "Error proxying websocket connection: {e}")
|
||||
}
|
||||
|
||||
@@ -337,10 +337,6 @@ impl WebServer {
|
||||
|
||||
/// Tells all clients to reload if possible for new changes.
|
||||
pub(crate) async fn send_reload_command(&mut self) {
|
||||
tracing::trace!("Sending reload to toast");
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
|
||||
|
||||
self.build_status.set(Status::Ready);
|
||||
self.send_build_status().await;
|
||||
self.send_devserver_message_to_all(DevserverMsg::FullReloadCommand)
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
use crate::{BuildTargets, BundleFormat, Cli, Commands, Workspace};
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use futures_util::{stream::FuturesUnordered, StreamExt};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fmt::Write,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
prelude::rust_2024::Future,
|
||||
};
|
||||
use target_lexicon::Triple;
|
||||
use tracing_subscriber::{prelude::*, util::SubscriberInitExt, EnvFilter, Layer};
|
||||
|
||||
#[tokio::test]
|
||||
async fn run_harness() {
|
||||
test_harnesses().await;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
async fn test_harnesses() {
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer().with_filter(EnvFilter::new("error,dx=debug,dioxus_cli=debug,manganis_cli_support=debug,wasm_split_cli=debug,subsecond_cli_support=debug",)))
|
||||
.init();
|
||||
|
||||
TestHarnessBuilder::run(vec![
|
||||
TestHarnessBuilder::new("harness-simple-web")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["web"] }"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
assert_eq!(t.client.triple, "wasm32-unknown-unknown".parse().unwrap());
|
||||
assert!(t.server.is_none());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-simple-desktop")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["desktop"] }"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
assert_eq!(t.client.triple, Triple::host());
|
||||
assert!(t.server.is_none());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-simple-mobile")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["mobile"] }"#)
|
||||
.asrt(
|
||||
"dx build",
|
||||
|targets| async move { assert!(targets.is_err()) },
|
||||
),
|
||||
TestHarnessBuilder::new("harness-simple-fullstack")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.fetr(r#"web=["dioxus/web"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-simple-fullstack-with-default")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.fetr(r#"default=["web", "server"]"#)
|
||||
.fetr(r#"web=["dioxus/web"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-simple-fullstack-native-with-default")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.fetr(r#"default=["native", "server"]"#)
|
||||
.fetr(r#"native=["dioxus/native"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
assert_eq!(t.client.features.len(), 1);
|
||||
assert_eq!(t.client.features[0], "native");
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-fullstack-multi-target")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.fetr(r#"default=["web", "desktop", "mobile", "server"]"#)
|
||||
.fetr(r#"web=["dioxus/web"]"#)
|
||||
.fetr(r#"desktop=["dioxus/desktop"]"#)
|
||||
.fetr(r#"mobile=["dioxus/mobile"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |t| async move { assert!(t.is_err()) })
|
||||
.asrt(r#"dx build --web"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
})
|
||||
.asrt(r#"dx build --desktop"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
})
|
||||
.asrt(r#"dx build --ios"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Ios);
|
||||
assert_eq!(t.client.triple, TestHarnessBuilder::host_ios_triple_sim());
|
||||
})
|
||||
.asrt(r#"dx build --ios --device"#, |targets| async move {
|
||||
let targets = targets.unwrap();
|
||||
assert_eq!(targets.client.bundle, BundleFormat::Ios);
|
||||
assert_eq!(targets.client.triple, "aarch64-apple-ios".parse().unwrap());
|
||||
})
|
||||
.asrt(r#"dx build --android --device"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Android);
|
||||
assert_eq!(t.client.triple, "aarch64-linux-android".parse().unwrap());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-fullstack-multi-target-no-default")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.fetr(r#"web=["dioxus/web"]"#)
|
||||
.fetr(r#"desktop=["dioxus/desktop"]"#)
|
||||
.fetr(r#"mobile=["dioxus/mobile"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
assert!(targets.is_err())
|
||||
})
|
||||
.asrt(r#"dx build --desktop"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
})
|
||||
.asrt(r#"dx build --ios"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Ios);
|
||||
assert_eq!(t.client.triple, TestHarnessBuilder::host_ios_triple_sim());
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-fullstack-desktop")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.fetr(r#"desktop=["dioxus/desktop"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-fullstack-desktop-with-features")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.deps(r#"anyhow = { workspace = true, optional = true }"#)
|
||||
.fetr(r#"desktop=["dioxus/desktop", "has_anyhow"]"#)
|
||||
.fetr(r#"has_anyhow=["dep:anyhow"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-fullstack-desktop-with-default")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.deps(r#"anyhow = { workspace = true, optional = true }"#)
|
||||
.fetr(r#"default=["desktop"]"#)
|
||||
.fetr(r#"desktop=["dioxus/desktop", "has_anyhow"]"#)
|
||||
.fetr(r#"has_anyhow=["dep:anyhow"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-no-dioxus")
|
||||
.deps(r#"anyhow = { workspace = true, optional = true }"#)
|
||||
.fetr(r#"web=["dep:anyhow"]"#)
|
||||
.fetr(r#"server=[]"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
assert!(t.server.is_none());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-simple-dedicated-server"),
|
||||
TestHarnessBuilder::new("harness-simple-dedicated-client")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["web"] }"#)
|
||||
.asrt(r#"dx build"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
assert!(t.server.is_none());
|
||||
})
|
||||
.asrt(r#"dx build @client --package harness-simple-dedicated-client @server --package harness-simple-dedicated-server"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
let s = t.server.unwrap();
|
||||
assert_eq!(s.bundle, BundleFormat::Server);
|
||||
assert_eq!(s.triple, Triple::host());
|
||||
},
|
||||
)
|
||||
.asrt(r#"dx build @client --package harness-simple-dedicated-client @server --package harness-simple-dedicated-server --target wasm32-unknown-unknown"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
let s = t.server.unwrap();
|
||||
assert_eq!(s.bundle, BundleFormat::Server);
|
||||
assert_eq!(s.triple, "wasm32-unknown-unknown".parse().unwrap());
|
||||
}),
|
||||
TestHarnessBuilder::new("harness-renderer-swap")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.fetr(r#"default=["desktop", "server"]"#)
|
||||
.fetr(r#"desktop=["dioxus/desktop"]"#)
|
||||
.fetr(r#"native=["dioxus/native"]"#)
|
||||
.fetr(r#"server=["dioxus/server"]"#)
|
||||
.asrt(
|
||||
r#"dx build --desktop --renderer native"#,
|
||||
|targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::host());
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
},
|
||||
),
|
||||
TestHarnessBuilder::new("harness-default-to-non-default")
|
||||
.deps(r#"dioxus = { workspace = true, features = [] }"#)
|
||||
.fetr(r#"default=["web"]"#)
|
||||
.fetr(r#"web=["dioxus/web"]"#)
|
||||
.asrt(
|
||||
r#"dx build --ios"#,
|
||||
|targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert!(t.server.is_none());
|
||||
assert_eq!(t.client.bundle, BundleFormat::Ios);
|
||||
assert_eq!(t.client.triple, TestHarnessBuilder::host_ios_triple_sim());
|
||||
assert!(t.client.no_default_features);
|
||||
},
|
||||
),
|
||||
TestHarnessBuilder::new("harness-fullstack-with-optional-tokio")
|
||||
.deps(r#"dioxus = { workspace = true, features = ["fullstack"] }"#)
|
||||
.deps(r#"serde = "1.0.219""#)
|
||||
.deps(r#"tokio = { workspace = true, features = ["full"], optional = true }"#)
|
||||
.fetr(r#"default = []"#)
|
||||
.fetr(r#"server = ["dioxus/server", "dep:tokio"]"#)
|
||||
.fetr(r#"web = ["dioxus/web"]"#)
|
||||
// .asrt(r#"dx build"#, |targets| async move {
|
||||
// assert!(targets.is_err())
|
||||
// })
|
||||
.asrt(r#"dx build --web"#, |targets| async move {
|
||||
let t = targets.unwrap();
|
||||
assert_eq!(t.client.bundle, BundleFormat::Web);
|
||||
assert_eq!(t.client.triple, "wasm32-unknown-unknown".parse().unwrap());
|
||||
let server = t.server.unwrap();
|
||||
assert_eq!(server.bundle, BundleFormat::Server);
|
||||
assert_eq!(server.triple, Triple::host());
|
||||
})
|
||||
])
|
||||
.await;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct TestHarnessBuilder {
|
||||
name: String,
|
||||
dependencies: String,
|
||||
features: String,
|
||||
futures: Vec<TestHarnessTestCase>,
|
||||
}
|
||||
|
||||
struct TestHarnessTestCase {
|
||||
args: String,
|
||||
#[allow(clippy::type_complexity)]
|
||||
callback: Box<dyn FnOnce(Result<BuildTargets>) -> Pin<Box<dyn Future<Output = ()>>>>,
|
||||
}
|
||||
|
||||
impl TestHarnessBuilder {
|
||||
fn new(name: &str) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
dependencies: Default::default(),
|
||||
features: Default::default(),
|
||||
futures: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a dependency to the test harness.
|
||||
fn deps(mut self, dependencies: impl Into<String>) -> Self {
|
||||
writeln!(&mut self.dependencies, "{}", dependencies.into()).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a feature to the test harness.
|
||||
fn fetr(mut self, features: impl Into<String>) -> Self {
|
||||
writeln!(&mut self.features, "{}", features.into()).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Assert the expected behavior of the test harness.
|
||||
fn asrt<F>(
|
||||
mut self,
|
||||
args: impl Into<String>,
|
||||
future: impl FnOnce(Result<BuildTargets>) -> F + 'static,
|
||||
) -> Self
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.futures.push(TestHarnessTestCase {
|
||||
args: args.into(),
|
||||
callback: Box::new(move |args| Box::pin(future(args))),
|
||||
});
|
||||
self
|
||||
}
|
||||
|
||||
/// Write the test harness to the filesystem.
|
||||
fn build(&self, harness_dir: &Path) {
|
||||
let name = self.name.clone();
|
||||
let dependencies = self.dependencies.clone();
|
||||
let features = self.features.clone();
|
||||
|
||||
let test_dir = harness_dir.join(&name);
|
||||
|
||||
_ = std::fs::remove_dir_all(&test_dir);
|
||||
std::fs::create_dir_all(&test_dir).unwrap();
|
||||
std::fs::create_dir_all(test_dir.join("src")).unwrap();
|
||||
|
||||
let cargo_toml = format!(
|
||||
r#"[package]
|
||||
name = "{name}"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
{dependencies}
|
||||
|
||||
[features]
|
||||
{features}
|
||||
"#,
|
||||
name = name,
|
||||
dependencies = dependencies,
|
||||
features = features
|
||||
);
|
||||
|
||||
std::fs::write(test_dir.join("Cargo.toml"), cargo_toml).unwrap();
|
||||
|
||||
let contents = if features.contains("dioxus") {
|
||||
r#"use dioxus::prelude::*;
|
||||
fn main() {
|
||||
dioxus::launch(|| rsx! { "hello world!" })
|
||||
}
|
||||
"#
|
||||
} else {
|
||||
r#"fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
"#
|
||||
};
|
||||
|
||||
std::fs::write(test_dir.join("src/main.rs"), contents).unwrap();
|
||||
}
|
||||
|
||||
async fn run(harnesses: Vec<Self>) {
|
||||
_ = crate::VERBOSITY.set(crate::Verbosity {
|
||||
verbose: true,
|
||||
trace: true,
|
||||
json_output: false,
|
||||
log_to_file: None,
|
||||
locked: false,
|
||||
offline: false,
|
||||
frozen: false,
|
||||
});
|
||||
|
||||
let cargo_manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap();
|
||||
|
||||
let harness_dir = cargo_manifest_dir.parent().unwrap().join("cli-harnesses");
|
||||
|
||||
// make sure we don't start deleting random stuff.
|
||||
if !harness_dir.exists() {
|
||||
panic!(
|
||||
"cli-harnesses directory does not exist, aborting: {:?}",
|
||||
harness_dir
|
||||
);
|
||||
}
|
||||
|
||||
// Erase old entries in the harness directory, but keep files (ie README.md) around
|
||||
for entry in std::fs::read_dir(&harness_dir).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
if entry.file_type().unwrap().is_dir() {
|
||||
std::fs::remove_dir_all(entry.path()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Now that the harnesses are written to the filesystem, we can call cargo_metadata
|
||||
// It will be cached from here
|
||||
let mut futures = FuturesUnordered::new();
|
||||
let mut seen_names = HashSet::new();
|
||||
|
||||
for harness in harnesses {
|
||||
if !seen_names.insert(harness.name.clone()) {
|
||||
panic!("Duplicate test harness name found: {}", harness.name);
|
||||
}
|
||||
|
||||
harness.build(&harness_dir);
|
||||
|
||||
for case in harness.futures {
|
||||
let mut escaped = shell_words::split(&case.args).unwrap();
|
||||
if !(escaped.contains(&"--package".to_string())
|
||||
|| escaped.contains(&"@server".to_string())
|
||||
|| escaped.contains(&"@client".to_string()))
|
||||
{
|
||||
escaped.push("--package".to_string());
|
||||
escaped.push(harness.name.clone());
|
||||
}
|
||||
let args = Cli::try_parse_from(escaped).unwrap();
|
||||
let Commands::Build(build_args) = args.action else {
|
||||
panic!("Expected build command");
|
||||
};
|
||||
|
||||
futures.push(async move {
|
||||
let targets = build_args.into_targets().await;
|
||||
(case.callback)(targets).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Give a moment for fs to catch up
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
let _workspace = Workspace::current().await.unwrap();
|
||||
|
||||
while let Some(_res) = futures.next().await {}
|
||||
}
|
||||
|
||||
fn host_ios_triple_sim() -> Triple {
|
||||
if cfg!(target_arch = "aarch64") {
|
||||
"aarch64-apple-ios-sim".parse().unwrap()
|
||||
} else {
|
||||
"x86_64-apple-ios".parse().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user