src/Pure/Concurrent/bash.scala
author wenzelm
Mon, 07 Mar 2016 18:20:22 +0100
changeset 62545 8ebffdaf2ce2
parent 62543 57f379ef662f
child 62558 c46418f12ee1
permissions -rw-r--r--
Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined; more robust File.bash_escape; more robust treatment of ML_OPTIONS; clarified prover args (again);
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     1
/*  Title:      Pure/Concurrent/bash.scala
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     2
    Author:     Makarius
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     3
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     4
GNU bash processes, with propagation of interrupts.
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     5
*/
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     6
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     7
package isabelle
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     8
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
     9
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    10
import java.io.{File => JFile, BufferedReader, InputStreamReader,
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    11
  BufferedWriter, OutputStreamWriter}
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    12
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    13
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    14
object Bash
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    15
{
62543
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    16
  private class Limited_Progress(proc: Process, progress_limit: Option[Long])
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    17
  {
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    18
    private var count = 0L
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    19
    def apply(progress: String => Unit)(line: String): Unit = synchronized {
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    20
      progress(line)
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    21
      count = count + line.length + 1
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    22
      progress_limit match {
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    23
        case Some(limit) if count > limit => proc.terminate
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    24
        case _ =>
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    25
      }
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    26
    }
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    27
  }
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
    28
62545
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    29
  def process(script: String,
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    30
      cwd: JFile = null, env: Map[String, String] = null, redirect: Boolean = false): Process =
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    31
    new Process(script, cwd, env, redirect)
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    32
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    33
  class Process private [Bash](
62545
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    34
      script: String, cwd: JFile, env: Map[String, String], redirect: Boolean)
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    35
    extends Prover.System_Process
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    36
  {
62545
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    37
    val script_file = Isabelle_System.tmp_file("bash_script")
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    38
    File.write(script_file, script)
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    39
62296
b04a5ddd6121 clarified bash process -- similar to ML version;
wenzelm
parents: 61025
diff changeset
    40
    private val proc =
62545
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    41
      Isabelle_System.process(cwd, Isabelle_System.settings(env), redirect,
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    42
        File.platform_path(Path.variable("ISABELLE_BASH_PROCESS")), "-",
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    43
          "bash", File.standard_path(script_file))
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    44
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    45
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    46
    // channels
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    47
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    48
    val stdin: BufferedWriter =
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    49
      new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, UTF8.charset))
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    50
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    51
    val stdout: BufferedReader =
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    52
      new BufferedReader(new InputStreamReader(proc.getInputStream, UTF8.charset))
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    53
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    54
    val stderr: BufferedReader =
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    55
      new BufferedReader(new InputStreamReader(proc.getErrorStream, UTF8.charset))
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    56
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    57
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    58
    // signals
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    59
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    60
    private val pid = stdout.readLine
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    61
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    62
    private def kill(signal: String): Boolean =
61025
636b578bfadd clarified kill on Windows: just one executable;
wenzelm
parents: 60991
diff changeset
    63
      Exn.Interrupt.postpone {
636b578bfadd clarified kill on Windows: just one executable;
wenzelm
parents: 60991
diff changeset
    64
        Isabelle_System.kill(signal, pid)
636b578bfadd clarified kill on Windows: just one executable;
wenzelm
parents: 60991
diff changeset
    65
        Isabelle_System.kill("0", pid)._2 == 0 } getOrElse true
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    66
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    67
    private def multi_kill(signal: String): Boolean =
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    68
    {
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    69
      var running = true
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    70
      var count = 10
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    71
      while (running && count > 0) {
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    72
        if (kill(signal)) {
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    73
          Exn.Interrupt.postpone {
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    74
            Thread.sleep(100)
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    75
            count -= 1
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    76
          }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    77
        }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    78
        else running = false
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    79
      }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    80
      running
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    81
    }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    82
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    83
    def interrupt() { multi_kill("INT") }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    84
    def terminate() { multi_kill("INT") && multi_kill("TERM") && kill("KILL"); proc.destroy }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    85
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    86
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    87
    // JVM shutdown hook
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    88
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    89
    private val shutdown_hook = new Thread { override def run = terminate() }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    90
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    91
    try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    92
    catch { case _: IllegalStateException => }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
    93
62545
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    94
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    95
    /* join */
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    96
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    97
    def join: Int =
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    98
    {
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
    99
      val rc = proc.waitFor
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
   100
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
   101
      try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
   102
      catch { case _: IllegalStateException => }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
   103
62545
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
   104
      script_file.delete
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
   105
62545
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
   106
      rc
8ebffdaf2ce2 Bash.process always uses a closed script instead of an open argument list, for extra robustness on Windows, where quoting is not well-defined;
wenzelm
parents: 62543
diff changeset
   107
    }
62543
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   108
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   109
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   110
    /* result */
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   111
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   112
    def result(
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   113
      progress_stdout: String => Unit = (_: String) => (),
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   114
      progress_stderr: String => Unit = (_: String) => (),
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   115
      progress_limit: Option[Long] = None,
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   116
      strict: Boolean = true): Process_Result =
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   117
    {
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   118
      stdin.close
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   119
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   120
      val limited = new Limited_Progress(this, progress_limit)
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   121
      val out_lines =
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   122
        Future.thread("bash_stdout") { File.read_lines(stdout, limited(progress_stdout)) }
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   123
      val err_lines =
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   124
        Future.thread("bash_stderr") { File.read_lines(stderr, limited(progress_stderr)) }
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   125
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   126
      val rc =
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   127
        try { join }
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   128
        catch { case Exn.Interrupt() => terminate; Exn.Interrupt.return_code }
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   129
      if (strict && rc == Exn.Interrupt.return_code) throw Exn.Interrupt()
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   130
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   131
      Process_Result(rc, out_lines.join, err_lines.join)
57f379ef662f clarified modules;
wenzelm
parents: 62400
diff changeset
   132
    }
60991
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
   133
  }
2fc5a44346b5 clarified modules, like ML version;
wenzelm
parents:
diff changeset
   134
}