src/Pure/Tools/build.scala
changeset 65291 57c85c83c11b
parent 65289 86d93effc3df
child 65294 69100bf4ead4
equal deleted inserted replaced
65290:6c1d7d5c2165 65291:57c85c83c11b
    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)
   369 
   334 
   370     // optional cleanup
   335     // optional cleanup
   371     if (clean_build) {
   336     if (clean_build) {
   372       for (name <- full_tree.graph.all_succs(selected)) {
   337       for (name <- full_tree.graph.all_succs(selected)) {
   373         val files =
   338         val files =
   374           List(Path.basic(name), store.log(name), store.log_gz(name)).
   339           List(Path.basic(name), store.database(name), store.log(name), store.log_gz(name)).
   375             map(store.output_dir + _).filter(_.is_file)
   340             map(store.output_dir + _).filter(_.is_file)
   376         if (files.nonEmpty) progress.echo("Cleaning " + name + " ...")
   341         if (files.nonEmpty) progress.echo("Cleaning " + name + " ...")
   377         if (!files.forall(p => p.file.delete)) progress.echo(name + " FAILED to delete")
   342         if (!files.forall(p => p.file.delete)) progress.echo(name + " FAILED to delete")
   378       }
   343       }
   379     }
   344     }
   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                   }