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 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 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 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 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 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 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(_) => { msg_sender.send_stdin_to_host_msg(cmd_id, line);
266 }
267 }
268 }
269 ChildStdIO::StdErr(line) => {
270 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 self.run(process, cmd_wrap.cmd_id, http);
319 }
320 BuildCmd::Stop => {
321 self.stop(cmd_wrap.cmd_id);
323 }
324 BuildCmd::HostToStdin(msg) => {
325 if let Ok(shared) = self.shared.read() {
328 for v in shared.processes.values() {
329 if v.cmd_id == cmd_wrap.cmd_id {
330 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 }
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);