CLI self-updater, warn on version mismatch, direct install.sh option, androidmanifest and info.plist customization, bump to 0.7-alpha.0 (#4095)
* wip.... bun installer * - warn on incompatible versions - clean up some logs - add coloring to log levels * add toasts to desktop/mobile * Auto open simulators * auto boot android * preserve url * pull out some of the warnings * add self-updater * wip: mcp * add install.sh and adjust binstall path * add some cuter styling to logs! * update "input.css" * update other input.css * style a bit more * change getting started * add windows installer * move dioxus-mobile into dioxus-desktop add sentry wip to cli * shorten timeout of toast * fix cli display * wire up customization of android manifest * udid is a real word * add 429 to lychee cache * accept all 200-204 and 429
This commit is contained in:
1 parent
53ef78823d
commit
4d3d55a7a3
56 files changed
+1560
-548
No files matched your search
@@ -136,6 +136,8 @@ backtrace = "0.3.74"
|
||||
ar = "0.9.0"
|
||||
wasm-bindgen-externref-xform = "0.2.100"
|
||||
pdb = "0.8.0"
|
||||
self_update = { version = "0.42.0", features = ["archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate"] }
|
||||
self-replace = "1.5.0"
|
||||
|
||||
[build-dependencies]
|
||||
built = { version = "0.7.5", features = ["git2"] }
|
||||
@@ -162,10 +164,7 @@ name = "dx"
|
||||
escargot = "0.5"
|
||||
|
||||
[package.metadata.binstall]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/dx-{ target }-v{ version }{ archive-suffix }"
|
||||
pkg-fmt = "tgz"
|
||||
|
||||
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||
pkg-url = "{ repo }/releases/download/v{ version }/dx-{ target }{ archive-suffix }"
|
||||
pkg-fmt = "zip"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application android:hasCode="true" android:supportsRtl="true" android:icon="@mipmap/ic_launcher"
|
||||
android:extractNativeLibs="true"
|
||||
@@ -16,5 +15,4 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use anyhow::Context;
|
||||
use dioxus_cli_opt::process_file_to;
|
||||
use futures_util::future::OptionFuture;
|
||||
use futures_util::{future::OptionFuture, pin_mut, FutureExt};
|
||||
use std::{
|
||||
env,
|
||||
time::{Duration, Instant, SystemTime},
|
||||
@@ -617,13 +617,15 @@ impl AppBuilder {
|
||||
_ => vec![],
|
||||
};
|
||||
|
||||
use crate::styles::{GLOW_STYLE, NOTE_STYLE};
|
||||
|
||||
let changed_file = changed_files.first().unwrap();
|
||||
tracing::info!(
|
||||
"Hot-patching: {} took {:?}ms",
|
||||
"Hot-patching: {NOTE_STYLE}{}{NOTE_STYLE:#} took {GLOW_STYLE}{:?}ms{GLOW_STYLE:#}",
|
||||
changed_file
|
||||
.strip_prefix(std::env::current_dir().unwrap())
|
||||
.unwrap_or(changed_file.as_path())
|
||||
.display(),
|
||||
.display()
|
||||
.to_string()
|
||||
.trim_start_matches(&self.build.crate_dir().display().to_string()),
|
||||
SystemTime::now()
|
||||
.duration_since(res.time_start)
|
||||
.unwrap()
|
||||
@@ -704,7 +706,7 @@ impl AppBuilder {
|
||||
let target = dioxus_cli_config::android_session_cache_dir().join(bundled_name);
|
||||
tracing::debug!("Pushing asset to device: {target:?}");
|
||||
|
||||
let res = tokio::process::Command::new(&self.build.workspace.android_tools()?.adb)
|
||||
let res = Command::new(&self.build.workspace.android_tools()?.adb)
|
||||
.arg("push")
|
||||
.arg(changed_file)
|
||||
.arg(&target)
|
||||
@@ -1140,7 +1142,7 @@ We checked the folder: {}
|
||||
let adb = self.build.workspace.android_tools()?.adb.clone();
|
||||
|
||||
// Start backgrounded since .open() is called while in the arm of the top-level match
|
||||
tokio::task::spawn(async move {
|
||||
let _handle: JoinHandle<Result<()>> = tokio::task::spawn(async move {
|
||||
// call `adb root` so we can push patches to the device
|
||||
if root {
|
||||
if let Err(e) = Command::new(&adb).arg("root").output().await {
|
||||
@@ -1159,54 +1161,79 @@ We checked the folder: {}
|
||||
tracing::error!("failed to forward port {port}: {e}");
|
||||
}
|
||||
|
||||
// Wait for device to be ready
|
||||
let cmd = Command::new(&adb)
|
||||
.arg("wait-for-device")
|
||||
.arg("shell")
|
||||
.arg(r#"while [[ -z $(getprop sys.boot_completed) ]]; do sleep 1; done;"#)
|
||||
.output();
|
||||
let cmd_future = cmd.fuse();
|
||||
pin_mut!(cmd_future);
|
||||
tokio::select! {
|
||||
_ = &mut cmd_future => {}
|
||||
_ = tokio::time::sleep(Duration::from_millis(50)) => {
|
||||
tracing::info!("Waiting for android emulator to be ready...");
|
||||
_ = cmd_future.await;
|
||||
}
|
||||
}
|
||||
|
||||
// Install
|
||||
// adb install -r app-debug.apk
|
||||
if let Err(e) = Command::new(&adb)
|
||||
let res = Command::new(&adb)
|
||||
.arg("install")
|
||||
.arg("-r")
|
||||
.arg(apk_path)
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to install apk with `adb`: {e}");
|
||||
};
|
||||
.await?;
|
||||
let std_err = String::from_utf8_lossy(&res.stderr);
|
||||
if !std_err.is_empty() {
|
||||
tracing::error!("Failed to install apk with `adb`: {std_err}");
|
||||
}
|
||||
|
||||
// Clear the session cache dir on the device
|
||||
Command::new(&adb)
|
||||
.arg("shell")
|
||||
.arg("rm")
|
||||
.arg("-rf")
|
||||
.arg(dioxus_cli_config::android_session_cache_dir())
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
// Write the env vars to a .env file in our session cache
|
||||
let env_file = session_cache.join(".env");
|
||||
let contents: String = envs
|
||||
.iter()
|
||||
.map(|(key, value)| format!("{key}={value}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
_ = std::fs::write(&env_file, contents);
|
||||
_ = std::fs::write(
|
||||
&env_file,
|
||||
envs.iter()
|
||||
.map(|(key, value)| format!("{key}={value}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n"),
|
||||
);
|
||||
|
||||
// Push the env file to the device
|
||||
if let Err(e) = tokio::process::Command::new(&adb)
|
||||
Command::new(&adb)
|
||||
.arg("push")
|
||||
.arg(env_file)
|
||||
.arg(dioxus_cli_config::android_session_cache_dir().join(".env"))
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to push asset to device")
|
||||
{
|
||||
tracing::error!("Failed to push .env file to device: {e}");
|
||||
}
|
||||
.await?;
|
||||
|
||||
// eventually, use the user's MainActivity, not our MainActivity
|
||||
// adb shell am start -n dev.dioxus.main/dev.dioxus.main.MainActivity
|
||||
let activity_name = format!("{}/dev.dioxus.main.MainActivity", full_mobile_app_name,);
|
||||
|
||||
if let Err(e) = Command::new(&adb)
|
||||
let res = Command::new(&adb)
|
||||
.arg("shell")
|
||||
.arg("am")
|
||||
.arg("start")
|
||||
.arg("-n")
|
||||
.arg(activity_name)
|
||||
.output()
|
||||
.await
|
||||
{
|
||||
tracing::error!("Failed to start app with `adb`: {e}");
|
||||
};
|
||||
.await?;
|
||||
let std_err = String::from_utf8_lossy(res.stderr.trim_ascii());
|
||||
if !std_err.is_empty() {
|
||||
tracing::error!("Failed to start app with `adb`: {std_err}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -329,7 +329,7 @@ use manganis::{AssetOptions, JsAssetOptions};
|
||||
use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
collections::{BTreeMap, HashSet},
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::Stdio,
|
||||
@@ -370,7 +370,7 @@ pub(crate) struct BuildRequest {
|
||||
pub(crate) platform: Platform,
|
||||
pub(crate) enabled_platforms: Vec<Platform>,
|
||||
pub(crate) triple: Triple,
|
||||
pub(crate) _device: bool,
|
||||
pub(crate) device: bool,
|
||||
pub(crate) package: String,
|
||||
pub(crate) features: Vec<String>,
|
||||
pub(crate) extra_cargo_args: Vec<String>,
|
||||
@@ -536,6 +536,10 @@ impl BuildRequest {
|
||||
// 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 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;
|
||||
@@ -552,6 +556,7 @@ impl BuildRequest {
|
||||
platform
|
||||
}
|
||||
},
|
||||
None if !using_dioxus_explicitly => Platform::autodetect_from_cargo_feature("desktop").unwrap(),
|
||||
None => match enabled_platforms.len() {
|
||||
0 => return Err(anyhow::anyhow!("No platform specified and no platform marked as default in Cargo.toml. Try specifying a platform with `--platform`").into()),
|
||||
1 => enabled_platforms[0],
|
||||
@@ -565,7 +570,9 @@ impl BuildRequest {
|
||||
};
|
||||
|
||||
// Add any features required to turn on the client
|
||||
features.push(Self::feature_for_platform(main_package, platform));
|
||||
if using_dioxus_explicitly {
|
||||
features.push(Self::feature_for_platform(main_package, platform));
|
||||
}
|
||||
|
||||
// Set the profile of the build if it's not already set
|
||||
// This is mostly used for isolation of builds (preventing thrashing) but also useful to have multiple performance profiles
|
||||
@@ -666,10 +673,10 @@ impl BuildRequest {
|
||||
|
||||
tracing::debug!(
|
||||
r#"Log Files:
|
||||
link_args_file: {},
|
||||
link_err_file: {},
|
||||
rustc_wrapper_args_file: {},
|
||||
session_cache_dir: {}"#,
|
||||
• link_args_file: {},
|
||||
• link_err_file: {},
|
||||
• rustc_wrapper_args_file: {},
|
||||
• session_cache_dir: {}"#,
|
||||
link_args_file.path().display(),
|
||||
link_err_file.path().display(),
|
||||
rustc_wrapper_args_file.path().display(),
|
||||
@@ -684,7 +691,7 @@ session_cache_dir: {}"#,
|
||||
crate_target,
|
||||
profile,
|
||||
triple,
|
||||
_device: device,
|
||||
device,
|
||||
workspace,
|
||||
config,
|
||||
enabled_platforms,
|
||||
@@ -886,7 +893,8 @@ session_cache_dir: {}"#,
|
||||
let time_end = SystemTime::now();
|
||||
let mode = ctx.mode.clone();
|
||||
let platform = self.platform;
|
||||
tracing::debug!("Build completed successfully - output location: {:?}", exe);
|
||||
|
||||
tracing::debug!("Build completed successfully: {:?}", exe);
|
||||
|
||||
Ok(BuildArtifacts {
|
||||
time_end,
|
||||
@@ -905,8 +913,6 @@ session_cache_dir: {}"#,
|
||||
/// This uses "known paths" that have stayed relatively stable during cargo's lifetime.
|
||||
/// One day this system might break and we might need to go back to using the linker approach.
|
||||
fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result<AssetManifest> {
|
||||
tracing::debug!("Collecting assets from exe at {} ...", exe.display());
|
||||
|
||||
// walk every file in the incremental cache dir, reading and inserting items into the manifest.
|
||||
let mut manifest = AssetManifest::default();
|
||||
|
||||
@@ -1151,7 +1157,7 @@ session_cache_dir: {}"#,
|
||||
//
|
||||
// Many args are passed twice, too, which can be confusing, but generally don't have any real
|
||||
// effect. Note that on macos/ios, there's a special macho header that needs to be set, otherwise
|
||||
// dyld will complain.a
|
||||
// dyld will complain.
|
||||
//
|
||||
// Also, some flags in darwin land might become deprecated, need to be super conservative:
|
||||
// - https://developer.apple.com/forums/thread/773907
|
||||
@@ -1223,7 +1229,6 @@ session_cache_dir: {}"#,
|
||||
|
||||
// And now we can run the linker with our new args
|
||||
let linker = self.select_linker()?;
|
||||
|
||||
let out_exe = self.patch_exe(artifacts.time_start);
|
||||
let out_arg = match self.triple.operating_system {
|
||||
OperatingSystem::Windows => vec![format!("/OUT:{}", out_exe.display())],
|
||||
@@ -1301,6 +1306,13 @@ session_cache_dir: {}"#,
|
||||
//
|
||||
// We don't use *any* of the original linker args since they do lots of custom exports
|
||||
// and other things that we don't need.
|
||||
//
|
||||
// The trickiest one here is -Crelocation-model=pic, which forces data symbols
|
||||
// into a GOT, making it possible to import them from the main module.
|
||||
//
|
||||
// I think we can make relocation-model=pic work for non-wasm platforms, enabling
|
||||
// fully relocatable modules with no host coordination in lieu of sending out
|
||||
// the aslr slide at runtime.
|
||||
OperatingSystem::Unknown if self.platform == Platform::Web => {
|
||||
out_args.extend([
|
||||
"--fatal-warnings".to_string(),
|
||||
@@ -1390,7 +1402,6 @@ session_cache_dir: {}"#,
|
||||
"/PDBALTPATH:%_PDB%".to_string(),
|
||||
"/EXPORT:main".to_string(),
|
||||
"/HIGHENTROPYVA:NO".to_string(),
|
||||
// "/SUBSYSTEM:WINDOWS".to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1764,7 +1775,6 @@ session_cache_dir: {}"#,
|
||||
cmd.current_dir(self.workspace_dir());
|
||||
cmd.env_clear();
|
||||
cmd.args(rustc_args.args[1..].iter());
|
||||
cmd.envs(rustc_args.envs.iter().cloned());
|
||||
cmd.env_remove("RUSTC_WORKSPACE_WRAPPER");
|
||||
cmd.env_remove("RUSTC_WRAPPER");
|
||||
cmd.env_remove(DX_RUSTC_WRAPPER_ENV_VAR);
|
||||
@@ -1775,7 +1785,11 @@ session_cache_dir: {}"#,
|
||||
cmd.arg("-Crelocation-model=pic");
|
||||
}
|
||||
|
||||
tracing::trace!("Direct rustc command: {:#?}", rustc_args.args);
|
||||
tracing::debug!("Direct rustc: {:#?}", cmd);
|
||||
|
||||
cmd.envs(rustc_args.envs.iter().cloned());
|
||||
|
||||
tracing::trace!("Setting env vars: {:#?}", rustc_args.envs);
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
@@ -1800,8 +1814,6 @@ session_cache_dir: {}"#,
|
||||
.args(self.cargo_build_arguments(ctx))
|
||||
.envs(self.cargo_build_env_vars(ctx)?);
|
||||
|
||||
tracing::trace!("Cargo command: {:#?}", cmd);
|
||||
|
||||
if ctx.mode == BuildMode::Fat {
|
||||
cmd.env(
|
||||
DX_RUSTC_WRAPPER_ENV_VAR,
|
||||
@@ -1816,6 +1828,8 @@ session_cache_dir: {}"#,
|
||||
);
|
||||
}
|
||||
|
||||
tracing::debug!("Cargo: {:#?}", cmd);
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
}
|
||||
@@ -1942,14 +1956,7 @@ session_cache_dir: {}"#,
|
||||
//
|
||||
// https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md
|
||||
//
|
||||
// The trickiest one here is -Crelocation-model=pic, which forces data symbols
|
||||
// into a GOT, making it possible to import them from the main module.
|
||||
//
|
||||
// I think we can make relocation-model=pic work for non-wasm platforms, enabling
|
||||
// fully relocatable modules with no host coordination in lieu of sending out
|
||||
// the aslr slide at runtime.
|
||||
//
|
||||
// The other tricky one is -Ctarget-cpu=mvp, which prevents rustc from generating externref
|
||||
// The tricky one is -Ctarget-cpu=mvp, which prevents rustc from generating externref
|
||||
// entries.
|
||||
//
|
||||
// https://blog.rust-lang.org/2024/09/24/webassembly-targets-change-in-default-target-features/#disabling-on-by-default-webassembly-proposals
|
||||
@@ -1958,7 +1965,6 @@ session_cache_dir: {}"#,
|
||||
if self.platform == Platform::Web
|
||||
|| self.triple.operating_system == OperatingSystem::Wasi
|
||||
{
|
||||
// cargo_args.push("-Crelocation-model=pic".into());
|
||||
cargo_args.push("-Ctarget-cpu=mvp".into());
|
||||
cargo_args.push("-Clink-arg=--no-gc-sections".into());
|
||||
cargo_args.push("-Clink-arg=--growable-table".into());
|
||||
@@ -2324,12 +2330,19 @@ session_cache_dir: {}"#,
|
||||
app.join("proguard-rules.pro"),
|
||||
include_bytes!("../../assets/android/gen/app/proguard-rules.pro"),
|
||||
)?;
|
||||
write(
|
||||
app.join("src").join("main").join("AndroidManifest.xml"),
|
||||
hbs.render_template(
|
||||
|
||||
let manifest_xml = match self.config.application.android_manifest.as_deref() {
|
||||
Some(manifest) => std::fs::read_to_string(self.package_manifest_dir().join(manifest))
|
||||
.context("Failed to locate custom AndroidManifest.xml")?,
|
||||
_ => hbs.render_template(
|
||||
include_str!("../../assets/android/gen/app/src/main/AndroidManifest.xml.hbs"),
|
||||
&hbs_data,
|
||||
)?,
|
||||
};
|
||||
|
||||
write(
|
||||
app.join("src").join("main").join("AndroidManifest.xml"),
|
||||
manifest_xml,
|
||||
)?;
|
||||
|
||||
// Write the main activity manually since tao dropped support for it
|
||||
@@ -2657,8 +2670,6 @@ session_cache_dir: {}"#,
|
||||
return platforms;
|
||||
};
|
||||
|
||||
tracing::debug!("Default features: {default:?}");
|
||||
|
||||
// we only trace features 1 level deep..
|
||||
for feature in default.iter() {
|
||||
// If the user directly specified a platform we can just use that.
|
||||
@@ -2688,8 +2699,6 @@ session_cache_dir: {}"#,
|
||||
platforms.sort();
|
||||
platforms.dedup();
|
||||
|
||||
tracing::debug!("Default platforms: {platforms:?}");
|
||||
|
||||
platforms
|
||||
}
|
||||
|
||||
@@ -2876,13 +2885,15 @@ session_cache_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);
|
||||
|
||||
ctx.status_compressing_assets();
|
||||
let asset_dir = self.asset_dir();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
crate::fastfs::pre_compress_folder(&asset_dir, pre_compress)
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
if pre_compress {
|
||||
ctx.status_compressing_assets();
|
||||
let asset_dir = self.asset_dir();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
crate::fastfs::pre_compress_folder(&asset_dir, pre_compress)
|
||||
})
|
||||
.await
|
||||
.unwrap()?;
|
||||
}
|
||||
}
|
||||
Platform::MacOS => {}
|
||||
Platform::Windows => {}
|
||||
@@ -3132,6 +3143,22 @@ session_cache_dir: {}"#,
|
||||
pub executable_name: String,
|
||||
}
|
||||
|
||||
// Attempt to use the user's manually specified
|
||||
let _app = &self.config.application;
|
||||
match platform {
|
||||
Platform::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 => {
|
||||
if let Some(macos_info_plist) = _app.ios_info_plist.as_deref() {
|
||||
return Ok(std::fs::read_to_string(macos_info_plist)?);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match platform {
|
||||
Platform::MacOS => handlebars::Handlebars::new()
|
||||
.render_template(
|
||||
@@ -3273,9 +3300,15 @@ session_cache_dir: {}"#,
|
||||
create_dir_all(self.exe_dir())?;
|
||||
create_dir_all(self.asset_dir())?;
|
||||
|
||||
tracing::debug!("Initialized Root dir: {:?}", self.root_dir());
|
||||
tracing::debug!("Initialized Exe dir: {:?}", self.exe_dir());
|
||||
tracing::debug!("Initialized Asset dir: {:?}", self.asset_dir());
|
||||
tracing::debug!(
|
||||
r#"Initialized build dirs:
|
||||
• root dir: {:?}
|
||||
• exe dir: {:?}
|
||||
• asset dir: {:?}"#,
|
||||
self.root_dir(),
|
||||
self.exe_dir(),
|
||||
self.asset_dir(),
|
||||
);
|
||||
|
||||
// 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
|
||||
@@ -3387,7 +3420,6 @@ session_cache_dir: {}"#,
|
||||
/// This should generally be only called on the first build since it takes time to verify the tooling
|
||||
/// is in place, and we don't want to slow down subsequent builds.
|
||||
pub(crate) async fn verify_tooling(&self, ctx: &BuildContext) -> Result<()> {
|
||||
tracing::debug!("Verifying tooling...");
|
||||
ctx.status_installing_tooling();
|
||||
|
||||
self
|
||||
@@ -3768,4 +3800,116 @@ r#" <script>
|
||||
.to_path_buf()
|
||||
.into()
|
||||
}
|
||||
|
||||
pub(crate) async fn start_simulators(&self) -> Result<()> {
|
||||
if self.device {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match self.platform {
|
||||
// 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 => {
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct XcrunListJson {
|
||||
// "com.apple.CoreSimulator.SimRuntime.iOS-18-4": [{}, {}, {}]
|
||||
devices: BTreeMap<String, Vec<XcrunDevice>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct XcrunDevice {
|
||||
#[serde(rename = "lastBootedAt")]
|
||||
last_booted_at: Option<String>,
|
||||
udid: String,
|
||||
name: String,
|
||||
state: String,
|
||||
}
|
||||
let xcrun_list = Command::new("xcrun")
|
||||
.arg("simctl")
|
||||
.arg("list")
|
||||
.arg("-j")
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
let as_str = String::from_utf8_lossy(&xcrun_list.stdout);
|
||||
let xcrun_list_json = serde_json::from_str::<XcrunListJson>(as_str.trim());
|
||||
if let Ok(xcrun_list_json) = xcrun_list_json {
|
||||
if xcrun_list_json.devices.is_empty() {
|
||||
tracing::warn!(
|
||||
"No iOS sdks installed found. Please install the iOS SDK in Xcode."
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((_rt, devices)) = xcrun_list_json.devices.iter().next() {
|
||||
if devices.iter().all(|device| device.state != "Booted") {
|
||||
let last_booted =
|
||||
devices
|
||||
.iter()
|
||||
.max_by_key(|device| match device.last_booted_at {
|
||||
Some(ref last_booted) => last_booted,
|
||||
None => "2000-01-01T01:01:01Z",
|
||||
});
|
||||
|
||||
if let Some(device) = last_booted {
|
||||
tracing::info!("Booting iOS simulator: \"{}\"", device.name);
|
||||
Command::new("xcrun")
|
||||
.arg("simctl")
|
||||
.arg("boot")
|
||||
.arg(&device.udid)
|
||||
.output()
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let path_to_xcode = Command::new("xcode-select")
|
||||
.arg("--print-path")
|
||||
.output()
|
||||
.await?;
|
||||
let path_to_xcode: PathBuf = String::from_utf8_lossy(&path_to_xcode.stdout)
|
||||
.as_ref()
|
||||
.trim()
|
||||
.into();
|
||||
let path_to_sim = path_to_xcode.join("Applications").join("Simulator.app");
|
||||
open::that(path_to_sim)?;
|
||||
}
|
||||
|
||||
Platform::Android => {
|
||||
let tools = self.workspace.android_tools()?;
|
||||
tokio::spawn(async move {
|
||||
let emulator = tools.emulator();
|
||||
let avds = Command::new(&emulator)
|
||||
.arg("-list-avds")
|
||||
.output()
|
||||
.await
|
||||
.unwrap();
|
||||
let avds = String::from_utf8_lossy(&avds.stdout);
|
||||
let avd = avds.trim().lines().next().map(|s| s.trim().to_string());
|
||||
if let Some(avd) = avd {
|
||||
tracing::info!("Booting Android emulator: \"{avd}\"");
|
||||
Command::new(&emulator)
|
||||
.arg("-avd")
|
||||
.arg(avd)
|
||||
.args(["-netdelay", "none", "-netspeed", "full"])
|
||||
.stdout(std::process::Stdio::null()) // prevent accumulating huge amounts of mem usage
|
||||
.stderr(std::process::Stdio::null()) // prevent accumulating huge amounts of mem usage
|
||||
.output()
|
||||
.await
|
||||
.unwrap();
|
||||
} else {
|
||||
tracing::warn!("No Android emulators found. Please create one using `emulator -avd <name>`");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_ => {
|
||||
// nothing - maybe on the web we should open the browser?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use tokio::process::Command;
|
||||
/// https://gist.github.com/Pulimet/5013acf2cd5b28e55036c82c91bd56d8?permalink_comment_id=3678614
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct AndroidTools {
|
||||
pub(crate) sdk: Option<PathBuf>,
|
||||
pub(crate) ndk: PathBuf,
|
||||
pub(crate) adb: PathBuf,
|
||||
pub(crate) java_home: Option<PathBuf>,
|
||||
@@ -114,6 +115,7 @@ pub fn get_android_tools() -> Option<Arc<AndroidTools>> {
|
||||
ndk,
|
||||
adb,
|
||||
java_home,
|
||||
sdk,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -168,6 +170,18 @@ impl AndroidTools {
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn sdk(&self) -> PathBuf {
|
||||
// /Users/jonathankelley/Library/Android/sdk/ndk/25.2/... (25.2 is the ndk here)
|
||||
// /Users/jonathankelley/Library/Android/sdk/
|
||||
self.sdk
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.ndk.parent().unwrap().parent().unwrap().to_path_buf())
|
||||
}
|
||||
|
||||
pub(crate) fn emulator(&self) -> PathBuf {
|
||||
self.sdk().join("emulator").join("emulator")
|
||||
}
|
||||
|
||||
// todo(jon): this should be configurable
|
||||
pub(crate) fn min_sdk_version(&self) -> u32 {
|
||||
24
|
||||
@@ -230,8 +244,11 @@ impl AndroidTools {
|
||||
"x86_64" => {
|
||||
triple.architecture = Architecture::X86_64;
|
||||
}
|
||||
"" => {
|
||||
tracing::debug!("No device running - probably waiting for emulator");
|
||||
}
|
||||
other => {
|
||||
tracing::warn!("Unknown architecture from adb: {other}");
|
||||
tracing::debug!("Unknown architecture from adb: {other}");
|
||||
}
|
||||
},
|
||||
Ok(Err(err)) => {
|
||||
@@ -248,10 +265,9 @@ impl AndroidTools {
|
||||
|
||||
fn var_or_debug(name: &str) -> Option<PathBuf> {
|
||||
use std::env::var;
|
||||
use tracing::debug;
|
||||
|
||||
var(name)
|
||||
.inspect_err(|_| debug!("{name} not set"))
|
||||
.inspect_err(|_| tracing::trace!("{name} not set"))
|
||||
.ok()
|
||||
.map(PathBuf::from)
|
||||
}
|
||||
@@ -89,6 +89,9 @@ impl BuildArgs {
|
||||
pub async fn into_targets(self) -> Result<BuildTargets> {
|
||||
let workspace = Workspace::current().await?;
|
||||
|
||||
// do some logging to ensure dx matches the dioxus version since we're not always API compatible
|
||||
workspace.check_dioxus_version_against_cli();
|
||||
|
||||
let mut server = None;
|
||||
|
||||
let client = match self.targets {
|
||||
|
||||
@@ -221,7 +221,7 @@ pub(crate) fn post_create(path: &Path) -> Result<()> {
|
||||
let mut file = std::fs::File::create(readme_path)?;
|
||||
file.write_all(new_readme.as_bytes())?;
|
||||
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Generated project at {}\n\n`cd` to your project and run `dx serve` to start developing.\nIf using Tailwind, make sure to run the Tailwind CLI.\nMore information is available in the generated `README.md`.\n\nBuild cool things! ✌️", path.display());
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Generated project at {}\n\n`cd` to your project and run `dx serve` to start developing.\nMore information is available in the generated `README.md`.\n\nBuild cool things! ✌️", path.display());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+59
-22
@@ -12,6 +12,7 @@ pub(crate) mod run;
|
||||
pub(crate) mod serve;
|
||||
pub(crate) mod target;
|
||||
pub(crate) mod translate;
|
||||
pub(crate) mod update;
|
||||
pub(crate) mod verbosity;
|
||||
|
||||
pub(crate) use build::*;
|
||||
@@ -20,6 +21,7 @@ pub(crate) use target::*;
|
||||
pub(crate) use verbosity::*;
|
||||
|
||||
use crate::{error::Result, Error, StructuredOutput};
|
||||
use clap::builder::styling::{AnsiColor, Effects, Style, Styles};
|
||||
use clap::{Parser, Subcommand};
|
||||
use html_parser::Dom;
|
||||
use once_cell::sync::Lazy;
|
||||
@@ -32,9 +34,10 @@ use std::{
|
||||
process::Command,
|
||||
};
|
||||
|
||||
/// Build, Bundle & Ship Dioxus Apps.
|
||||
/// Dioxus: build web, desktop, and mobile apps with a single codebase.
|
||||
#[derive(Parser)]
|
||||
#[clap(name = "dioxus", version = VERSION.as_str())]
|
||||
#[clap(styles = CARGO_STYLING)]
|
||||
pub(crate) struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub(crate) action: Commands,
|
||||
@@ -45,21 +48,29 @@ pub(crate) struct Cli {
|
||||
|
||||
#[derive(Subcommand)]
|
||||
pub(crate) enum Commands {
|
||||
/// Create a new Dioxus project.
|
||||
#[clap(name = "new")]
|
||||
New(create::Create),
|
||||
|
||||
/// Build, watch, and serve the project.
|
||||
#[clap(name = "serve")]
|
||||
Serve(serve::ServeArgs),
|
||||
|
||||
/// Bundle the Dioxus app into a shippable object.
|
||||
#[clap(name = "bundle")]
|
||||
Bundle(bundle::Bundle),
|
||||
|
||||
/// Build the Dioxus project and all of its assets.
|
||||
#[clap(name = "build")]
|
||||
Build(build::BuildArgs),
|
||||
|
||||
/// Translate a source file into Dioxus code.
|
||||
#[clap(name = "translate")]
|
||||
Translate(translate::Translate),
|
||||
/// Run the project without any hotreloading.
|
||||
#[clap(name = "run")]
|
||||
Run(run::RunArgs),
|
||||
|
||||
/// Build, watch & serve the Dioxus project and all of its assets.
|
||||
#[clap(name = "serve")]
|
||||
Serve(serve::ServeArgs),
|
||||
|
||||
/// Create a new project for Dioxus.
|
||||
#[clap(name = "new")]
|
||||
New(create::Create),
|
||||
/// Build the assets for a specific target.
|
||||
#[clap(name = "assets")]
|
||||
BuildAssets(build_assets::BuildAssets),
|
||||
|
||||
/// Init a new project for Dioxus in the current directory (by default).
|
||||
/// Will attempt to keep your project in a good state.
|
||||
@@ -70,9 +81,9 @@ pub(crate) enum Commands {
|
||||
#[clap(name = "clean")]
|
||||
Clean(clean::Clean),
|
||||
|
||||
/// Bundle the Dioxus app into a shippable object.
|
||||
#[clap(name = "bundle")]
|
||||
Bundle(bundle::Bundle),
|
||||
/// Translate a source file into Dioxus code.
|
||||
#[clap(name = "translate")]
|
||||
Translate(translate::Translate),
|
||||
|
||||
/// Automatically format RSX.
|
||||
#[clap(name = "fmt")]
|
||||
@@ -82,18 +93,14 @@ pub(crate) enum Commands {
|
||||
#[clap(name = "check")]
|
||||
Check(check::Check),
|
||||
|
||||
/// Run the project without any hotreloading
|
||||
#[clap(name = "run")]
|
||||
Run(run::RunArgs),
|
||||
|
||||
/// Dioxus config file controls.
|
||||
#[clap(subcommand)]
|
||||
#[clap(name = "config")]
|
||||
Config(config::Config),
|
||||
|
||||
/// Build the assets for a specific target.
|
||||
#[clap(name = "assets")]
|
||||
BuildAssets(build_assets::BuildAssets),
|
||||
/// Update the Dioxus CLI to the latest version.
|
||||
#[clap(name = "self-update")]
|
||||
SelfUpdate(update::SelfUpdate),
|
||||
}
|
||||
|
||||
impl Display for Commands {
|
||||
@@ -110,7 +117,8 @@ impl Display for Commands {
|
||||
Commands::Check(_) => write!(f, "check"),
|
||||
Commands::Bundle(_) => write!(f, "bundle"),
|
||||
Commands::Run(_) => write!(f, "run"),
|
||||
Commands::BuildAssets(_) => write!(f, "build_assets"),
|
||||
Commands::BuildAssets(_) => write!(f, "assets"),
|
||||
Commands::SelfUpdate(_) => write!(f, "self-update"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,3 +130,32 @@ pub(crate) static VERSION: Lazy<String> = Lazy::new(|| {
|
||||
crate::dx_build_info::GIT_COMMIT_HASH_SHORT.unwrap_or("was built without git repository")
|
||||
)
|
||||
});
|
||||
|
||||
/// Cargo's color style
|
||||
/// [source](https://github.com/crate-ci/clap-cargo/blob/master/src/style.rs)
|
||||
pub(crate) const CARGO_STYLING: Styles = Styles::styled()
|
||||
.header(styles::HEADER)
|
||||
.usage(styles::USAGE)
|
||||
.literal(styles::LITERAL)
|
||||
.placeholder(styles::PLACEHOLDER)
|
||||
.error(styles::ERROR)
|
||||
.valid(styles::VALID)
|
||||
.invalid(styles::INVALID);
|
||||
|
||||
pub mod styles {
|
||||
use super::*;
|
||||
pub(crate) const HEADER: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);
|
||||
pub(crate) const USAGE: Style = AnsiColor::Green.on_default().effects(Effects::BOLD);
|
||||
pub(crate) const LITERAL: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);
|
||||
pub(crate) const PLACEHOLDER: Style = AnsiColor::Cyan.on_default();
|
||||
pub(crate) const ERROR: Style = AnsiColor::Red.on_default().effects(Effects::BOLD);
|
||||
|
||||
pub(crate) const VALID: Style = AnsiColor::Cyan.on_default().effects(Effects::BOLD);
|
||||
pub(crate) const INVALID: Style = AnsiColor::Yellow.on_default().effects(Effects::BOLD);
|
||||
|
||||
// extra styles for styling logs
|
||||
// we can style stuff using the ansi sequences like: "hotpatched in {GLOW_STYLE}{}{GLOW_STYLE:X}ms"
|
||||
pub(crate) const GLOW_STYLE: Style = AnsiColor::Yellow.on_default();
|
||||
pub(crate) const NOTE_STYLE: Style = AnsiColor::Green.on_default();
|
||||
pub(crate) const LINK_STYLE: Style = AnsiColor::Blue.on_default();
|
||||
}
|
||||
@@ -106,7 +106,7 @@ impl RunArgs {
|
||||
print!("{}", message);
|
||||
}
|
||||
BuilderUpdate::BuildFailed { err } => {
|
||||
tracing::error!("Build failed: {:#?}", err);
|
||||
tracing::error!("Build failed: {}", err);
|
||||
}
|
||||
BuilderUpdate::StdoutReceived { msg } => {
|
||||
tracing::info!("[{platform}] {msg}");
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
use super::*;
|
||||
use crate::{Result, Workspace};
|
||||
use anyhow::Context;
|
||||
use itertools::Itertools;
|
||||
use self_update::cargo_crate_version;
|
||||
|
||||
/// Run the project with the given arguments
|
||||
///
|
||||
/// This is a shorthand for `dx serve` with interactive mode and hot-reload disabled.
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
pub(crate) struct SelfUpdate {
|
||||
/// Use the latest nightly build.
|
||||
#[clap(long, default_value = "false")]
|
||||
pub nightly: bool,
|
||||
|
||||
/// Specify a version to install.
|
||||
#[clap(long)]
|
||||
pub version: Option<String>,
|
||||
|
||||
/// Install the update.
|
||||
#[clap(long, default_value = "true")]
|
||||
pub install: bool,
|
||||
|
||||
/// List available versions.
|
||||
#[clap(long, default_value = "false")]
|
||||
pub list: bool,
|
||||
|
||||
/// Force the update even if the current version is up to date.
|
||||
#[clap(long, default_value = "false")]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
impl SelfUpdate {
|
||||
pub async fn self_update(self) -> Result<StructuredOutput> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let start = std::time::Instant::now();
|
||||
if self.list {
|
||||
let res = self_update::backends::github::Update::configure()
|
||||
.repo_owner("dioxuslabs")
|
||||
.repo_name("dioxus")
|
||||
.bin_name("dx")
|
||||
.current_version(cargo_crate_version!())
|
||||
.build()
|
||||
.unwrap()
|
||||
.get_latest_releases(cargo_crate_version!())
|
||||
.context("Failed to fetch latest version")?;
|
||||
|
||||
if res.is_empty() {
|
||||
tracing::info!("Your version {} is up to date!", cargo_crate_version!());
|
||||
} else {
|
||||
tracing::info!("Your version {} is out of date!", cargo_crate_version!());
|
||||
tracing::info!(
|
||||
"Available versions: [{}]",
|
||||
res.iter()
|
||||
.map(|r| r.version.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
}
|
||||
|
||||
return Ok(StructuredOutput::Success);
|
||||
}
|
||||
|
||||
let repo = self_update::backends::github::Update::configure()
|
||||
.repo_owner("dioxuslabs")
|
||||
.repo_name("dioxus")
|
||||
.bin_name("dx")
|
||||
.current_version(cargo_crate_version!())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let force = self.force || self.version.is_some();
|
||||
let latest = match self.version {
|
||||
Some(version) => repo
|
||||
.get_release_version(&version)
|
||||
.context("Failed to fetch release by tag")?,
|
||||
None => repo
|
||||
.get_latest_release()
|
||||
.context("Failed to fetch latest version")?,
|
||||
};
|
||||
|
||||
if latest.version == cargo_crate_version!() && !force {
|
||||
tracing::info!("Your version {} is up to date!", cargo_crate_version!());
|
||||
return Ok(StructuredOutput::Success);
|
||||
}
|
||||
|
||||
tracing::info!("Your version is out of date!");
|
||||
tracing::info!("- Yours: {}", cargo_crate_version!());
|
||||
tracing::info!("- Latest: {}", latest.version);
|
||||
|
||||
let cur_arch = if cfg!(target_arch = "x86_64") {
|
||||
"x86_64"
|
||||
} else if cfg!(target_arch = "aarch64") {
|
||||
"aarch64"
|
||||
} else {
|
||||
return Err(Error::Unique("Unsupported architecture".to_string()));
|
||||
};
|
||||
|
||||
let cur_os = if cfg!(target_os = "windows") {
|
||||
"windows"
|
||||
} else if cfg!(target_os = "linux") {
|
||||
"linux"
|
||||
} else if cfg!(target_os = "macos") {
|
||||
"darwin"
|
||||
} else {
|
||||
return Err(Error::Unique("Unsupported OS".to_string()));
|
||||
};
|
||||
|
||||
let zip_ext = "zip";
|
||||
|
||||
tracing::debug!("Available assets: {:?}", latest.assets);
|
||||
|
||||
let asset = latest
|
||||
.assets
|
||||
.iter()
|
||||
.find(|a| {
|
||||
a.name.contains(cur_os)
|
||||
&& a.name.contains(cur_arch)
|
||||
&& a.name.ends_with(zip_ext)
|
||||
})
|
||||
.ok_or_else(|| Error::Unique("No suitable release found found".to_string()))?;
|
||||
|
||||
let install_dir = Workspace::dioxus_home_dir().join("self-update");
|
||||
std::fs::create_dir_all(&install_dir).context("Failed to create install directory")?;
|
||||
|
||||
tracing::info!("Downloading update from Github");
|
||||
tracing::debug!("Download URL: {}", asset.download_url);
|
||||
let body = latest.body.unwrap_or_default();
|
||||
let brief = vec![
|
||||
latest.name.to_string(),
|
||||
"".to_string(),
|
||||
latest.date.to_string(),
|
||||
asset.download_url.to_string(),
|
||||
"".to_string(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(body.lines().map(ToString::to_string).take(7))
|
||||
.chain(std::iter::once(" ...".to_string()))
|
||||
.map(|line| format!(" | {line}"))
|
||||
.join("\n");
|
||||
|
||||
tracing::info!("{}", brief.trim());
|
||||
|
||||
let archive_path = install_dir.join(&asset.name);
|
||||
_ = std::fs::remove_file(&archive_path).ok();
|
||||
let archive_file = std::fs::File::create(&archive_path)?;
|
||||
let download_url = asset.download_url.clone();
|
||||
self_update::Download::from_url(&download_url)
|
||||
.set_header(
|
||||
hyper::http::header::ACCEPT,
|
||||
"application/octet-stream".parse().unwrap(),
|
||||
)
|
||||
.download_to(archive_file)
|
||||
.context("Failed to download update")?;
|
||||
|
||||
let install_dir = install_dir.join("dx");
|
||||
_ = std::fs::remove_dir_all(&install_dir);
|
||||
self_update::Extract::from_source(&archive_path)
|
||||
.extract_into(&install_dir)
|
||||
.context("Failed to extract update")?;
|
||||
|
||||
let executable = install_dir.join("dx");
|
||||
if !executable.exists() {
|
||||
return Err(Error::Unique(format!(
|
||||
"Executable not found in {}",
|
||||
install_dir.display()
|
||||
)));
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Successfully downloaded update in {}ms! 👍",
|
||||
start.elapsed().as_millis()
|
||||
);
|
||||
|
||||
if self.install {
|
||||
tracing::info!(
|
||||
"Installing dx v{} to {}",
|
||||
latest.version,
|
||||
std::env::current_exe()?.display()
|
||||
);
|
||||
|
||||
if !self.force {
|
||||
tracing::warn!("Continue? (y/n)");
|
||||
print!(" > ");
|
||||
std::io::stdout()
|
||||
.flush()
|
||||
.context("Failed to flush stdout")?;
|
||||
let mut input = String::new();
|
||||
std::io::stdin()
|
||||
.read_line(&mut input)
|
||||
.context("Failed to read input")?;
|
||||
if !input.trim().to_ascii_lowercase().starts_with('y') {
|
||||
tracing::info!("Aborting update");
|
||||
return Ok(StructuredOutput::Success);
|
||||
}
|
||||
}
|
||||
|
||||
self_update::self_replace::self_replace(executable)?;
|
||||
let time_taken = start.elapsed().as_millis();
|
||||
tracing::info!("Done in {} ms! 💫", time_taken)
|
||||
} else {
|
||||
tracing::info!("Update downloaded to {}", install_dir.display());
|
||||
tracing::info!("Run `dx self-update --install` to install the update");
|
||||
}
|
||||
|
||||
Ok(StructuredOutput::Success)
|
||||
})
|
||||
.await
|
||||
.context("Failed to run self-update")?
|
||||
}
|
||||
}
|
||||
|
||||
/// Check against the github release list to see if the currently released `dx` version is
|
||||
/// more up-to-date than our own.
|
||||
///
|
||||
/// We only toss out this warning once and then save to the settings file to ignore this version
|
||||
/// in the future.
|
||||
pub fn log_if_cli_could_update() {
|
||||
tokio::task::spawn_blocking(|| {
|
||||
let release = self_update::backends::github::Update::configure()
|
||||
.repo_owner("dioxuslabs")
|
||||
.repo_name("dioxus")
|
||||
.bin_name("dx")
|
||||
.current_version(cargo_crate_version!())
|
||||
.build()
|
||||
.unwrap()
|
||||
.get_latest_release();
|
||||
|
||||
if let Ok(release) = release {
|
||||
let old = krates::semver::Version::parse(cargo_crate_version!());
|
||||
let new = krates::semver::Version::parse(&release.version);
|
||||
|
||||
if let (Ok(old), Ok(new)) = (old, new) {
|
||||
if old < new {
|
||||
_ = crate::CliSettings::modify_settings(|f| {
|
||||
let ignored = f.ignore_version_update.as_deref().unwrap_or_default();
|
||||
if release.version != ignored {
|
||||
use crate::styles::GLOW_STYLE;
|
||||
tracing::warn!("A new dx version is available: {new}! Run {GLOW_STYLE}dx self-update{GLOW_STYLE:#} to update.");
|
||||
f.ignore_version_update = Some(new.to_string());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -3,11 +3,6 @@ use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct ApplicationConfig {
|
||||
pub(crate) asset_dir: Option<PathBuf>,
|
||||
|
||||
#[serde(default)]
|
||||
pub(crate) sub_package: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub(crate) out_dir: Option<PathBuf>,
|
||||
|
||||
@@ -16,4 +11,27 @@ pub(crate) struct ApplicationConfig {
|
||||
|
||||
#[serde(default)]
|
||||
pub(crate) tailwind_output: Option<PathBuf>,
|
||||
|
||||
/// Use this file for the info.plist associated with the iOS app.
|
||||
/// `dx` will merge any required settings into this file required to build the app
|
||||
#[serde(default)]
|
||||
pub(crate) ios_info_plist: Option<PathBuf>,
|
||||
|
||||
/// Use this file for the info.plist associated with the macOS app.
|
||||
/// `dx` will merge any required settings into this file required to build the app
|
||||
#[serde(default)]
|
||||
pub(crate) macos_info_plist: Option<PathBuf>,
|
||||
|
||||
/// Use this file for the entitlements.plist associated with the iOS app.
|
||||
#[serde(default)]
|
||||
pub(crate) ios_entitlements: Option<PathBuf>,
|
||||
|
||||
/// Use this file for the entitlements.plist associated with the macOS app.
|
||||
#[serde(default)]
|
||||
pub(crate) macos_entitlements: Option<PathBuf>,
|
||||
|
||||
/// Use this file for the AndroidManifest.xml associated with the Android app.
|
||||
/// `dx` will merge any required settings into this file required to build the app
|
||||
#[serde(default)]
|
||||
pub(crate) android_manifest: Option<PathBuf>,
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents configuration items for the desktop platform.
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct DesktopConfig {}
|
||||
@@ -8,9 +8,6 @@ pub(crate) struct DioxusConfig {
|
||||
#[serde(default)]
|
||||
pub(crate) web: WebConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub(crate) desktop: DesktopConfig,
|
||||
|
||||
#[serde(default)]
|
||||
pub(crate) bundle: BundleConfig,
|
||||
}
|
||||
@@ -19,11 +16,14 @@ impl Default for DioxusConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
application: ApplicationConfig {
|
||||
asset_dir: None,
|
||||
sub_package: None,
|
||||
out_dir: None,
|
||||
tailwind_input: None,
|
||||
tailwind_output: None,
|
||||
ios_info_plist: None,
|
||||
android_manifest: None,
|
||||
macos_info_plist: None,
|
||||
ios_entitlements: None,
|
||||
macos_entitlements: None,
|
||||
},
|
||||
web: WebConfig {
|
||||
app: WebAppConfig {
|
||||
@@ -49,7 +49,6 @@ impl Default for DioxusConfig {
|
||||
pre_compress: true,
|
||||
wasm_opt: Default::default(),
|
||||
},
|
||||
desktop: DesktopConfig::default(),
|
||||
bundle: BundleConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
mod app;
|
||||
mod bundle;
|
||||
mod desktop;
|
||||
mod dioxus_config;
|
||||
mod serve;
|
||||
mod web;
|
||||
|
||||
pub(crate) use app::*;
|
||||
pub(crate) use bundle::*;
|
||||
pub(crate) use desktop::*;
|
||||
pub(crate) use dioxus_config::*;
|
||||
pub(crate) use serve::*;
|
||||
pub(crate) use web::*;
|
||||
@@ -49,7 +49,6 @@ async fn main() {
|
||||
}
|
||||
|
||||
let args = TraceController::initialize();
|
||||
|
||||
let result = match args.action {
|
||||
Commands::Translate(opts) => opts.translate(),
|
||||
Commands::New(opts) => opts.create(),
|
||||
@@ -63,6 +62,7 @@ async fn main() {
|
||||
Commands::Bundle(opts) => opts.bundle().await,
|
||||
Commands::Run(opts) => opts.run().await,
|
||||
Commands::BuildAssets(opts) => opts.run().await,
|
||||
Commands::SelfUpdate(opts) => opts.self_update().await,
|
||||
};
|
||||
|
||||
// Provide a structured output for third party tools that can consume the output of the CLI
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::{AppBuilder, BuildId, BuildMode, BuilderUpdate, Result, ServeArgs, TraceController};
|
||||
use crate::{
|
||||
styles::{GLOW_STYLE, LINK_STYLE},
|
||||
AppBuilder, BuildId, BuildMode, BuilderUpdate, Result, ServeArgs, TraceController,
|
||||
};
|
||||
|
||||
mod ansi_buffer;
|
||||
mod output;
|
||||
@@ -34,26 +37,25 @@ pub(crate) use update::*;
|
||||
/// - I want us to be able to detect a `server_fn` in the project and then upgrade from a static server
|
||||
/// to a dynamic one on the fly.
|
||||
pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> Result<()> {
|
||||
// Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc
|
||||
let mut builder = AppServer::start(args).await?;
|
||||
let mut devserver = WebServer::start(&builder)?;
|
||||
let mut screen = Output::start(builder.interactive).await?;
|
||||
|
||||
// This is our default splash screen. We might want to make this a fancier splash screen in the future
|
||||
// Also, these commands might not be the most important, but it's all we've got enabled right now
|
||||
tracing::info!(
|
||||
r#"-----------------------------------------------------------------
|
||||
Serving your Dioxus app: {} 🚀
|
||||
• Press `ctrl+c` to exit the server
|
||||
• Press `r` to rebuild the app
|
||||
• Press `p` to toggle automatic rebuilds
|
||||
• Press `v` to toggle verbose logging
|
||||
• Press `/` for more commands and shortcuts
|
||||
Learn more at https://dioxuslabs.com/learn/0.6/getting_started
|
||||
Serving your Dioxus app! 🚀
|
||||
• Press {GLOW_STYLE}`ctrl+c`{GLOW_STYLE:#} to exit the server
|
||||
• Press {GLOW_STYLE}`r`{GLOW_STYLE:#} to rebuild the app
|
||||
• Press {GLOW_STYLE}`p`{GLOW_STYLE:#} to toggle automatic rebuilds
|
||||
• Press {GLOW_STYLE}`v`{GLOW_STYLE:#} to toggle verbose logging
|
||||
• Press {GLOW_STYLE}`/`{GLOW_STYLE:#} for more commands and shortcuts
|
||||
Learn more at {LINK_STYLE}https://dioxuslabs.com/learn/0.7/getting_started{LINK_STYLE:#}
|
||||
----------------------------------------------------------------"#,
|
||||
builder.app_name()
|
||||
);
|
||||
|
||||
// Load the args into a plan, resolving all tooling, build dirs, arguments, decoding the multi-target, etc
|
||||
let mut builder = AppServer::start(args).await?;
|
||||
let mut devserver = WebServer::start(&builder)?;
|
||||
let mut screen = Output::start(builder.interactive).await?;
|
||||
|
||||
loop {
|
||||
// Draw the state of the server to the screen
|
||||
screen.render(&builder, &devserver);
|
||||
@@ -89,7 +91,6 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
// Run the server in the background
|
||||
// Waiting for updates here lets us tap into when clients are added/removed
|
||||
ServeUpdate::NewConnection { id, aslr_reference } => {
|
||||
// Send the client
|
||||
devserver
|
||||
.send_hotreload(builder.applied_hot_reload_changes(BuildId::CLIENT))
|
||||
.await;
|
||||
@@ -128,7 +129,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
screen.push_cargo_log(message);
|
||||
}
|
||||
BuilderUpdate::BuildFailed { err } => {
|
||||
tracing::error!("Build failed: {:#?}", err);
|
||||
tracing::error!("Build failed: {}", err);
|
||||
}
|
||||
BuilderUpdate::BuildReady { bundle } => match bundle.mode {
|
||||
BuildMode::Thin { ref cache, .. } => {
|
||||
@@ -204,7 +205,6 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
||||
ServeUpdate::Exit { error } => {
|
||||
_ = builder.cleanup_all().await;
|
||||
_ = devserver.shutdown().await;
|
||||
_ = screen.shutdown();
|
||||
|
||||
match error {
|
||||
Some(err) => return Err(anyhow::anyhow!("{}", err).into()),
|
||||
|
||||
@@ -163,13 +163,6 @@ impl Output {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call the shutdown functions that might mess with the terminal settings - see the related code
|
||||
/// in "startup" for more details about what we need to unset
|
||||
pub(crate) fn shutdown(&self) -> io::Result<()> {
|
||||
Self::remote_shutdown(self.interactive)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn remote_shutdown(interactive: bool) -> io::Result<()> {
|
||||
if interactive {
|
||||
stdout()
|
||||
@@ -966,11 +959,23 @@ impl Output {
|
||||
" ".repeat(3usize.saturating_sub(log.source.to_string().len()))
|
||||
))
|
||||
.style(match log.source {
|
||||
TraceSrc::App(_platform) => Style::new().blue(),
|
||||
TraceSrc::Dev => Style::new().magenta(),
|
||||
TraceSrc::Build => Style::new().yellow(),
|
||||
TraceSrc::Bundle => Style::new().magenta(),
|
||||
TraceSrc::App(_platform) => match log.level {
|
||||
Level::ERROR => Style::new().red(),
|
||||
Level::WARN => Style::new().yellow(),
|
||||
Level::INFO => Style::new().magenta(),
|
||||
Level::DEBUG => Style::new().magenta(),
|
||||
Level::TRACE => Style::new().magenta(),
|
||||
},
|
||||
TraceSrc::Dev => match log.level {
|
||||
Level::ERROR => Style::new().red(),
|
||||
Level::WARN => Style::new().yellow(),
|
||||
Level::INFO => Style::new().blue(),
|
||||
Level::DEBUG => Style::new().blue(),
|
||||
Level::TRACE => Style::new().blue(),
|
||||
},
|
||||
TraceSrc::Cargo => Style::new().yellow(),
|
||||
TraceSrc::Build => Style::new().blue(),
|
||||
TraceSrc::Bundle => Style::new().blue(),
|
||||
TraceSrc::Unknown => Style::new().gray(),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -155,10 +155,12 @@ impl AppServer {
|
||||
client.build.package_manifest_dir(),
|
||||
client.build.config.application.tailwind_input.clone(),
|
||||
client.build.config.application.tailwind_output.clone(),
|
||||
)
|
||||
.await?;
|
||||
);
|
||||
|
||||
tracing::debug!("Proxied port: {:?}", proxied_port);
|
||||
_ = client.build.start_simulators().await;
|
||||
|
||||
// Encourage the user to update to a new dx version
|
||||
crate::update::log_if_cli_could_update();
|
||||
|
||||
// Create the runner
|
||||
let mut runner = Self {
|
||||
@@ -446,7 +448,13 @@ impl AppServer {
|
||||
// Also make sure the builder isn't busy since that might cause issues with hotreloads
|
||||
// https://github.com/DioxusLabs/dioxus/issues/3361
|
||||
if !msg.is_empty() && self.client.can_receive_hotreloads() {
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Hotreloading: {}", file);
|
||||
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 {
|
||||
tracing::warn!("No clients to hotreload - try reloading the app!");
|
||||
}
|
||||
|
||||
server.send_hotreload(msg).await;
|
||||
} else {
|
||||
tracing::debug!(dx_src = ?TraceSrc::Dev, "Ignoring file change: {}", file);
|
||||
@@ -473,6 +481,8 @@ impl AppServer {
|
||||
let should_open = self.client.stage == BuildStage::Success
|
||||
&& (self.server.as_ref().map(|s| s.stage == BuildStage::Success)).unwrap_or(true);
|
||||
|
||||
use crate::cli::styles::GLOW_STYLE;
|
||||
|
||||
if should_open {
|
||||
let time_taken = artifacts
|
||||
.time_end
|
||||
@@ -481,11 +491,14 @@ impl AppServer {
|
||||
|
||||
if self.client.builds_opened == 0 {
|
||||
tracing::info!(
|
||||
"Build completed successfully in {:?}ms, launching app! 💫",
|
||||
"Build completed successfully in {GLOW_STYLE}{:?}ms{GLOW_STYLE:#}, launching app! 💫",
|
||||
time_taken.as_millis()
|
||||
);
|
||||
} else {
|
||||
tracing::info!("Build completed in {:?}ms", time_taken.as_millis());
|
||||
tracing::info!(
|
||||
"Build completed in {GLOW_STYLE}{:?}ms{GLOW_STYLE:#}",
|
||||
time_taken.as_millis()
|
||||
);
|
||||
}
|
||||
|
||||
let open_browser = self.client.builds_opened == 0 && self.open_browser;
|
||||
|
||||
@@ -264,6 +264,10 @@ impl WebServer {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_hotreload_sockets(&self) -> bool {
|
||||
!self.hot_reload_sockets.is_empty()
|
||||
}
|
||||
|
||||
/// Sends hot reloadable changes to all clients.
|
||||
pub(crate) async fn send_hotreload(&mut self, reload: HotReloadMsg) {
|
||||
if reload.is_empty() {
|
||||
|
||||
@@ -26,6 +26,8 @@ pub(crate) struct CliSettings {
|
||||
pub(crate) wsl_file_poll_interval: Option<u16>,
|
||||
/// Use tooling from path rather than downloading them.
|
||||
pub(crate) no_downloads: Option<bool>,
|
||||
/// Ignore updates for this version
|
||||
pub(crate) ignore_version_update: Option<String>,
|
||||
}
|
||||
|
||||
impl CliSettings {
|
||||
|
||||
@@ -19,12 +19,12 @@ impl TailwindCli {
|
||||
Self { version }
|
||||
}
|
||||
|
||||
pub(crate) async fn serve(
|
||||
pub(crate) fn serve(
|
||||
manifest_dir: PathBuf,
|
||||
input_path: Option<PathBuf>,
|
||||
output_path: Option<PathBuf>,
|
||||
) -> Result<tokio::task::JoinHandle<Result<()>>> {
|
||||
Ok(tokio::spawn(async move {
|
||||
) -> tokio::task::JoinHandle<Result<()>> {
|
||||
tokio::spawn(async move {
|
||||
let Some(tailwind) = Self::autodetect(&manifest_dir) else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -38,12 +38,16 @@ impl TailwindCli {
|
||||
proc.wait_with_output().await?;
|
||||
|
||||
Ok(())
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/// Use the correct tailwind version based on the manifest directory.
|
||||
///
|
||||
/// - If `tailwind.config.js` or `tailwind.config.ts` exists, use v3.
|
||||
/// - If `tailwind.css` exists, use v4.
|
||||
///
|
||||
/// Note that v3 still uses the tailwind.css file, but usually the accompanying js file indicates
|
||||
/// that the project is using v3.
|
||||
pub(crate) fn autodetect(manifest_dir: &Path) -> Option<Self> {
|
||||
if manifest_dir.join("tailwind.config.js").exists() {
|
||||
return Some(Self::v3());
|
||||
@@ -92,8 +96,9 @@ impl TailwindCli {
|
||||
.arg("--output")
|
||||
.arg(output_path)
|
||||
.arg("--watch")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?;
|
||||
|
||||
Ok(proc)
|
||||
|
||||
@@ -156,7 +156,7 @@ impl WasmBindgen {
|
||||
.expect("input_path should be valid utf8");
|
||||
args.push(input_path);
|
||||
|
||||
tracing::debug!("wasm-bindgen args: {:#?}", args);
|
||||
tracing::debug!("wasm-bindgen: {:#?}", args);
|
||||
|
||||
// Run bindgen
|
||||
let output = Command::new(binary).args(args).output().await?;
|
||||
|
||||
@@ -3,11 +3,11 @@ use crate::Result;
|
||||
use crate::{config::DioxusConfig, AndroidTools};
|
||||
use anyhow::Context;
|
||||
use ignore::gitignore::Gitignore;
|
||||
use krates::KrateDetails;
|
||||
use krates::{semver::Version, KrateDetails, LockOptions};
|
||||
use krates::{Cmd, Krates, NodeId};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashSet, path::Path};
|
||||
use target_lexicon::Triple;
|
||||
use tokio::process::Command;
|
||||
|
||||
@@ -34,9 +34,12 @@ impl Workspace {
|
||||
return Ok(ws.clone());
|
||||
}
|
||||
|
||||
tracing::debug!("Loading workspace!");
|
||||
|
||||
let cmd = Cmd::new();
|
||||
let mut cmd = Cmd::new();
|
||||
cmd.lock_opts(LockOptions {
|
||||
offline: true,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
});
|
||||
let mut builder = krates::Builder::new();
|
||||
builder.workspace(true);
|
||||
let krates = builder
|
||||
@@ -79,6 +82,23 @@ impl Workspace {
|
||||
android_tools,
|
||||
});
|
||||
|
||||
tracing::debug!(
|
||||
r#"Initialized workspace:
|
||||
• sysroot: {sysroot}
|
||||
• rustc version: {rustc_version}
|
||||
• workspace root: {workspace_root}
|
||||
• dioxus versions: [{dioxus_versions:?}]"#,
|
||||
sysroot = workspace.sysroot.display(),
|
||||
rustc_version = workspace.rustc_version,
|
||||
workspace_root = workspace.workspace_root().display(),
|
||||
dioxus_versions = workspace
|
||||
.dioxus_versions()
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
|
||||
lock.replace(workspace.clone());
|
||||
|
||||
Ok(workspace)
|
||||
@@ -116,6 +136,50 @@ impl Workspace {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn check_dioxus_version_against_cli(&self) {
|
||||
let dx_semver = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
||||
let dioxus_versions = self.dioxus_versions();
|
||||
|
||||
tracing::trace!("dx version: {}", dx_semver);
|
||||
tracing::trace!("dioxus versions: {:?}", dioxus_versions);
|
||||
|
||||
// if there are no dioxus versions in the workspace, we don't need to check anything
|
||||
// dx is meant to be compatible with non-dioxus projects too.
|
||||
if dioxus_versions.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let min = dioxus_versions.iter().min().unwrap();
|
||||
let max = dioxus_versions.iter().max().unwrap();
|
||||
|
||||
// If the minimum dioxus version is greater than the current cli version, warn the user
|
||||
if min > &dx_semver || max < &dx_semver {
|
||||
tracing::error!(
|
||||
r#"🚫dx and dioxus versions are incompatible!
|
||||
• dx version: {dx_semver}
|
||||
• dioxus versions: [{}]"#,
|
||||
dioxus_versions
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get all the versions of dioxus in the workspace
|
||||
pub fn dioxus_versions(&self) -> Vec<Version> {
|
||||
let mut versions = HashSet::new();
|
||||
for krate in self.krates.krates() {
|
||||
if krate.name == "dioxus" {
|
||||
versions.insert(krate.version.clone());
|
||||
}
|
||||
}
|
||||
let mut versions = versions.into_iter().collect::<Vec<_>>();
|
||||
versions.sort();
|
||||
versions
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn rust_lld(&self) -> PathBuf {
|
||||
self.sysroot
|
||||
|
||||
Reference in new issue
Block a user