more robust and portable invocation of kill as bash builtin, instead of external executable -- NB: /bin/kill on Mac OS X Mountain Lion chokes on Linux workaround from 3610ae73cfdb;
1 /* Title: Pure/System/isabelle_system.scala
4 Fundamental Isabelle system environment: quasi-static module with
5 optional init operation.
10 import java.lang.System
11 import java.util.regex.Pattern
12 import java.io.{File => JFile, BufferedReader, InputStreamReader,
13 BufferedWriter, OutputStreamWriter}
15 import scala.util.matching.Regex
18 object Isabelle_System
20 /** bootstrap information **/
22 def jdk_home(): String =
24 val java_home = System.getProperty("java.home", "")
25 val home = new JFile(java_home)
26 val parent = home.getParent
27 if (home.getName == "jre" && parent != null &&
28 (new JFile(new JFile(parent, "bin"), "javac")).exists) parent
32 private def find_cygwin_root(cygwin_root0: String = ""): String =
34 require(Platform.is_windows)
36 val cygwin_root1 = System.getenv("CYGWIN_ROOT")
37 val cygwin_root2 = System.getProperty("cygwin.root")
39 if (cygwin_root0 != null && cygwin_root0 != "") cygwin_root0
40 else if (cygwin_root1 != null && cygwin_root1 != "") cygwin_root1
41 else if (cygwin_root2 != null && cygwin_root2 != "") cygwin_root2
42 else error("Cannot determine Cygwin root directory")
47 /** implicit settings environment **/
49 @volatile private var _settings: Option[Map[String, String]] = None
51 def settings(): Map[String, String] =
53 if (_settings.isEmpty) init() // unsynchronized check
58 Isabelle home precedence:
59 (1) isabelle_home as explicit argument
60 (2) ISABELLE_HOME process environment variable (e.g. inherited from running isabelle tool)
61 (3) isabelle.home system property (e.g. via JVM application boot process)
63 def init(isabelle_home: String = "", cygwin_root: String = ""): Unit = synchronized {
64 if (_settings.isEmpty) {
65 import scala.collection.JavaConversions._
69 if (Platform.is_windows)
70 _settings = Some(_settings.getOrElse(Map.empty) +
71 ("CYGWIN_ROOT" -> find_cygwin_root(cygwin_root)))
75 val env0 = sys.env + ("ISABELLE_JDK_HOME" -> posix_path(jdk_home()))
77 val user_home = System.getProperty("user.home", "")
79 if (user_home == "" || env0.isDefinedAt("HOME")) env0
80 else env0 + ("HOME" -> user_home)
83 if (isabelle_home != null && isabelle_home != "") isabelle_home
85 env.get("ISABELLE_HOME") match {
86 case None | Some("") =>
87 val path = System.getProperty("isabelle.home", "")
88 if (path == "") error("Unknown Isabelle home directory")
90 case Some(path) => path
94 File.with_tmp_file("settings") { dump =>
96 if (Platform.is_windows) List(find_cygwin_root(cygwin_root) + "\\bin\\bash", "-l")
99 shell_prefix ::: List(system_home + "/bin/isabelle", "getenv", "-d", dump.toString)
100 val (output, rc) = process_output(raw_execute(null, env, true, cmdline: _*))
101 if (rc != 0) error(output)
104 (for (entry <- File.read(dump) split "\0" if entry != "") yield {
105 val i = entry.indexOf('=')
106 if (i <= 0) (entry -> "")
107 else (entry.substring(0, i) -> entry.substring(i + 1))
109 entries + ("PATH" -> entries("PATH_JVM")) - "PATH_JVM"
111 _settings = Some(settings)
119 def getenv(name: String): String = settings.getOrElse(name, "")
121 def getenv_strict(name: String): String =
123 val value = getenv(name)
124 if (value != "") value else error("Undefined environment variable: " + name)
127 def get_cygwin_root(): String = getenv_strict("CYGWIN_ROOT")
131 /** file-system operations **/
135 private val Cygdrive = new Regex("/cygdrive/([a-zA-Z])($|/.*)")
136 private val Named_Root = new Regex("//+([^/]*)(.*)")
138 def jvm_path(posix_path: String): String =
139 if (Platform.is_windows) {
140 val result_path = new StringBuilder
143 case Cygdrive(drive, rest) =>
144 result_path ++= (Library.uppercase(drive) + ":" + JFile.separator)
146 case Named_Root(root, rest) =>
147 result_path ++= JFile.separator
148 result_path ++= JFile.separator
151 case path if path.startsWith("/") =>
152 result_path ++= get_cygwin_root()
156 for (p <- space_explode('/', rest) if p != "") {
157 val len = result_path.length
158 if (len > 0 && result_path(len - 1) != JFile.separatorChar)
159 result_path += JFile.separatorChar
169 def posix_path(jvm_path: String): String =
170 if (Platform.is_windows) {
171 val Platform_Root = new Regex("(?i)" +
172 Pattern.quote(get_cygwin_root()) + """(?:\\+|\z)(.*)""")
173 val Drive = new Regex("""([a-zA-Z]):\\*(.*)""")
175 jvm_path.replace('/', '\\') match {
176 case Platform_Root(rest) => "/" + rest.replace('\\', '/')
177 case Drive(letter, rest) =>
178 "/cygdrive/" + Library.lowercase(letter) +
179 (if (rest == "") "" else "/" + rest.replace('\\', '/'))
180 case path => path.replace('\\', '/')
185 def posix_path(file: JFile): String = posix_path(file.getPath)
188 /* misc path specifications */
190 def standard_path(path: Path): String = path.expand.implode
192 def platform_path(path: Path): String = jvm_path(standard_path(path))
193 def platform_file(path: Path): JFile = new JFile(platform_path(path))
195 def platform_file_url(raw_path: Path): String =
197 val path = raw_path.expand
198 require(path.is_absolute)
199 val s = platform_path(path).replaceAll(" ", "%20")
200 if (!Platform.is_windows) "file://" + s
201 else if (s.startsWith("\\\\")) "file:" + s.replace('\\', '/')
202 else "file:///" + s.replace('\\', '/')
206 /* source files of Isabelle/ML bootstrap */
208 def source_file(path: Path): Option[Path] =
210 def check(p: Path): Option[Path] = if (p.is_file) Some(p) else None
212 if (path.is_absolute || path.is_current) check(path)
214 check(Path.explode("~~/src/Pure") + path) orElse
215 (if (getenv("ML_SOURCES") == "") None else check(Path.explode("$ML_SOURCES") + path))
222 def mkdirs(path: Path)
225 if (!path.is_dir) error("Cannot create directory: " + quote(platform_path(path)))
230 /** external processes **/
232 /* raw execute for bootstrapping */
234 def raw_execute(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*): Process =
236 val cmdline = new java.util.LinkedList[String]
237 for (s <- args) cmdline.add(s)
239 val proc = new ProcessBuilder(cmdline)
240 if (cwd != null) proc.directory(cwd)
242 proc.environment.clear
243 for ((x, y) <- env) proc.environment.put(x, y)
245 proc.redirectErrorStream(redirect)
249 private def process_output(proc: Process): (String, Int) =
251 proc.getOutputStream.close
252 val output = File.read_stream(proc.getInputStream)
256 proc.getInputStream.close
257 proc.getErrorStream.close
267 def execute_env(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*): Process =
270 if (Platform.is_windows) List(get_cygwin_root() + "\\bin\\env.exe") ++ args
272 val env1 = if (env == null) settings else settings ++ env
273 raw_execute(cwd, env1, redirect, cmdline: _*)
276 def execute(redirect: Boolean, args: String*): Process =
277 execute_env(null, null, redirect, args: _*)
280 /* managed process */
282 class Managed_Process(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*)
285 List(standard_path(Path.explode("~~/lib/scripts/process")), "group", "-", "no_script")
286 private val proc = execute_env(cwd, env, redirect, (params ++ args):_*)
291 val stdin: BufferedWriter =
292 new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset))
294 val stdout: BufferedReader =
295 new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset))
297 val stderr: BufferedReader =
298 new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset))
303 private val pid = stdout.readLine
305 private def kill_cmd(signal: String): Int =
306 execute(true, "/bin/bash", "-c", "kill -" + signal + " -" + pid).waitFor
308 private def kill(signal: String): Boolean =
314 catch { case _: InterruptedException => true }
317 private def multi_kill(signal: String): Boolean =
321 while (running && count > 0) {
323 try { Thread.sleep(100) } catch { case _: InterruptedException => }
331 def interrupt() { multi_kill("INT") }
332 def terminate() { multi_kill("INT") && multi_kill("TERM") && kill("KILL"); proc.destroy }
337 private val shutdown_hook = new Thread { override def run = terminate() }
339 try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
340 catch { case _: IllegalStateException => }
342 private def cleanup() =
343 try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
344 catch { case _: IllegalStateException => }
349 def join: Int = { val rc = proc.waitFor; cleanup(); rc }
355 final case class Bash_Result(out_lines: List[String], err_lines: List[String], rc: Int)
357 def out: String = cat_lines(out_lines)
358 def err: String = cat_lines(err_lines)
359 def add_err(s: String): Bash_Result = copy(err_lines = err_lines ::: List(s))
360 def set_rc(i: Int): Bash_Result = copy(rc = i)
363 def bash_env(cwd: JFile, env: Map[String, String], script: String,
364 progress_stdout: String => Unit = (_: String) => (),
365 progress_stderr: String => Unit = (_: String) => (),
366 progress_limit: Option[Long] = None): Bash_Result =
368 File.with_tmp_file("isabelle_script") { script_file =>
369 File.write(script_file, script)
370 val proc = new Managed_Process(cwd, env, false, "bash", posix_path(script_file))
373 val limited = new Object {
374 private var count = 0L
375 def apply(progress: String => Unit)(line: String): Unit = synchronized {
377 count = count + line.length + 1
378 progress_limit match {
379 case Some(limit) if count > limit => proc.terminate
385 Simple_Thread.future("bash_stdout") {
386 File.read_lines(proc.stdout, limited(progress_stdout))
389 Simple_Thread.future("bash_stderr") {
390 File.read_lines(proc.stderr, limited(progress_stderr))
395 catch { case e: InterruptedException => proc.terminate; 130 }
396 Bash_Result(stdout.join, stderr.join, rc)
400 def bash(script: String): Bash_Result = bash_env(null, null, script)
405 def isabelle_tool(name: String, args: String*): (String, Int) =
407 Path.split(getenv_strict("ISABELLE_TOOLS")).find { dir =>
408 val file = (dir + Path.basic(name)).file
410 file.isFile && file.canRead && file.canExecute &&
411 !name.endsWith("~") && !name.endsWith(".orig")
413 catch { case _: SecurityException => false }
416 val file = standard_path(dir + Path.basic(name))
417 process_output(execute(true, (List(file) ++ args): _*))
418 case None => ("Unknown Isabelle tool: " + name, 2)
424 /** Isabelle resources **/
428 def components(): List[Path] =
429 Path.split(getenv_strict("ISABELLE_COMPONENTS"))
434 def find_logics_dirs(): List[Path] =
436 val ml_ident = Path.explode("$ML_IDENTIFIER").expand
437 Path.split(getenv_strict("ISABELLE_PATH")).map(_ + ml_ident)
440 def find_logics(): List[String] =
442 dir <- find_logics_dirs()
443 files = dir.file.listFiles() if files != null
444 file <- files.toList if file.isFile } yield file.getName).sorted
446 def default_logic(args: String*): String =
448 args.find(_ != "") match {
449 case Some(logic) => logic
450 case None => Isabelle_System.getenv_strict("ISABELLE_LOGIC")