/* Title: Pure/Tools/build.scala
Author: Makarius
Options: :folding=explicit:collapseFolds=1:
Build and manage Isabelle sessions.
*/
package isabelle
import java.util.{Timer, TimerTask}
import java.io.{BufferedInputStream, FileInputStream,
BufferedReader, InputStreamReader, IOException}
import java.util.zip.GZIPInputStream
import scala.collection.SortedSet
import scala.collection.mutable
import scala.annotation.tailrec
object Build
{
/** progress context **/
class Progress
{
def echo(msg: String) {}
def theory(session: String, theory: String) {}
def stopped: Boolean = false
}
object Ignore_Progress extends Progress
class Console_Progress(verbose: Boolean) extends Progress
{
override def echo(msg: String) { java.lang.System.out.println(msg) }
override def theory(session: String, theory: String): Unit =
if (verbose) echo(session + ": theory " + theory)
@volatile private var is_stopped = false
def interrupt_handler[A](e: => A): A = Interrupt.handler { is_stopped = true } { e }
override def stopped: Boolean = is_stopped
}
/** session information **/
// external version
abstract class Entry
sealed case class Chapter(name: String) extends Entry
sealed case class Session_Entry(
pos: Position.T,
name: String,
groups: List[String],
path: String,
parent: Option[String],
description: String,
options: List[Options.Spec],
theories: List[(List[Options.Spec], List[String])],
files: List[String]) extends Entry
// internal version
sealed case class Session_Info(
chapter: String,
select: Boolean,
pos: Position.T,
groups: List[String],
dir: Path,
parent: Option[String],
description: String,
options: Options,
theories: List[(Options, List[Path])],
files: List[Path],
entry_digest: SHA1.Digest)
def is_pure(name: String): Boolean = name == "RAW" || name == "Pure"
def session_info(options: Options, select: Boolean, dir: Path,
chapter: String, entry: Session_Entry): (String, Session_Info) =
try {
val name = entry.name
if (name == "") error("Bad session name")
if (is_pure(name) && entry.parent.isDefined) error("Illegal parent session")
if (!is_pure(name) && !entry.parent.isDefined) error("Missing parent session")
val session_options = options ++ entry.options
val theories =
entry.theories.map({ case (opts, thys) =>
(session_options ++ opts, thys.map(Path.explode(_))) })
val files = entry.files.map(Path.explode(_))
val entry_digest =
SHA1.digest((chapter, name, entry.parent, entry.options, entry.theories).toString)
val info =
Session_Info(chapter, select, entry.pos, entry.groups, dir + Path.explode(entry.path),
entry.parent, entry.description, session_options, theories, files, entry_digest)
(name, info)
}
catch {
case ERROR(msg) =>
error(msg + "\nThe error(s) above occurred in session entry " +
quote(entry.name) + Position.here(entry.pos))
}
/* session tree */
object Session_Tree
{
def apply(infos: Seq[(String, Session_Info)]): Session_Tree =
{
val graph1 =
(Graph.string[Session_Info] /: infos) {
case (graph, (name, info)) =>
if (graph.defined(name))
error("Duplicate session " + quote(name) + Position.here(info.pos) +
Position.here(graph.get_node(name).pos))
else graph.new_node(name, info)
}
val graph2 =
(graph1 /: graph1.entries) {
case (graph, (name, (info, _))) =>
info.parent match {
case None => graph
case Some(parent) =>
if (!graph.defined(parent))
error("Bad parent session " + quote(parent) + " for " +
quote(name) + Position.here(info.pos))
try { graph.add_edge_acyclic(parent, name) }
catch {
case exn: Graph.Cycles[_] =>
error(cat_lines(exn.cycles.map(cycle =>
"Cyclic session dependency of " +
cycle.map(c => quote(c.toString)).mkString(" via "))) +
Position.here(info.pos))
}
}
}
new Session_Tree(graph2)
}
}
final class Session_Tree private(val graph: Graph[String, Session_Info])
extends PartialFunction[String, Session_Info]
{
def apply(name: String): Session_Info = graph.get_node(name)
def isDefinedAt(name: String): Boolean = graph.defined(name)
def selection(requirements: Boolean, all_sessions: Boolean,
session_groups: List[String], sessions: List[String]): (List[String], Session_Tree) =
{
val bad_sessions = sessions.filterNot(isDefinedAt(_))
if (!bad_sessions.isEmpty) error("Undefined session(s): " + commas_quote(bad_sessions))
val pre_selected =
{
if (all_sessions) graph.keys.toList
else {
val select_group = session_groups.toSet
val select = sessions.toSet
(for {
(name, (info, _)) <- graph.entries
if info.select || select(name) || apply(name).groups.exists(select_group)
} yield name).toList
}
}
val selected =
if (requirements) (graph.all_preds(pre_selected).toSet -- pre_selected).toList
else pre_selected
val tree1 = new Session_Tree(graph.restrict(graph.all_preds(selected).toSet))
(selected, tree1)
}
def topological_order: List[(String, Session_Info)] =
graph.topological_order.map(name => (name, apply(name)))
override def toString: String = graph.entries.map(_._1).toList.sorted.mkString(",")
}
/* parser */
val chapter_default = "Unsorted"
private val CHAPTER = "chapter"
private val SESSION = "session"
private val IN = "in"
private val DESCRIPTION = "description"
private val OPTIONS = "options"
private val THEORIES = "theories"
private val FILES = "files"
lazy val root_syntax =
Outer_Syntax.init() + "(" + ")" + "+" + "," + "=" + "[" + "]" +
(CHAPTER, Keyword.THY_DECL) + (SESSION, Keyword.THY_DECL) +
IN + DESCRIPTION + OPTIONS + THEORIES + FILES
private object Parser extends Parse.Parser
{
def entry(pos: Position.T): Parser[Entry] = chapter(pos) | session_entry(pos)
def chapter(pos: Position.T): Parser[Chapter] =
{
val chapter_name = atom("chapter name", _.is_name)
command(CHAPTER) ~! chapter_name ^^ { case _ ~ a => Chapter(a) }
}
def session_entry(pos: Position.T): Parser[Session_Entry] =
{
val session_name = atom("session name", _.is_name)
val option =
name ~ opt(keyword("=") ~! name ^^ { case _ ~ x => x }) ^^ { case x ~ y => (x, y) }
val options = keyword("[") ~> rep1sep(option, keyword(",")) <~ keyword("]")
val theories =
keyword(THEORIES) ~! ((options | success(Nil)) ~ rep(theory_name)) ^^
{ case _ ~ (x ~ y) => (x, y) }
command(SESSION) ~!
(session_name ~
((keyword("(") ~! (rep1(name) <~ keyword(")")) ^^ { case _ ~ x => x }) | success(Nil)) ~
((keyword(IN) ~! path ^^ { case _ ~ x => x }) | success(".")) ~
(keyword("=") ~!
(opt(session_name ~! keyword("+") ^^ { case x ~ _ => x }) ~
((keyword(DESCRIPTION) ~! text ^^ { case _ ~ x => x }) | success("")) ~
((keyword(OPTIONS) ~! options ^^ { case _ ~ x => x }) | success(Nil)) ~
rep1(theories) ~
((keyword(FILES) ~! rep1(path) ^^ { case _ ~ x => x }) | success(Nil))))) ^^
{ case _ ~ (a ~ b ~ c ~ (_ ~ (d ~ e ~ f ~ g ~ h))) =>
Session_Entry(pos, a, b, c, d, e, f, g, h) }
}
def parse_entries(root: Path): List[(String, Session_Entry)] =
{
val toks = root_syntax.scan(File.read(root))
parse_all(rep(entry(root.position)), Token.reader(toks, root.implode)) match {
case Success(result, _) =>
var chapter = chapter_default
val entries = new mutable.ListBuffer[(String, Session_Entry)]
result.foreach {
case Chapter(name) => chapter = name
case session_entry: Session_Entry => entries += ((chapter, session_entry))
}
entries.toList
case bad => error(bad.toString)
}
}
}
/* find sessions within certain directories */
private val ROOT = Path.explode("ROOT")
private val ROOTS = Path.explode("ROOTS")
private def is_session_dir(dir: Path): Boolean =
(dir + ROOT).is_file || (dir + ROOTS).is_file
private def check_session_dir(dir: Path): Path =
if (is_session_dir(dir)) dir
else error("Bad session root directory: " + dir.toString)
def find_sessions(options: Options, more_dirs: List[(Boolean, Path)]): Session_Tree =
{
def find_dir(select: Boolean, dir: Path): List[(String, Session_Info)] =
find_root(select, dir) ::: find_roots(select, dir)
def find_root(select: Boolean, dir: Path): List[(String, Session_Info)] =
{
val root = dir + ROOT
if (root.is_file)
Parser.parse_entries(root).map(p => session_info(options, select, dir, p._1, p._2))
else Nil
}
def find_roots(select: Boolean, dir: Path): List[(String, Session_Info)] =
{
val roots = dir + ROOTS
if (roots.is_file) {
for {
line <- split_lines(File.read(roots))
if !(line == "" || line.startsWith("#"))
dir1 =
try { check_session_dir(dir + Path.explode(line)) }
catch {
case ERROR(msg) =>
error(msg + "\nThe error(s) above occurred in session catalog " + roots.toString)
}
info <- find_dir(select, dir1)
} yield info
}
else Nil
}
val default_dirs = Isabelle_System.components().filter(is_session_dir(_)).map((false, _))
more_dirs foreach { case (_, dir) => check_session_dir(dir) }
Session_Tree(
for {
(select, dir) <- default_dirs ::: more_dirs
info <- find_dir(select, dir)
} yield info)
}
/** build **/
/* queue */
object Queue
{
def apply(tree: Session_Tree, load_timings: String => (List[Properties.T], Double)): Queue =
{
val graph = tree.graph
val sessions = graph.keys.toList
val timings =
sessions.par.map((name: String) =>
Exn.capture { (name, load_timings(name)) }).toList.map(Exn.release(_))
val command_timings =
Map(timings.map({ case (name, (ts, _)) => (name, ts) }): _*).withDefaultValue(Nil)
val session_timing =
Map(timings.map({ case (name, (_, t)) => (name, t) }): _*).withDefaultValue(0.0)
def outdegree(name: String): Int = graph.imm_succs(name).size
def timeout(name: String): Double = tree(name).options.real("timeout")
object Ordering extends scala.math.Ordering[String]
{
def compare_timing(name1: String, name2: String): Int =
{
val t1 = session_timing(name1)
val t2 = session_timing(name2)
if (t1 == 0.0 || t2 == 0.0) 0
else t1 compare t2
}
def compare(name1: String, name2: String): Int =
outdegree(name2) compare outdegree(name1) match {
case 0 =>
compare_timing(name2, name1) match {
case 0 =>
timeout(name2) compare timeout(name1) match {
case 0 => name1 compare name2
case ord => ord
}
case ord => ord
}
case ord => ord
}
}
new Queue(graph, SortedSet(sessions: _*)(Ordering), command_timings)
}
}
final class Queue private(
graph: Graph[String, Session_Info],
order: SortedSet[String],
val command_timings: String => List[Properties.T])
{
def is_inner(name: String): Boolean = !graph.is_maximal(name)
def is_empty: Boolean = graph.is_empty
def - (name: String): Queue =
new Queue(graph.del_node(name),
order - name, // FIXME scala-2.10.0 TreeSet problem!?
command_timings)
def dequeue(skip: String => Boolean): Option[(String, Session_Info)] =
{
val it = order.iterator.dropWhile(name =>
skip(name)
|| !graph.defined(name) // FIXME scala-2.10.0 TreeSet problem!?
|| !graph.is_minimal(name))
if (it.hasNext) { val name = it.next; Some((name, graph.get_node(name))) }
else None
}
}
/* source dependencies and static content */
sealed case class Session_Content(
loaded_theories: Set[String],
keywords: Thy_Header.Keywords,
syntax: Outer_Syntax,
sources: List[(Path, SHA1.Digest)])
sealed case class Deps(deps: Map[String, Session_Content])
{
def is_empty: Boolean = deps.isEmpty
def apply(name: String): Session_Content = deps(name)
def sources(name: String): List[SHA1.Digest] = deps(name).sources.map(_._2)
}
def dependencies(progress: Progress, inlined_files: Boolean,
verbose: Boolean, list_files: Boolean, tree: Session_Tree): Deps =
Deps((Map.empty[String, Session_Content] /: tree.topological_order)(
{ case (deps, (name, info)) =>
try {
val (preloaded, parent_syntax) =
info.parent match {
case None =>
(Set.empty[String], Outer_Syntax.init())
case Some(parent_name) =>
val parent = deps(parent_name)
(parent.loaded_theories, parent.syntax)
}
val thy_load = new Thy_Load(preloaded, parent_syntax)
val thy_info = new Thy_Info(thy_load)
if (verbose || list_files) {
val groups =
if (info.groups.isEmpty) ""
else info.groups.mkString(" (", " ", ")")
progress.echo("Session " + info.chapter + "/" + name + groups)
}
val thy_deps =
thy_info.dependencies(
info.theories.map(_._2).flatten.
map(thy => thy_load.node_name(info.dir + Thy_Load.thy_path(thy))))
thy_deps.errors match {
case Nil =>
case errs => error(cat_lines(errs))
}
val loaded_theories = thy_deps.loaded_theories
val keywords = thy_deps.keywords
val syntax = thy_deps.syntax
val body_files = if (inlined_files) thy_deps.load_files else Nil
val all_files =
(thy_deps.deps.map(dep => Path.explode(dep.name.node)) ::: body_files :::
info.files.map(file => info.dir + file)).map(_.expand)
if (list_files) {
progress.echo(cat_lines(all_files.map(_.implode).sorted.map(" " + _)))
for {
file <- all_files
if file.split_ext._2 == "ML"
} {
val path = info.dir + file
try { Symbol.decode_strict(File.read(path)) }
catch {
case ERROR(msg) => cat_error(msg, "The error(s) above occurred in file " + path)
}
}
}
val sources = all_files.map(p => (p, SHA1.digest(p.file)))
deps + (name -> Session_Content(loaded_theories, keywords, syntax, sources))
}
catch {
case ERROR(msg) =>
cat_error(msg, "The error(s) above occurred in session " +
quote(name) + Position.here(info.pos))
}
}))
def session_dependencies(
options: Options,
inlined_files: Boolean,
dirs: List[Path],
sessions: List[String]): Deps =
{
val (_, tree) =
find_sessions(options, dirs.map((false, _))).selection(false, false, Nil, sessions)
dependencies(Ignore_Progress, inlined_files, false, false, tree)
}
def session_content(
options: Options,
inlined_files: Boolean,
dirs: List[Path],
session: String): Session_Content =
{
session_dependencies(options, inlined_files, dirs, List(session))(session)
}
def outer_syntax(options: Options, session: String): Outer_Syntax =
session_content(options, false, Nil, session).syntax
/* jobs */
private class Job(progress: Progress,
name: String, val info: Session_Info, output: Path, do_output: Boolean,
verbose: Boolean, browser_info: Path, command_timings: List[Properties.T])
{
def output_path: Option[Path] = if (do_output) Some(output) else None
private val parent = info.parent.getOrElse("")
private val args_file = File.tmp_file("args")
File.write(args_file, YXML.string_of_body(
if (is_pure(name)) Options.encode(info.options)
else
{
import XML.Encode._
pair(list(properties), pair(bool, pair(Options.encode, pair(bool, pair(Path.encode,
pair(string, pair(string, pair(string,
list(pair(Options.encode, list(Path.encode)))))))))))(
(command_timings, (do_output, (info.options, (verbose, (browser_info,
(parent, (info.chapter, (name, info.theories)))))))))
}))
private val env =
Map("INPUT" -> parent, "TARGET" -> name, "OUTPUT" -> Isabelle_System.standard_path(output),
(if (is_pure(name)) "ISABELLE_PROCESS_OPTIONS" else "ARGS_FILE") ->
Isabelle_System.posix_path(args_file))
private val script =
if (is_pure(name)) {
if (do_output) "./build " + name + " \"$OUTPUT\""
else """ rm -f "$OUTPUT"; ./build """ + name
}
else {
"""
. "$ISABELLE_HOME/lib/scripts/timestart.bash"
""" +
(if (do_output)
"""
"$ISABELLE_PROCESS" -e "Build.build \"$ARGS_FILE\";" -q -w "$INPUT" "$OUTPUT"
"""
else
"""
rm -f "$OUTPUT"; "$ISABELLE_PROCESS" -e "Build.build \"$ARGS_FILE\";" -r -q "$INPUT"
""") +
"""
RC="$?"
. "$ISABELLE_HOME/lib/scripts/timestop.bash"
if [ "$RC" -eq 0 ]; then
echo "Finished $TARGET ($TIMES_REPORT)" >&2
fi
exit "$RC"
"""
}
private val (thread, result) =
Simple_Thread.future("build") {
Isabelle_System.bash_env(info.dir.file, env, script,
progress_stdout = (line: String) =>
Library.try_unprefix("\floading_theory = ", line) match {
case Some(theory) => progress.theory(name, theory)
case None =>
},
progress_limit =
info.options.int("process_output_limit") match {
case 0 => None
case m => Some(m * 1000000L)
})
}
def terminate: Unit = thread.interrupt
def is_finished: Boolean = result.is_finished
@volatile private var timeout = false
private val time = info.options.seconds("timeout")
private val timer: Option[Timer] =
if (time.seconds > 0.0) {
val t = new Timer("build", true)
t.schedule(new TimerTask { def run = { terminate; timeout = true } }, time.ms)
Some(t)
}
else None
def join: Isabelle_System.Bash_Result =
{
val res = result.join
args_file.delete
timer.map(_.cancel())
if (res.rc == 130) {
if (timeout) res.add_err("*** Timeout").set_rc(1)
else res.add_err("*** Interrupt")
}
else res
}
}
/* inlined properties (YXML) */
object Props
{
def parse(text: String): Properties.T = XML.Decode.properties(YXML.parse_body(text))
def parse_lines(prefix: String, lines: List[String]): List[Properties.T] =
for (line <- lines; s <- Library.try_unprefix(prefix, line)) yield parse(s)
def find_parse_line(prefix: String, lines: List[String]): Option[Properties.T] =
lines.find(_.startsWith(prefix)).map(line => parse(line.substring(prefix.length)))
}
/* log files */
private val LOG = Path.explode("log")
private def log(name: String): Path = LOG + Path.basic(name)
private def log_gz(name: String): Path = log(name).ext("gz")
private val SESSION_NAME = "\fSession.name = "
sealed case class Log_Info(
name: String,
stats: List[Properties.T],
tasks: List[Properties.T],
command_timings: List[Properties.T],
session_timing: Properties.T)
def parse_log(full_stats: Boolean, text: String): Log_Info =
{
val lines = split_lines(text)
val xml_cache = new XML.Cache()
def parse_lines(prfx: String): List[Properties.T] =
Props.parse_lines(prfx, lines).map(xml_cache.props(_))
val name =
lines.find(_.startsWith(SESSION_NAME)).map(_.substring(SESSION_NAME.length)) getOrElse ""
val stats = if (full_stats) parse_lines("\fML_statistics = ") else Nil
val tasks = if (full_stats) parse_lines("\ftask_statistics = ") else Nil
val command_timings = parse_lines("\fcommand_timing = ")
val session_timing = Props.find_parse_line("\fTiming = ", lines) getOrElse Nil
Log_Info(name, stats, tasks, command_timings, session_timing)
}
/* sources and heaps */
private def sources_stamp(digests: List[SHA1.Digest]): String =
digests.map(_.toString).sorted.mkString("sources: ", " ", "")
private val no_heap: String = "heap: -"
private def heap_stamp(heap: Option[Path]): String =
{
"heap: " +
(heap match {
case Some(path) =>
val file = path.file
if (file.isFile) file.length.toString + " " + file.lastModified.toString
else "-"
case None => "-"
})
}
private def read_stamps(path: Path): Option[(String, String, String)] =
if (path.is_file) {
val stream = new GZIPInputStream (new BufferedInputStream(new FileInputStream(path.file)))
val reader = new BufferedReader(new InputStreamReader(stream, UTF8.charset))
val (s, h1, h2) =
try { (reader.readLine, reader.readLine, reader.readLine) }
finally { reader.close }
if (s != null && s.startsWith("sources: ") &&
h1 != null && h1.startsWith("heap: ") &&
h2 != null && h2.startsWith("heap: ")) Some((s, h1, h2))
else None
}
else None
/* build_results */
def build_results(
options: Options,
progress: Progress = Ignore_Progress,
requirements: Boolean = false,
all_sessions: Boolean = false,
build_heap: Boolean = false,
clean_build: Boolean = false,
more_dirs: List[(Boolean, Path)] = Nil,
session_groups: List[String] = Nil,
max_jobs: Int = 1,
list_files: Boolean = false,
no_build: Boolean = false,
system_mode: Boolean = false,
verbose: Boolean = false,
sessions: List[String] = Nil): Map[String, Int] =
{
/* session tree and dependencies */
val full_tree = find_sessions(options, more_dirs)
val (selected, selected_tree) =
full_tree.selection(requirements, all_sessions, session_groups, sessions)
val deps = dependencies(progress, true, verbose, list_files, selected_tree)
def make_stamp(name: String): String =
sources_stamp(selected_tree(name).entry_digest :: deps.sources(name))
/* persistent information */
val (input_dirs, output_dir, browser_info) =
if (system_mode) {
val output_dir = Path.explode("~~/heaps/$ML_IDENTIFIER")
(List(output_dir), output_dir, Path.explode("~~/browser_info"))
}
else {
val output_dir = Path.explode("$ISABELLE_OUTPUT")
(output_dir :: Isabelle_System.find_logics_dirs(), output_dir,
Path.explode("$ISABELLE_BROWSER_INFO"))
}
def find_log(name: String): Option[(Path, Path)] =
input_dirs.find(dir => (dir + log(name)).is_file).map(dir => (dir, dir + log(name)))
/* queue with scheduling information */
def load_timings(name: String): (List[Properties.T], Double) =
{
val (path, text) =
find_log(name + ".gz") match {
case Some((_, path)) => (path, File.read_gzip(path))
case None =>
find_log(name) match {
case Some((_, path)) => (path, File.read(path))
case None => (Path.current, "")
}
}
def ignore_error(msg: String): (List[Properties.T], Double) =
{
java.lang.System.err.println("### Ignoring bad log file: " + path +
(if (msg == "") "" else "\n" + msg))
(Nil, 0.0)
}
try {
val info = parse_log(false, text)
val session_timing = Markup.Elapsed.unapply(info.session_timing) getOrElse 0.0
(info.command_timings, session_timing)
}
catch {
case ERROR(msg) => ignore_error(msg)
case exn: java.lang.Error => ignore_error(Exn.message(exn))
case _: XML.Error => ignore_error("")
}
}
val queue = Queue(selected_tree, load_timings)
/* main build process */
// prepare log dir
Isabelle_System.mkdirs(output_dir + LOG)
// optional cleanup
if (clean_build) {
for (name <- full_tree.graph.all_succs(selected)) {
val files =
List(Path.basic(name), log(name), log_gz(name)).map(output_dir + _).filter(_.is_file)
if (!files.isEmpty) progress.echo("Cleaning " + name + " ...")
if (!files.forall(p => p.file.delete)) progress.echo(name + " FAILED to delete")
}
}
// scheduler loop
case class Result(current: Boolean, heap: String, rc: Int)
def sleep(): Unit = Thread.sleep(500)
@tailrec def loop(
pending: Queue,
running: Map[String, (String, Job)],
results: Map[String, Result]): Map[String, Result] =
{
if (pending.is_empty) results
else {
if (progress.stopped)
for ((_, (_, job)) <- running) job.terminate
running.find({ case (_, (_, job)) => job.is_finished }) match {
case Some((name, (parent_heap, job))) =>
//{{{ finish job
val res = job.join
progress.echo(res.err)
val heap =
if (res.rc == 0) {
(output_dir + log(name)).file.delete
val sources = make_stamp(name)
val heap = heap_stamp(job.output_path)
File.write_gzip(output_dir + log_gz(name),
Library.terminate_lines(sources :: parent_heap :: heap :: res.out_lines))
heap
}
else {
(output_dir + Path.basic(name)).file.delete
(output_dir + log_gz(name)).file.delete
File.write(output_dir + log(name), Library.terminate_lines(res.out_lines))
progress.echo(name + " FAILED")
if (res.rc != 130) {
progress.echo("(see also " + (output_dir + log(name)).file.toString + ")")
val lines = res.out_lines.filterNot(_.startsWith("\f"))
val tail = lines.drop(lines.length - 20 max 0)
progress.echo("\n" + cat_lines(tail))
}
no_heap
}
loop(pending - name, running - name,
results + (name -> Result(false, heap, res.rc)))
//}}}
case None if (running.size < (max_jobs max 1)) =>
//{{{ check/start next job
pending.dequeue(running.isDefinedAt(_)) match {
case Some((name, info)) =>
val parent_result =
info.parent match {
case None => Result(true, no_heap, 0)
case Some(parent) => results(parent)
}
val output = output_dir + Path.basic(name)
val do_output = build_heap || queue.is_inner(name)
val (current, heap) =
{
find_log(name + ".gz") match {
case Some((dir, path)) =>
read_stamps(path) match {
case Some((s, h1, h2)) =>
val heap = heap_stamp(Some(dir + Path.basic(name)))
(s == make_stamp(name) && h1 == parent_result.heap && h2 == heap &&
!(do_output && heap == no_heap), heap)
case None => (false, no_heap)
}
case None => (false, no_heap)
}
}
val all_current = current && parent_result.current
if (all_current)
loop(pending - name, running, results + (name -> Result(true, heap, 0)))
else if (no_build) {
if (verbose) progress.echo("Skipping " + name + " ...")
loop(pending - name, running, results + (name -> Result(false, heap, 1)))
}
else if (parent_result.rc == 0 && !progress.stopped) {
progress.echo((if (do_output) "Building " else "Running ") + name + " ...")
val job =
new Job(progress, name, info, output, do_output, verbose, browser_info,
queue.command_timings(name))
loop(pending, running + (name -> (parent_result.heap, job)), results)
}
else {
progress.echo(name + " CANCELLED")
loop(pending - name, running, results + (name -> Result(false, heap, 1)))
}
case None => sleep(); loop(pending, running, results)
}
///}}}
case None => sleep(); loop(pending, running, results)
}
}
}
/* build results */
val results =
if (deps.is_empty) {
progress.echo("### Nothing to build")
Map.empty[String, Result]
}
else loop(queue, Map.empty, Map.empty)
/* global browser info */
if (!no_build) {
val browser_chapters =
(for {
(name, result) <- results.iterator
if result.rc == 0
info = full_tree(name)
if info.options.bool("browser_info")
} yield (info.chapter, (name, info.description))).toList.groupBy(_._1).
map({ case (chapter, es) => (chapter, es.map(_._2)) }).filterNot(_._2.isEmpty)
for ((chapter, entries) <- browser_chapters)
Present.update_chapter_index(browser_info, chapter, entries)
if (!browser_chapters.isEmpty && !(browser_info + Path.explode("index.html")).is_file)
{
Isabelle_System.mkdirs(browser_info)
File.copy(Path.explode("~~/lib/logo/isabelle.gif"),
browser_info + Path.explode("isabelle.gif"))
File.write(browser_info + Path.explode("index.html"),
File.read(Path.explode("~~/lib/html/library_index_header.template")) +
File.read(Path.explode("~~/lib/html/library_index_content.template")) +
File.read(Path.explode("~~/lib/html/library_index_footer.template")))
}
}
/* results */
results.map({ case (name, result) => (name, result.rc) })
}
/* build */
def build(
options: Options,
progress: Progress = Ignore_Progress,
requirements: Boolean = false,
all_sessions: Boolean = false,
build_heap: Boolean = false,
clean_build: Boolean = false,
more_dirs: List[(Boolean, Path)] = Nil,
session_groups: List[String] = Nil,
max_jobs: Int = 1,
list_files: Boolean = false,
no_build: Boolean = false,
system_mode: Boolean = false,
verbose: Boolean = false,
sessions: List[String] = Nil): Int =
{
val results =
build_results(options, progress, requirements, all_sessions,
build_heap, clean_build, more_dirs, session_groups, max_jobs, list_files, no_build,
system_mode, verbose, sessions)
val rc = (0 /: results)({ case (rc1, (_, rc2)) => rc1 max rc2 })
if (rc != 0 && (verbose || !no_build)) {
val unfinished =
(for ((name, r) <- results.iterator if r != 0) yield name).toList.sorted
progress.echo("Unfinished session(s): " + commas(unfinished))
}
rc
}
/* command line entry point */
def main(args: Array[String])
{
Command_Line.tool {
args.toList match {
case
Properties.Value.Boolean(requirements) ::
Properties.Value.Boolean(all_sessions) ::
Properties.Value.Boolean(build_heap) ::
Properties.Value.Boolean(clean_build) ::
Properties.Value.Int(max_jobs) ::
Properties.Value.Boolean(list_files) ::
Properties.Value.Boolean(no_build) ::
Properties.Value.Boolean(system_mode) ::
Properties.Value.Boolean(verbose) ::
Command_Line.Chunks(select_dirs, include_dirs, session_groups, build_options, sessions) =>
val options = (Options.init() /: build_options)(_ + _)
val more_dirs =
select_dirs.map(d => (true, Path.explode(d))) :::
include_dirs.map(d => (false, Path.explode(d)))
val progress = new Console_Progress(verbose)
progress.interrupt_handler {
build(options, progress, requirements, all_sessions,
build_heap, clean_build, more_dirs, session_groups, max_jobs, list_files, no_build,
system_mode, verbose, sessions)
}
case _ => error("Bad arguments:\n" + cat_lines(args))
}
}
}
}