wenzelm@48276: /* Title: Pure/System/build.scala wenzelm@48276: Author: Makarius wenzelm@48276: wenzelm@48276: Build and manage Isabelle sessions. wenzelm@48276: */ wenzelm@48276: wenzelm@48276: package isabelle wenzelm@48276: wenzelm@48276: wenzelm@48335: import java.io.File wenzelm@48335: wenzelm@48337: import scala.collection.mutable wenzelm@48340: import scala.annotation.tailrec wenzelm@48337: wenzelm@48335: wenzelm@48276: object Build wenzelm@48276: { wenzelm@48337: /** session information **/ wenzelm@48334: wenzelm@48334: type Options = List[(String, Option[String])] wenzelm@48334: wenzelm@48349: object Session wenzelm@48349: { wenzelm@48349: object Key wenzelm@48349: { wenzelm@48349: object Ordering extends scala.math.Ordering[Key] wenzelm@48349: { wenzelm@48349: def compare(key1: Key, key2: Key): Int = wenzelm@48350: key1.order compare key2.order match { wenzelm@48349: case 0 => key1.name compare key2.name wenzelm@48349: case ord => ord wenzelm@48349: } wenzelm@48349: } wenzelm@48349: } wenzelm@48349: wenzelm@48349: sealed case class Key(name: String, order: Int) wenzelm@48349: { wenzelm@48349: override def toString: String = name wenzelm@48349: } wenzelm@48349: wenzelm@48349: sealed case class Info( wenzelm@48349: dir: Path, wenzelm@48349: description: String, wenzelm@48349: options: Options, wenzelm@48349: theories: List[(Options, String)], wenzelm@48349: files: List[String]) wenzelm@48349: wenzelm@48349: object Queue wenzelm@48349: { wenzelm@48349: val empty: Queue = new Queue() wenzelm@48349: } wenzelm@48349: wenzelm@48349: final class Queue private( wenzelm@48349: keys: Map[String, Key] = Map.empty, wenzelm@48349: graph: Graph[Key, Info] = Graph.empty(Key.Ordering)) wenzelm@48349: { wenzelm@48351: def defined(name: String): Boolean = keys.isDefinedAt(name) wenzelm@48349: wenzelm@48351: def + (key: Key, info: Info, parent: Option[String]): Queue = wenzelm@48349: { wenzelm@48349: val keys1 = wenzelm@48351: if (defined(key.name)) error("Duplicate session: " + quote(key.name)) wenzelm@48351: else keys + (key.name -> key) wenzelm@48349: wenzelm@48349: val graph1 = wenzelm@48349: try { wenzelm@48351: graph.new_node(key, info).add_deps_acyclic(key, parent.toList.map(keys(_))) wenzelm@48349: } wenzelm@48349: catch { wenzelm@48349: case exn: Graph.Cycles[_] => wenzelm@48349: error(cat_lines(exn.cycles.map(cycle => wenzelm@48349: "Cyclic session dependency of " + wenzelm@48349: cycle.map(key => quote(key.toString)).mkString(" via ")))) wenzelm@48349: } wenzelm@48349: new Queue(keys1, graph1) wenzelm@48349: } wenzelm@48349: wenzelm@48351: def topological_order: List[(Key, Info)] = wenzelm@48351: graph.topological_order.map(key => (key, graph.get_node(key))) wenzelm@48349: } wenzelm@48349: } wenzelm@48334: wenzelm@48337: wenzelm@48337: /* parsing */ wenzelm@48337: wenzelm@48337: private case class Session_Entry( wenzelm@48337: name: String, wenzelm@48337: reset: Boolean, wenzelm@48349: order: Int, wenzelm@48337: path: Option[String], wenzelm@48347: parent: Option[String], wenzelm@48337: description: String, wenzelm@48337: options: Options, wenzelm@48337: theories: List[(Options, List[String])], wenzelm@48337: files: List[String]) wenzelm@48337: wenzelm@48334: private object Parser extends Parse.Parser wenzelm@48334: { wenzelm@48334: val SESSION = "session" wenzelm@48334: val IN = "in" wenzelm@48334: val DESCRIPTION = "description" wenzelm@48334: val OPTIONS = "options" wenzelm@48334: val THEORIES = "theories" wenzelm@48334: val FILES = "files" wenzelm@48334: wenzelm@48334: val syntax = wenzelm@48336: Outer_Syntax.empty + "!" + "(" + ")" + "+" + "," + "=" + "[" + "]" + wenzelm@48336: SESSION + IN + DESCRIPTION + OPTIONS + THEORIES + FILES wenzelm@48334: wenzelm@48337: val session_entry: Parser[Session_Entry] = wenzelm@48334: { wenzelm@48334: val session_name = atom("session name", _.is_name) wenzelm@48334: val theory_name = atom("theory name", _.is_name) wenzelm@48280: wenzelm@48334: val option = wenzelm@48334: name ~ opt(keyword("=") ~! name ^^ { case _ ~ x => x }) ^^ { case x ~ y => (x, y) } wenzelm@48337: val options = keyword("[") ~> repsep(option, keyword(",")) <~ keyword("]") wenzelm@48334: wenzelm@48334: val theories = wenzelm@48334: keyword(THEORIES) ~! ((options | success(Nil)) ~ rep1(theory_name)) ^^ wenzelm@48334: { case _ ~ (x ~ y) => (x, y) } wenzelm@48334: wenzelm@48334: ((keyword(SESSION) ~! session_name) ^^ { case _ ~ x => x }) ~ wenzelm@48336: (keyword("!") ^^^ true | success(false)) ~ wenzelm@48349: (keyword("(") ~! (nat <~ keyword(")")) ^^ { case _ ~ x => x } | success(Integer.MAX_VALUE)) ~ wenzelm@48337: (opt(keyword(IN) ~! string ^^ { case _ ~ x => x })) ~ wenzelm@48347: (keyword("=") ~> opt(session_name <~ keyword("+"))) ~ wenzelm@48334: (keyword(DESCRIPTION) ~! text ^^ { case _ ~ x => x } | success("")) ~ wenzelm@48334: (keyword(OPTIONS) ~! options ^^ { case _ ~ x => x } | success(Nil)) ~ wenzelm@48347: rep(theories) ~ wenzelm@48336: (keyword(FILES) ~! rep1(path) ^^ { case _ ~ x => x } | success(Nil)) ^^ wenzelm@48349: { case a ~ b ~ c ~ d ~ e ~ f ~ g ~ h ~ i => Session_Entry(a, b, c, d, e, f, g, h, i) } wenzelm@48334: } wenzelm@48334: wenzelm@48337: def parse_entries(root: File): List[Session_Entry] = wenzelm@48334: { wenzelm@48335: val toks = syntax.scan(Standard_System.read_file(root)) wenzelm@48337: parse_all(rep(session_entry), Token.reader(toks, root.toString)) match { wenzelm@48334: case Success(result, _) => result wenzelm@48334: case bad => error(bad.toString) wenzelm@48334: } wenzelm@48334: } wenzelm@48334: } wenzelm@48280: wenzelm@48337: wenzelm@48341: /* find sessions */ wenzelm@48337: wenzelm@48352: private def sessions_root(dir: Path, root: File, sessions: Session.Queue): Session.Queue = wenzelm@48280: { wenzelm@48352: (sessions /: Parser.parse_entries(root))((sessions1, entry) => wenzelm@48337: try { wenzelm@48347: if (entry.name == "") error("Bad session name") wenzelm@48347: wenzelm@48337: val full_name = wenzelm@48347: if (entry.name == "RAW" || entry.name == "Pure") { wenzelm@48347: if (entry.parent.isDefined) error("Illegal parent session") wenzelm@48347: else entry.name wenzelm@48347: } wenzelm@48347: else wenzelm@48347: entry.parent match { wenzelm@48352: case Some(parent_name) if sessions1.defined(parent_name) => wenzelm@48347: if (entry.reset) entry.name wenzelm@48351: else parent_name + "-" + entry.name wenzelm@48351: case _ => error("Bad parent session") wenzelm@48347: } wenzelm@48339: wenzelm@48337: val path = wenzelm@48337: entry.path match { wenzelm@48337: case Some(p) => Path.explode(p) wenzelm@48337: case None => Path.basic(entry.name) wenzelm@48337: } wenzelm@48339: wenzelm@48351: val key = Session.Key(full_name, entry.order) wenzelm@48351: val info = Session.Info(dir + path, entry.description, entry.options, wenzelm@48351: entry.theories.map({ case (x, ys) => ys.map(y => (x, y)) }).flatten, entry.files) wenzelm@48351: wenzelm@48352: sessions1 + (key, info, entry.parent) wenzelm@48337: } wenzelm@48337: catch { wenzelm@48337: case ERROR(msg) => wenzelm@48337: error(msg + "\nThe error(s) above occurred in session entry " + wenzelm@48337: quote(entry.name) + " (file " + quote(root.toString) + ")") wenzelm@48352: }) wenzelm@48352: } wenzelm@48352: wenzelm@48352: private def sessions_dir(strict: Boolean, dir: Path, sessions: Session.Queue): Session.Queue = wenzelm@48352: { wenzelm@48352: val root = Isabelle_System.platform_file(dir + Path.basic("ROOT")) wenzelm@48352: if (root.isFile) sessions_root(dir, root, sessions) wenzelm@48352: else if (strict) error("Bad session root file: " + quote(root.toString)) wenzelm@48352: else sessions wenzelm@48352: } wenzelm@48352: wenzelm@48352: private def sessions_catalog(dir: Path, catalog: File, sessions: Session.Queue): Session.Queue = wenzelm@48352: { wenzelm@48354: val dirs = wenzelm@48354: split_lines(Standard_System.read_file(catalog)). wenzelm@48354: filterNot(line => line == "" || line.startsWith("#")) wenzelm@48352: (sessions /: dirs)((sessions1, dir1) => wenzelm@48352: try { wenzelm@48352: val dir2 = dir + Path.explode(dir1) wenzelm@48352: if (Isabelle_System.platform_file(dir2).isDirectory) sessions_dir(true, dir2, sessions1) wenzelm@48352: else error("Bad session directory: " + dir2.toString) wenzelm@48337: } wenzelm@48352: catch { wenzelm@48352: case ERROR(msg) => wenzelm@48352: error(msg + "\nThe error(s) above occurred in session catalog " + quote(catalog.toString)) wenzelm@48352: }) wenzelm@48352: } wenzelm@48352: wenzelm@48352: def find_sessions(more_dirs: List[Path]): Session.Queue = wenzelm@48352: { wenzelm@48352: var sessions = Session.Queue.empty wenzelm@48352: wenzelm@48352: for (dir <- Isabelle_System.components()) { wenzelm@48352: sessions = sessions_dir(false, dir, sessions) wenzelm@48352: wenzelm@48352: val catalog = Isabelle_System.platform_file(dir + Path.explode("etc/sessions")) wenzelm@48352: if (catalog.isFile) wenzelm@48352: sessions = sessions_catalog(dir, catalog, sessions) wenzelm@48337: } wenzelm@48352: wenzelm@48352: for (dir <- more_dirs) sessions = sessions_dir(true, dir, sessions) wenzelm@48352: wenzelm@48349: sessions wenzelm@48280: } wenzelm@48341: wenzelm@48341: wenzelm@48341: wenzelm@48341: /** build **/ wenzelm@48341: wenzelm@48341: def build(all_sessions: Boolean, build_images: Boolean, list_only: Boolean, wenzelm@48341: more_dirs: List[Path], options: List[String], sessions: List[String]): Int = wenzelm@48341: { wenzelm@48341: println("more_dirs = " + more_dirs.toString) wenzelm@48341: println("options = " + options.toString) wenzelm@48341: println("sessions = " + sessions.toString) wenzelm@48341: wenzelm@48351: for ((key, info) <- find_sessions(more_dirs).topological_order) wenzelm@48351: println(key.name + " in " + info.dir) wenzelm@48341: wenzelm@48341: 0 wenzelm@48341: } wenzelm@48341: wenzelm@48341: wenzelm@48346: /* command line entry point */ wenzelm@48341: wenzelm@48341: def main(args: Array[String]) wenzelm@48341: { wenzelm@48346: Command_Line.tool { wenzelm@48346: args.toList match { wenzelm@48346: case wenzelm@48346: Properties.Value.Boolean(all_sessions) :: wenzelm@48346: Properties.Value.Boolean(build_images) :: wenzelm@48346: Properties.Value.Boolean(list_only) :: wenzelm@48346: Command_Line.Chunks(more_dirs, options, sessions) => wenzelm@48346: build(all_sessions, build_images, list_only, wenzelm@48346: more_dirs.map(Path.explode), options, sessions) wenzelm@48346: case _ => error("Bad arguments:\n" + cat_lines(args)) wenzelm@48341: } wenzelm@48346: } wenzelm@48341: } wenzelm@48276: } wenzelm@48276: