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 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 shared: Arc<RwLock<BuildServerShared>>,
58 msg_sender: Box<dyn MsgSender>,
59}
60impl 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 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 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 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 std::thread::spawn(move || {
305 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 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 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 } else if line.trim().starts_with("Checking ") {
345 } else if line.trim().starts_with("Finished ") {
347 } 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 self.run(process, cmd_wrap.cmd_id, http);
373 }
374 BuildCmd::Stop => {
375 self.stop(cmd_wrap.cmd_id);
377 }
378 BuildCmd::HostToStdin(msg) => {
379 if let Ok(shared) = self.shared.read() {
382 for v in shared.processes.values() {
383 if v.cmd_id == cmd_wrap.cmd_id {
384 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 } 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}