Draft auto-update process for comet in devtools
This commit is contained in:
parent
a105ad4f9f
commit
267583c2a9
6 changed files with 250 additions and 107 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2464,6 +2464,7 @@ dependencies = [
|
||||||
"iced_debug",
|
"iced_debug",
|
||||||
"iced_program",
|
"iced_program",
|
||||||
"iced_widget",
|
"iced_widget",
|
||||||
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ use crate::futures::Subscription;
|
||||||
|
|
||||||
pub use internal::Span;
|
pub use internal::Span;
|
||||||
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Primitive {
|
pub enum Primitive {
|
||||||
Quad,
|
Quad,
|
||||||
|
|
@ -36,8 +34,8 @@ pub fn init(name: &str) {
|
||||||
internal::init(name);
|
internal::init(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_comet() -> Result<(), io::Error> {
|
pub fn quit() -> bool {
|
||||||
internal::toggle_comet()
|
internal::quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
|
pub fn theme_changed(f: impl FnOnce() -> Option<theme::Palette>) {
|
||||||
|
|
@ -118,8 +116,6 @@ mod internal {
|
||||||
use beacon::client::{self, Client};
|
use beacon::client::{self, Client};
|
||||||
use beacon::span;
|
use beacon::span;
|
||||||
|
|
||||||
use std::io;
|
|
||||||
use std::process;
|
|
||||||
use std::sync::atomic::{self, AtomicBool, AtomicUsize};
|
use std::sync::atomic::{self, AtomicBool, AtomicUsize};
|
||||||
use std::sync::{LazyLock, RwLock};
|
use std::sync::{LazyLock, RwLock};
|
||||||
|
|
||||||
|
|
@ -129,25 +125,13 @@ mod internal {
|
||||||
name.clone_into(&mut NAME.write().expect("Write application name"));
|
name.clone_into(&mut NAME.write().expect("Write application name"));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle_comet() -> Result<(), io::Error> {
|
pub fn quit() -> bool {
|
||||||
if BEACON.is_connected() {
|
if BEACON.is_connected() {
|
||||||
BEACON.quit();
|
BEACON.quit();
|
||||||
|
|
||||||
Ok(())
|
true
|
||||||
} else {
|
} else {
|
||||||
let _ = process::Command::new("iced_comet")
|
false
|
||||||
.stdin(process::Stdio::null())
|
|
||||||
.stdout(process::Stdio::null())
|
|
||||||
.stderr(process::Stdio::null())
|
|
||||||
.spawn()?;
|
|
||||||
|
|
||||||
if let Some(palette) =
|
|
||||||
LAST_PALETTE.read().expect("Read last palette").as_ref()
|
|
||||||
{
|
|
||||||
log(client::Event::ThemeChanged(*palette));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,8 +307,8 @@ mod internal {
|
||||||
|
|
||||||
pub fn init(_name: &str) {}
|
pub fn init(_name: &str) {}
|
||||||
|
|
||||||
pub fn toggle_comet() -> Result<(), io::Error> {
|
pub fn quit() -> bool {
|
||||||
Ok(())
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn theme_changed(_f: impl FnOnce() -> Option<theme::Palette>) {}
|
pub fn theme_changed(_f: impl FnOnce() -> Option<theme::Palette>) {}
|
||||||
|
|
|
||||||
|
|
@ -20,3 +20,5 @@ time-travel = ["iced_program/time-travel"]
|
||||||
iced_debug.workspace = true
|
iced_debug.workspace = true
|
||||||
iced_program.workspace = true
|
iced_program.workspace = true
|
||||||
iced_widget.workspace = true
|
iced_widget.workspace = true
|
||||||
|
|
||||||
|
log.workspace = true
|
||||||
|
|
|
||||||
108
devtools/src/comet.rs
Normal file
108
devtools/src/comet.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
use crate::executor;
|
||||||
|
use crate::runtime::Task;
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use std::process;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub const COMPATIBLE_REVISION: &str =
|
||||||
|
"69dd2283886dccdaa1ee6e1c274af62f7250bc38";
|
||||||
|
|
||||||
|
pub fn launch() -> Task<Result<(), Error>> {
|
||||||
|
executor::try_spawn_blocking(|mut sender| {
|
||||||
|
let cargo_install = process::Command::new("cargo")
|
||||||
|
.args(["install", "--list"])
|
||||||
|
.output()?;
|
||||||
|
|
||||||
|
let installed_packages = String::from_utf8_lossy(&cargo_install.stdout);
|
||||||
|
|
||||||
|
for line in installed_packages.lines() {
|
||||||
|
if !line.starts_with("iced_comet ") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((_, revision)) = line.rsplit_once("?rev=") else {
|
||||||
|
return Err(Error::Outdated { revision: None });
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some((revision, _)) = revision.rsplit_once("#") else {
|
||||||
|
return Err(Error::Outdated { revision: None });
|
||||||
|
};
|
||||||
|
|
||||||
|
if revision != COMPATIBLE_REVISION {
|
||||||
|
return Err(Error::Outdated {
|
||||||
|
revision: Some(revision.to_owned()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = process::Command::new("iced_comet")
|
||||||
|
.stdin(process::Stdio::null())
|
||||||
|
.stdout(process::Stdio::null())
|
||||||
|
.stderr(process::Stdio::null())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let _ = sender.try_send(());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(Error::NotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn install() -> Task<Result<Installation, Error>> {
|
||||||
|
executor::try_spawn_blocking(|mut sender| {
|
||||||
|
use std::io::{BufRead, BufReader};
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
let install = Command::new("cargo")
|
||||||
|
.args([
|
||||||
|
"install",
|
||||||
|
"--locked",
|
||||||
|
"--git",
|
||||||
|
"https://github.com/iced-rs/comet.git",
|
||||||
|
"--rev",
|
||||||
|
COMPATIBLE_REVISION,
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
|
.stdout(Stdio::null())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
let mut stderr =
|
||||||
|
BufReader::new(install.stderr.expect("stderr must be piped"));
|
||||||
|
|
||||||
|
let mut log = String::new();
|
||||||
|
|
||||||
|
while let Ok(n) = stderr.read_line(&mut log) {
|
||||||
|
if n == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.try_send(Installation::Logged(log.clone()));
|
||||||
|
log.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = sender.try_send(Installation::Finished);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Installation {
|
||||||
|
Logged(String),
|
||||||
|
Finished,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Error {
|
||||||
|
NotFound,
|
||||||
|
Outdated { revision: Option<String> },
|
||||||
|
IoFailed(Arc<io::Error>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error {
|
||||||
|
fn from(error: io::Error) -> Self {
|
||||||
|
Self::IoFailed(Arc::new(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::futures::futures::channel::mpsc;
|
use crate::futures::futures::channel::mpsc;
|
||||||
|
use crate::futures::futures::channel::oneshot;
|
||||||
|
use crate::futures::futures::stream::{self, StreamExt};
|
||||||
use crate::runtime::Task;
|
use crate::runtime::Task;
|
||||||
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
@ -17,3 +19,25 @@ where
|
||||||
|
|
||||||
Task::stream(receiver)
|
Task::stream(receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn try_spawn_blocking<T, E>(
|
||||||
|
f: impl FnOnce(mpsc::Sender<T>) -> Result<(), E> + Send + 'static,
|
||||||
|
) -> Task<Result<T, E>>
|
||||||
|
where
|
||||||
|
T: Send + 'static,
|
||||||
|
E: Send + 'static,
|
||||||
|
{
|
||||||
|
let (sender, receiver) = mpsc::channel(1);
|
||||||
|
let (error_sender, error_receiver) = oneshot::channel();
|
||||||
|
|
||||||
|
let _ = thread::spawn(move || {
|
||||||
|
if let Err(error) = f(sender) {
|
||||||
|
let _ = error_sender.send(Err(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Task::stream(stream::select(
|
||||||
|
receiver.map(Ok),
|
||||||
|
stream::once(error_receiver).filter_map(async |result| result.ok()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use iced_widget::core;
|
||||||
use iced_widget::runtime;
|
use iced_widget::runtime;
|
||||||
use iced_widget::runtime::futures;
|
use iced_widget::runtime::futures;
|
||||||
|
|
||||||
|
mod comet;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod time_machine;
|
mod time_machine;
|
||||||
|
|
||||||
|
|
@ -24,7 +25,6 @@ use crate::widget::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
pub fn attach(program: impl Program + 'static) -> impl Program {
|
pub fn attach(program: impl Program + 'static) -> impl Program {
|
||||||
|
|
@ -124,9 +124,9 @@ where
|
||||||
enum Message {
|
enum Message {
|
||||||
HideNotification,
|
HideNotification,
|
||||||
ToggleComet,
|
ToggleComet,
|
||||||
|
CometLaunched(Result<(), comet::Error>),
|
||||||
InstallComet,
|
InstallComet,
|
||||||
InstallationLogged(String),
|
InstallationProgressed(Result<comet::Installation, comet::Error>),
|
||||||
InstallationFinished,
|
|
||||||
CancelSetup,
|
CancelSetup,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,10 +136,15 @@ enum Mode {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Setup {
|
enum Setup {
|
||||||
Idle,
|
Idle { goal: Goal },
|
||||||
Running { logs: Vec<String> },
|
Running { logs: Vec<String> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Goal {
|
||||||
|
Installation,
|
||||||
|
Update { revision: Option<String> },
|
||||||
|
}
|
||||||
|
|
||||||
impl<P> DevTools<P>
|
impl<P> DevTools<P>
|
||||||
where
|
where
|
||||||
P: Program + 'static,
|
P: Program + 'static,
|
||||||
|
|
@ -174,12 +179,34 @@ where
|
||||||
}
|
}
|
||||||
Message::ToggleComet => {
|
Message::ToggleComet => {
|
||||||
if let Mode::Setup(setup) = &self.mode {
|
if let Mode::Setup(setup) = &self.mode {
|
||||||
if matches!(setup, Setup::Idle) {
|
if matches!(setup, Setup::Idle { .. }) {
|
||||||
self.mode = Mode::None;
|
self.mode = Mode::None;
|
||||||
}
|
}
|
||||||
} else if let Err(error) = debug::toggle_comet() {
|
|
||||||
if error.kind() == io::ErrorKind::NotFound {
|
Task::none()
|
||||||
self.mode = Mode::Setup(Setup::Idle);
|
} else if debug::quit() {
|
||||||
|
Task::none()
|
||||||
|
} else {
|
||||||
|
comet::launch()
|
||||||
|
.map(Message::CometLaunched)
|
||||||
|
.map(Event::Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::CometLaunched(Ok(())) => Task::none(),
|
||||||
|
Message::CometLaunched(Err(error)) => {
|
||||||
|
match error {
|
||||||
|
comet::Error::NotFound => {
|
||||||
|
self.mode = Mode::Setup(Setup::Idle {
|
||||||
|
goal: Goal::Installation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
comet::Error::Outdated { revision } => {
|
||||||
|
self.mode = Mode::Setup(Setup::Idle {
|
||||||
|
goal: Goal::Update { revision },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
comet::Error::IoFailed(error) => {
|
||||||
|
log::error!("comet failed to run: {error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,62 +216,30 @@ where
|
||||||
self.mode =
|
self.mode =
|
||||||
Mode::Setup(Setup::Running { logs: Vec::new() });
|
Mode::Setup(Setup::Running { logs: Vec::new() });
|
||||||
|
|
||||||
executor::spawn_blocking(|mut sender| {
|
comet::install()
|
||||||
use std::io::{BufRead, BufReader};
|
.map(Message::InstallationProgressed)
|
||||||
use std::process::{Command, Stdio};
|
.map(Event::Message)
|
||||||
|
}
|
||||||
|
|
||||||
let Ok(install) = Command::new("cargo")
|
Message::InstallationProgressed(Ok(installation)) => {
|
||||||
.args([
|
let Mode::Setup(Setup::Running { logs }) = &mut self.mode
|
||||||
"install",
|
else {
|
||||||
"--locked",
|
return Task::none();
|
||||||
"--git",
|
};
|
||||||
"https://github.com/iced-rs/comet.git",
|
|
||||||
"--rev",
|
|
||||||
"eb114ba564a872acbd95e337d13e55f5f667b2f3",
|
|
||||||
])
|
|
||||||
.stdin(Stdio::null())
|
|
||||||
.stdout(Stdio::null())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut stderr = BufReader::new(
|
match installation {
|
||||||
install.stderr.expect("stderr must be piped"),
|
comet::Installation::Logged(log) => {
|
||||||
);
|
logs.push(log);
|
||||||
|
Task::none()
|
||||||
let mut log = String::new();
|
}
|
||||||
|
comet::Installation::Finished => {
|
||||||
while let Ok(n) = stderr.read_line(&mut log) {
|
self.mode = Mode::None;
|
||||||
if n == 0 {
|
comet::launch().discard()
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = sender.try_send(
|
|
||||||
Message::InstallationLogged(log.clone()),
|
|
||||||
);
|
|
||||||
|
|
||||||
log.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = sender.try_send(Message::InstallationFinished);
|
|
||||||
})
|
|
||||||
.map(Event::Message)
|
|
||||||
}
|
|
||||||
Message::InstallationLogged(log) => {
|
|
||||||
if let Mode::Setup(Setup::Running { logs }) = &mut self.mode
|
|
||||||
{
|
|
||||||
logs.push(log);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Task::none()
|
|
||||||
}
|
}
|
||||||
Message::InstallationFinished => {
|
Message::InstallationProgressed(_error) => {
|
||||||
self.mode = Mode::None;
|
// TODO
|
||||||
|
|
||||||
let _ = debug::toggle_comet();
|
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::CancelSetup => {
|
Message::CancelSetup => {
|
||||||
|
|
@ -317,40 +312,69 @@ where
|
||||||
Mode::None => None,
|
Mode::None => None,
|
||||||
Mode::Setup(setup) => {
|
Mode::Setup(setup) => {
|
||||||
let stage: Element<'_, _, Theme, P::Renderer> = match setup {
|
let stage: Element<'_, _, Theme, P::Renderer> = match setup {
|
||||||
Setup::Idle => {
|
Setup::Idle { goal } => {
|
||||||
let controls = row![
|
let controls = row![
|
||||||
button(text("Cancel").center().width(Fill))
|
button(text("Cancel").center().width(Fill))
|
||||||
.width(100)
|
.width(100)
|
||||||
.on_press(Message::CancelSetup)
|
.on_press(Message::CancelSetup)
|
||||||
.style(button::danger),
|
.style(button::danger),
|
||||||
horizontal_space(),
|
horizontal_space(),
|
||||||
button(text("Install").center().width(Fill))
|
button(
|
||||||
.width(100)
|
text(match goal {
|
||||||
.on_press(Message::InstallComet)
|
Goal::Installation => "Install",
|
||||||
.style(button::success),
|
Goal::Update { .. } => "Update",
|
||||||
|
})
|
||||||
|
.center()
|
||||||
|
.width(Fill)
|
||||||
|
)
|
||||||
|
.width(100)
|
||||||
|
.on_press(Message::InstallComet)
|
||||||
|
.style(button::success),
|
||||||
];
|
];
|
||||||
|
|
||||||
column![
|
let command = container(
|
||||||
text("comet is not installed!").size(20),
|
text(
|
||||||
"In order to display performance metrics, the \
|
"cargo install --locked \
|
||||||
comet debugger must be installed in your system.",
|
--git https://github.com/iced-rs/comet.git",
|
||||||
"The comet debugger is an official companion tool \
|
|
||||||
that helps you debug your iced applications.",
|
|
||||||
"Do you wish to install it with the following \
|
|
||||||
command?",
|
|
||||||
container(
|
|
||||||
text(
|
|
||||||
"cargo install --locked \
|
|
||||||
--git https://github.com/iced-rs/comet.git"
|
|
||||||
)
|
|
||||||
.size(14)
|
|
||||||
)
|
)
|
||||||
.width(Fill)
|
.size(14),
|
||||||
.padding(5)
|
)
|
||||||
.style(container::dark),
|
.width(Fill)
|
||||||
controls,
|
.padding(5)
|
||||||
]
|
.style(container::dark);
|
||||||
.spacing(20)
|
|
||||||
|
match goal {
|
||||||
|
Goal::Installation => column![
|
||||||
|
text("comet is not installed!").size(20),
|
||||||
|
"In order to display performance \
|
||||||
|
metrics, the comet debugger must \
|
||||||
|
be installed in your system.",
|
||||||
|
"The comet debugger is an official \
|
||||||
|
companion tool that helps you debug \
|
||||||
|
your iced applications.",
|
||||||
|
"Do you wish to install it with the \
|
||||||
|
following command?",
|
||||||
|
command,
|
||||||
|
controls,
|
||||||
|
]
|
||||||
|
.spacing(20),
|
||||||
|
Goal::Update { revision } => column![
|
||||||
|
text("comet is out of date!").size(20),
|
||||||
|
text!(
|
||||||
|
"The installed revision is \"{current}\", \
|
||||||
|
but the latest compatible is \"{compatible}\".",
|
||||||
|
current = revision
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("Unknown"),
|
||||||
|
compatible = comet::COMPATIBLE_REVISION,
|
||||||
|
),
|
||||||
|
"Do you wish to update it with the following \
|
||||||
|
command?",
|
||||||
|
command,
|
||||||
|
controls,
|
||||||
|
]
|
||||||
|
.spacing(20),
|
||||||
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
Setup::Running { logs } => column![
|
Setup::Running { logs } => column![
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue