19 |
19 |
20 object Build |
20 object Build |
21 { |
21 { |
22 /** auxiliary **/ |
22 /** auxiliary **/ |
23 |
23 |
|
24 /* persistent build info */ |
|
25 |
|
26 sealed case class Session_Info( |
|
27 sources: List[String], |
|
28 input_heaps: List[String], |
|
29 output_heap: Option[String], |
|
30 return_code: Int) |
|
31 |
|
32 |
24 /* queue with scheduling information */ |
33 /* queue with scheduling information */ |
25 |
34 |
26 private object Queue |
35 private object Queue |
27 { |
36 { |
28 def load_timings(store: Sessions.Store, name: String): (List[Properties.T], Double) = |
37 def load_timings(store: Sessions.Store, name: String): (List[Properties.T], Double) = |
29 { |
38 { |
30 val (path, text) = |
39 val no_timings: (List[Properties.T], Double) = (Nil, 0.0) |
31 store.find_log_gz(name) match { |
40 |
32 case Some(path) => (path, File.read_gzip(path)) |
41 store.find_database(name) match { |
33 case None => |
42 case None => no_timings |
34 store.find_log(name) match { |
43 case Some(database) => |
35 case Some(path) => (path, File.read(path)) |
44 def ignore_error(msg: String) = |
36 case None => (Path.current, "") |
45 { |
37 } |
46 Output.warning("Ignoring bad database: " + database + (if (msg == "") "" else "\n" + msg)) |
38 } |
47 no_timings |
39 |
48 } |
40 def ignore_error(msg: String): (List[Properties.T], Double) = |
49 try { |
41 { |
50 using(SQLite.open_database(database))(db => |
42 Output.warning("Ignoring bad log file: " + path + (if (msg == "") "" else "\n" + msg)) |
51 { |
43 (Nil, 0.0) |
52 val build_log = |
44 } |
53 Sessions.Session_Info.read_build_log(store, db, name, command_timings = true) |
45 |
54 val session_timing = Markup.Elapsed.unapply(build_log.session_timing) getOrElse 0.0 |
46 try { |
55 (build_log.command_timings, session_timing) |
47 val info = Build_Log.Log_File(name, text).parse_session_info(name, command_timings = true) |
56 }) |
48 val session_timing = Markup.Elapsed.unapply(info.session_timing) getOrElse 0.0 |
57 } |
49 (info.command_timings, session_timing) |
58 catch { |
50 } |
59 case ERROR(msg) => ignore_error(msg) |
51 catch { |
60 case exn: java.lang.Error => ignore_error(Exn.message(exn)) |
52 case ERROR(msg) => ignore_error(msg) |
61 case _: XML.Error => ignore_error("") |
53 case exn: java.lang.Error => ignore_error(Exn.message(exn)) |
62 } |
54 case _: XML.Error => ignore_error("") |
|
55 } |
63 } |
56 } |
64 } |
57 |
65 |
58 def apply(tree: Sessions.Tree, store: Sessions.Store): Queue = |
66 def apply(tree: Sessions.Tree, store: Sessions.Store): Queue = |
59 { |
67 { |
60 val graph = tree.graph |
68 val graph = tree.graph |
61 val sessions = graph.keys |
69 val sessions = graph.keys |
62 |
70 |
63 val timings = Par_List.map((name: String) => (name, load_timings(store, name)), sessions) |
71 val timings = sessions.map(name => (name, load_timings(store, name))) |
64 val command_timings = |
72 val command_timings = |
65 Map(timings.map({ case (name, (ts, _)) => (name, ts) }): _*).withDefaultValue(Nil) |
73 Map(timings.map({ case (name, (ts, _)) => (name, ts) }): _*).withDefaultValue(Nil) |
66 val session_timing = |
74 val session_timing = |
67 Map(timings.map({ case (name, (_, t)) => (name, t) }): _*).withDefaultValue(0.0) |
75 Map(timings.map({ case (name, (_, t)) => (name, t) }): _*).withDefaultValue(0.0) |
68 |
76 |
227 else result.error(Output.error_text("Interrupt")) |
235 else result.error(Output.error_text("Interrupt")) |
228 } |
236 } |
229 else result |
237 else result |
230 } |
238 } |
231 } |
239 } |
232 |
|
233 |
|
234 /* sources and heaps */ |
|
235 |
|
236 sealed case class Session_Info( |
|
237 sources: List[String], input_heaps: List[String], output_heap: String, return_code: Int) |
|
238 |
|
239 private val SOURCES = "sources: " |
|
240 private val INPUT_HEAP = "input_heap: " |
|
241 private val OUTPUT_HEAP = "output_heap: " |
|
242 private val LOG_START = "log:" |
|
243 private val line_prefixes = List(SOURCES, INPUT_HEAP, OUTPUT_HEAP, LOG_START) |
|
244 |
|
245 private def sources_stamp(digests: List[SHA1.Digest]): String = |
|
246 digests.map(_.toString).sorted.mkString(SOURCES, " ", "") |
|
247 |
|
248 private def read_stamps(path: Path): Option[(String, List[String], List[String])] = |
|
249 if (path.is_file) { |
|
250 val stream = new GZIPInputStream(new BufferedInputStream(new FileInputStream(path.file))) |
|
251 val reader = new BufferedReader(new InputStreamReader(stream, UTF8.charset)) |
|
252 val lines = |
|
253 { |
|
254 val lines = new mutable.ListBuffer[String] |
|
255 try { |
|
256 var finished = false |
|
257 while (!finished) { |
|
258 val line = reader.readLine |
|
259 if (line != null && line_prefixes.exists(line.startsWith(_))) |
|
260 lines += line |
|
261 else finished = true |
|
262 } |
|
263 } |
|
264 finally { reader.close } |
|
265 lines.toList |
|
266 } |
|
267 |
|
268 if (!lines.isEmpty && lines.last.startsWith(LOG_START)) { |
|
269 lines.find(_.startsWith(SOURCES)).map(s => |
|
270 (s, lines.filter(_.startsWith(INPUT_HEAP)), lines.filter(_.startsWith(OUTPUT_HEAP)))) |
|
271 } |
|
272 else None |
|
273 } |
|
274 else None |
|
275 |
240 |
276 |
241 |
277 |
242 |
278 /** build with results **/ |
243 /** build with results **/ |
279 |
244 |
354 val full_tree = Sessions.load(build_options, dirs, select_dirs) |
319 val full_tree = Sessions.load(build_options, dirs, select_dirs) |
355 val (selected, selected_tree) = selection(full_tree) |
320 val (selected, selected_tree) = selection(full_tree) |
356 val deps = |
321 val deps = |
357 Sessions.dependencies(progress, true, verbose, list_files, check_keywords, selected_tree) |
322 Sessions.dependencies(progress, true, verbose, list_files, check_keywords, selected_tree) |
358 |
323 |
359 def session_sources_stamp(name: String): String = |
324 def sources_stamp(name: String): List[String] = |
360 sources_stamp(selected_tree(name).meta_digest :: deps.sources(name)) |
325 (selected_tree(name).meta_digest :: deps.sources(name)).map(_.toString).sorted |
361 |
326 |
362 |
327 |
363 /* main build process */ |
328 /* main build process */ |
364 |
329 |
365 val store = Sessions.store(system_mode) |
330 val store = Sessions.store(system_mode) |
438 val heap_stamp = |
403 val heap_stamp = |
439 for (path <- job.output_path if path.is_file) |
404 for (path <- job.output_path if path.is_file) |
440 yield Sessions.write_heap_digest(path) |
405 yield Sessions.write_heap_digest(path) |
441 |
406 |
442 File.write_gzip(store.output_dir + store.log_gz(name), |
407 File.write_gzip(store.output_dir + store.log_gz(name), |
443 terminate_lines( |
408 terminate_lines(process_result.out_lines)) |
444 session_sources_stamp(name) :: |
|
445 input_heaps.map(INPUT_HEAP + _) ::: |
|
446 heap_stamp.toList.map(OUTPUT_HEAP + _) ::: |
|
447 List(LOG_START) ::: process_result.out_lines)) |
|
448 |
409 |
449 heap_stamp |
410 heap_stamp |
450 } |
411 } |
451 else { |
412 else { |
452 (store.output_dir + Path.basic(name)).file.delete |
413 (store.output_dir + Path.basic(name)).file.delete |
457 progress.echo(name + " FAILED") |
418 progress.echo(name + " FAILED") |
458 if (!process_result.interrupted) progress.echo(process_result_tail.out) |
419 if (!process_result.interrupted) progress.echo(process_result_tail.out) |
459 |
420 |
460 None |
421 None |
461 } |
422 } |
|
423 |
|
424 // write database |
|
425 { |
|
426 val database = store.output_dir + store.database(name) |
|
427 database.file.delete |
|
428 |
|
429 using(SQLite.open_database(database))(db => |
|
430 Sessions.Session_Info.write(store, db, |
|
431 build_log = |
|
432 Build_Log.Log_File(name, process_result.out_lines). |
|
433 parse_session_info(name, |
|
434 command_timings = true, ml_statistics = true, task_statistics = true), |
|
435 build = |
|
436 Session_Info(sources_stamp(name), input_heaps, heap_stamp, process_result.rc))) |
|
437 } |
|
438 |
462 loop(pending - name, running - name, |
439 loop(pending - name, running - name, |
463 results + (name -> Result(false, heap_stamp, Some(process_result_tail), job.info))) |
440 results + (name -> Result(false, heap_stamp, Some(process_result_tail), job.info))) |
464 //}}} |
441 //}}} |
465 case None if running.size < (max_jobs max 1) => |
442 case None if running.size < (max_jobs max 1) => |
466 //{{{ check/start next job |
443 //{{{ check/start next job |
471 |
448 |
472 val do_output = build_heap || Sessions.pure_name(name) || queue.is_inner(name) |
449 val do_output = build_heap || Sessions.pure_name(name) || queue.is_inner(name) |
473 |
450 |
474 val (current, heap_stamp) = |
451 val (current, heap_stamp) = |
475 { |
452 { |
476 store.find(name) match { |
453 store.find_database_heap(name) match { |
477 case Some((log_gz, heap_stamp)) => |
454 case Some((database, heap_stamp)) => |
478 read_stamps(log_gz) match { |
455 using(SQLite.open_database(database))( |
479 case Some((sources, input_heaps, output_heaps)) => |
456 Sessions.Session_Info.read_build(store, _)) match |
|
457 { |
|
458 case Some(build) => |
480 val current = |
459 val current = |
481 sources == session_sources_stamp(name) && |
460 build.sources == sources_stamp(name) && |
482 input_heaps == ancestor_heaps.map(INPUT_HEAP + _) && |
461 build.input_heaps == ancestor_heaps && |
483 output_heaps == heap_stamp.toList.map(OUTPUT_HEAP + _) && |
462 build.output_heap == heap_stamp && |
484 !(do_output && heap_stamp.isEmpty) |
463 !(do_output && heap_stamp.isEmpty) && |
|
464 build.return_code == 0 |
485 (current, heap_stamp) |
465 (current, heap_stamp) |
486 case None => (false, None) |
466 case None => (false, None) |
487 } |
467 } |
488 case None => (false, None) |
468 case None => (false, None) |
489 } |
469 } |