feat: android bundling, red/blue exe names, session cache (#3608)
* feat: android bundling * fix: add hashing to exe names for red/blue exes * feat: implement session cache for window restoration
This commit is contained in:
1 parent
024285c548
commit
d342630a0a
11 files changed
+334
-185
No files matched your search
@@ -730,23 +730,7 @@ impl AppBundle {
|
||||
if let Platform::Android = self.build.build.platform() {
|
||||
self.build.status_running_gradle();
|
||||
|
||||
// make sure we can execute the gradlew script
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
std::fs::set_permissions(
|
||||
self.build.root_dir().join("gradlew"),
|
||||
std::fs::Permissions::from_mode(0o755),
|
||||
)?;
|
||||
}
|
||||
|
||||
let gradle_exec_name = match cfg!(windows) {
|
||||
true => "gradlew.bat",
|
||||
false => "gradlew",
|
||||
};
|
||||
let gradle_exec = self.build.root_dir().join(gradle_exec_name);
|
||||
|
||||
let output = Command::new(gradle_exec)
|
||||
let output = Command::new(self.gradle_exe()?)
|
||||
.arg("assembleDebug")
|
||||
.current_dir(self.build.root_dir())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
@@ -762,6 +746,62 @@ impl AppBundle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run bundleRelease and return the path to the `.aab` file
|
||||
///
|
||||
/// https://stackoverflow.com/questions/57072558/whats-the-difference-between-gradlewassemblerelease-gradlewinstallrelease-and
|
||||
pub(crate) async fn android_gradle_bundle(&self) -> Result<PathBuf> {
|
||||
let output = Command::new(self.gradle_exe()?)
|
||||
.arg("bundleRelease")
|
||||
.current_dir(self.build.root_dir())
|
||||
.output()
|
||||
.await
|
||||
.context("Failed to run gradle bundleRelease")?;
|
||||
|
||||
if !output.status.success() {
|
||||
return Err(anyhow::anyhow!("Failed to bundleRelease: {output:?}").into());
|
||||
}
|
||||
|
||||
let app_release = self
|
||||
.build
|
||||
.root_dir()
|
||||
.join("app")
|
||||
.join("build")
|
||||
.join("outputs")
|
||||
.join("bundle")
|
||||
.join("release");
|
||||
|
||||
// Rename it to Name-arch.aab
|
||||
let from = app_release.join("app-release.aab");
|
||||
let to = app_release.join(format!(
|
||||
"{}-{}.aab",
|
||||
self.build.krate.bundled_app_name(),
|
||||
self.build.build.target_args.arch()
|
||||
));
|
||||
|
||||
std::fs::rename(from, &to).context("Failed to rename aab")?;
|
||||
|
||||
Ok(to)
|
||||
}
|
||||
|
||||
fn gradle_exe(&self) -> Result<PathBuf> {
|
||||
// make sure we can execute the gradlew script
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::prelude::PermissionsExt;
|
||||
std::fs::set_permissions(
|
||||
self.build.root_dir().join("gradlew"),
|
||||
std::fs::Permissions::from_mode(0o755),
|
||||
)?;
|
||||
}
|
||||
|
||||
let gradle_exec_name = match cfg!(windows) {
|
||||
true => "gradlew.bat",
|
||||
false => "gradlew",
|
||||
};
|
||||
|
||||
Ok(self.build.root_dir().join(gradle_exec_name))
|
||||
}
|
||||
|
||||
pub(crate) fn apk_path(&self) -> PathBuf {
|
||||
self.build
|
||||
.root_dir()
|
||||
|
||||
@@ -76,11 +76,12 @@ impl Bundle {
|
||||
Platform::Server => bundles.push(bundle.build.root_dir()),
|
||||
Platform::Liveview => bundles.push(bundle.build.root_dir()),
|
||||
|
||||
// todo(jon): we can technically create apks (already do...) just need to expose it
|
||||
Platform::Android => {
|
||||
return Err(Error::UnsupportedFeature(
|
||||
"Android bundles are not yet supported".into(),
|
||||
));
|
||||
let aab = bundle
|
||||
.android_gradle_bundle()
|
||||
.await
|
||||
.context("Failed to run gradle bundleRelease")?;
|
||||
bundles.push(aab);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ impl RunArgs {
|
||||
tracing::info!("[{platform}]: {msg}")
|
||||
}
|
||||
ServeUpdate::ProcessExited { platform, status } => {
|
||||
runner.kill(platform).await;
|
||||
runner.cleanup().await;
|
||||
tracing::info!("[{platform}]: process exited with status: {status:?}");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -217,3 +217,15 @@ impl TryFrom<String> for Arch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Arch {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Arch::Arm => "armv7l",
|
||||
Arch::Arm64 => "aarch64",
|
||||
Arch::X86 => "i386",
|
||||
Arch::X64 => "x86_64",
|
||||
}
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
@@ -149,6 +149,16 @@ impl DioxusCrate {
|
||||
files
|
||||
}
|
||||
|
||||
/// Get the directory where this app can write to for this session that's guaranteed to be stable
|
||||
/// for the same app. This is useful for emitting state like window position and size.
|
||||
///
|
||||
/// The directory is specific for this app and might be
|
||||
pub(crate) fn session_cache_dir(&self) -> PathBuf {
|
||||
self.internal_out_dir()
|
||||
.join(self.executable_name())
|
||||
.join("session-cache")
|
||||
}
|
||||
|
||||
/// Get the outdir specified by the Dioxus.toml, relative to the crate directory.
|
||||
/// We don't support workspaces yet since that would cause a collision of bundles per project.
|
||||
pub(crate) fn crate_out_dir(&self) -> Option<PathBuf> {
|
||||
|
||||
@@ -33,6 +33,7 @@ pub(crate) enum Error {
|
||||
#[error("Failed to bundle project: {0}")]
|
||||
BundleFailed(#[from] tauri_bundler::Error),
|
||||
|
||||
#[allow(unused)]
|
||||
#[error("Unsupported feature: {0}")]
|
||||
UnsupportedFeature(String),
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ pub(crate) struct AppHandle {
|
||||
pub(crate) server_stdout: Option<Lines<BufReader<ChildStdout>>>,
|
||||
pub(crate) server_stderr: Option<Lines<BufReader<ChildStderr>>>,
|
||||
|
||||
/// The executables but with some extra entropy in their name so we can run two instances of the
|
||||
/// same app without causing collisions on the filesystem.
|
||||
pub(crate) entropy_app_exe: Option<PathBuf>,
|
||||
pub(crate) entropy_server_exe: Option<PathBuf>,
|
||||
|
||||
/// The virtual directory that assets will be served from
|
||||
/// Used mostly for apk/ipa builds since they live in simulator
|
||||
pub(crate) runtime_asst_dir: Option<PathBuf>,
|
||||
@@ -54,6 +59,8 @@ impl AppHandle {
|
||||
server_child: None,
|
||||
server_stdout: None,
|
||||
server_stderr: None,
|
||||
entropy_app_exe: None,
|
||||
entropy_server_exe: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -79,6 +86,15 @@ impl AppHandle {
|
||||
// unset the cargo dirs in the event we're running `dx` locally
|
||||
// since the child process will inherit the env vars, we don't want to confuse the downstream process
|
||||
("CARGO_MANIFEST_DIR", "".to_string()),
|
||||
(
|
||||
dioxus_cli_config::SESSION_CACHE_DIR,
|
||||
self.app
|
||||
.build
|
||||
.krate
|
||||
.session_cache_dir()
|
||||
.display()
|
||||
.to_string(),
|
||||
),
|
||||
];
|
||||
|
||||
if let Some(base_path) = &self.app.build.krate.config.web.app.base_path {
|
||||
@@ -87,7 +103,7 @@ impl AppHandle {
|
||||
|
||||
// Launch the server if we were given an address to start it on, and the build includes a server. After we
|
||||
// start the server, consume its stdout/stderr.
|
||||
if let (Some(addr), Some(server)) = (start_fullstack_on_address, self.app.server_exe()) {
|
||||
if let (Some(addr), Some(server)) = (start_fullstack_on_address, self.server_exe()) {
|
||||
tracing::debug!("Proxying fullstack server from port {:?}", addr);
|
||||
envs.push((dioxus_cli_config::SERVER_IP_ENV, addr.ip().to_string()));
|
||||
envs.push((dioxus_cli_config::SERVER_PORT_ENV, addr.port().to_string()));
|
||||
@@ -147,6 +163,70 @@ impl AppHandle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Gracefully kill the process and all of its children
|
||||
///
|
||||
/// Uses the `SIGTERM` signal on unix and `taskkill` on windows.
|
||||
/// This complex logic is necessary for things like window state preservation to work properly.
|
||||
///
|
||||
/// Also wipes away the entropy executables if they exist.
|
||||
pub(crate) async fn cleanup(&mut self) {
|
||||
tracing::debug!("Cleaning up process");
|
||||
|
||||
// Soft-kill the process by sending a sigkill, allowing the process to clean up
|
||||
self.soft_kill().await;
|
||||
|
||||
// Wipe out the entropy executables if they exist
|
||||
if let Some(entropy_app_exe) = self.entropy_app_exe.take() {
|
||||
_ = std::fs::remove_file(entropy_app_exe);
|
||||
}
|
||||
|
||||
if let Some(entropy_server_exe) = self.entropy_server_exe.take() {
|
||||
_ = std::fs::remove_file(entropy_server_exe);
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill the app and server exes
|
||||
pub(crate) async fn soft_kill(&mut self) {
|
||||
use futures_util::FutureExt;
|
||||
|
||||
// Kill any running executables on Windows
|
||||
let server_process = self.server_child.take();
|
||||
let client_process = self.app_child.take();
|
||||
let processes = [server_process, client_process]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for mut process in processes {
|
||||
let Some(pid) = process.id() else {
|
||||
_ = process.kill().await;
|
||||
continue;
|
||||
};
|
||||
|
||||
// on unix, we can send a signal to the process to shut down
|
||||
#[cfg(unix)]
|
||||
{
|
||||
_ = Command::new("kill")
|
||||
.args(["-s", "TERM", &pid.to_string()])
|
||||
.spawn();
|
||||
}
|
||||
|
||||
// on windows, use the `taskkill` command
|
||||
#[cfg(windows)]
|
||||
{
|
||||
_ = Command::new("taskkill")
|
||||
.args(["/F", "/PID", &pid.to_string()])
|
||||
.spawn();
|
||||
}
|
||||
|
||||
// join the wait with a 100ms timeout
|
||||
futures_util::select! {
|
||||
_ = process.wait().fuse() => {}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_millis(1000)).fuse() => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Hotreload an asset in the running app.
|
||||
///
|
||||
/// This will modify the build dir in place! Be careful! We generally assume you want all bundles
|
||||
@@ -236,12 +316,15 @@ impl AppHandle {
|
||||
///
|
||||
/// Server/liveview/desktop are all basically the same, though
|
||||
fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<Child> {
|
||||
let child = Command::new(self.app.main_exe())
|
||||
// Create a new entropy app exe if we need to
|
||||
let main_exe = self.app_exe();
|
||||
let child = Command::new(main_exe)
|
||||
.envs(envs)
|
||||
.stderr(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
@@ -650,4 +733,72 @@ We checked the folder: {}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn make_entropy_path(exe: &PathBuf) -> PathBuf {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
let name = id.to_string();
|
||||
let some_entropy = name.split('-').next().unwrap();
|
||||
|
||||
// Make a copy of the server exe with a new name
|
||||
let entropy_server_exe = exe.with_file_name(format!(
|
||||
"{}-{}",
|
||||
exe.file_name().unwrap().to_str().unwrap(),
|
||||
some_entropy
|
||||
));
|
||||
|
||||
std::fs::copy(exe, &entropy_server_exe).unwrap();
|
||||
|
||||
entropy_server_exe
|
||||
}
|
||||
|
||||
fn server_exe(&mut self) -> Option<PathBuf> {
|
||||
let mut server = self.app.server_exe()?;
|
||||
|
||||
// Create a new entropy server exe if we need to
|
||||
if cfg!(target_os = "windows") || cfg!(target_os = "linux") {
|
||||
// If we already have an entropy server exe, return it - this is useful for re-opening the same app
|
||||
if let Some(existing_server) = self.entropy_server_exe.clone() {
|
||||
return Some(existing_server);
|
||||
}
|
||||
|
||||
// Otherwise, create a new entropy server exe and save it for re-opning
|
||||
let entropy_server_exe = Self::make_entropy_path(&server);
|
||||
self.entropy_server_exe = Some(entropy_server_exe.clone());
|
||||
server = entropy_server_exe;
|
||||
}
|
||||
|
||||
Some(server)
|
||||
}
|
||||
|
||||
fn app_exe(&mut self) -> PathBuf {
|
||||
let mut main_exe = self.app.main_exe();
|
||||
|
||||
// The requirement here is based on the platform, not necessarily our current architecture.
|
||||
let requires_entropy = match self.app.build.build.platform() {
|
||||
// When running "bundled", we don't need entropy
|
||||
Platform::Web => false,
|
||||
Platform::MacOS => false,
|
||||
Platform::Ios => false,
|
||||
Platform::Android => false,
|
||||
|
||||
// But on platforms that aren't running as "bundled", we do.
|
||||
Platform::Windows => true,
|
||||
Platform::Linux => true,
|
||||
Platform::Server => true,
|
||||
Platform::Liveview => true,
|
||||
};
|
||||
|
||||
if requires_entropy || std::env::var("DIOXUS_ENTROPY").is_ok() {
|
||||
// If we already have an entropy app exe, return it - this is useful for re-opening the same app
|
||||
if let Some(existing_app_exe) = self.entropy_app_exe.clone() {
|
||||
return existing_app_exe;
|
||||
}
|
||||
|
||||
let entropy_app_exe = Self::make_entropy_path(&main_exe);
|
||||
self.entropy_app_exe = Some(entropy_app_exe.clone());
|
||||
main_exe = entropy_app_exe;
|
||||
}
|
||||
|
||||
main_exe
|
||||
}
|
||||
}
|
||||
@@ -107,11 +107,6 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
|
||||
} else if runner.should_full_rebuild {
|
||||
tracing::info!(dx_src = ?TraceSrc::Dev, "Full rebuild: {}", file);
|
||||
|
||||
// Kill any running executables on Windows
|
||||
if cfg!(windows) {
|
||||
runner.kill_all().await;
|
||||
}
|
||||
|
||||
// We're going to kick off a new build, interrupting the current build if it's ongoing
|
||||
builder.rebuild(args.build_arguments.clone());
|
||||
|
||||
@@ -199,8 +194,6 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
|
||||
- To exit the server, press `ctrl+c`"#
|
||||
);
|
||||
}
|
||||
|
||||
runner.kill(platform).await;
|
||||
}
|
||||
|
||||
ServeUpdate::StdoutReceived { platform, msg } => {
|
||||
@@ -221,11 +214,6 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
|
||||
// `Hotreloading:` to keep the alignment during long edit sessions
|
||||
tracing::info!("Full rebuild: triggered manually");
|
||||
|
||||
// Kill any running executables on Windows
|
||||
if cfg!(windows) {
|
||||
runner.kill_all().await;
|
||||
}
|
||||
|
||||
builder.rebuild(args.build_arguments.clone());
|
||||
runner.file_map.force_rebuild();
|
||||
devserver.send_reload_start().await;
|
||||
@@ -261,9 +249,10 @@ pub(crate) async fn serve_all(mut args: ServeArgs) -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
_ = runner.cleanup().await;
|
||||
_ = devserver.shutdown().await;
|
||||
_ = screen.shutdown();
|
||||
builder.abort_all();
|
||||
_ = screen.shutdown();
|
||||
|
||||
if let Err(err) = err {
|
||||
eprintln!("Exiting with error: {}", err);
|
||||
|
||||
@@ -5,17 +5,16 @@ use crate::{
|
||||
use dioxus_core::internal::TemplateGlobalKey;
|
||||
use dioxus_devtools_types::HotReloadMsg;
|
||||
use dioxus_html::HtmlCtx;
|
||||
use futures_util::{future::OptionFuture, stream::FuturesUnordered, FutureExt};
|
||||
use futures_util::future::OptionFuture;
|
||||
use ignore::gitignore::Gitignore;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
net::SocketAddr,
|
||||
path::PathBuf,
|
||||
};
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
pub(crate) struct AppRunner {
|
||||
pub(crate) running: HashMap<Platform, AppHandle>,
|
||||
pub(crate) running: Option<AppHandle>,
|
||||
pub(crate) krate: DioxusCrate,
|
||||
pub(crate) file_map: HotreloadFilemap,
|
||||
pub(crate) ignore: Gitignore,
|
||||
@@ -44,52 +43,47 @@ impl AppRunner {
|
||||
runner.fill_filemap(krate);
|
||||
}
|
||||
|
||||
// Ensure the session cache dir exists and is empty
|
||||
runner.flush_session_cache();
|
||||
|
||||
runner
|
||||
}
|
||||
|
||||
pub(crate) async fn wait(&mut self) -> ServeUpdate {
|
||||
// If there are no running apps, we can just return pending to avoid deadlocking
|
||||
if self.running.is_empty() {
|
||||
let Some(handle) = self.running.as_mut() else {
|
||||
return futures_util::future::pending().await;
|
||||
}
|
||||
};
|
||||
|
||||
self.running
|
||||
.iter_mut()
|
||||
.map(|(platform, handle)| async {
|
||||
use ServeUpdate::*;
|
||||
let platform = *platform;
|
||||
tokio::select! {
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.app_stdout.as_mut().map(|f| f.next_line())) => {
|
||||
StdoutReceived { platform, msg }
|
||||
},
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.app_stderr.as_mut().map(|f| f.next_line())) => {
|
||||
StderrReceived { platform, msg }
|
||||
},
|
||||
Some(status) = OptionFuture::from(handle.app_child.as_mut().map(|f| f.wait())) => {
|
||||
match status {
|
||||
Ok(status) => ProcessExited { status, platform },
|
||||
Err(_err) => todo!("handle error in process joining?"),
|
||||
}
|
||||
}
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.server_stdout.as_mut().map(|f| f.next_line())) => {
|
||||
StdoutReceived { platform: Platform::Server, msg }
|
||||
},
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.server_stderr.as_mut().map(|f| f.next_line())) => {
|
||||
StderrReceived { platform: Platform::Server, msg }
|
||||
},
|
||||
Some(status) = OptionFuture::from(handle.server_child.as_mut().map(|f| f.wait())) => {
|
||||
match status {
|
||||
Ok(status) => ProcessExited { status, platform },
|
||||
Err(_err) => todo!("handle error in process joining?"),
|
||||
}
|
||||
}
|
||||
else => futures_util::future::pending().await
|
||||
use ServeUpdate::*;
|
||||
let platform = handle.app.build.build.platform();
|
||||
tokio::select! {
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.app_stdout.as_mut().map(|f| f.next_line())) => {
|
||||
StdoutReceived { platform, msg }
|
||||
},
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.app_stderr.as_mut().map(|f| f.next_line())) => {
|
||||
StderrReceived { platform, msg }
|
||||
},
|
||||
Some(status) = OptionFuture::from(handle.app_child.as_mut().map(|f| f.wait())) => {
|
||||
match status {
|
||||
Ok(status) => ProcessExited { status, platform },
|
||||
Err(_err) => todo!("handle error in process joining?"),
|
||||
}
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.next()
|
||||
.await
|
||||
.expect("Stream to pending if not empty")
|
||||
}
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.server_stdout.as_mut().map(|f| f.next_line())) => {
|
||||
StdoutReceived { platform: Platform::Server, msg }
|
||||
},
|
||||
Some(Ok(Some(msg))) = OptionFuture::from(handle.server_stderr.as_mut().map(|f| f.next_line())) => {
|
||||
StderrReceived { platform: Platform::Server, msg }
|
||||
},
|
||||
Some(status) = OptionFuture::from(handle.server_child.as_mut().map(|f| f.wait())) => {
|
||||
match status {
|
||||
Ok(status) => ProcessExited { status, platform },
|
||||
Err(_err) => todo!("handle error in process joining?"),
|
||||
}
|
||||
}
|
||||
else => futures_util::future::pending().await
|
||||
}
|
||||
}
|
||||
|
||||
/// Finally "bundle" this app and return a handle to it
|
||||
@@ -100,16 +94,9 @@ impl AppRunner {
|
||||
fullstack_address: Option<SocketAddr>,
|
||||
should_open_web: bool,
|
||||
) -> Result<&AppHandle> {
|
||||
let platform = app.build.build.platform();
|
||||
|
||||
// Drop the old handle
|
||||
// todo(jon): we should instead be sending the kill signal rather than dropping the process
|
||||
// This would allow a more graceful shutdown and fix bugs like desktop not retaining its size
|
||||
self.kill(platform).await;
|
||||
|
||||
// wait a tiny sec for the processes to die so we don't have fullstack servers on top of each other
|
||||
// todo(jon): we should allow rebinding to the same port in fullstack itself
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
// This is a more forceful kill than soft_kill since the app entropy will be wiped
|
||||
self.cleanup().await;
|
||||
|
||||
// Add some cute logging
|
||||
if self.builds_opened == 0 {
|
||||
@@ -132,81 +119,32 @@ impl AppRunner {
|
||||
.await?;
|
||||
|
||||
self.builds_opened += 1;
|
||||
self.running.insert(platform, handle);
|
||||
self.running = Some(handle);
|
||||
|
||||
Ok(self.running.get(&platform).unwrap())
|
||||
}
|
||||
|
||||
/// Gracefully kill the process and all of its children
|
||||
///
|
||||
/// Uses the `SIGTERM` signal on unix and `taskkill` on windows.
|
||||
/// This complex logic is necessary for things like window state preservation to work properly.
|
||||
pub(crate) async fn kill(&mut self, platform: Platform) {
|
||||
use tokio::process::Command;
|
||||
|
||||
let Some(mut process) = self.running.remove(&platform) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let server_process = process.server_child.take();
|
||||
let client_process = process.app_child.take();
|
||||
let processes = [server_process, client_process]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for mut process in processes {
|
||||
let Some(pid) = process.id() else {
|
||||
_ = process.kill().await;
|
||||
continue;
|
||||
};
|
||||
|
||||
// on unix, we can send a signal to the process to shut down
|
||||
#[cfg(unix)]
|
||||
{
|
||||
_ = Command::new("kill")
|
||||
.args(["-s", "TERM", &pid.to_string()])
|
||||
.spawn();
|
||||
}
|
||||
|
||||
// on windows, use the `taskkill` command
|
||||
#[cfg(windows)]
|
||||
{
|
||||
_ = Command::new("taskkill")
|
||||
.args(["/F", "/PID", &pid.to_string()])
|
||||
.spawn();
|
||||
}
|
||||
|
||||
// join the wait with a 100ms timeout
|
||||
futures_util::select! {
|
||||
_ = process.wait().fuse() => {}
|
||||
_ = tokio::time::sleep(std::time::Duration::from_millis(1000)).fuse() => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn kill_all(&mut self) {
|
||||
let keys = self.running.keys().cloned().collect::<Vec<_>>();
|
||||
for platform in keys {
|
||||
self.kill(platform).await;
|
||||
}
|
||||
Ok(self.running.as_ref().unwrap())
|
||||
}
|
||||
|
||||
/// Open an existing app bundle, if it exists
|
||||
pub(crate) async fn open_existing(&mut self, devserver: &WebServer) -> Result<()> {
|
||||
let fullstack_address = devserver.proxied_server_address();
|
||||
|
||||
if let Some((_, app)) = self
|
||||
.running
|
||||
.iter_mut()
|
||||
.find(|(platform, _)| **platform != Platform::Server)
|
||||
{
|
||||
app.open(devserver.devserver_address(), fullstack_address, true)
|
||||
if let Some(runner) = self.running.as_mut() {
|
||||
runner.soft_kill().await;
|
||||
runner
|
||||
.open(devserver.devserver_address(), fullstack_address, true)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Shutdown all the running processes
|
||||
pub(crate) async fn cleanup(&mut self) {
|
||||
if let Some(mut process) = self.running.take() {
|
||||
process.cleanup().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn attempt_hot_reload(
|
||||
&mut self,
|
||||
modified_files: Vec<PathBuf>,
|
||||
@@ -237,7 +175,7 @@ impl AppRunner {
|
||||
}
|
||||
|
||||
// Otherwise, it might be an asset and we should look for it in all the running apps
|
||||
for runner in self.running.values() {
|
||||
if let Some(runner) = self.running.as_mut() {
|
||||
if let Some(bundled_name) = runner.hotreload_bundled_asset(&path).await {
|
||||
// todo(jon): don't hardcode this here
|
||||
let asset_relative = PathBuf::from("/assets/").join(bundled_name);
|
||||
@@ -329,27 +267,29 @@ impl AppRunner {
|
||||
}
|
||||
|
||||
pub(crate) async fn client_connected(&mut self) {
|
||||
for (platform, runner) in self.running.iter_mut() {
|
||||
// Assign the runtime asset dir to the runner
|
||||
if *platform == Platform::Ios {
|
||||
// xcrun simctl get_app_container booted com.dioxuslabs
|
||||
let res = tokio::process::Command::new("xcrun")
|
||||
.arg("simctl")
|
||||
.arg("get_app_container")
|
||||
.arg("booted")
|
||||
.arg(runner.app.build.krate.bundle_identifier())
|
||||
.output()
|
||||
.await;
|
||||
let Some(handle) = self.running.as_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Ok(res) = res {
|
||||
tracing::trace!("Using runtime asset dir: {:?}", res);
|
||||
// Assign the runtime asset dir to the runner
|
||||
if handle.app.build.build.platform() == Platform::Ios {
|
||||
// xcrun simctl get_app_container booted com.dioxuslabs
|
||||
let res = tokio::process::Command::new("xcrun")
|
||||
.arg("simctl")
|
||||
.arg("get_app_container")
|
||||
.arg("booted")
|
||||
.arg(handle.app.build.krate.bundle_identifier())
|
||||
.output()
|
||||
.await;
|
||||
|
||||
if let Ok(out) = String::from_utf8(res.stdout) {
|
||||
let out = out.trim();
|
||||
if let Ok(res) = res {
|
||||
tracing::trace!("Using runtime asset dir: {:?}", res);
|
||||
|
||||
tracing::trace!("Setting Runtime asset dir: {out:?}");
|
||||
runner.runtime_asst_dir = Some(PathBuf::from(out));
|
||||
}
|
||||
if let Ok(out) = String::from_utf8(res.stdout) {
|
||||
let out = out.trim();
|
||||
|
||||
tracing::trace!("Setting Runtime asset dir: {out:?}");
|
||||
handle.runtime_asst_dir = Some(PathBuf::from(out));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -388,4 +328,10 @@ impl AppRunner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_session_cache(&self) {
|
||||
let cache_dir = self.krate.session_cache_dir();
|
||||
_ = std::fs::remove_dir_all(&cache_dir);
|
||||
_ = std::fs::create_dir_all(&cache_dir);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user