makepad_studio/build_manager/
build_server.rs

1use {
2    crate::{
3        build_manager::{
4            build_protocol::*,
5            child_process::{ChildProcess, ChildStdIO, ChildStdIn},
6            rustc_json::*,
7        },
8        makepad_code_editor::text::Position,
9        makepad_live_id::*,
10        makepad_micro_serde::*,
11        makepad_file_server::FileSystemRoots,
12        makepad_platform::log::LogLevel,
13    },
14    std::{
15        collections::HashMap,
16        env, fmt,
17        sync::{mpsc::Sender, Arc, Mutex, RwLock},
18    },
19};
20
21struct BuildServerProcess {
22    cmd_id: LiveId,
23    stdin_sender: Mutex<Sender<ChildStdIn>>,
24    line_sender: Mutex<Sender<ChildStdIO>>,
25}
26
27struct BuildServerShared {
28    roots: FileSystemRoots,
29    // here we should store our connections send slots
30    processes: HashMap<BuildProcess, BuildServerProcess>,
31}
32
33pub struct BuildServer {
34    shared: Arc<RwLock<BuildServerShared>>,
35}
36
37impl BuildServer {
38    pub fn new(roots: FileSystemRoots) -> BuildServer {
39        BuildServer {
40            shared: Arc::new(RwLock::new(BuildServerShared {
41                roots,
42                processes: Default::default(),
43            })),
44        }
45    }
46
47    pub fn connect(&mut self, msg_sender: Box<dyn MsgSender>) -> BuildConnection {
48        BuildConnection {
49            shared: self.shared.clone(),
50            msg_sender,
51        }
52    }
53}
54
55pub struct BuildConnection {
56    //    connection_id: ConnectionId,
57    shared: Arc<RwLock<BuildServerShared>>,
58    msg_sender: Box<dyn MsgSender>,
59}
60/*
61#[derive(Debug, PartialEq)]
62enum StdErrState {
63    First,
64    Sync,
65    Desync,
66    Running,
67}*/
68
69impl BuildConnection {
70    pub fn stop(&self, cmd_id: LiveId) {
71        let shared = self.shared.clone();
72
73        let shared = shared.write().unwrap();
74        if let Some(proc) = shared.processes.values().find(|v| v.cmd_id == cmd_id) {
75            let line_sender = proc.line_sender.lock().unwrap();
76            let _ = line_sender.send(ChildStdIO::Kill);
77        }
78    }
79
80    pub fn run(&self, what: BuildProcess, cmd_id: LiveId, http: String) {
81        let shared = self.shared.clone();
82        let msg_sender = self.msg_sender.clone();
83        // alright lets run a cargo check and parse its output
84        let path = shared.read().unwrap().roots.find_root(&what.root).unwrap().clone();
85
86        let http = format!("{}/{}", http, cmd_id.0);
87        let mut env = vec![("RUST_BACKTRACE","1"),("MAKEPAD_STUDIO_HTTP", http.as_str()), ("MAKEPAD", "lines")];
88
89        let args: Vec<String> = match &what.target {
90            BuildTarget::ReleaseStudio => vec![
91                "run".into(),
92                "-p".into(),
93                what.binary.clone(),
94                "--message-format=json".into(),
95                "--release".into(),
96                "--".into(),
97                "--message-format=json".into(),
98                "--stdin-loop".into(),
99            ],
100            BuildTarget::DebugStudio => vec![
101                "run".into(),
102                "-p".into(),
103                what.binary.clone(),
104                "--message-format=json".into(),
105                "--".into(),
106                "--message-format=json".into(),
107                "--stdin-loop".into(),
108            ],
109            BuildTarget::Release => vec![
110                "run".into(),
111                "-p".into(),
112                what.binary.clone(),
113                "--message-format=json".into(),
114                "--release".into(),
115                "--".into(),
116                "--message-format=json".into(),
117            ],
118            BuildTarget::Debug => vec![
119                "run".into(),
120                "-p".into(),
121                what.binary.clone(),
122                "--message-format=json".into(),
123                "--".into(),
124                "--message-format=json".into(),
125            ],
126            BuildTarget::Profiler => vec![
127                "instruments".into(),
128                "-t".into(),
129                "time".into(),
130                "-p".into(),
131                what.binary.clone(),
132                "--release".into(),
133                "--message-format=json".into(),
134                "--".into(),
135                "--message-format=json".into(),
136            ],
137            BuildTarget::IosSim => vec![
138                "makepad".into(),
139                "apple".into(),
140                "ios".into(),
141                format!("--org={}", "makepad"),
142                format!("--app={}", "example"),
143                "run-sim".into(),
144                "-p".into(),
145                what.binary.clone(),
146                "--release".into(),
147                "--message-format=json".into(),
148            ],
149            BuildTarget::IosDevice => vec![
150                "makepad".into(),
151                "ios".into(),
152                format!("--org={}", "makepad"),
153                format!("--app={}", "example"),
154                "run-device".into(),
155                "-p".into(),
156                what.binary.clone(),
157                "--release".into(),
158                "--message-format=json".into(),
159            ],
160            BuildTarget::TvosSim => vec![
161                "makepad".into(),
162                "apple".into(),
163                "tvos".into(),
164                format!("--org={}", "makepad"),
165                format!("--app={}", "example"),
166                "run-sim".into(),
167                "-p".into(),
168                what.binary.clone(),
169                "--release".into(),
170                "--message-format=json".into(),
171            ],
172            BuildTarget::TvosDevice => vec![
173                "makepad".into(),
174                "apple".into(),
175                "tvos".into(),
176                "--org=makepad".into(),
177                "--app=aiview".into(),
178                "--app=aiview".into(),
179                "--cert=61".into(),
180                "--device=F8,27".into(),
181                "--profile=./local/tvos4.mobileprovision".into(),
182                "run-device".into(),
183                "-p".into(),
184                what.binary.clone(),
185                "--release".into(),
186                "--message-format=json".into(),
187            ],
188            BuildTarget::Android => vec![
189                "makepad".into(),
190                "android".into(),
191                "--variant=default".into(),
192                "run".into(),
193                "-p".into(),
194                what.binary.clone(),
195                "--release".into(),
196                "--message-format=json".into(),
197            ],
198            BuildTarget::Quest => vec![
199                "makepad".into(),
200                "android".into(),
201                "--variant=quest".into(),
202                "run".into(),
203                "-p".into(),
204                what.binary.clone(),
205                "--release".into(),
206                "--message-format=json".into(),
207            ],
208            BuildTarget::Harmony => {
209                env.push(("MAKEPAD", "no_android_choreographer"));
210                vec![
211                    "makepad".into(),
212                    "android".into(),
213                    "run".into(),
214                    "-p".into(),
215                    what.binary.clone(),
216                    "--release".into(),
217                    "--message-format=json".into(),
218                ]
219            }
220            BuildTarget::WebAssembly => vec![
221                "makepad".into(),
222                "wasm".into(),
223                "run".into(),
224                "-p".into(),
225                what.binary.clone(),
226                "--release".into(),
227                "--message-format=json".into(),
228            ],
229            BuildTarget::CheckMacos => vec![
230                "check".into(),
231                "--target=aarch64-apple-darwin".into(),
232                "-p".into(),
233                what.binary.clone(),
234                "--release".into(),
235                "--message-format=json".into(),
236            ],
237            BuildTarget::CheckWindows => vec![
238                "check".into(),
239                "--target=x86_64-pc-windows-msvc".into(),
240                "-p".into(),
241                what.binary.clone(),
242                "--release".into(),
243                "--message-format=json".into(),
244            ],
245            BuildTarget::CheckLinux => vec![
246                "check".into(),
247                "--target=x86_64-unknown-linux-gnu".into(),
248                "-p".into(),
249                what.binary.clone(),
250                "--release".into(),
251                "--message-format=json".into(),
252            ],
253            BuildTarget::CheckAll => vec![
254                "makepad".into(),
255                "check".into(),
256                "all".into(),
257                "-p".into(),
258                what.binary.clone(),
259                "--release".into(),
260                "--message-format=json".into(),
261            ],
262        };
263
264        let is_in_studio = match what.target {
265            BuildTarget::ReleaseStudio | BuildTarget::DebugStudio => true,
266            _ => false,
267        };
268
269        // Default to nightly rustc but don't overwrite any user request for a
270        // specific nightly version.
271        // FIXME: also apply this for overrides set using rustup override rather
272        // than using an env var or as commandline argument.
273        if !env::var("RUSTUP_TOOLCHAIN").map_or(false, |toolchain| toolchain.contains("nightly")) {
274            env.push(("RUSTUP_TOOLCHAIN", "nightly"));
275        }
276
277        let process = ChildProcess::start("cargo", &args, path.to_path_buf(), &env, is_in_studio)
278            .expect("Cannot start process");
279
280        shared.write().unwrap().processes.insert(
281            what,
282            BuildServerProcess {
283                cmd_id,
284                stdin_sender: Mutex::new(process.stdin_sender.clone()),
285                line_sender: Mutex::new(process.line_sender.clone()),
286            },
287        );
288
289        // HACK(eddyb) do this first, as there is no way to actually send the
290        // initial swapchain to the client at all, unless we have this first
291        // (thankfully sending this before we ever read from the client means
292        // it will definitely arrive before C->H ReadyToStart triggers anything)
293        if is_in_studio {
294            msg_sender.send_message(BuildClientMessageWrap {
295                cmd_id,
296                message: BuildClientMessage::AuxChanHostEndpointCreated(
297                    process.aux_chan_host_endpoint.clone().unwrap(),
298                ),
299            });
300        }
301
302        // let mut stderr_state = StdErrState::First;
303        //let stdin_sender = process.stdin_sender.clone();
304        std::thread::spawn(move || {
305            // lets create a BuildProcess and run it
306            while let Ok(line) = process.line_receiver.recv() {
307                match line {
308                    ChildStdIO::StdOut(line) => {
309                        let comp_msg: Result<RustcCompilerMessage, DeJsonErr> =
310                            DeJson::deserialize_json(&line);
311                        match comp_msg {
312                            Ok(msg) => {
313                                // alright we have a couple of 'reasons'
314                                match msg.reason.as_str() {
315                                    "makepad-error-log" | "compiler-message" => {
316                                        msg_sender.process_compiler_message(cmd_id, msg);
317                                    }
318                                    "build-finished" => {
319                                        if Some(true) == msg.success {
320                                        } else {
321                                        }
322                                    }
323                                    "compiler-artifact" => {}
324                                    _ => (),
325                                }
326                            }
327                            Err(_) => {
328                                // we should output a log string
329                                //eprintln!("GOT ERROR {:?}", err);
330                                msg_sender.send_stdin_to_host_msg(cmd_id, line);
331                            }
332                        }
333                    }
334                    ChildStdIO::StdErr(line) => {
335                        if line.trim().starts_with("Running ") {
336                            msg_sender.send_bare_message(cmd_id, LogLevel::Wait, line);
337                        } else if line.trim().starts_with("Compiling ") {
338                            msg_sender.send_bare_message(cmd_id, LogLevel::Wait, line);
339                        } else if line
340                            .trim()
341                            .starts_with("Blocking waiting for file lock on package cache")
342                        {
343                            //msg_sender.send_bare_msg(cmd_id, LogItemLevel::Wait, line);
344                        } else if line.trim().starts_with("Checking ") {
345                            //msg_sender.send_bare_msg(cmd_id, LogItemLevel::Wait, line);
346                        } else if line.trim().starts_with("Finished ") {
347                            //stderr_state = StdErrState::Running;
348                        } else {
349                            msg_sender.send_bare_message(cmd_id, LogLevel::Error, line);
350                        }
351                    }
352                    ChildStdIO::Term => {
353                        msg_sender.send_bare_message(
354                            cmd_id,
355                            LogLevel::Log,
356                            "process terminated".into(),
357                        );
358                        break;
359                    }
360                    ChildStdIO::Kill => {
361                        return process.kill();
362                    }
363                }
364            }
365        });
366    }
367
368    pub fn handle_cmd(&self, cmd_wrap: BuildCmdWrap) {
369        match cmd_wrap.cmd {
370            BuildCmd::Run(process, http) => {
371                // lets kill all other 'whats'
372                self.run(process, cmd_wrap.cmd_id, http);
373            }
374            BuildCmd::Stop => {
375                // lets kill all other 'whats'
376                self.stop(cmd_wrap.cmd_id);
377            }
378            BuildCmd::HostToStdin(msg) => {
379                // ok lets fetch the running process from the cmd_id
380                // and plug this msg on the standard input as serialiser json
381                if let Ok(shared) = self.shared.read() {
382                    for v in shared.processes.values() {
383                        if v.cmd_id == cmd_wrap.cmd_id {
384                            // lets send it on sender
385                            if let Ok(stdin_sender) = v.stdin_sender.lock() {
386                                let _ = stdin_sender.send(ChildStdIn::Send(msg));
387                            }
388                            break;
389                        }
390                    }
391                }
392            }
393        }
394    }
395}
396
397pub trait MsgSender: Send {
398    fn box_clone(&self) -> Box<dyn MsgSender>;
399    fn send_message(&self, wrap: BuildClientMessageWrap);
400
401    fn send_bare_message(&self, cmd_id: LiveId, level: LogLevel, line: String) {
402        let line = line.trim();
403        self.send_message(BuildClientMessageWrap {
404            cmd_id,
405            message: BuildClientMessage::LogItem(LogItem::Bare(LogItemBare {
406                line: line.to_string(),
407                level,
408            })),
409        });
410    }
411
412    fn send_stdin_to_host_msg(&self, cmd_id: LiveId, line: String) {
413        self.send_message(BuildClientMessageWrap {
414            cmd_id,
415            message: BuildClientMessage::LogItem(LogItem::StdinToHost(line)),
416        });
417    }
418
419    fn send_location_msg(
420        &self,
421        cmd_id: LiveId,
422        level: LogLevel,
423        file_name: String,
424        start: Position,
425        end: Position,
426        message: String,
427        explanation: Option<String>,
428    ) {
429        self.send_message(BuildClientMessageWrap {
430            cmd_id,
431            message: BuildClientMessage::LogItem(LogItem::Location(LogItemLocation {
432                level,
433                file_name: file_name.replace("\\", "/"),
434                start,
435                end,
436                message,
437                explanation
438            })),
439        });
440    }
441
442    fn process_compiler_message(&self, cmd_id: LiveId, msg: RustcCompilerMessage) {
443        if let Some(msg) = msg.message {
444            let level = match msg.level.as_ref() {
445                "error" => LogLevel::Error,
446                "warning" => LogLevel::Warning,
447                "log" => LogLevel::Log,
448                "failure-note" => LogLevel::Error,
449                "panic" => LogLevel::Panic,
450                other => {
451                    self.send_bare_message(
452                        cmd_id,
453                        LogLevel::Error,
454                        format!("process_compiler_message: unexpected level {}", other),
455                    );
456                    return;
457                }
458            };
459            if let LogLevel::Warning = level {
460                if msg.message.starts_with("unstable feature specified for") {
461                    return;
462                }
463            }
464            if let Some(span) = msg.spans.iter().find(|span| span.is_primary) {
465                self.send_location_msg(
466                    cmd_id,
467                    level,
468                    span.file_name.clone(),
469                    span.start(),
470                    span.end(),
471                    msg.message,
472                    msg.rendered
473                );
474                /*
475                if let Some(label) = &span.label {
476                    self.send_location_msg(cmd_id, level, span.file_name.clone(), range, label.clone());
477                }
478                else if let Some(text) = span.text.iter().next() {
479                    self.send_location_msg(cmd_id, level, span.file_name.clone(), range, text.text.clone());
480                }
481                else {
482                    self.send_location_msg(cmd_id, level, span.file_name.clone(), range, msg.message.clone());
483                }*/
484            } else {
485                if msg
486                    .message
487                    .trim()
488                    .starts_with("Some errors have detailed explanations")
489                    || msg
490                        .message
491                        .trim()
492                        .starts_with("For more information about an error")
493                    || msg.message.trim().contains("warnings emitted")
494                    || msg.message.trim().contains("warning emitted")
495                {
496                } else {
497                    self.send_bare_message(cmd_id, LogLevel::Warning, msg.message);
498                }
499            }
500        }
501    }
502}
503
504impl<F: Clone + Fn(BuildClientMessageWrap) + Send + 'static> MsgSender for F {
505    fn box_clone(&self) -> Box<dyn MsgSender> {
506        Box::new(self.clone())
507    }
508
509    fn send_message(&self, wrap: BuildClientMessageWrap) {
510        self(wrap)
511    }
512}
513
514impl Clone for Box<dyn MsgSender> {
515    fn clone(&self) -> Self {
516        self.box_clone()
517    }
518}
519
520impl fmt::Debug for dyn MsgSender {
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        write!(f, "MsgSender")
523    }
524}