| author | wenzelm | 
| Sat, 29 Apr 2017 10:53:02 +0200 | |
| changeset 65631 | ee917f172912 | 
| parent 65566 | 94c514ea2846 | 
| child 65824 | 4ff79bd2b265 | 
| permissions | -rw-r--r-- | 
| 65557 | 1 | /* Title: Pure/Tools/imports.scala | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 2 | Author: Makarius | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 3 | |
| 65557 | 4 | Maintain theory imports wrt. session structure. | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 5 | */ | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 6 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 7 | package isabelle | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 8 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 9 | |
| 65526 | 10 | import java.io.{File => JFile}
 | 
| 11 | ||
| 12 | ||
| 65557 | 13 | object Imports | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 14 | {
 | 
| 65561 | 15 | /* manifest */ | 
| 16 | ||
| 17 | def manifest_files(start: Path, pred: JFile => Boolean = _ => true): List[JFile] = | |
| 18 |     Mercurial.find_repository(start) match {
 | |
| 19 | case None => | |
| 65564 | 20 |         Output.warning("Ignoring directory " + start + " (no Mercurial repository)")
 | 
| 65561 | 21 | Nil | 
| 22 | case Some(hg) => | |
| 23 | val start_path = start.file.getCanonicalFile.toPath | |
| 24 |         for {
 | |
| 25 | name <- hg.manifest() | |
| 26 | file = (hg.root + Path.explode(name)).file | |
| 27 | if pred(file) && file.getCanonicalFile.toPath.startsWith(start_path) | |
| 28 | } yield file | |
| 29 | } | |
| 30 | ||
| 31 | ||
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 32 | /* update imports */ | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 33 | |
| 65526 | 34 | sealed case class Update(range: Text.Range, old_text: String, new_text: String) | 
| 35 |   {
 | |
| 36 | def edits: List[Text.Edit] = | |
| 37 | Text.Edit.replace(range.start, old_text, new_text) | |
| 38 | ||
| 39 | override def toString: String = | |
| 40 | range.toString + ": " + old_text + " -> " + new_text | |
| 41 | } | |
| 42 | ||
| 43 | def update_name(keywords: Keyword.Keywords, pos: Position.T, update: String => String) | |
| 44 | : Option[(JFile, Update)] = | |
| 45 |   {
 | |
| 46 | val file = | |
| 47 |       pos match {
 | |
| 48 | case Position.File(file) => Path.explode(file).file.getCanonicalFile | |
| 49 |         case _ => error("Missing file in position" + Position.here(pos))
 | |
| 50 | } | |
| 51 | ||
| 52 | val text = File.read(file) | |
| 53 | ||
| 54 | val range = | |
| 55 |       pos match {
 | |
| 56 | case Position.Range(symbol_range) => Symbol.Text_Chunk(text).decode(symbol_range) | |
| 57 |         case _ => error("Missing range in position" + Position.here(pos))
 | |
| 58 | } | |
| 59 | ||
| 60 |     Token.read_name(keywords, range.substring(text)) match {
 | |
| 61 | case Some(tok) => | |
| 62 | val s1 = tok.source | |
| 63 | val s2 = Token.quote_name(keywords, update(tok.content)) | |
| 64 | if (s1 == s2) None else Some((file, Update(range, s1, s2))) | |
| 65 |       case None => error("Name token expected" + Position.here(pos))
 | |
| 66 | } | |
| 67 | } | |
| 68 | ||
| 65561 | 69 | |
| 70 | /* collective operations */ | |
| 71 | ||
| 65558 | 72 | def imports( | 
| 73 | options: Options, | |
| 65566 | 74 | operation_imports: Boolean = false, | 
| 65561 | 75 | operation_manifest: Boolean = false, | 
| 65558 | 76 | operation_update: Boolean = false, | 
| 77 | progress: Progress = No_Progress, | |
| 78 | selection: Sessions.Selection = Sessions.Selection.empty, | |
| 79 | dirs: List[Path] = Nil, | |
| 80 | select_dirs: List[Path] = Nil, | |
| 81 | verbose: Boolean = false) = | |
| 82 |   {
 | |
| 83 | val full_sessions = Sessions.load(options, dirs, select_dirs) | |
| 84 | val (selected, selected_sessions) = full_sessions.selection(selection) | |
| 85 | ||
| 86 | val deps = | |
| 87 | Sessions.deps(selected_sessions, progress = progress, verbose = verbose, | |
| 65561 | 88 | global_theories = full_sessions.global_theories, | 
| 89 | all_known = true) | |
| 90 | ||
| 65566 | 91 | val root_keywords = Sessions.root_syntax.keywords | 
| 92 | ||
| 93 | ||
| 94 |     if (operation_imports) {
 | |
| 95 |       progress.echo("\nPotential session imports:")
 | |
| 96 | selected.sorted.foreach(session_name => | |
| 97 |       {
 | |
| 98 | val info = full_sessions(session_name) | |
| 99 | val session_resources = new Resources(deps(session_name)) | |
| 100 | ||
| 101 | val declared_imports = | |
| 102 | full_sessions.imports_ancestors(session_name).toSet + session_name | |
| 103 | val extra_imports = | |
| 104 |           (for {
 | |
| 105 | (_, a) <- session_resources.session_base.known.theories.iterator | |
| 106 | if session_resources.theory_qualifier(a) == info.theory_qualifier | |
| 107 | b <- deps.all_known.get_file(Path.explode(a.node).file) | |
| 108 | qualifier = session_resources.theory_qualifier(b) | |
| 109 | if !declared_imports.contains(qualifier) | |
| 110 | } yield qualifier).toSet | |
| 111 | ||
| 112 |         if (extra_imports.nonEmpty) {
 | |
| 113 |           progress.echo("session " + Token.quote_name(root_keywords, session_name) + ": " +
 | |
| 114 |             extra_imports.toList.sorted.map(Token.quote_name(root_keywords, _)).mkString(" "))
 | |
| 115 | } | |
| 116 | }) | |
| 117 | } | |
| 118 | ||
| 65561 | 119 |     if (operation_manifest) {
 | 
| 120 |       progress.echo("\nManifest check:")
 | |
| 121 | val unused_files = | |
| 122 |         for {
 | |
| 123 | (_, dir) <- Sessions.directories(dirs, select_dirs) | |
| 124 |           file <- manifest_files(dir, file => file.getName.endsWith(".thy"))
 | |
| 125 | if deps.all_known.get_file(file).isEmpty | |
| 126 | } yield file | |
| 127 |       unused_files.foreach(file => progress.echo("unused file " + quote(file.toString)))
 | |
| 128 | } | |
| 65558 | 129 | |
| 130 |     if (operation_update) {
 | |
| 65564 | 131 |       progress.echo("\nUpdate theory imports:")
 | 
| 65558 | 132 | val updates = | 
| 133 | selected.flatMap(session_name => | |
| 134 |         {
 | |
| 135 | val info = full_sessions(session_name) | |
| 136 | val session_base = deps(session_name) | |
| 137 | val session_resources = new Resources(session_base) | |
| 138 | val imports_resources = new Resources(session_base.get_imports) | |
| 139 | ||
| 140 | def standard_import(qualifier: String, dir: String, s: String): String = | |
| 141 |           {
 | |
| 142 | val name = imports_resources.import_name(qualifier, dir, s) | |
| 143 | val file = Path.explode(name.node).file | |
| 144 | val s1 = | |
| 145 |               imports_resources.session_base.known.get_file(file) match {
 | |
| 146 | case Some(name1) if session_resources.theory_qualifier(name1) != qualifier => | |
| 147 | name1.theory | |
| 148 | case Some(name1) if Thy_Header.is_base_name(s) => | |
| 149 | name1.theory_base_name | |
| 150 | case _ => s | |
| 151 | } | |
| 152 | val name2 = imports_resources.import_name(qualifier, dir, s1) | |
| 153 | if (name.node == name2.node) s1 else s | |
| 154 | } | |
| 155 | ||
| 156 | val updates_root = | |
| 157 |             for {
 | |
| 158 | (_, pos) <- info.theories.flatMap(_._2) | |
| 65566 | 159 | upd <- update_name(root_keywords, pos, | 
| 65558 | 160 | standard_import(info.theory_qualifier, info.dir.implode, _)) | 
| 161 | } yield upd | |
| 162 | ||
| 163 | val updates_theories = | |
| 164 |             for {
 | |
| 165 | (_, name) <- session_base.known.theories_local.toList | |
| 166 | (_, pos) <- session_resources.check_thy(name, Token.Pos.file(name.node)).imports | |
| 167 | upd <- update_name(session_base.syntax.keywords, pos, | |
| 168 | standard_import(session_resources.theory_qualifier(name), name.master_dir, _)) | |
| 169 | } yield upd | |
| 170 | ||
| 171 | updates_root ::: updates_theories | |
| 172 | }) | |
| 65564 | 173 | |
| 174 | val file_updates = (Multi_Map.empty[JFile, Update] /: updates)(_ + _) | |
| 175 | val conflicts = | |
| 176 |         file_updates.iterator_list.flatMap({ case (file, upds) =>
 | |
| 177 | Library.duplicates(upds.sortBy(_.range.start), | |
| 178 | (x: Update, y: Update) => x.range overlaps y.range) match | |
| 179 |           {
 | |
| 180 | case Nil => None | |
| 181 | case bad => Some((file, bad)) | |
| 182 | } | |
| 183 | }) | |
| 184 | if (conflicts.nonEmpty) | |
| 185 | error(cat_lines( | |
| 186 |           conflicts.map({ case (file, bad) =>
 | |
| 187 |             "Conflicting updates for file " + file + bad.mkString("\n  ", "\n  ", "") })))
 | |
| 188 | ||
| 65566 | 189 |       for ((file, upds) <- file_updates.iterator_list.toList.sortBy(p => p._1.toString)) {
 | 
| 65564 | 190 |         progress.echo("file " + quote(file.toString))
 | 
| 191 | val edits = | |
| 192 | upds.sortBy(upd => - upd.range.start).flatMap(upd => | |
| 193 | Text.Edit.replace(upd.range.start, upd.old_text, upd.new_text)) | |
| 194 | val new_text = | |
| 195 |           (File.read(file) /: edits)({ case (text, edit) =>
 | |
| 196 |             edit.edit(text, 0) match {
 | |
| 197 | case (None, text1) => text1 | |
| 198 |               case (Some(_), _) => error("Failed to apply edit " + edit + " to file " + file)
 | |
| 199 | } | |
| 200 | }) | |
| 201 | File.write_backup2(Path.explode(File.standard_path(file)), new_text) | |
| 202 | } | |
| 65558 | 203 | } | 
| 204 | } | |
| 205 | ||
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 206 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 207 | /* Isabelle tool wrapper */ | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 208 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 209 | val isabelle_tool = | 
| 65557 | 210 |     Isabelle_Tool("imports", "maintain theory imports wrt. session structure", args =>
 | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 211 |     {
 | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 212 | var select_dirs: List[Path] = Nil | 
| 65566 | 213 | var operation_imports = false | 
| 65561 | 214 | var operation_manifest = false | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 215 | var requirements = false | 
| 65558 | 216 | var operation_update = false | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 217 | var exclude_session_groups: List[String] = Nil | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 218 | var all_sessions = false | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 219 | var dirs: List[Path] = Nil | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 220 | var session_groups: List[String] = Nil | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 221 | var options = Options.init() | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 222 | var verbose = false | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 223 | var exclude_sessions: List[String] = Nil | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 224 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 225 |       val getopts = Getopts("""
 | 
| 65557 | 226 | Usage: isabelle imports [OPTIONS] [SESSIONS ...] | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 227 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 228 | Options are: | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 229 | -D DIR include session directory and select its sessions | 
| 65566 | 230 | -I operation: report potential session imports | 
| 65561 | 231 | -M operation: Mercurial manifest check for imported theory files | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 232 | -R operate on requirements of selected sessions | 
| 65558 | 233 | -U operation: update theory imports to use session qualifiers | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 234 | -X NAME exclude sessions from group NAME and all descendants | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 235 | -a select all sessions | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 236 | -d DIR include session directory | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 237 | -g NAME select session group NAME | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 238 | -o OPTION override Isabelle system OPTION (via NAME=VAL or NAME) | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 239 | -v verbose | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 240 | -x NAME exclude session NAME and all descendants | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 241 | |
| 65558 | 242 | Maintain theory imports wrt. session structure. At least one operation | 
| 65566 | 243 | needs to be specified (see options -I -M -U). | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 244 | """, | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 245 | "D:" -> (arg => select_dirs = select_dirs ::: List(Path.explode(arg))), | 
| 65566 | 246 | "I" -> (_ => operation_imports = true), | 
| 65561 | 247 | "M" -> (_ => operation_manifest = true), | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 248 | "R" -> (_ => requirements = true), | 
| 65558 | 249 | "U" -> (_ => operation_update = true), | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 250 | "X:" -> (arg => exclude_session_groups = exclude_session_groups ::: List(arg)), | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 251 | "a" -> (_ => all_sessions = true), | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 252 | "d:" -> (arg => dirs = dirs ::: List(Path.explode(arg))), | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 253 | "g:" -> (arg => session_groups = session_groups ::: List(arg)), | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 254 | "o:" -> (arg => options = options + arg), | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 255 | "v" -> (_ => verbose = true), | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 256 | "x:" -> (arg => exclude_sessions = exclude_sessions ::: List(arg))) | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 257 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 258 | val sessions = getopts(args) | 
| 65566 | 259 | if (args.isEmpty || !(operation_imports || operation_manifest || operation_update)) | 
| 65561 | 260 | getopts.usage() | 
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 261 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 262 | val selection = | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 263 | Sessions.Selection(requirements, all_sessions, exclude_session_groups, | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 264 | exclude_sessions, session_groups, sessions) | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 265 | |
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 266 | val progress = new Console_Progress(verbose = verbose) | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 267 | |
| 65566 | 268 | imports(options, operation_imports = operation_imports, | 
| 269 | operation_manifest = operation_manifest, operation_update = operation_update, | |
| 65561 | 270 | progress = progress, selection = selection, dirs = dirs, select_dirs = select_dirs, | 
| 271 | verbose = verbose) | |
| 65518 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 272 | }) | 
| 
bc8fa59211b7
wrapper for "isabelle update_imports" with selection options like "isabelle build";
 wenzelm parents: diff
changeset | 273 | } |