makepad_studio/build_manager/
build_server.rs

1use {
2    crate::{
3        makepad_code_editor::text::{Position, Length},
4        makepad_micro_serde::*,
5        build_manager::{
6            build_protocol::*,
7            child_process::{
8                ChildStdIn,
9                ChildProcess,
10                ChildStdIO
11            },
12            rustc_json::*,
13        },
14    },
15    std::{
16        collections::HashMap,
17        fmt,
18        path::PathBuf,
19        sync::{Arc, RwLock, Mutex, mpsc::Sender},
20    },
21};
22
23struct BuildServerProcess {
24    cmd_id: BuildCmdId,
25    stdin_sender: Mutex<Sender<ChildStdIn> >,
26    line_sender: Mutex<Sender<ChildStdIO> >,
27}
28
29struct BuildServerShared {
30    path: PathBuf,
31    // here we should store our connections send slots
32    processes: HashMap<BuildProcess, BuildServerProcess>
33}
34
35pub struct BuildServer {
36    shared: Arc<RwLock<BuildServerShared >>,
37}
38
39impl BuildServer {
40    pub fn new<P: Into<PathBuf >> (path: P) -> BuildServer {
41        BuildServer {
42            shared: Arc::new(RwLock::new(BuildServerShared {
43                path: path.into(),
44                processes: Default::default()
45            })),
46        }
47    }
48    
49    pub fn connect(&mut self, msg_sender: Box<dyn MsgSender>) -> BuildConnection {
50        BuildConnection {
51            shared: self.shared.clone(),
52            msg_sender,
53        }
54    }
55}
56
57pub struct BuildConnection {
58    //    connection_id: ConnectionId,
59    shared: Arc<RwLock<BuildServerShared >>,
60    msg_sender: Box<dyn MsgSender>,
61}
62
63#[derive(Debug, PartialEq)]
64enum StdErrState {
65    First,
66    Sync,
67    Desync,
68    Running,
69}
70
71
72impl BuildConnection {
73    
74    pub fn stop(&self, cmd_id: BuildCmdId) {
75        let shared = self.shared.clone();
76        
77        let shared = shared.write().unwrap();
78        if let Some(proc) = shared.processes.values().find(|v| v.cmd_id == cmd_id) {
79            let line_sender = proc.line_sender.lock().unwrap();
80            let _ = line_sender.send(ChildStdIO::Kill);
81        }
82    }
83    
84    pub fn run(&self, what: BuildProcess, cmd_id: BuildCmdId, http:String) {
85        
86        let shared = self.shared.clone();
87        let msg_sender = self.msg_sender.clone();
88        // alright lets run a cargo check and parse its output
89        let path = shared.read().unwrap().path.clone();
90        
91        let args: Vec<String> = match &what.target {
92            #[cfg(not(target_os="windows"))]
93            BuildTarget::ReleaseStudio => vec![
94                "run".into(),
95                "nightly".into(),
96                "cargo".into(),
97                "run".into(),
98                "-p".into(),
99                what.binary.clone(),
100                "--message-format=json".into(),
101                "--release".into(),
102                "--".into(),
103                "--message-format=json".into(),
104                "--stdin-loop".into(),
105            ],
106            #[cfg(not(target_os="windows"))]
107            BuildTarget::DebugStudio => vec![
108                "run".into(),
109                "nightly".into(),
110                "cargo".into(),
111                "run".into(),
112                "-p".into(),
113                what.binary.clone(),
114                "--message-format=json".into(),
115                "--".into(),
116                "--message-format=json".into(),
117                "--stdin-loop".into(),
118            ],
119            BuildTarget::Release => vec![
120                "run".into(),
121                "nightly".into(),
122                "cargo".into(),
123                "run".into(),
124                "-p".into(),
125                what.binary.clone(),
126                "--message-format=json".into(),
127                "--release".into(),
128                "--".into(),
129                "--message-format=json".into(),
130            ],
131            BuildTarget::Debug => vec![
132                "run".into(),
133                "nightly".into(),
134                "cargo".into(),
135                "run".into(),
136                "-p".into(),
137                what.binary.clone(),
138                "--message-format=json".into(),
139                "--".into(),
140                "--message-format=json".into(),
141            ],
142            BuildTarget::Profiler => vec![
143                "run".into(),
144                "nightly".into(),
145                "cargo".into(),
146                "instruments".into(),
147                "-t".into(),
148                "time".into(),
149                "-p".into(),
150                what.binary.clone(),
151                "--release".into(),
152                "--message-format=json".into(),
153                "--".into(),
154                "--message-format=json".into(),
155            ],
156            BuildTarget::IosSim {org, app} => vec![
157                "run".into(),
158                "nightly".into(),
159                "cargo".into(),
160                "makepad".into(),
161                "ios".into(),
162                format!("--org={org}"),
163                format!("--app={app}"),
164                "run-sim".into(),
165                "-p".into(),
166                what.binary.clone(),
167                "--release".into(),
168                "--message-format=json".into(),
169            ],
170            BuildTarget::IosDevice {org, app} => vec![
171                "run".into(),
172                "nightly".into(),
173                "cargo".into(),
174                "makepad".into(),
175                "ios".into(),
176                format!("--org={org}"),
177                format!("--app={app}"),
178                "run-device".into(),
179                "-p".into(),
180                what.binary.clone(),
181                "--release".into(),
182                "--message-format=json".into(),
183            ],
184            BuildTarget::Android => vec![
185                "run".into(),
186                "nightly".into(),
187                "cargo".into(),
188                "makepad".into(),
189                "android".into(),
190                "run".into(),
191                "-p".into(),
192                what.binary.clone(),
193                "--release".into(),
194                "--message-format=json".into(),
195            ],
196            BuildTarget::WebAssembly => vec![
197                "run".into(),
198                "nightly".into(),
199                "cargo".into(),
200                "makepad".into(),
201                "wasm".into(),
202                "build".into(),
203                "-p".into(),
204                what.binary.clone(),
205                "--release".into(),
206                "--message-format=json".into(),
207            ]
208        };
209        
210        let env = [
211            ("MAKEPAD_STUDIO_HTTP", http.as_str()),
212            ("MAKEPAD", "lines")
213        ];
214        let process = ChildProcess::start("rustup", &args, path, &env).expect("Cannot start process");
215        
216        shared.write().unwrap().processes.insert(
217            what,
218            BuildServerProcess {
219                cmd_id,
220                stdin_sender: Mutex::new(process.stdin_sender.clone()),
221                line_sender: Mutex::new(process.line_sender.clone()),
222            }
223        );
224
225        // HACK(eddyb) do this first, as there is no way to actually send the
226        // initial swapchain to the client at all, unless we have this first
227        // (thankfully sending this before we ever read from the client means
228        // it will definitely arrive before C->H ReadyToStart triggers anything).
229        msg_sender.send_message(cmd_id.wrap_msg(
230            LogItem::AuxChanHostEndpointCreated(process.aux_chan_host_endpoint.clone()),
231        ));
232
233        let mut stderr_state = StdErrState::First;
234        let stdin_sender = process.stdin_sender.clone();
235        std::thread::spawn(move || {
236            // lets create a BuildProcess and run it
237            while let Ok(line) = process.line_receiver.recv() {
238                
239                match line {
240                    ChildStdIO::StdOut(line) => {
241                        let comp_msg: Result<RustcCompilerMessage, DeJsonErr> = DeJson::deserialize_json(&line);
242                        match comp_msg {
243                            Ok(msg) => {
244                                // alright we have a couple of 'reasons'
245                                match msg.reason.as_str() {
246                                    "makepad-signal" => {
247                                        let _ = stdin_sender.send(ChildStdIn::Send(format!("{{\"Signal\":[{}]}}\n", msg.signal.unwrap())));
248                                    }
249                                    "makepad-error-log" | "compiler-message" => {
250                                        msg_sender.process_compiler_message(cmd_id, msg);
251                                    }
252                                    "build-finished" => {
253                                        if Some(true) == msg.success {
254                                        }
255                                        else {
256                                        }
257                                    }
258                                    "compiler-artifact" => {
259                                    }
260                                    _ => ()
261                                }
262                            }
263                            Err(_) => { // we should output a log string
264                                //eprintln!("GOT ERROR {:?}", err);
265                                msg_sender.send_stdin_to_host_msg(cmd_id, line);
266                            }
267                        }
268                    }
269                    ChildStdIO::StdErr(line) => {
270                        // attempt to clean up stderr of cargo
271                        match stderr_state {
272                            StdErrState::First => {
273                                if line.trim().starts_with("Compiling ") {
274                                    msg_sender.send_bare_msg(cmd_id, LogItemLevel::Wait, line);
275                                }
276                                else if line.trim().starts_with("Finished ") {
277                                    stderr_state = StdErrState::Running;
278                                }
279                                else if line.trim().starts_with("error: could not compile ") {
280                                    msg_sender.send_bare_msg(cmd_id, LogItemLevel::Error, line);
281                                }
282                                else {
283                                    stderr_state = StdErrState::Desync;
284                                    msg_sender.send_bare_msg(cmd_id, LogItemLevel::Error, line);
285                                }
286                            }
287                            StdErrState::Sync | StdErrState::Desync => {
288                                msg_sender.send_bare_msg(cmd_id, LogItemLevel::Error, line);
289                            }
290                            StdErrState::Running => {
291                                if line.trim().starts_with("Running ") {
292                                    msg_sender.send_bare_msg(cmd_id, LogItemLevel::Wait, format!("{}", line.trim()));
293                                    stderr_state = StdErrState::Sync
294                                }
295                                else {
296                                    stderr_state = StdErrState::Desync;
297                                    msg_sender.send_bare_msg(cmd_id, LogItemLevel::Error, line);
298                                }
299                            }
300                        }
301                    }
302                    ChildStdIO::Term => {
303                        msg_sender.send_bare_msg(cmd_id, LogItemLevel::Log, "process terminated".into());
304                        break;
305                    }
306                    ChildStdIO::Kill => {
307                        return process.kill();
308                    }
309                }
310            };
311        });
312    }
313    
314    pub fn handle_cmd(&self, cmd_wrap: BuildCmdWrap) {
315        match cmd_wrap.cmd {
316            BuildCmd::Run(process, http) => {
317                // lets kill all other 'whats'
318                self.run(process, cmd_wrap.cmd_id, http);
319            }
320            BuildCmd::Stop => {
321                // lets kill all other 'whats'
322                self.stop(cmd_wrap.cmd_id);
323            }
324            BuildCmd::HostToStdin(msg) => {
325                // ok lets fetch the running process from the cmd_id
326                // and plug this msg on the standard input as serialiser json
327                if let Ok(shared) = self.shared.read() {
328                    for v in shared.processes.values() {
329                        if v.cmd_id == cmd_wrap.cmd_id {
330                            // lets send it on sender
331                            if let Ok(stdin_sender) = v.stdin_sender.lock() {
332                                let _ = stdin_sender.send(ChildStdIn::Send(msg));
333                            }
334                            break;
335                        }
336                    }
337                }
338            }
339        }
340    }
341    
342}
343
344pub trait MsgSender: Send {
345    fn box_clone(&self) -> Box<dyn MsgSender>;
346    fn send_message(&self, wrap: LogItemWrap);
347    
348    fn send_bare_msg(&self, cmd_id: BuildCmdId, level: LogItemLevel, line: String) {
349        let line = line.trim();
350        self.send_message(
351            cmd_id.wrap_msg(LogItem::Bare(LogItemBare {
352                line: line.to_string(),
353                level
354            }))
355        );
356    }
357    
358    fn send_stdin_to_host_msg(&self, cmd_id: BuildCmdId, line: String) {
359        self.send_message(
360            cmd_id.wrap_msg(LogItem::StdinToHost(line))
361        );
362    }
363    
364
365    fn send_location_msg(&self, cmd_id: BuildCmdId, level: LogItemLevel, file_name: String, start: Position, length: Length, msg: String) {
366        self.send_message(
367            cmd_id.wrap_msg(LogItem::Location(LogItemLocation {
368                level,
369                file_name,
370                start,
371                length,
372                msg
373            }))
374        );
375    }
376    
377    fn process_compiler_message(&self, cmd_id: BuildCmdId, msg: RustcCompilerMessage) {
378        if let Some(msg) = msg.message {
379            
380            let level = match msg.level.as_ref() {
381                "error" => LogItemLevel::Error,
382                "warning" => LogItemLevel::Warning,
383                "log" => LogItemLevel::Log,
384                "failure-note" => LogItemLevel::Error,
385                "panic" => LogItemLevel::Panic,
386                other => {
387                    self.send_bare_msg(cmd_id, LogItemLevel::Error, format!("process_compiler_message: unexpected level {}", other));
388                    return
389                }
390            };
391            if let Some(span) = msg.spans.iter().find( | span | span.is_primary) {
392                self.send_location_msg(cmd_id, level, span.file_name.clone(), span.start(), span.length(), msg.message.clone());
393                /*
394                if let Some(label) = &span.label {
395                    self.send_location_msg(cmd_id, level, span.file_name.clone(), range, label.clone());
396                }
397                else if let Some(text) = span.text.iter().next() {
398                    self.send_location_msg(cmd_id, level, span.file_name.clone(), range, text.text.clone());
399                }
400                else {
401                    self.send_location_msg(cmd_id, level, span.file_name.clone(), range, msg.message.clone());
402                }*/
403            }
404            else {
405                if msg.message.trim().starts_with("aborting due to ") ||
406                msg.message.trim().starts_with("For more information about this error") ||
407                msg.message.trim().ends_with("warning emitted") ||
408                msg.message.trim().ends_with("warnings emitted") {
409                }
410                else {
411                    self.send_bare_msg(cmd_id, LogItemLevel::Warning, msg.message);
412                }
413            }
414        }
415    }
416}
417
418impl<F: Clone + Fn(LogItemWrap) + Send + 'static> MsgSender for F {
419    fn box_clone(&self) -> Box<dyn MsgSender> {
420        Box::new(self.clone())
421    }
422    
423    fn send_message(&self, wrap: LogItemWrap) {
424        self (wrap)
425    }
426}
427
428impl Clone for Box<dyn MsgSender> {
429    fn clone(&self) -> Self {
430        self.box_clone()
431    }
432}
433
434impl fmt::Debug for dyn MsgSender {
435    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436        write!(f, "MsgSender")
437    }
438}
439
440#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
441struct ConnectionId(usize);