debugger support for dx (#3814)
* add vscode debugger for apps * todo: need to disconnect when rebuilding * update debugger * add uri handler to remote launch the debugger * attach * dont panic * fixed it - hung processes are not errors * wip: android debugger * clean it up a bit * revert ios stdout/stderr * wire up pid from ios * use target family instead of target_arch * fix android * bring back softkill * typo
This commit is contained in:
1 parent
eb15b42ab1
commit
b2bd1f48d4
13 files changed
+305
-41
No files matched your search
@@ -1,10 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform, ProgressRx, ProgressTx,
|
serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform,
|
||||||
Result, StructuredOutput,
|
ProgressRx, ProgressTx, Result, StructuredOutput,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use dioxus_cli_opt::process_file_to;
|
use dioxus_cli_opt::process_file_to;
|
||||||
use futures_util::{future::OptionFuture, pin_mut, FutureExt};
|
use futures_util::{future::OptionFuture, pin_mut, FutureExt};
|
||||||
|
use itertools::Itertools;
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
@@ -91,6 +92,9 @@ pub(crate) struct AppBuilder {
|
|||||||
pub compile_end: Option<Instant>,
|
pub compile_end: Option<Instant>,
|
||||||
pub bundle_start: Option<Instant>,
|
pub bundle_start: Option<Instant>,
|
||||||
pub bundle_end: Option<Instant>,
|
pub bundle_end: Option<Instant>,
|
||||||
|
|
||||||
|
/// The debugger for the app - must be enabled with the `d` key
|
||||||
|
pub(crate) pid: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppBuilder {
|
impl AppBuilder {
|
||||||
@@ -156,6 +160,7 @@ impl AppBuilder {
|
|||||||
entropy_app_exe: None,
|
entropy_app_exe: None,
|
||||||
artifacts: None,
|
artifacts: None,
|
||||||
patch_cache: None,
|
patch_cache: None,
|
||||||
|
pid: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,12 +188,16 @@ impl AppBuilder {
|
|||||||
StderrReceived { msg }
|
StderrReceived { msg }
|
||||||
},
|
},
|
||||||
Some(status) = OptionFuture::from(self.child.as_mut().map(|f| f.wait())) => {
|
Some(status) = OptionFuture::from(self.child.as_mut().map(|f| f.wait())) => {
|
||||||
// Panicking here is on purpose. If the task crashes due to a JoinError (a panic),
|
match status {
|
||||||
// we want to propagate that panic up to the serve controller.
|
Ok(status) => {
|
||||||
let status = status.unwrap();
|
self.child = None;
|
||||||
self.child = None;
|
ProcessExited { status }
|
||||||
|
},
|
||||||
ProcessExited { status }
|
Err(err) => {
|
||||||
|
let () = futures_util::future::pending().await;
|
||||||
|
ProcessWaitFailed { err }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -263,6 +272,7 @@ impl AppBuilder {
|
|||||||
StdoutReceived { .. } => {}
|
StdoutReceived { .. } => {}
|
||||||
StderrReceived { .. } => {}
|
StderrReceived { .. } => {}
|
||||||
ProcessExited { .. } => {}
|
ProcessExited { .. } => {}
|
||||||
|
ProcessWaitFailed { .. } => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
update
|
update
|
||||||
@@ -402,6 +412,7 @@ impl AppBuilder {
|
|||||||
BuilderUpdate::StdoutReceived { .. } => {}
|
BuilderUpdate::StdoutReceived { .. } => {}
|
||||||
BuilderUpdate::StderrReceived { .. } => {}
|
BuilderUpdate::StderrReceived { .. } => {}
|
||||||
BuilderUpdate::ProcessExited { .. } => {}
|
BuilderUpdate::ProcessExited { .. } => {}
|
||||||
|
BuilderUpdate::ProcessWaitFailed { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,7 +475,7 @@ impl AppBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We try to use stdin/stdout to communicate with the app
|
// We try to use stdin/stdout to communicate with the app
|
||||||
let running_process = match self.build.platform {
|
match self.build.platform {
|
||||||
// Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead
|
// Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead
|
||||||
// use use the websocket to communicate with it. I wish we could merge the concepts here,
|
// use use the websocket to communicate with it. I wish we could merge the concepts here,
|
||||||
// like say, opening the socket as a subprocess, but alas, it's simpler to do that somewhere else.
|
// like say, opening the socket as a subprocess, but alas, it's simpler to do that somewhere else.
|
||||||
@@ -473,15 +484,12 @@ impl AppBuilder {
|
|||||||
if open_browser {
|
if open_browser {
|
||||||
self.open_web(open_address.unwrap_or(devserver_ip));
|
self.open_web(open_address.unwrap_or(devserver_ip));
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform::Ios => Some(self.open_ios_sim(envs).await?),
|
Platform::Ios => self.open_ios_sim(envs).await?,
|
||||||
|
|
||||||
Platform::Android => {
|
Platform::Android => {
|
||||||
self.open_android_sim(false, devserver_ip, envs).await?;
|
self.open_android_sim(false, devserver_ip, envs).await?;
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// These are all just basically running the main exe, but with slightly different resource dir paths
|
// These are all just basically running the main exe, but with slightly different resource dir paths
|
||||||
@@ -489,18 +497,9 @@ impl AppBuilder {
|
|||||||
| Platform::MacOS
|
| Platform::MacOS
|
||||||
| Platform::Windows
|
| Platform::Windows
|
||||||
| Platform::Linux
|
| Platform::Linux
|
||||||
| Platform::Liveview => Some(self.open_with_main_exe(envs)?),
|
| Platform::Liveview => self.open_with_main_exe(envs)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we have a running process, we need to attach to it and wait for its outputs
|
|
||||||
if let Some(mut child) = running_process {
|
|
||||||
let stdout = BufReader::new(child.stdout.take().unwrap());
|
|
||||||
let stderr = BufReader::new(child.stderr.take().unwrap());
|
|
||||||
self.stdout = Some(stdout.lines());
|
|
||||||
self.stderr = Some(stderr.lines());
|
|
||||||
self.child = Some(child);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.builds_opened += 1;
|
self.builds_opened += 1;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -728,19 +727,25 @@ impl AppBuilder {
|
|||||||
/// paths right now, but they will when we start to enable things like swift integration.
|
/// paths right now, but they will when we start to enable things like swift integration.
|
||||||
///
|
///
|
||||||
/// Server/liveview/desktop are all basically the same, though
|
/// Server/liveview/desktop are all basically the same, though
|
||||||
fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<Child> {
|
fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<()> {
|
||||||
let main_exe = self.app_exe();
|
let main_exe = self.app_exe();
|
||||||
|
|
||||||
tracing::debug!("Opening app with main exe: {main_exe:?}");
|
tracing::debug!("Opening app with main exe: {main_exe:?}");
|
||||||
|
|
||||||
let child = Command::new(main_exe)
|
let mut child = Command::new(main_exe)
|
||||||
.envs(envs)
|
.envs(envs)
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.kill_on_drop(true)
|
.kill_on_drop(true)
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
Ok(child)
|
let stdout = BufReader::new(child.stdout.take().unwrap());
|
||||||
|
let stderr = BufReader::new(child.stderr.take().unwrap());
|
||||||
|
self.stdout = Some(stdout.lines());
|
||||||
|
self.stderr = Some(stderr.lines());
|
||||||
|
self.child = Some(child);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the web app by opening the browser to the given address.
|
/// Open the web app by opening the browser to the given address.
|
||||||
@@ -765,7 +770,7 @@ impl AppBuilder {
|
|||||||
///
|
///
|
||||||
/// TODO(jon): we should probably check if there's a simulator running before trying to install,
|
/// TODO(jon): we should probably check if there's a simulator running before trying to install,
|
||||||
/// and open the simulator if we have to.
|
/// and open the simulator if we have to.
|
||||||
async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<Child> {
|
async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<()> {
|
||||||
tracing::debug!("Installing app to simulator {:?}", self.build.root_dir());
|
tracing::debug!("Installing app to simulator {:?}", self.build.root_dir());
|
||||||
|
|
||||||
let res = Command::new("xcrun")
|
let res = Command::new("xcrun")
|
||||||
@@ -784,7 +789,7 @@ impl AppBuilder {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (format!("SIMCTL_CHILD_{k}"), v.clone()));
|
.map(|(k, v)| (format!("SIMCTL_CHILD_{k}"), v.clone()));
|
||||||
|
|
||||||
let child = Command::new("xcrun")
|
let mut child = Command::new("xcrun")
|
||||||
.arg("simctl")
|
.arg("simctl")
|
||||||
.arg("launch")
|
.arg("launch")
|
||||||
.arg("--console")
|
.arg("--console")
|
||||||
@@ -796,7 +801,13 @@ impl AppBuilder {
|
|||||||
.kill_on_drop(true)
|
.kill_on_drop(true)
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
Ok(child)
|
let stdout = BufReader::new(child.stdout.take().unwrap());
|
||||||
|
let stderr = BufReader::new(child.stderr.take().unwrap());
|
||||||
|
self.stdout = Some(stdout.lines());
|
||||||
|
self.stderr = Some(stderr.lines());
|
||||||
|
self.child = Some(child);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We have this whole thing figured out, but we don't actually use it yet.
|
/// We have this whole thing figured out, but we don't actually use it yet.
|
||||||
@@ -1339,4 +1350,173 @@ We checked the folder: {}
|
|||||||
pub(crate) fn can_receive_hotreloads(&self) -> bool {
|
pub(crate) fn can_receive_hotreloads(&self) -> bool {
|
||||||
matches!(&self.stage, BuildStage::Success | BuildStage::Failed)
|
matches!(&self.stage, BuildStage::Success | BuildStage::Failed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn open_debugger(&mut self, server: &WebServer) -> Result<()> {
|
||||||
|
let url = match self.build.platform {
|
||||||
|
Platform::MacOS
|
||||||
|
| Platform::Windows
|
||||||
|
| Platform::Linux
|
||||||
|
| Platform::Server
|
||||||
|
| Platform::Liveview => {
|
||||||
|
let Some(Some(pid)) = self.child.as_mut().map(|f| f.id()) else {
|
||||||
|
tracing::warn!("No process to attach debugger to");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{}}}",
|
||||||
|
pid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::Web => {
|
||||||
|
// code --open-url "vscode://DioxusLabs.dioxus/debugger?uri=http://127.0.0.1:8080"
|
||||||
|
// todo - debugger could open to the *current* page afaik we don't have a way to have that info
|
||||||
|
let address = server.devserver_address();
|
||||||
|
let base_path = self.build.config.web.app.base_path.clone();
|
||||||
|
let https = self.build.config.web.https.enabled.unwrap_or_default();
|
||||||
|
let protocol = if https { "https" } else { "http" };
|
||||||
|
let base_path = match base_path.as_deref() {
|
||||||
|
Some(base_path) => format!("/{}", base_path.trim_matches('/')),
|
||||||
|
None => "".to_owned(),
|
||||||
|
};
|
||||||
|
format!("vscode://DioxusLabs.dioxus/debugger?uri={protocol}://{address}{base_path}")
|
||||||
|
}
|
||||||
|
|
||||||
|
Platform::Ios => {
|
||||||
|
let Some(pid) = self.pid else {
|
||||||
|
tracing::warn!("No process to attach debugger to");
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/53733781/how-do-i-use-lldb-to-debug-c-code-on-android-on-command-line/64997332#64997332
|
||||||
|
// https://android.googlesource.com/platform/development/+/refs/heads/main/scripts/gdbclient.py
|
||||||
|
// run lldbserver on the device and then connect
|
||||||
|
//
|
||||||
|
// # TODO: https://code.visualstudio.com/api/references/vscode-api#debug and
|
||||||
|
// # https://code.visualstudio.com/api/extension-guides/debugger-extension and
|
||||||
|
// # https://github.com/vadimcn/vscode-lldb/blob/6b775c439992b6615e92f4938ee4e211f1b060cf/extension/pickProcess.ts#L6
|
||||||
|
//
|
||||||
|
// res = {
|
||||||
|
// "name": "(lldbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port),
|
||||||
|
// "type": "lldb",
|
||||||
|
// "request": "custom",
|
||||||
|
// "relativePathBase": root,
|
||||||
|
// "sourceMap": { "/b/f/w" : root, '': root, '.': root },
|
||||||
|
// "initCommands": ['settings append target.exec-search-paths {}'.format(' '.join(solib_search_path))],
|
||||||
|
// "targetCreateCommands": ["target create {}".format(binary_name),
|
||||||
|
// "target modules search-paths add / {}/".format(sysroot)],
|
||||||
|
// "processCreateCommands": ["gdb-remote {}".format(str(port))]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// https://github.com/vadimcn/codelldb/issues/213
|
||||||
|
//
|
||||||
|
// lots of pain to figure this out:
|
||||||
|
//
|
||||||
|
// (lldb) image add target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so
|
||||||
|
// (lldb) settings append target.exec-search-paths target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so
|
||||||
|
// (lldb) process handle SIGSEGV --pass true --stop false --notify true (otherwise the java threads cause crash)
|
||||||
|
//
|
||||||
|
Platform::Android => {
|
||||||
|
// adb push ./sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/20/lib/linux/aarch64/lldb-server /tmp
|
||||||
|
// adb shell "/tmp/lldb-server --server --listen ..."
|
||||||
|
// "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'connect','port': {}}}",
|
||||||
|
// format!(
|
||||||
|
// "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}"
|
||||||
|
// )
|
||||||
|
let tools = &self.build.workspace.android_tools()?;
|
||||||
|
|
||||||
|
// get the pid of the app
|
||||||
|
let pid = Command::new(&tools.adb)
|
||||||
|
.arg("shell")
|
||||||
|
.arg("pidof")
|
||||||
|
.arg(self.build.bundle_identifier())
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||||
|
.and_then(|s| s.trim().parse::<u32>().ok())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// copy the lldb-server to the device
|
||||||
|
let lldb_server = tools
|
||||||
|
.android_tools_dir()
|
||||||
|
.parent()
|
||||||
|
.unwrap()
|
||||||
|
.join("lib")
|
||||||
|
.join("clang")
|
||||||
|
.join("20")
|
||||||
|
.join("lib")
|
||||||
|
.join("linux")
|
||||||
|
.join("aarch64")
|
||||||
|
.join("lldb-server");
|
||||||
|
|
||||||
|
tracing::info!("Copying lldb-server to device: {lldb_server:?}");
|
||||||
|
|
||||||
|
_ = Command::new(&tools.adb)
|
||||||
|
.arg("push")
|
||||||
|
.arg(lldb_server)
|
||||||
|
.arg("/tmp/lldb-server")
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Forward requests on 10086 to the device
|
||||||
|
_ = Command::new(&tools.adb)
|
||||||
|
.arg("forward")
|
||||||
|
.arg("tcp:10086")
|
||||||
|
.arg("tcp:10086")
|
||||||
|
.output()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// start the server - running it multiple times will make the subsequent ones fail (which is fine)
|
||||||
|
_ = Command::new(&tools.adb)
|
||||||
|
.arg("shell")
|
||||||
|
.arg(r#"cd /tmp && ./lldb-server platform --server --listen '*:10086'"#)
|
||||||
|
.kill_on_drop(false)
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
let program_path = self.build.main_exe();
|
||||||
|
format!(
|
||||||
|
r#"vscode://vadimcn.vscode-lldb/launch/config?{{
|
||||||
|
'name':'Attach to Android',
|
||||||
|
'type':'lldb',
|
||||||
|
'request':'attach',
|
||||||
|
'pid': '{pid}',
|
||||||
|
'processCreateCommands': [
|
||||||
|
'platform select remote-android',
|
||||||
|
'platform connect connect://localhost:10086',
|
||||||
|
'settings set target.inherit-env false',
|
||||||
|
'settings set target.inline-breakpoint-strategy always',
|
||||||
|
'settings set target.process.thread.step-avoid-regexp \"JavaBridge|JDWP|Binder|ReferenceQueueDaemon\"',
|
||||||
|
'process handle SIGSEGV --pass true --stop false --notify true"',
|
||||||
|
'settings append target.exec-search-paths {program_path}',
|
||||||
|
'attach --pid {pid}',
|
||||||
|
'continue'
|
||||||
|
]
|
||||||
|
}}"#,
|
||||||
|
program_path = program_path.display(),
|
||||||
|
)
|
||||||
|
.lines()
|
||||||
|
.map(|line| line.trim())
|
||||||
|
.join("")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("Opening debugger for [{}]: {url}", self.build.platform);
|
||||||
|
|
||||||
|
_ = tokio::process::Command::new("code")
|
||||||
|
.arg("--open-url")
|
||||||
|
.arg(url)
|
||||||
|
.spawn();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,12 @@ pub enum BuilderUpdate {
|
|||||||
ProcessExited {
|
ProcessExited {
|
||||||
status: ExitStatus,
|
status: ExitStatus,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Waiting for the process failed. This might be because it's hung or being debugged.
|
||||||
|
/// This is not the same as the process exiting, so it should just be logged but not treated as an error.
|
||||||
|
ProcessWaitFailed {
|
||||||
|
err: std::io::Error,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuildContext {
|
impl BuildContext {
|
||||||
|
|||||||
@@ -2128,6 +2128,12 @@ impl BuildRequest {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for debuggability, we need to make sure android studio can properly understand our build
|
||||||
|
// https://stackoverflow.com/questions/68481401/debugging-a-prebuilt-shared-library-in-android-studio
|
||||||
|
if self.platform == Platform::Android {
|
||||||
|
cargo_args.push("-Clink-arg=-Wl,--build-id=sha1".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
// Handle frameworks/dylibs by setting the rpath
|
// Handle frameworks/dylibs by setting the rpath
|
||||||
// This is dependent on the bundle structure - in this case, appimage and appbundle for mac/linux
|
// This is dependent on the bundle structure - in this case, appimage and appbundle for mac/linux
|
||||||
// todo: we need to figure out what to do for windows
|
// todo: we need to figure out what to do for windows
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ impl RunArgs {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
BuilderUpdate::ProcessWaitFailed { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -90,7 +90,11 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
|||||||
|
|
||||||
// Run the server in the background
|
// Run the server in the background
|
||||||
// Waiting for updates here lets us tap into when clients are added/removed
|
// Waiting for updates here lets us tap into when clients are added/removed
|
||||||
ServeUpdate::NewConnection { id, aslr_reference } => {
|
ServeUpdate::NewConnection {
|
||||||
|
id,
|
||||||
|
aslr_reference,
|
||||||
|
pid,
|
||||||
|
} => {
|
||||||
devserver
|
devserver
|
||||||
.send_hotreload(builder.applied_hot_reload_changes(BuildId::CLIENT))
|
.send_hotreload(builder.applied_hot_reload_changes(BuildId::CLIENT))
|
||||||
.await;
|
.await;
|
||||||
@@ -101,7 +105,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.client_connected(id, aslr_reference).await;
|
builder.client_connected(id, aslr_reference, pid).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Received a message from the devtools server - currently we only use this for
|
// Received a message from the devtools server - currently we only use this for
|
||||||
@@ -173,6 +177,11 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
|||||||
tracing::error!("Application [{platform}] exited with error: {status}");
|
tracing::error!("Application [{platform}] exited with error: {status}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BuilderUpdate::ProcessWaitFailed { err } => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to wait for process - maybe it's hung or being debugged?: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +211,10 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) ->
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServeUpdate::OpenDebugger { id } => {
|
||||||
|
builder.open_debugger(&devserver, id).await;
|
||||||
|
}
|
||||||
|
|
||||||
ServeUpdate::Exit { error } => {
|
ServeUpdate::Exit { error } => {
|
||||||
_ = builder.shutdown().await;
|
_ = builder.shutdown().await;
|
||||||
_ = devserver.shutdown().await;
|
_ = devserver.shutdown().await;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
serve::{ansi_buffer::AnsiStringLine, ServeUpdate, WebServer},
|
serve::{ansi_buffer::AnsiStringLine, ServeUpdate, WebServer},
|
||||||
BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc,
|
BuildId, BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc,
|
||||||
};
|
};
|
||||||
use cargo_metadata::diagnostic::Diagnostic;
|
use cargo_metadata::diagnostic::Diagnostic;
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
@@ -245,6 +245,16 @@ impl Output {
|
|||||||
self.trace = !self.trace;
|
self.trace = !self.trace;
|
||||||
tracing::info!("Tracing is now {}", if self.trace { "on" } else { "off" });
|
tracing::info!("Tracing is now {}", if self.trace { "on" } else { "off" });
|
||||||
}
|
}
|
||||||
|
KeyCode::Char('D') => {
|
||||||
|
return Ok(Some(ServeUpdate::OpenDebugger {
|
||||||
|
id: BuildId::SERVER,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
KeyCode::Char('d') => {
|
||||||
|
return Ok(Some(ServeUpdate::OpenDebugger {
|
||||||
|
id: BuildId::CLIENT,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
KeyCode::Char('c') => {
|
KeyCode::Char('c') => {
|
||||||
stdout()
|
stdout()
|
||||||
|
|||||||
@@ -692,6 +692,7 @@ impl AppServer {
|
|||||||
&mut self,
|
&mut self,
|
||||||
build_id: BuildId,
|
build_id: BuildId,
|
||||||
aslr_reference: Option<u64>,
|
aslr_reference: Option<u64>,
|
||||||
|
pid: Option<u32>,
|
||||||
) {
|
) {
|
||||||
match build_id {
|
match build_id {
|
||||||
BuildId::CLIENT => {
|
BuildId::CLIENT => {
|
||||||
@@ -701,6 +702,9 @@ impl AppServer {
|
|||||||
if let Some(aslr_reference) = aslr_reference {
|
if let Some(aslr_reference) = aslr_reference {
|
||||||
self.client.aslr_reference = Some(aslr_reference);
|
self.client.aslr_reference = Some(aslr_reference);
|
||||||
}
|
}
|
||||||
|
if let Some(pid) = pid {
|
||||||
|
self.client.pid = Some(pid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BuildId::SERVER => {
|
BuildId::SERVER => {
|
||||||
@@ -970,6 +974,24 @@ impl AppServer {
|
|||||||
|
|
||||||
server.compiled_crates as f64 / server.expected_crates as f64
|
server.compiled_crates as f64 / server.expected_crates as f64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn open_debugger(&mut self, dev: &WebServer, build: BuildId) {
|
||||||
|
if self.use_hotpatch_engine {
|
||||||
|
tracing::warn!("Debugging symbols might not work properly with hotpatching enabled. Consider disabling hotpatching for debugging.");
|
||||||
|
}
|
||||||
|
|
||||||
|
match build {
|
||||||
|
BuildId::CLIENT => {
|
||||||
|
_ = self.client.open_debugger(dev).await;
|
||||||
|
}
|
||||||
|
BuildId::SERVER => {
|
||||||
|
if let Some(server) = self.server.as_mut() {
|
||||||
|
_ = server.open_debugger(dev).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bind a listener to any point and return it
|
/// Bind a listener to any point and return it
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ pub(crate) struct ConnectedWsClient {
|
|||||||
socket: WebSocket,
|
socket: WebSocket,
|
||||||
build_id: Option<BuildId>,
|
build_id: Option<BuildId>,
|
||||||
aslr_reference: Option<u64>,
|
aslr_reference: Option<u64>,
|
||||||
|
pid: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebServer {
|
impl WebServer {
|
||||||
@@ -146,12 +147,13 @@ impl WebServer {
|
|||||||
new_hot_reload_socket = &mut new_hot_reload_socket => {
|
new_hot_reload_socket = &mut new_hot_reload_socket => {
|
||||||
if let Some(new_socket) = new_hot_reload_socket {
|
if let Some(new_socket) = new_hot_reload_socket {
|
||||||
let aslr_reference = new_socket.aslr_reference;
|
let aslr_reference = new_socket.aslr_reference;
|
||||||
|
let pid = new_socket.pid;
|
||||||
let id = new_socket.build_id.unwrap_or(BuildId::CLIENT);
|
let id = new_socket.build_id.unwrap_or(BuildId::CLIENT);
|
||||||
|
|
||||||
drop(new_message);
|
drop(new_message);
|
||||||
self.hot_reload_sockets.push(new_socket);
|
self.hot_reload_sockets.push(new_socket);
|
||||||
|
|
||||||
return ServeUpdate::NewConnection { aslr_reference, id };
|
return ServeUpdate::NewConnection { aslr_reference, id, pid };
|
||||||
} else {
|
} else {
|
||||||
panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use");
|
panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use");
|
||||||
}
|
}
|
||||||
@@ -261,6 +263,7 @@ impl WebServer {
|
|||||||
BuilderUpdate::StdoutReceived { .. } => {}
|
BuilderUpdate::StdoutReceived { .. } => {}
|
||||||
BuilderUpdate::StderrReceived { .. } => {}
|
BuilderUpdate::StderrReceived { .. } => {}
|
||||||
BuilderUpdate::ProcessExited { .. } => {}
|
BuilderUpdate::ProcessExited { .. } => {}
|
||||||
|
BuilderUpdate::ProcessWaitFailed { .. } => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,6 +498,7 @@ fn build_devserver_router(
|
|||||||
struct ConnectionQuery {
|
struct ConnectionQuery {
|
||||||
aslr_reference: Option<u64>,
|
aslr_reference: Option<u64>,
|
||||||
build_id: Option<BuildId>,
|
build_id: Option<BuildId>,
|
||||||
|
pid: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup websocket endpoint - and pass in the extension layer immediately after
|
// Setup websocket endpoint - and pass in the extension layer immediately after
|
||||||
@@ -506,7 +510,7 @@ fn build_devserver_router(
|
|||||||
get(
|
get(
|
||||||
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>, query: Query<ConnectionQuery>| async move {
|
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>, query: Query<ConnectionQuery>| async move {
|
||||||
tracing::debug!("New devtool websocket connection: {:?}", query);
|
tracing::debug!("New devtool websocket connection: {:?}", query);
|
||||||
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id }) })
|
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id, pid: query.pid }) })
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -515,7 +519,7 @@ fn build_devserver_router(
|
|||||||
"/build_status",
|
"/build_status",
|
||||||
get(
|
get(
|
||||||
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>| async move {
|
|ws: WebSocketUpgrade, ext: Extension<UnboundedSender<ConnectedWsClient>>| async move {
|
||||||
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None }) })
|
ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None, pid: None }) })
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ pub(crate) enum ServeUpdate {
|
|||||||
NewConnection {
|
NewConnection {
|
||||||
id: BuildId,
|
id: BuildId,
|
||||||
aslr_reference: Option<u64>,
|
aslr_reference: Option<u64>,
|
||||||
|
pid: Option<u32>,
|
||||||
},
|
},
|
||||||
WsMessage {
|
WsMessage {
|
||||||
platform: Platform,
|
platform: Platform,
|
||||||
@@ -32,6 +33,10 @@ pub(crate) enum ServeUpdate {
|
|||||||
|
|
||||||
ToggleShouldRebuild,
|
ToggleShouldRebuild,
|
||||||
|
|
||||||
|
OpenDebugger {
|
||||||
|
id: BuildId,
|
||||||
|
},
|
||||||
|
|
||||||
Redraw,
|
Redraw,
|
||||||
|
|
||||||
TracingLog {
|
TracingLog {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), Pat
|
|||||||
/// Connect to the devserver and handle its messages with a callback.
|
/// Connect to the devserver and handle its messages with a callback.
|
||||||
///
|
///
|
||||||
/// This doesn't use any form of security or protocol, so it's not safe to expose to the internet.
|
/// This doesn't use any form of security or protocol, so it's not safe to expose to the internet.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
|
pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
|
||||||
let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {
|
let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {
|
||||||
return;
|
return;
|
||||||
@@ -64,7 +64,7 @@ pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
|
|||||||
/// This is intended to be used by non-dioxus projects that want to use hotpatching.
|
/// This is intended to be used by non-dioxus projects that want to use hotpatching.
|
||||||
///
|
///
|
||||||
/// To handle the full devserver protocol, use `connect` instead.
|
/// To handle the full devserver protocol, use `connect` instead.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
pub fn connect_subsecond() {
|
pub fn connect_subsecond() {
|
||||||
connect(|msg| {
|
connect(|msg| {
|
||||||
if let DevserverMsg::HotReload(hot_reload_msg) = msg {
|
if let DevserverMsg::HotReload(hot_reload_msg) = msg {
|
||||||
@@ -75,13 +75,14 @@ pub fn connect_subsecond() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
|
pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let uri = format!(
|
let uri = format!(
|
||||||
"{endpoint}?aslr_reference={}&build_id={}",
|
"{endpoint}?aslr_reference={}&build_id={}&pid={}",
|
||||||
subsecond::aslr_reference(),
|
subsecond::aslr_reference(),
|
||||||
dioxus_cli_config::build_id()
|
dioxus_cli_config::build_id(),
|
||||||
|
std::process::id()
|
||||||
);
|
);
|
||||||
|
|
||||||
let (mut websocket, _req) = match tungstenite::connect(uri) {
|
let (mut websocket, _req) = match tungstenite::connect(uri) {
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ tsconfig.lsif.json
|
|||||||
*.lsif
|
*.lsif
|
||||||
*.db
|
*.db
|
||||||
*.vsix
|
*.vsix
|
||||||
|
pkg/
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "dioxus",
|
"name": "dioxus",
|
||||||
"displayName": "Dioxus",
|
"displayName": "Dioxus",
|
||||||
"description": "Useful tools for working with Dioxus",
|
"description": "Useful tools for working with Dioxus",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"publisher": "DioxusLabs",
|
"publisher": "DioxusLabs",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
vscode.commands.registerCommand('extension.formatRsxDocument', formatRsxDocument),
|
vscode.commands.registerCommand('extension.formatRsxDocument', formatRsxDocument),
|
||||||
vscode.workspace.onWillSaveTextDocument(fmtDocumentOnSave)
|
vscode.workspace.onWillSaveTextDocument(fmtDocumentOnSave)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(vscode.window.registerUriHandler(new UriLaunchServer()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function translate(component: boolean) {
|
function translate(component: boolean) {
|
||||||
@@ -171,3 +174,15 @@ function fmtDocument(document: vscode.TextDocument) {
|
|||||||
vscode.window.showWarningMessage(`Errors occurred while formatting. Make sure you have the most recent Dioxus-CLI installed! \n${error}`);
|
vscode.window.showWarningMessage(`Errors occurred while formatting. Make sure you have the most recent Dioxus-CLI installed! \n${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UriLaunchServer implements vscode.UriHandler {
|
||||||
|
handleUri(uri: vscode.Uri): vscode.ProviderResult<void> {
|
||||||
|
if (uri.path === '/debugger') {
|
||||||
|
let query = decodeURIComponent(uri.query);
|
||||||
|
let params = new URLSearchParams(query);
|
||||||
|
let route = params.get('uri');
|
||||||
|
vscode.window.showInformationMessage(`Opening Chrome debugger: ${route}`);
|
||||||
|
vscode.commands.executeCommand('extension.js-debug.debugLink', route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user