1use crate::process::*;
2use crate::hubmsg::*;
3use crate::hubrouter::*;
4use crate::hubclient::*;
5use crate::httpserver::*;
6use crate::wasmstrip::*;
7
8use serde::{Deserialize};
9use std::sync::{Arc, Mutex};
10use std::fs;
11use std::sync::{mpsc};
12use std::sync::mpsc::RecvTimeoutError;
13use toml::Value;
14use std::collections::HashMap;
15use std::net::{SocketAddr};
16
17pub struct HubBuilder {
18 pub route_send: HubRouteSend,
19 pub http_server: Arc<Mutex<Option<HttpServer>>>,
20 pub workspaces: Arc<Mutex<HashMap<String, String>>>,
21 pub builder: String,
22 pub abs_cwd_path: String,
23 pub processes: Arc<Mutex<Vec<HubProcess>>>,
24}
25
26pub struct HubWorkspace {
27 pub name: String,
28 pub abs_path: String
29}
30
31pub struct HubProcess {
32 uid: HubUid,
33 process: Process,
34}
35
36pub enum HubWsError {
37 Error(String),
38 LocErrors(Vec<LocMessage>)
39}
40
41const INCLUDED_FILES: &[&'static str] = &[".json", ".toml", ".js", ".rs", ".txt", ".text", ".ron", ".html"];
42const EXCLUDED_FILES: &[&'static str] = &["key.ron", "makepad_state.ron"];
43const EXCLUDED_DIRS: &[&'static str] = &["target", ".git", ".github", "edit_repo"];
44
45impl HubBuilder {
46
47 pub fn run_builder_direct<F>(builder: &str, hub_router: &mut HubRouter, event_handler: F) -> HubRouteSend
48 where F: Fn(&mut HubBuilder, FromHubMsg) -> Result<(), HubWsError> + Clone + Send + 'static {
49 let workspaces = Arc::new(Mutex::new(HashMap::<String, String>::new()));
50 let http_server = Arc::new(Mutex::new(None));
51 let abs_cwd_path = format!("{}", std::env::current_dir().unwrap().display());
52 let processes = Arc::new(Mutex::new(Vec::<HubProcess>::new()));
53
54 let (tx_write, rx_write) = mpsc::channel::<FromHubMsg>();
55
56 let route_send = hub_router.connect_direct(HubRouteType::Builder(builder.to_string()), tx_write);
57 let _thread = {
59 let builder = builder.to_string();
60 let route_send = route_send.clone();
61 let event_handler = event_handler.clone();
62 std::thread::spawn(move || {
63
64 route_send.send(ToHubMsg {
65 to: HubMsgTo::All,
66 msg: HubMsg::ConnectBuilder(builder.to_string())
67 });
68
69 while let Ok(htc) = rx_write.recv() {
70 let is_blocking = htc.msg.is_blocking();
71 let thread = {
72 let event_handler = event_handler.clone();
73 let mut hub_builder = HubBuilder {
74 route_send: route_send.clone(),
75 http_server: Arc::clone(&http_server),
76 workspaces: Arc::clone(&workspaces),
77 processes: Arc::clone(&processes),
78 builder: builder.to_string(),
79 abs_cwd_path: abs_cwd_path.clone(),
80 };
81 std::thread::spawn(move || {
82 let is_build_uid = if let HubMsg::Build {uid, ..} = &htc.msg {Some(*uid)}else {None};
83
84 let result = event_handler(&mut hub_builder, htc);
85
86 if let Some(is_build_uid) = is_build_uid {
87 if result.is_ok() {
88 hub_builder.route_send.send(ToHubMsg {
89 to: HubMsgTo::UI,
90 msg: HubMsg::BuildFailure {uid: is_build_uid}
91 });
92 }
93 else {
94 hub_builder.route_send.send(ToHubMsg {
95 to: HubMsgTo::UI,
96 msg: HubMsg::BuildSuccess {uid: is_build_uid}
97 });
98 }
99 }
100 })
101 };
102 if is_blocking {
103 let _ = thread.join();
104 }
105 }
106 })
107 };
108 route_send.clone()
109 }
110
111 pub fn run_builder_networked<F>(digest: Digest, in_address: Option<SocketAddr>, builder: &str, hub_log: HubLog, event_handler: F)
112 where F: Fn(&mut HubBuilder, FromHubMsg) -> Result<(), HubWsError> + Clone + Send + 'static {
113
114 let workspaces = Arc::new(Mutex::new(HashMap::<String, String>::new()));
115 let http_server = Arc::new(Mutex::new(None));
116 let abs_cwd_path = format!("{}", std::env::current_dir().unwrap().display());
117 let processes = Arc::new(Mutex::new(Vec::<HubProcess>::new()));
118
119 loop {
120 let address = if let Some(address) = in_address {
121 address
122 }
123 else {
124 hub_log.msg("Builder waiting for hub announcement..", &builder);
125
126 HubClient::wait_for_announce(digest.clone()).expect("cannot wait for announce")
127 };
128
129 hub_log.msg("Builder connecting to {:?}", &address);
130
131 let mut hub_client = if let Ok(hub_client) = HubClient::connect_to_server(digest.clone(), address, hub_log.clone()) {
132 hub_client
133 }
134 else {
135 println!("Builder cannot connect to to {:?}, retrying", address);
136 std::thread::sleep(std::time::Duration::from_millis(500));
137 continue;
138 };
139
140 println!("Builder connected to {:?}", hub_client.own_addr);
141
142 let route_send = hub_client.get_route_send();
143
144 route_send.send(ToHubMsg {
145 to: HubMsgTo::All,
146 msg: HubMsg::ConnectBuilder(builder.to_string())
147 });
148
149 let rx_read = hub_client.rx_read.take().unwrap();
150
151 while let Ok(htc) = rx_read.recv() {
152 match &htc.msg {
153 HubMsg::ConnectionError(_e) => {
154 println!("Got connection error, need to restart loop TODO kill all processes!");
155 break;
156 },
157 _ => ()
158 }
159 let is_blocking = htc.msg.is_blocking();
160 let thread = {
161 let event_handler = event_handler.clone();
162 let mut hub_builder = HubBuilder {
163 route_send: route_send.clone(),
164 http_server: Arc::clone(&http_server),
165 workspaces: Arc::clone(&workspaces),
166 processes: Arc::clone(&processes),
167 builder: builder.to_string(),
168 abs_cwd_path: abs_cwd_path.clone(),
169 };
170 std::thread::spawn(move || {
171 let is_build_uid = if let HubMsg::Build {uid, ..} = &htc.msg {Some(*uid)}else {None};
172
173 let result = event_handler(&mut hub_builder, htc);
174
175 if let Some(is_build_uid) = is_build_uid {
176 if result.is_ok() {
177 hub_builder.route_send.send(ToHubMsg {
178 to: HubMsgTo::UI,
179 msg: HubMsg::BuildFailure {uid: is_build_uid}
180 });
181 }
182 else {
183 hub_builder.route_send.send(ToHubMsg {
184 to: HubMsgTo::UI,
185 msg: HubMsg::BuildSuccess {uid: is_build_uid}
186 });
187 }
188 }
189 })
190 };
191 if is_blocking {
192 let _ = thread.join();
193 }
194 }
195 }
196 }
197
198 pub fn run_builder_commandline<F>(args: Vec<String>, event_handler: F)
199 where F: Fn(&mut HubBuilder, FromHubMsg) -> Result<(), HubWsError> + Clone + Send + 'static {
200
201 fn print_help() {
202 println!("----- Builder commandline interface -----");
203 println!("Connect to a specific hub server:");
204 println!("cargo run -p builder -- connect <ip>:<port> <key.ron> <workspace>");
205 println!("example: cargo run -p builder -- connect 127.0.0.1:7243 key.ron windows");
206 println!("");
207 println!("Listen to hub server announce");
208 println!("cargo run -p builder -- listen <key.ron> <workspace>");
209 println!("example: cargo run -p builder -- listen key.ron windows");
210 println!("");
211 println!("Build a specific package");
212 println!("cargo run -p builder -- build <path> <package> <config>");
213 println!("example: cargo run -p builder -- build edit_repo makepad release");
214 println!("");
215 println!("List packages");
216 println!("cargo run -p builder -- list <path>");
217 println!("example: cargo run -p builder -- list edit_repo");
218 println!("");
219 println!("Build index.ron");
220 println!("cargo run -p builder -- index <path>");
221 println!("example: cargo run -p workspace -- index edit_repo");
222 }
223
224 if args.len()<2 {
225 return print_help();
226 }
227
228 let (message, path) = match args[1].as_ref() {
229 "listen" => {
230 if args.len() != 4 {
231 return print_help();
232 }
233 let key_file = args[2].to_string();
234 let builder = args[3].to_string();
235 let utf8_data = std::fs::read_to_string(key_file).expect("Can't read key file");
236 let digest: Digest = ron::de::from_str(&utf8_data).expect("Can't load key file");
237 println!("Starting workspace listening to announce");
238 Self::run_builder_networked(digest, None, &builder, HubLog::None, event_handler);
239 return
240 },
241 "connect" => {
242 if args.len() != 5 {
243 return print_help();
244 }
245 let addr = args[2].parse().expect("cant parse address");
246 let key_file = args[3].to_string();
247 let builder = args[4].to_string();
248 let utf8_data = std::fs::read_to_string(key_file).expect("Can't read key file");
249 let digest: Digest = ron::de::from_str(&utf8_data).expect("Can't load key file");
250 println!("Starting workspace connecting to ip");
251 Self::run_builder_networked(digest, Some(addr), &builder, HubLog::None, event_handler);
252 return
253 },
254 "list" => {
255 if args.len() != 3 {
256 return print_help();
257 }
258 (HubMsg::ListPackagesRequest {
259 uid: HubUid::zero()
260 }, args[2].clone())
261 },
262 "build" => {
263 if args.len() != 5 {
264 return print_help();
265 }
266 (HubMsg::Build {
267 uid: HubUid::zero(),
268 workspace: "main".to_string(),
269 package: args[3].clone(),
270 config: args[4].clone()
271 }, args[2].clone())
272 },
273 "index" => {
274 if args.len() != 3 {
275 return print_help();
276 }
277 (HubMsg::BuilderFileTreeRequest {
278 uid: HubUid::zero(),
279 create_digest: false,
280 }, args[2].clone())
281 },
282 _ => {
283 return print_help();
284 }
285 };
286
287 let workspaces = Arc::new(Mutex::new(HashMap::<String, String>::new()));
288 let http_server = Arc::new(Mutex::new(None));
289 let processes = Arc::new(Mutex::new(Vec::<HubProcess>::new()));
290 let abs_cwd_path = format!("{}", std::env::current_dir().unwrap().display());
291
292 if let Ok(mut workspaces) = workspaces.lock() {
293 workspaces.insert(
294 "main".to_string(),
295 rel_to_abs_path(&abs_cwd_path, &path)
296 );
297 };
298
299 let (tx_write, rx_write) = mpsc::channel::<(HubAddr, ToHubMsg)>();
300
301 let mut hub_builder = HubBuilder {
302 route_send: HubRouteSend::Direct {
303 uid_alloc: Arc::new(Mutex::new(0)),
304 tx_pump: tx_write.clone(),
305 own_addr: HubAddr::None
306 },
307 http_server: Arc::clone(&http_server),
308 builder: "".to_string(),
309 processes: Arc::clone(&processes),
310 workspaces: Arc::clone(&workspaces),
311 abs_cwd_path: abs_cwd_path.clone()
312 };
313
314 let thread = std::thread::spawn(move || {
316 while let Ok((_addr, htc)) = rx_write.recv() {
317 match htc.msg {
318 HubMsg::BuilderFileTreeResponse {tree, ..} => {
319 if let BuilderFileTreeNode::Folder {folder, ..} = tree {
321 let ron = ron::ser::to_string_pretty(&folder[0], ron::ser::PrettyConfig::default()).expect("cannot serialize settings");
322 fs::write("index.ron", ron).expect("cannot write index.ron");
323 println!("Written index.ron")
324 }
325 return
326 },
327 HubMsg::ListPackagesResponse {packages, ..} => {
328 println!("{:?}", packages);
329 },
330 HubMsg::LogItem {item, ..} => {
331 println!("{:?}", item)
332 },
333 HubMsg::CargoArtifact {package_id, ..} => {
334 println!("{}", package_id)
335 },
336 HubMsg::CargoEnd {build_result, ..} => {
337 println!("CargoEnd {:?}", build_result);
338 }
339 HubMsg::BuildSuccess {..} => {
340 println!("Success!");
341 return
342 },
343 HubMsg::BuildFailure {..} => {
344 println!("Failure!");
345 return
346 },
347 _ => ()
348 }
349 }
350 });
351
352 let result = event_handler(&mut hub_builder, FromHubMsg {
353 from: HubAddr::None,
354 msg: message
355 });
356 if result.is_ok() {
357 let _ = tx_write.send((HubAddr::None, ToHubMsg {
358 to: HubMsgTo::All,
359 msg: HubMsg::BuildSuccess {uid: HubUid {id: 0, addr: HubAddr::None}}
360 }));
361 }
362 else {
363 let _ = tx_write.send((HubAddr::None, ToHubMsg {
364 to: HubMsgTo::All,
365 msg: HubMsg::BuildFailure {uid: HubUid {id: 0, addr: HubAddr::None}}
366 }));
367 }
368 let _ = thread.join();
369 }
370
371 pub fn set_config(&mut self, _uid: HubUid, config: HubBuilderConfig) -> Result<(), HubWsError> {
372 if let Ok(mut workspaces) = self.workspaces.lock() {
374 *workspaces = config.workspaces;
375 for (_, rel_path) in workspaces.iter_mut() {
376 *rel_path = rel_to_abs_path(&self.abs_cwd_path, &rel_path)
377 }
378 };
379
380 let workspaces = Arc::clone(&self.workspaces);
381
382 if let Ok(mut http_server) = self.http_server.lock() {
383 if let Some(http_server) = &mut *http_server {
384 http_server.terminate();
385 }
386
387 *http_server = HttpServer::start_http_server(&config.http_server, workspaces);
388 }
389
390
391 Ok(())
392 }
393
394 pub fn default(&mut self, htc: FromHubMsg) -> Result<(), HubWsError> {
395 let ws = self;
396 match htc.msg {
397 HubMsg::BuilderConfig {uid, config} => {
398 ws.set_config(uid, config)
399 },
400 HubMsg::BuilderFileTreeRequest {uid, create_digest} => {
401 let tree = ws.workspace_file_tree(
402 create_digest,
403 INCLUDED_FILES,
404 EXCLUDED_FILES,
405 EXCLUDED_DIRS
406 );
407 ws.route_send.send(ToHubMsg {
408 to: HubMsgTo::Client(htc.from),
409 msg: HubMsg::BuilderFileTreeResponse {
410 uid: uid,
411 tree: tree
412 }
413 });
414 Ok(())
415 },
416 HubMsg::FileReadRequest {uid, path} => {
417 ws.file_read(htc.from, uid, &path);
418 Ok(())
419 },
420 HubMsg::FileWriteRequest {uid, path, data} => {
421 ws.file_write(htc.from, uid, &path, data);
422 Ok(())
423 },
424 HubMsg::BuildKill {uid} => {
425 ws.process_kill(uid);
426 Ok(())
427 },
428 HubMsg::ProgramKill {uid} => {
429 ws.process_kill(uid);
430 Ok(())
431 },
432 HubMsg::ProgramRun {uid, path, args} => {
433 let v: Vec<&str> = args.iter().map( | v | v.as_ref()).collect();
434 ws.program_run(uid, &path, &v) ?;
435 Ok(())
436 },
437 _ => Ok(())
438 }
439 }
440
441 pub fn process_kill(&mut self, uid: HubUid) {
442 if let Ok(mut procs) = self.processes.lock() {
443 for proc in procs.iter_mut() {
444 if proc.uid == uid {
445 proc.process.kill();
446 }
447 }
448 };
449 }
450
451 pub fn workspace_split_from_path(&mut self, uid: HubUid, path: &str) -> Result<(String, String, String), HubWsError> {
452 if let Some(workspace_pos) = path.find("/") {
453 let (workspace, rest) = path.split_at(workspace_pos);
454 let (_, rest) = rest.split_at(1);
455 let abs_dir = self.get_workspace_abs(uid, workspace) ?;
456 return Ok((abs_dir.to_string(), workspace.to_string(), rest.to_string()));
457 }
458 Err(
459 self.error(uid, format!("Builder {} path {} incorrent", self.builder, path))
460 )
461 }
462
463 pub fn get_workspace_abs(&mut self, uid: HubUid, workspace: &str) -> Result<String, HubWsError> {
464 if let Ok(workspaces) = self.workspaces.lock() {
465 if let Some(abs_dir) = workspaces.get(workspace) {
466 return Ok(abs_dir.to_string())
467 }
468 }
469 Err(
470 self.error(uid, format!("Builder {} workspace {} not found", self.builder, workspace))
471 )
472 }
473
474 pub fn program_run(&mut self, uid: HubUid, path: &str, args: &[&str]) -> Result<(), HubWsError> {
475
476 let (abs_dir, workspace, sub_path) = self.workspace_split_from_path(uid, path) ?;
477
478 let process = Process::start(&sub_path, args, &abs_dir, &[("RUST_BACKTRACE", "full")]);
479 if let Err(e) = process {
480 return Err(
481 self.error(uid, format!("Builder {} program run {} {} not found {:?}", self.builder, abs_dir, sub_path, e))
482 );
483 }
484 let mut process = process.unwrap();
485
486 let route_mode = self.route_send.clone();
487
488 let rx_line = process.rx_line.take().unwrap();
489
490 if let Ok(mut processes) = self.processes.lock() {
491 processes.push(HubProcess {
492 uid: uid,
493 process: process,
494 });
495 };
496
497 let builder = self.builder.clone();
498 route_mode.send(ToHubMsg {
499 to: HubMsgTo::UI,
500 msg: HubMsg::ProgramBegin {uid: uid}
501 });
502
503 fn starts_with_digit(val: &str) -> bool {
504 if val.len() <= 1 {
505 return false;
506 }
507 let c1 = val.chars().next().unwrap();
508 c1 >= '0' && c1 <= '9'
509 }
510
511 let mut stderr: Vec<String> = Vec::new();
512
513 fn try_parse_stderr(uid: HubUid, builder: &str, workspace: &str, stderr: &Vec<String>, route_send: &HubRouteSend) {
514
515 let mut tracing_panic = false;
516 let mut panic_stack = Vec::new();
517 for line in stderr {
518 let mut trimmed = line.trim_start().to_string();
519 trimmed.retain( | c | c != '\0');
520 if tracing_panic == false && (trimmed.starts_with("thread '") || trimmed.starts_with("0:")) { tracing_panic = true;
522 panic_stack.truncate(0);
523 }
524 if tracing_panic {
525 panic_stack.push(trimmed);
526 }
527 else {
528 route_send.send(ToHubMsg {
529 to: HubMsgTo::UI,
530 msg: HubMsg::LogItem {
531 uid: uid,
532 item: HubLogItem::Error(trimmed.clone())
533 }
534 });
535 }
536 }
537
538 let mut path = None;
539 let mut row = 0;
540 let mut rendered = Vec::new();
541 if panic_stack.len()>0 {rendered.push(panic_stack[0].clone())};
542 let mut last_fn_name = String::new();
543 for panic_line in &panic_stack {
544 if panic_line.starts_with("at ")
545 && !panic_line.starts_with("at /")
546 && !panic_line.starts_with("at src/libstd/")
547 && !panic_line.starts_with("at src/libcore/")
548 && !panic_line.starts_with("at src/libpanic_unwind/") {
549 if let Some(end) = panic_line.find(":") {
550 let proc_path = format!("{}/{}/{}", builder, workspace, panic_line.get(3..end).unwrap().to_string());
551 let proc_row = panic_line.get((end + 1)..(panic_line.len() - 1)).unwrap().parse::<usize>().unwrap();
552
553 rendered.push(format!("{}:{} - {}", proc_path, proc_row, last_fn_name));
554 if path.is_none() {
555 path = Some(proc_path);
556 row = proc_row
557 }
558 }
559 }
560 else if starts_with_digit(panic_line) {
561 if let Some(pos) = panic_line.find(" 0x") {
562 last_fn_name = panic_line.get((pos + 15)..).unwrap().to_string();
563 }
564 }
565 }
566 rendered.push("\n".to_string());
567
568 if panic_stack.len()<3 {
569 return;
570 }
571
572 route_send.send(ToHubMsg {
573 to: HubMsgTo::UI,
574 msg: HubMsg::LogItem {
575 uid: uid,
576 item: HubLogItem::LocPanic(LocMessage {
577 path: if let Some(path) = path {path}else {"".to_string()},
578 row: row,
579 col: 1,
580 range: None,
581 body: rendered[0].clone(),
582 rendered: Some(rendered.join("")),
583 explanation: Some(panic_stack[1..].join("")),
584 })
585 }
586 });
587 }
588
589 loop {
590 let result = rx_line.recv_timeout(std::time::Duration::from_millis(100));
591 match result {
592 Ok(line) => {
593 if let Some((is_stderr, line)) = line {
594 if is_stderr { stderr.push(line);
596 }
597 else {
598 if stderr.len() > 0 {
599 try_parse_stderr(uid, &builder, &workspace, &stderr, &route_mode);
600 stderr.truncate(0);
601 }
602
603 route_mode.send(ToHubMsg {
604 to: HubMsgTo::UI,
605 msg: HubMsg::LogItem {
606 uid: uid,
607 item: HubLogItem::Message(line.clone())
608 }
609 });
610 }
611 }
612 },
613 Err(err) => {
614 if stderr.len() > 0 {
615 try_parse_stderr(uid, &builder, &workspace, &stderr, &route_mode);
616 stderr.truncate(0);
617 }
618 if let RecvTimeoutError::Disconnected = err {
619 break
620 }
621 }
622 }
623 }
624
625 route_mode.send(ToHubMsg {
626 to: HubMsgTo::UI,
627 msg: HubMsg::ProgramEnd {
628 uid: uid,
629 }
630 });
631
632 if let Ok(mut processes) = self.processes.lock() {
633 if let Some(index) = processes.iter().position( | p | p.uid == uid) {
634 processes.remove(index);
635 }
636 };
637
638 Ok(())
639 }
640
641
642 pub fn cannot_find_build(&mut self, uid: HubUid, package: &str, target: &str) -> Result<(), HubWsError> {
643 Err(
644 self.error(uid, format!("Builder {} Cannot find package {} and target {}", self.builder, package, target))
645 )
646 }
647
648 pub fn cargo(&mut self, uid: HubUid, workspace: &str, args: &[&str], env: &[(&str, &str)]) -> Result<BuildResult, HubWsError> {
649
650 if let Ok(mut http_server) = self.http_server.lock() {
651 if let Some(http_server) = &mut *http_server {
652 http_server.send_build_start();
653 }
654 };
655
656 let abs_root_path = self.get_workspace_abs(uid, workspace) ?;
657
658 let mut extargs = args.to_vec();
659 extargs.push("--message-format=json");
660 let mut process = Process::start("cargo", &extargs, &abs_root_path, env).expect("Cannot start process");
661
662 let route_send = self.route_send.clone();
663
664 let rx_line = process.rx_line.take().unwrap();
665
666 if let Ok(mut processes) = self.processes.lock() {
667 processes.push(HubProcess {
668 uid: uid,
669 process: process,
670 });
671 };
672
673 route_send.send(ToHubMsg {
674 to: HubMsgTo::UI,
675 msg: HubMsg::CargoBegin {uid: uid}
676 });
677
678 let builder = self.builder.clone();
679
680 let mut errors = Vec::new();
681 let mut build_result = BuildResult::NoOutput;
682 while let Ok(line) = rx_line.recv() {
683 if let Some((is_stderr, line)) = line {
684 if is_stderr && line != "\n"
685 && !line.contains("Finished")
686 && !line.contains("Blocking")
687 && !line.contains("Compiling")
688 && !line.contains("--verbose") {
689 route_send.send(ToHubMsg {
690 to: HubMsgTo::UI,
691 msg: HubMsg::LogItem {
692 uid: uid,
693 item: HubLogItem::Error(line.clone())
694 }
695 });
696 }
697
698 let mut parsed: serde_json::Result<RustcCompilerMessage> = serde_json::from_str(&line);
699 match &mut parsed {
700 Err(_) => (), Ok(parsed) => {
702 if let Some(message) = &mut parsed.message { let spans = &message.spans;
704 for i in 0..spans.len() {
705 let span = spans[i].clone();
706 if !span.is_primary {
707 continue
708 }
709
710 let mut msg = message.message.clone();
711
712 for child in &message.children {
713 msg.push_str(" - ");
714 msg.push_str(&child.message);
715 }
716 msg = msg.replace("\n", "");
717 let mut path = span.file_name;
719 let row = span.line_start as usize;
720 let col = span.column_start as usize;
721 if let Some(rendered) = &message.rendered {
722 let lines: Vec<&str> = rendered.split('\n').collect();
723 if lines.len() >= 1 {
724 if let Some(start) = lines[1].find("--> ") {
725 if let Some(end) = lines[1].find(":") {
726 path = lines[1].get((start + 4)..end).unwrap().to_string();
727 }
730 }
731 }
732 }
733 let loc_message = LocMessage {
734 path: format!("{}/{}/{}", builder, workspace, de_relativize_path(&path)).replace("\\", "/"),
735 row: row,
736 col: col,
737 range: Some((span.byte_start as usize, span.byte_end as usize)),
738 body: msg,
739 rendered: message.rendered.clone(),
740 explanation: if let Some(code) = &message.code {code.explanation.clone()}else {None},
741 };
742 let item = match message.level.as_ref() {
743 "error" => {
744 errors.push(loc_message.clone());
745 HubLogItem::LocError(loc_message)
746 },
747 _ => HubLogItem::LocWarning(loc_message),
748 };
749
750 route_send.send(ToHubMsg {
751 to: HubMsgTo::UI,
752 msg: HubMsg::LogItem {
753 uid: uid,
754 item: item
755 }
756 });
757 }
758 }
759 else {
760 route_send.send(ToHubMsg {
761 to: HubMsgTo::UI,
762 msg: HubMsg::CargoArtifact {
763 uid: uid,
764 package_id: parsed.package_id.clone(),
765 fresh: if let Some(fresh) = parsed.fresh {fresh}else {false}
766 }
767 });
768 if errors.len() == 0 {
769 build_result = BuildResult::NoOutput;
770 if let Some(executable) = &parsed.executable {
771 if !executable.ends_with(".rmeta") && abs_root_path.len() + 1 < executable.len() {
772 let last = executable.clone().split_off(abs_root_path.len() + 1);
773
774 build_result = BuildResult::Executable {path: format!("{}/{}", workspace, last).replace("\\", "/")};
775 }
776 }
777 if let Some(filenames) = &parsed.filenames {
779 for filename in filenames {
780 if filename.ends_with(".wasm") && abs_root_path.len() + 1 < filename.len() {
781 let last = filename.clone().split_off(abs_root_path.len() + 1);
782 let path = format!("{}/{}", workspace, last).replace("\\", "/");
783 if let Ok(mut http_server) = self.http_server.lock() {
784 if let Some(http_server) = &mut *http_server {
785 http_server.send_file_change(&path);
786 }
787 };
788 build_result = BuildResult::Wasm {path: path};
789 }
790 }
791 }
792 }
793 else {
794 build_result = BuildResult::Error;
795 }
796 }
797 }
798 }
799 }
800 else { break;
802 }
803 }
804
805 route_send.send(ToHubMsg {
807 to: HubMsgTo::UI,
808 msg: HubMsg::CargoEnd {
809 uid: uid,
810 build_result: build_result.clone()
811 }
812 });
813
814 if let Ok(mut processes) = self.processes.lock() {
816 if let Some(index) = processes.iter().position( | p | p.uid == uid) {
817 processes[index].process.wait();
818 processes.remove(index);
819 }
820 };
821 if let BuildResult::Error = build_result {
822 return Err(HubWsError::LocErrors(errors))
823 }
824 return Ok(build_result);
825 }
826
827 pub fn packages_response(&mut self, from: HubAddr, uid: HubUid, packages: Vec<HubPackage>) {
828
829 self.route_send.send(ToHubMsg {
830 to: HubMsgTo::Client(from),
831 msg: HubMsg::ListPackagesResponse {
832 uid: uid,
833 packages: packages
834 }
835 });
836 }
837
838 pub fn error(&mut self, uid: HubUid, msg: String) -> HubWsError {
839 self.route_send.send(ToHubMsg {
840 to: HubMsgTo::UI,
841 msg: HubMsg::LogItem {uid: uid, item: HubLogItem::Error(msg.clone())}
842 });
843
844 return HubWsError::Error(msg)
845 }
846
847 pub fn message(&mut self, uid: HubUid, msg: String) {
848 self.route_send.send(ToHubMsg {
849 to: HubMsgTo::UI,
850 msg: HubMsg::LogItem {uid: uid, item: HubLogItem::Message(msg.clone())}
851 });
852 }
853
854 pub fn wasm_strip_debug(&mut self, uid: HubUid, path: &str) -> Result<BuildResult, HubWsError> {
855
856 let (abs_root_path, _project, sub_path) = self.workspace_split_from_path(uid, path) ?;
857
858 let filepath = format!("{}/{}", abs_root_path, sub_path);
859
860 if let Ok(data) = fs::read(&filepath) {
861 if let Ok(strip) = wasm_strip_debug(&data) {
862 let uncomp_len = strip.len();
863 let mut enc = snap::Encoder::new();
864 let comp_len = if let Ok(compressed) = enc.compress_vec(&strip) {compressed.len()}else {0};
865
866 if let Err(_) = fs::write(&filepath, strip) {
867 return Err(self.error(uid, format!("Cannot write stripped wasm {}", filepath)));
868 }
869 else {
870 self.message(uid, format!("Wasm file stripped size: {} kb uncompressed {} kb with snap", uncomp_len>>10, comp_len>>10));
871 return Ok(BuildResult::Wasm {path: path.to_string()})
872 }
873 }
874 else {
875 return Err(self.error(uid, format!("Cannot parse wasm {}", filepath)));
876 }
877 }
878 Err(self.error(uid, format!("Cannot read wasm {}", filepath)))
879 }
880
881 pub fn read_packages(&mut self, uid: HubUid) -> Vec<(String, String)> {
882 let mut packages = Vec::new();
883 let workspaces = Arc::clone(&self.workspaces);
884 if let Ok(workspaces) = workspaces.lock() {
885 for (workspace, abs_path) in workspaces.iter() {
886 let vis_path = format!("{}/{}/Cargo.toml", self.builder, workspace);
887 let root_cargo = match std::fs::read_to_string(format!("{}/Cargo.toml", abs_path)) {
888 Err(_) => {
889 self.error(uid, format!("Cannot read cargo {}", vis_path));
890 continue;
891 },
892 Ok(v) => v
893 };
894 let value = match root_cargo.parse::<Value>() {
895 Err(e) => {
896 self.error(uid, format!("Cannot parse {} {:?}", vis_path, e));
897 continue;
898 },
899 Ok(v) => v
900 };
901 let mut ws_members = Vec::new();
902 if let Value::Table(table) = &value {
903 if let Some(table) = table.get("workspace") {
904 if let Value::Table(table) = table {
905 if let Some(members) = table.get("members") {
906 if let Value::Array(members) = members {
907 for member in members {
908 if let Value::String(member) = member {
909 ws_members.push(member);
910 }
911 }
912 }
913 }
914 }
915 }
916 else if let Some(table) = table.get("package") {
917 if let Value::Table(table) = table {
918 if let Some(name) = table.get("name") {
919 if let Value::String(name) = name {
920 packages.push((workspace.clone(), name.clone()));
921 }
922 }
923 }
924 }
925 }
926 for member in ws_members {
927 let file_path = format!("{}/{}/Cargo.toml", abs_path, member);
928 let vis_path = format!("{}/{}/{}/Cargo.toml", self.builder, workspace, member);
929 let cargo = match std::fs::read_to_string(&file_path) {
930 Err(_) => {
931 self.error(uid, format!("Cannot read cargo {}", vis_path));
932 continue;
933 },
934 Ok(v) => v
935 };
936 let value = match cargo.parse::<Value>() {
937 Err(e) => {
938 self.error(uid, format!("Cannot parse cargo {} {:?}", vis_path, e));
939 continue;
940 },
941 Ok(v) => v
942 };
943 if let Value::Table(table) = &value {
944 if let Some(table) = table.get("package") {
945 if let Value::Table(table) = table {
946 if let Some(name) = table.get("name") {
947 if let Value::String(name) = name {
948 packages.push((workspace.clone(), name.clone()));
949 }
950 }
951 }
952 }
953 }
954 }
955 }
956 }
957 return packages
958 }
959
960 pub fn file_read(&mut self, from: HubAddr, uid: HubUid, path: &str) {
961
962 if let Ok((abs_dir, _workspace, sub_path)) = self.workspace_split_from_path(uid, path) {
963
964 if let Some(_) = sub_path.find("..") {
965 self.error(uid, format!("file_read got relative path, ignoring {}", path));
966 return
967 }
968 if sub_path.ends_with("key.ron") {
969 self.error(uid, format!("Ends with key.ron, ignoring {}", path));
970 return
971 }
972
973 let data = if let Ok(data) = std::fs::read(format!("{}/{}", abs_dir, sub_path)) {
974 Some(data)
975 }
976 else {
977 None
978 };
979
980 self.route_send.send(ToHubMsg {
981 to: HubMsgTo::Client(from),
982 msg: HubMsg::FileReadResponse {
983 uid: uid,
984 path: path.to_string(),
985 data: data
986 }
987 });
988 }
989 }
990
991 pub fn file_write(&mut self, from: HubAddr, uid: HubUid, path: &str, data: Vec<u8>) {
992 if let Ok((abs_dir, _project, sub_path)) = self.workspace_split_from_path(uid, path) {
993
994 if path.contains("..") {
995 self.error(uid, format!("file_write got relative path, ignoring {}", path));
996 println!("file_read got relative path, ignoring {}", path);
997 return
998 }
999
1000 let done = std::fs::write(format!("{}/{}", abs_dir, sub_path), &data).is_ok();
1001
1002 if let Ok(mut http_server) = self.http_server.lock() {
1003 if let Some(http_server) = &mut *http_server {
1004 http_server.send_file_change(path);
1005 }
1006 };
1007
1008 self.route_send.send(ToHubMsg {
1009 to: HubMsgTo::Client(from),
1010 msg: HubMsg::FileWriteResponse {
1011 uid: uid,
1012 path: path.to_string(),
1013 done: done
1014 }
1015 });
1016 }
1017 }
1018
1019 pub fn workspace_file_tree(&mut self, create_digest: bool, ext_inc: &[&str], file_ex: &[&str], dir_ex: &[&str]) -> BuilderFileTreeNode {
1020 fn digest_folder(create_digest: bool, name: &str, folder: &Vec<BuilderFileTreeNode>) -> Option<Box<Digest>> {
1021 if !create_digest {
1022 return None;
1023 }
1024 let mut digest_out = Digest::default();
1025 for item in folder {
1026 match item {
1027 BuilderFileTreeNode::File {digest, ..} => {
1028 if let Some(digest) = digest {
1029 digest_out.digest_other(&*digest);
1030 }
1031 },
1032 BuilderFileTreeNode::Folder {digest, ..} => {
1033 if let Some(digest) = digest {
1034 digest_out.digest_other(&*digest);
1035 }
1036 },
1037 }
1038 }
1039 digest_out.digest_buffer(name.as_bytes());
1040 Some(Box::new(digest_out))
1041 }
1042
1043 fn read_recur(path: &str, create_digest: bool, ext_inc: &Vec<String>, file_ex: &Vec<String>, dir_ex: &Vec<String>) -> Vec<BuilderFileTreeNode> {
1044 let mut ret = Vec::new();
1045 if let Ok(read_dir) = fs::read_dir(path) {
1046 for entry in read_dir {
1047 if let Ok(entry) = entry {
1048 if let Ok(ty) = entry.file_type() {
1049 if let Ok(name) = entry.file_name().into_string() {
1050 if ty.is_dir() {
1051 if dir_ex.iter().find( | dir | **dir == name).is_some() {
1052 continue
1053 }
1054 let folder = read_recur(&format!("{}/{}", path, name), create_digest, ext_inc, file_ex, dir_ex);
1055 ret.push(BuilderFileTreeNode::Folder {
1056 name: name.clone(),
1057 digest: digest_folder(create_digest, &name, &folder),
1058 folder: folder
1059 });
1060 }
1061 else {
1062 if file_ex.iter().find( | file | **file == name).is_some() {
1063 continue
1064 }
1065 if ext_inc.iter().find(|ext| name.ends_with(*ext)).is_some(){
1066 if create_digest {
1067
1068 }
1069 ret.push(BuilderFileTreeNode::File {
1070 digest: None,
1071 name: name
1072 });
1073 }
1074 }
1075 }
1076 }
1077 }
1078 }
1079 }
1080 ret.sort();
1081
1082 ret
1083 }
1084
1085 let ext_inc: Vec<String> = ext_inc.to_vec().iter().map( | v | v.to_string()).collect();
1086 let file_ex: Vec<String> = file_ex.to_vec().iter().map( | v | v.to_string()).collect();
1087 let dir_ex: Vec<String> = dir_ex.to_vec().iter().map( | v | v.to_string()).collect();
1088
1089 let mut root_folder = Vec::new();
1090
1091 if let Ok(workspaces) = self.workspaces.lock() {
1092 for (project, abs_path) in workspaces.iter() {
1093
1094 let folder = read_recur(&abs_path, create_digest, &ext_inc, &file_ex, &dir_ex);
1095 let tree = BuilderFileTreeNode::Folder {
1096 name: project.clone(),
1097 digest: digest_folder(create_digest, &project, &folder),
1098 folder: folder
1099 };
1100 root_folder.push(tree);
1101 }
1102 }
1103 let root = BuilderFileTreeNode::Folder {
1104 name: self.builder.clone(),
1105 digest: digest_folder(create_digest, &self.builder, &root_folder),
1106 folder: root_folder
1107 };
1108 root
1109 }
1110}
1111
1112fn rel_to_abs_path(abs_root: &str, path: &str) -> String {
1113 if path.starts_with("/") {
1114 return path.to_string();
1115 }
1116 de_relativize_path(&format!("{}/{}", abs_root, path))
1117}
1118
1119fn de_relativize_path(path: &str) -> String {
1120 let splits: Vec<&str> = path.split("/").collect();
1121 let mut out = Vec::new();
1122 for split in splits {
1123 if split == ".." && out.len()>0 {
1124 out.pop();
1125 }
1126 else if split != "." {
1127 out.push(split);
1128 }
1129 }
1130 out.join("/")
1131}
1132
1133
1134#[derive(Clone, Deserialize, Default)]
1136pub struct RustcTarget {
1137 kind: Vec<String>,
1138 crate_types: Vec<String>,
1139 name: String,
1140 src_path: String,
1141 edition: String
1142}
1143
1144#[derive(Clone, Deserialize, Default)]
1145pub struct RustcText {
1146 text: String,
1147 highlight_start: u32,
1148 highlight_end: u32
1149}
1150
1151#[derive(Clone, Deserialize, Default)]
1152pub struct RustcSpan {
1153 file_name: String,
1154 byte_start: u32,
1155 byte_end: u32,
1156 line_start: u32,
1157 line_end: u32,
1158 column_start: u32,
1159 column_end: u32,
1160 is_primary: bool,
1161 text: Vec<RustcText>,
1162 label: Option<String>,
1163 suggested_replacement: Option<String>,
1164 sugggested_applicability: Option<String>,
1165 expansion: Option<Box<RustcExpansion>>,
1166 level: Option<String>
1167}
1168
1169#[derive(Clone, Deserialize, Default)]
1170pub struct RustcExpansion {
1171 span: Option<RustcSpan>,
1172 macro_decl_name: String,
1173 def_site_span: Option<RustcSpan>
1174}
1175
1176#[derive(Clone, Deserialize, Default)]
1177pub struct RustcCode {
1178 code: String,
1179 explanation: Option<String>
1180}
1181
1182#[derive(Clone, Deserialize, Default)]
1183pub struct RustcMessage {
1184 message: String,
1185 code: Option<RustcCode>,
1186 level: String,
1187 spans: Vec<RustcSpan>,
1188 children: Vec<RustcMessage>,
1189 rendered: Option<String>
1190}
1191
1192#[derive(Clone, Deserialize, Default)]
1193pub struct RustcProfile {
1194 opt_level: String,
1195 debuginfo: Option<u32>,
1196 debug_assertions: bool,
1197 overflow_checks: bool,
1198 test: bool
1199}
1200
1201#[derive(Clone, Deserialize, Default)]
1202pub struct RustcCompilerMessage {
1203 reason: String,
1204 package_id: String,
1205 target: Option<RustcTarget>,
1206 message: Option<RustcMessage>,
1207 profile: Option<RustcProfile>,
1208 features: Option<Vec<String>>,
1209 filenames: Option<Vec<String>>,
1210 executable: Option<String>,
1211 fresh: Option<bool>
1212}