src/Pure/System/build.scala
changeset 48675 10f5303f86e5
parent 48674 03e88e4619a2
child 48676 3ef82491cdd6
equal deleted inserted replaced
48674:03e88e4619a2 48675:10f5303f86e5
    18 
    18 
    19 object Build
    19 object Build
    20 {
    20 {
    21   /** session information **/
    21   /** session information **/
    22 
    22 
       
    23   // external version
       
    24   sealed case class Session_Entry(
       
    25     pos: Position.T,
       
    26     base_name: String,
       
    27     this_name: Boolean,
       
    28     groups: List[String],
       
    29     path: Option[String],
       
    30     parent: Option[String],
       
    31     description: String,
       
    32     options: List[Options.Spec],
       
    33     theories: List[(List[Options.Spec], List[String])],
       
    34     files: List[String])
       
    35 
       
    36   // internal version
       
    37   sealed case class Session_Info(
       
    38     pos: Position.T,
       
    39     groups: List[String],
       
    40     dir: Path,
       
    41     parent: Option[String],
       
    42     description: String,
       
    43     options: Options,
       
    44     theories: List[(Options, List[Path])],
       
    45     files: List[Path],
       
    46     entry_digest: SHA1.Digest)
       
    47 
       
    48   def is_pure(name: String): Boolean = name == "RAW" || name == "Pure"
       
    49 
       
    50   def session_info(options: Options, dir: Path, entry: Session_Entry): (String, Session_Info) =
       
    51     try {
       
    52       if (entry.base_name == "") error("Bad session name")
       
    53 
       
    54       val full_name =
       
    55         if (is_pure(entry.base_name)) {
       
    56           if (entry.parent.isDefined) error("Illegal parent session")
       
    57           else entry.base_name
       
    58         }
       
    59         else
       
    60           entry.parent match {
       
    61             case None => error("Missing parent session")
       
    62             case Some(parent_name) =>
       
    63               if (entry.this_name) entry.base_name
       
    64               else parent_name + "-" + entry.base_name
       
    65           }
       
    66 
       
    67       val path =
       
    68         entry.path match {
       
    69           case Some(p) => Path.explode(p)
       
    70           case None => Path.basic(entry.base_name)
       
    71         }
       
    72 
       
    73       val session_options = options ++ entry.options
       
    74 
       
    75       val theories =
       
    76         entry.theories.map({ case (opts, thys) =>
       
    77           (session_options ++ opts, thys.map(Path.explode(_))) })
       
    78       val files = entry.files.map(Path.explode(_))
       
    79       val entry_digest =
       
    80         SHA1.digest((full_name, entry.parent, entry.options, entry.theories).toString)
       
    81 
       
    82       val info =
       
    83         Session_Info(entry.pos, entry.groups, dir + path, entry.parent, entry.description,
       
    84           session_options, theories, files, entry_digest)
       
    85 
       
    86       (full_name, info)
       
    87     }
       
    88     catch {
       
    89       case ERROR(msg) =>
       
    90         error(msg + "\nThe error(s) above occurred in session entry " +
       
    91           quote(entry.base_name) + Position.str_of(entry.pos))
       
    92     }
       
    93 
       
    94 
       
    95   /* parser */
       
    96 
       
    97   private object Parser extends Parse.Parser
       
    98   {
       
    99     val SESSION = "session"
       
   100     val IN = "in"
       
   101     val DESCRIPTION = "description"
       
   102     val OPTIONS = "options"
       
   103     val THEORIES = "theories"
       
   104     val FILES = "files"
       
   105 
       
   106     val syntax =
       
   107       Outer_Syntax.empty + "!" + "(" + ")" + "+" + "," + "=" + "[" + "]" +
       
   108         SESSION + IN + DESCRIPTION + OPTIONS + THEORIES + FILES
       
   109 
       
   110     def session_entry(pos: Position.T): Parser[Session_Entry] =
       
   111     {
       
   112       val session_name = atom("session name", _.is_name)
       
   113 
       
   114       val option =
       
   115         name ~ opt(keyword("=") ~! name ^^ { case _ ~ x => x }) ^^ { case x ~ y => (x, y) }
       
   116       val options = keyword("[") ~> repsep(option, keyword(",")) <~ keyword("]")
       
   117 
       
   118       val theories =
       
   119         keyword(THEORIES) ~! ((options | success(Nil)) ~ rep1(theory_name)) ^^
       
   120           { case _ ~ (x ~ y) => (x, y) }
       
   121 
       
   122       ((keyword(SESSION) ~! session_name) ^^ { case _ ~ x => x }) ~
       
   123         (keyword("!") ^^^ true | success(false)) ~
       
   124         (keyword("(") ~! (rep1(name) <~ keyword(")")) ^^ { case _ ~ x => x } | success(Nil)) ~
       
   125         (opt(keyword(IN) ~! path ^^ { case _ ~ x => x })) ~
       
   126         (keyword("=") ~> opt(session_name <~ keyword("+"))) ~
       
   127         (keyword(DESCRIPTION) ~! text ^^ { case _ ~ x => x } | success("")) ~
       
   128         (keyword(OPTIONS) ~! options ^^ { case _ ~ x => x } | success(Nil)) ~
       
   129         rep(theories) ~
       
   130         (keyword(FILES) ~! rep1(path) ^^ { case _ ~ x => x } | success(Nil)) ^^
       
   131           { case a ~ b ~ c ~ d ~ e ~ f ~ g ~ h ~ i => Session_Entry(pos, a, b, c, d, e, f, g, h, i) }
       
   132     }
       
   133 
       
   134     def parse_entries(root: Path): List[Session_Entry] =
       
   135     {
       
   136       val toks = syntax.scan(File.read(root))
       
   137       parse_all(rep(session_entry(root.position)), Token.reader(toks, root.implode)) match {
       
   138         case Success(result, _) => result
       
   139         case bad => error(bad.toString)
       
   140       }
       
   141     }
       
   142   }
       
   143 
       
   144 
       
   145   /* find sessions within certain directories */
       
   146 
       
   147   private val ROOT = Path.explode("ROOT")
       
   148 
       
   149   private def is_session_dir(dir: Path): Boolean = (dir + ROOT).is_file
       
   150 
       
   151   private def sessions_root(options: Options, dir: Path): List[(String, Session_Info)] =
       
   152   {
       
   153     val root = dir + ROOT
       
   154     if (root.is_file) Parser.parse_entries(dir + ROOT).map(session_info(options, dir, _))
       
   155     else error("Bad session root file: " + root.toString)
       
   156   }
       
   157 
       
   158   def find_sessions(options: Options, more_dirs: List[Path]): List[(String, Session_Info)] =
       
   159   {
       
   160     val dirs = Isabelle_System.components().filter(is_session_dir(_)) ::: more_dirs
       
   161     dirs.map(sessions_root(options, _)).flatten
       
   162   }
       
   163 
       
   164 
    23   object Session
   165   object Session
    24   {
   166   {
    25     /* Info */
       
    26 
       
    27     sealed case class Info(
       
    28       groups: List[String],
       
    29       dir: Path,
       
    30       parent: Option[String],
       
    31       description: String,
       
    32       options: Options,
       
    33       theories: List[(Options, List[Path])],
       
    34       files: List[Path],
       
    35       entry_digest: SHA1.Digest)
       
    36 
       
    37 
       
    38     /* Queue */
       
    39 
       
    40     object Queue
   167     object Queue
    41     {
   168     {
    42       val empty: Queue = new Queue()
   169       val empty: Queue = new Queue()
    43     }
   170       def apply(args: Seq[(String, Session_Info)]): Queue =
    44 
   171         (empty /: args) { case (queue, (name, info)) => queue + (name, info) }
    45     final class Queue private(graph: Graph[String, Option[Info]] = Graph.string)
   172     }
    46       extends PartialFunction[String, Info]
   173 
       
   174     final class Queue private(graph: Graph[String, Option[Session_Info]] = Graph.string)
       
   175       extends PartialFunction[String, Session_Info]
    47     {
   176     {
    48       def apply(name: String): Info = graph.get_node(name).get
   177       def apply(name: String): Session_Info = graph.get_node(name).get
    49       def isDefinedAt(name: String): Boolean =
   178       def isDefinedAt(name: String): Boolean =
    50         graph.defined(name) && graph.get_node(name).isDefined
   179         graph.defined(name) && graph.get_node(name).isDefined
    51 
   180 
    52       def is_inner(name: String): Boolean = !graph.is_maximal(name)
   181       def is_inner(name: String): Boolean = !graph.is_maximal(name)
    53 
   182 
    54       def is_empty: Boolean = graph.is_empty
   183       def is_empty: Boolean = graph.is_empty
    55 
   184 
    56       def + (name: String, info: Info): Queue =
   185       def + (name: String, info: Session_Info): Queue =
    57       {
   186       {
    58         val parents = info.parent.toList
   187         val parents = info.parent.toList
    59 
   188 
    60         val graph1 = (graph /: (name :: parents))(_.default_node(_, None))
   189         val graph1 = (graph /: (name :: parents))(_.default_node(_, None))
    61         if (graph1.get_node(name).isDefined)
   190         if (graph1.get_node(name).isDefined)
    62           error("Duplicate session: " + quote(name))
   191           error("Duplicate session: " + quote(name) + Position.str_of(info.pos))
    63 
   192 
    64         new Queue(
   193         new Queue(
    65           try { graph1.map_node(name, _ => Some(info)).add_deps_acyclic(name, parents) }
   194           try { graph1.map_node(name, _ => Some(info)).add_deps_acyclic(name, parents) }
    66           catch {
   195           catch {
    67             case exn: Graph.Cycles[_] =>
   196             case exn: Graph.Cycles[_] =>
   104         val descendants = graph.all_succs(selected)
   233         val descendants = graph.all_succs(selected)
   105         val queue1 = new Queue(graph.restrict(graph.all_preds(selected).toSet))
   234         val queue1 = new Queue(graph.restrict(graph.all_preds(selected).toSet))
   106         (descendants, queue1)
   235         (descendants, queue1)
   107       }
   236       }
   108 
   237 
   109       def dequeue(skip: String => Boolean): Option[(String, Info)] =
   238       def dequeue(skip: String => Boolean): Option[(String, Session_Info)] =
   110       {
   239       {
   111         val it = graph.entries.dropWhile(
   240         val it = graph.entries.dropWhile(
   112           { case (name, (info, (deps, _))) => !deps.isEmpty || info.isEmpty || skip(name) })
   241           { case (name, (info, (deps, _))) => !deps.isEmpty || info.isEmpty || skip(name) })
   113         if (it.hasNext) { val (name, (info, _)) = it.next; Some((name, info.get)) }
   242         if (it.hasNext) { val (name, (info, _)) = it.next; Some((name, info.get)) }
   114         else None
   243         else None
   115       }
   244       }
   116 
   245 
   117       def topological_order: List[(String, Info)] =
   246       def topological_order: List[(String, Session_Info)] =
   118         graph.topological_order.map(name => (name, apply(name)))
   247         graph.topological_order.map(name => (name, apply(name)))
   119     }
   248     }
   120   }
       
   121 
       
   122 
       
   123   /* parsing */
       
   124 
       
   125   private case class Session_Entry(
       
   126     base_name: String,
       
   127     this_name: Boolean,
       
   128     groups: List[String],
       
   129     path: Option[String],
       
   130     parent: Option[String],
       
   131     description: String,
       
   132     options: List[Options.Spec],
       
   133     theories: List[(List[Options.Spec], List[String])],
       
   134     files: List[String])
       
   135 
       
   136   private object Parser extends Parse.Parser
       
   137   {
       
   138     val SESSION = "session"
       
   139     val IN = "in"
       
   140     val DESCRIPTION = "description"
       
   141     val OPTIONS = "options"
       
   142     val THEORIES = "theories"
       
   143     val FILES = "files"
       
   144 
       
   145     val syntax =
       
   146       Outer_Syntax.empty + "!" + "(" + ")" + "+" + "," + "=" + "[" + "]" +
       
   147         SESSION + IN + DESCRIPTION + OPTIONS + THEORIES + FILES
       
   148 
       
   149     val session_entry: Parser[Session_Entry] =
       
   150     {
       
   151       val session_name = atom("session name", _.is_name)
       
   152 
       
   153       val option =
       
   154         name ~ opt(keyword("=") ~! name ^^ { case _ ~ x => x }) ^^ { case x ~ y => (x, y) }
       
   155       val options = keyword("[") ~> repsep(option, keyword(",")) <~ keyword("]")
       
   156 
       
   157       val theories =
       
   158         keyword(THEORIES) ~! ((options | success(Nil)) ~ rep1(theory_name)) ^^
       
   159           { case _ ~ (x ~ y) => (x, y) }
       
   160 
       
   161       ((keyword(SESSION) ~! session_name) ^^ { case _ ~ x => x }) ~
       
   162         (keyword("!") ^^^ true | success(false)) ~
       
   163         (keyword("(") ~! (rep1(name) <~ keyword(")")) ^^ { case _ ~ x => x } | success(Nil)) ~
       
   164         (opt(keyword(IN) ~! path ^^ { case _ ~ x => x })) ~
       
   165         (keyword("=") ~> opt(session_name <~ keyword("+"))) ~
       
   166         (keyword(DESCRIPTION) ~! text ^^ { case _ ~ x => x } | success("")) ~
       
   167         (keyword(OPTIONS) ~! options ^^ { case _ ~ x => x } | success(Nil)) ~
       
   168         rep(theories) ~
       
   169         (keyword(FILES) ~! rep1(path) ^^ { case _ ~ x => x } | success(Nil)) ^^
       
   170           { case a ~ b ~ c ~ d ~ e ~ f ~ g ~ h ~ i => Session_Entry(a, b, c, d, e, f, g, h, i) }
       
   171     }
       
   172 
       
   173     def parse_entries(root: Path): List[Session_Entry] =
       
   174     {
       
   175       val toks = syntax.scan(File.read(root))
       
   176       parse_all(rep(session_entry), Token.reader(toks, root.implode)) match {
       
   177         case Success(result, _) => result
       
   178         case bad => error(bad.toString)
       
   179       }
       
   180     }
       
   181   }
       
   182 
       
   183 
       
   184   /* find sessions */
       
   185 
       
   186   private val ROOT = Path.explode("ROOT")
       
   187   private val SESSIONS = Path.explode("etc/sessions")
       
   188 
       
   189   private def is_pure(name: String): Boolean = name == "RAW" || name == "Pure"
       
   190 
       
   191   private def sessions_root(options: Options, dir: Path, queue: Session.Queue, root: Path)
       
   192     : Session.Queue =
       
   193   {
       
   194     (queue /: Parser.parse_entries(root))((queue1, entry) =>
       
   195       try {
       
   196         if (entry.base_name == "") error("Bad session name")
       
   197 
       
   198         val full_name =
       
   199           if (is_pure(entry.base_name)) {
       
   200             if (entry.parent.isDefined) error("Illegal parent session")
       
   201             else entry.base_name
       
   202           }
       
   203           else
       
   204             entry.parent match {
       
   205               case None => error("Missing parent session")
       
   206               case Some(parent_name) =>
       
   207                 if (entry.this_name) entry.base_name
       
   208                 else parent_name + "-" + entry.base_name
       
   209             }
       
   210 
       
   211         val path =
       
   212           entry.path match {
       
   213             case Some(p) => Path.explode(p)
       
   214             case None => Path.basic(entry.base_name)
       
   215           }
       
   216 
       
   217         val session_options = options ++ entry.options
       
   218 
       
   219         val theories =
       
   220           entry.theories.map({ case (opts, thys) =>
       
   221             (session_options ++ opts, thys.map(Path.explode(_))) })
       
   222         val files = entry.files.map(Path.explode(_))
       
   223         val entry_digest =
       
   224           SHA1.digest((full_name, entry.parent, entry.options, entry.theories).toString)
       
   225 
       
   226         val info =
       
   227           Session.Info(entry.groups, dir + path, entry.parent, entry.description,
       
   228             session_options, theories, files, entry_digest)
       
   229 
       
   230         queue1 + (full_name, info)
       
   231       }
       
   232       catch {
       
   233         case ERROR(msg) =>
       
   234           error(msg + "\nThe error(s) above occurred in session entry " +
       
   235             quote(entry.base_name) + Position.str_of(root.position))
       
   236       })
       
   237   }
       
   238 
       
   239   private def sessions_dir(options: Options, strict: Boolean, queue: Session.Queue, dir: Path)
       
   240     : Session.Queue =
       
   241   {
       
   242     val root = dir + ROOT
       
   243     if (root.is_file) sessions_root(options, dir, queue, root)
       
   244     else if (strict) error("Bad session root file: " + root.toString)
       
   245     else queue
       
   246   }
       
   247 
       
   248   def find_sessions(options: Options, more_dirs: List[Path]): Session.Queue =
       
   249   {
       
   250     val queue1 =
       
   251       (Session.Queue.empty /: Isabelle_System.components())(sessions_dir(options, false, _, _))
       
   252     val queue2 = (queue1 /: more_dirs)(sessions_dir(options, true, _, _))
       
   253     queue2
       
   254   }
   249   }
   255 
   250 
   256 
   251 
   257 
   252 
   258   /** build **/
   253   /** build **/
   321             }).flatten ::: info.files.map(file => info.dir + file)
   316             }).flatten ::: info.files.map(file => info.dir + file)
   322           val sources =
   317           val sources =
   323             try { all_files.map(p => (p, SHA1.digest(p))) }
   318             try { all_files.map(p => (p, SHA1.digest(p))) }
   324             catch {
   319             catch {
   325               case ERROR(msg) =>
   320               case ERROR(msg) =>
   326                 error(msg + "\nThe error(s) above occurred in session " + quote(name))
   321                 error(msg + "\nThe error(s) above occurred in session " +
       
   322                   quote(name) + Position.str_of(info.pos))
   327             }
   323             }
   328 
   324 
   329           deps + (name -> Node(loaded_theories, syntax, sources))
   325           deps + (name -> Node(loaded_theories, syntax, sources))
   330       }))
   326       }))
   331 
   327 
   332 
   328 
   333   /* jobs */
   329   /* jobs */
   334 
   330 
   335   private class Job(name: String, info: Session.Info, output: Path, do_output: Boolean,
   331   private class Job(name: String, info: Session_Info, output: Path, do_output: Boolean,
   336     verbose: Boolean, browser_info: Path)
   332     verbose: Boolean, browser_info: Path)
   337   {
   333   {
   338     // global browser info dir
   334     // global browser info dir
   339     if (info.options.bool("browser_info") && !(browser_info + Path.explode("index.html")).is_file)
   335     if (info.options.bool("browser_info") && !(browser_info + Path.explode("index.html")).is_file)
   340     {
   336     {
   478     verbose: Boolean = false,
   474     verbose: Boolean = false,
   479     sessions: List[String] = Nil): Int =
   475     sessions: List[String] = Nil): Int =
   480   {
   476   {
   481     val options = (Options.init() /: build_options)(_.define_simple(_))
   477     val options = (Options.init() /: build_options)(_.define_simple(_))
   482     val (descendants, queue) =
   478     val (descendants, queue) =
   483       find_sessions(options, more_dirs).required(all_sessions, session_groups, sessions)
   479       Session.Queue(find_sessions(options, more_dirs))
       
   480         .required(all_sessions, session_groups, sessions)
   484     val deps = dependencies(verbose, queue)
   481     val deps = dependencies(verbose, queue)
   485 
   482 
   486     def make_stamp(name: String): String =
   483     def make_stamp(name: String): String =
   487       sources_stamp(queue(name).entry_digest :: deps.sources(name))
   484       sources_stamp(queue(name).entry_digest :: deps.sources(name))
   488 
   485 
   645   /* static outer syntax */
   642   /* static outer syntax */
   646 
   643 
   647   // FIXME Symbol.decode!?
   644   // FIXME Symbol.decode!?
   648   def outer_syntax(session: String): Outer_Syntax =
   645   def outer_syntax(session: String): Outer_Syntax =
   649   {
   646   {
   650     val (_, queue) = find_sessions(Options.init(), Nil).required(false, Nil, List(session))
   647     val (_, queue) =
       
   648       Session.Queue(find_sessions(Options.init(), Nil)).required(false, Nil, List(session))
   651     dependencies(false, queue)(session).syntax
   649     dependencies(false, queue)(session).syntax
   652   }
   650   }
   653 }
   651 }
   654 
   652