wenzelm@30176
|
1 |
/* Title: Pure/System/isabelle_system.scala
|
wenzelm@27919
|
2 |
Author: Makarius
|
wenzelm@27919
|
3 |
|
wenzelm@44569
|
4 |
Fundamental Isabelle system environment: quasi-static module with
|
wenzelm@44569
|
5 |
optional init operation.
|
wenzelm@27919
|
6 |
*/
|
wenzelm@27919
|
7 |
|
wenzelm@27919
|
8 |
package isabelle
|
wenzelm@27919
|
9 |
|
wenzelm@44400
|
10 |
import java.lang.System
|
wenzelm@31535
|
11 |
import java.util.regex.Pattern
|
wenzelm@31820
|
12 |
import java.util.Locale
|
wenzelm@49424
|
13 |
import java.io.{InputStream, OutputStream, File => JFile, BufferedReader, InputStreamReader,
|
wenzelm@45919
|
14 |
BufferedWriter, OutputStreamWriter, IOException}
|
wenzelm@34024
|
15 |
import java.awt.{GraphicsEnvironment, Font}
|
wenzelm@37468
|
16 |
import java.awt.font.TextAttribute
|
wenzelm@28063
|
17 |
|
wenzelm@28063
|
18 |
import scala.io.Source
|
wenzelm@31535
|
19 |
import scala.util.matching.Regex
|
wenzelm@34170
|
20 |
import scala.collection.mutable
|
wenzelm@27936
|
21 |
|
wenzelm@27919
|
22 |
|
wenzelm@44385
|
23 |
object Isabelle_System
|
wenzelm@44385
|
24 |
{
|
wenzelm@44532
|
25 |
/** implicit state **/
|
wenzelm@44385
|
26 |
|
wenzelm@44569
|
27 |
private case class State(standard_system: Standard_System, settings: Map[String, String])
|
wenzelm@37024
|
28 |
|
wenzelm@44532
|
29 |
@volatile private var _state: Option[State] = None
|
wenzelm@37024
|
30 |
|
wenzelm@44532
|
31 |
private def check_state(): State =
|
wenzelm@44532
|
32 |
{
|
wenzelm@44532
|
33 |
if (_state.isEmpty) init() // unsynchronized check
|
wenzelm@44532
|
34 |
_state.get
|
wenzelm@44532
|
35 |
}
|
wenzelm@44532
|
36 |
|
wenzelm@44532
|
37 |
def standard_system: Standard_System = check_state().standard_system
|
wenzelm@44532
|
38 |
def settings: Map[String, String] = check_state().settings
|
wenzelm@31799
|
39 |
|
wenzelm@37024
|
40 |
/*
|
wenzelm@37024
|
41 |
isabelle_home precedence:
|
wenzelm@37024
|
42 |
(1) this_isabelle_home as explicit argument
|
wenzelm@37024
|
43 |
(2) ISABELLE_HOME process environment variable (e.g. inherited from running isabelle tool)
|
wenzelm@37024
|
44 |
(3) isabelle.home system property (e.g. via JVM application boot process)
|
wenzelm@37024
|
45 |
*/
|
wenzelm@44532
|
46 |
def init(this_isabelle_home: String = null) = synchronized {
|
wenzelm@44532
|
47 |
if (_state.isEmpty) {
|
wenzelm@44532
|
48 |
import scala.collection.JavaConversions._
|
wenzelm@37024
|
49 |
|
wenzelm@44532
|
50 |
val standard_system = new Standard_System
|
wenzelm@44532
|
51 |
val settings =
|
wenzelm@44532
|
52 |
{
|
wenzelm@49208
|
53 |
val env0 = sys.env + ("ISABELLE_JDK_HOME" -> standard_system.this_jdk_home())
|
wenzelm@31484
|
54 |
|
wenzelm@48605
|
55 |
val user_home = System.getProperty("user.home")
|
wenzelm@48605
|
56 |
val env =
|
wenzelm@48605
|
57 |
if (user_home == null || env0.isDefinedAt("HOME")) env0
|
wenzelm@48605
|
58 |
else env0 + ("HOME" -> user_home)
|
wenzelm@48605
|
59 |
|
wenzelm@44532
|
60 |
val isabelle_home =
|
wenzelm@44532
|
61 |
if (this_isabelle_home != null) this_isabelle_home
|
wenzelm@44532
|
62 |
else
|
wenzelm@44532
|
63 |
env.get("ISABELLE_HOME") match {
|
wenzelm@44532
|
64 |
case None | Some("") =>
|
wenzelm@44532
|
65 |
val path = System.getProperty("isabelle.home")
|
wenzelm@44532
|
66 |
if (path == null || path == "") error("Unknown Isabelle home directory")
|
wenzelm@44532
|
67 |
else path
|
wenzelm@44532
|
68 |
case Some(path) => path
|
wenzelm@44532
|
69 |
}
|
wenzelm@31484
|
70 |
|
wenzelm@49426
|
71 |
File.with_tmp_file("settings") { dump =>
|
wenzelm@44532
|
72 |
val shell_prefix =
|
wenzelm@44532
|
73 |
if (Platform.is_windows) List(standard_system.platform_root + "\\bin\\bash", "-l")
|
wenzelm@44532
|
74 |
else Nil
|
wenzelm@44532
|
75 |
val cmdline =
|
wenzelm@44532
|
76 |
shell_prefix ::: List(isabelle_home + "/bin/isabelle", "getenv", "-d", dump.toString)
|
wenzelm@44532
|
77 |
val (output, rc) = Standard_System.raw_exec(null, env, true, cmdline: _*)
|
wenzelm@44532
|
78 |
if (rc != 0) error(output)
|
wenzelm@31484
|
79 |
|
wenzelm@44532
|
80 |
val entries =
|
wenzelm@49426
|
81 |
(for (entry <- File.read(dump) split "\0" if entry != "") yield {
|
wenzelm@44532
|
82 |
val i = entry.indexOf('=')
|
wenzelm@44532
|
83 |
if (i <= 0) (entry -> "")
|
wenzelm@44532
|
84 |
else (entry.substring(0, i) -> entry.substring(i + 1))
|
wenzelm@48548
|
85 |
}).toMap
|
wenzelm@48548
|
86 |
entries + ("PATH" -> entries("PATH_JVM")) - "PATH_JVM"
|
wenzelm@44532
|
87 |
}
|
wenzelm@34201
|
88 |
}
|
wenzelm@44569
|
89 |
_state = Some(State(standard_system, settings))
|
wenzelm@44532
|
90 |
}
|
wenzelm@27953
|
91 |
}
|
wenzelm@27919
|
92 |
|
wenzelm@31799
|
93 |
|
wenzelm@31799
|
94 |
/* getenv */
|
wenzelm@31799
|
95 |
|
wenzelm@48538
|
96 |
def getenv(name: String): String = settings.getOrElse(name, "")
|
wenzelm@31484
|
97 |
|
wenzelm@31484
|
98 |
def getenv_strict(name: String): String =
|
wenzelm@31484
|
99 |
{
|
wenzelm@31234
|
100 |
val value = getenv(name)
|
wenzelm@27993
|
101 |
if (value != "") value else error("Undefined environment variable: " + name)
|
wenzelm@27919
|
102 |
}
|
wenzelm@27919
|
103 |
|
wenzelm@31799
|
104 |
|
wenzelm@44479
|
105 |
/** file-system operations **/
|
wenzelm@31799
|
106 |
|
wenzelm@44479
|
107 |
/* path specifications */
|
wenzelm@31799
|
108 |
|
wenzelm@44539
|
109 |
def standard_path(path: Path): String = path.expand.implode
|
wenzelm@36144
|
110 |
|
wenzelm@44532
|
111 |
def platform_path(path: Path): String = standard_system.jvm_path(standard_path(path))
|
wenzelm@49424
|
112 |
def platform_file(path: Path): JFile = new JFile(platform_path(path))
|
wenzelm@29152
|
113 |
|
wenzelm@45985
|
114 |
def platform_file_url(raw_path: Path): String =
|
wenzelm@45985
|
115 |
{
|
wenzelm@45985
|
116 |
val path = raw_path.expand
|
wenzelm@45985
|
117 |
require(path.is_absolute)
|
wenzelm@45986
|
118 |
val s = platform_path(path).replaceAll(" ", "%20")
|
wenzelm@45986
|
119 |
if (!Platform.is_windows) "file://" + s
|
wenzelm@45986
|
120 |
else if (s.startsWith("\\\\")) "file:" + s.replace('\\', '/')
|
wenzelm@45986
|
121 |
else "file:///" + s.replace('\\', '/')
|
wenzelm@45985
|
122 |
}
|
wenzelm@45985
|
123 |
|
wenzelm@44532
|
124 |
def posix_path(jvm_path: String): String = standard_system.posix_path(jvm_path)
|
wenzelm@44532
|
125 |
|
wenzelm@27953
|
126 |
|
wenzelm@37006
|
127 |
/* try_read */
|
wenzelm@37006
|
128 |
|
wenzelm@44479
|
129 |
def try_read(paths: Seq[Path]): String =
|
wenzelm@37006
|
130 |
{
|
wenzelm@37098
|
131 |
val buf = new StringBuilder
|
wenzelm@37098
|
132 |
for {
|
wenzelm@37098
|
133 |
path <- paths
|
wenzelm@37098
|
134 |
file = platform_file(path) if file.isFile
|
wenzelm@49426
|
135 |
} { buf.append(File.read(file)); buf.append('\n') }
|
wenzelm@37098
|
136 |
buf.toString
|
wenzelm@37006
|
137 |
}
|
wenzelm@37006
|
138 |
|
wenzelm@37006
|
139 |
|
wenzelm@31440
|
140 |
/* source files */
|
wenzelm@31440
|
141 |
|
wenzelm@49424
|
142 |
private def try_file(file: JFile) = if (file.isFile) Some(file) else None
|
wenzelm@31440
|
143 |
|
wenzelm@49424
|
144 |
def source_file(path: Path): Option[JFile] =
|
wenzelm@31484
|
145 |
{
|
wenzelm@44479
|
146 |
if (path.is_absolute || path.is_current)
|
wenzelm@31440
|
147 |
try_file(platform_file(path))
|
wenzelm@31440
|
148 |
else {
|
wenzelm@49388
|
149 |
val pure_file = (Path.explode("~~/src/Pure") + path).file
|
wenzelm@31440
|
150 |
if (pure_file.isFile) Some(pure_file)
|
wenzelm@49388
|
151 |
else if (getenv("ML_SOURCES") != "") try_file((Path.explode("$ML_SOURCES") + path).file)
|
wenzelm@31440
|
152 |
else None
|
wenzelm@31440
|
153 |
}
|
wenzelm@31440
|
154 |
}
|
wenzelm@31440
|
155 |
|
wenzelm@31440
|
156 |
|
wenzelm@32450
|
157 |
|
wenzelm@39847
|
158 |
/** external processes **/
|
wenzelm@39847
|
159 |
|
wenzelm@39847
|
160 |
/* plain execute */
|
wenzelm@39847
|
161 |
|
wenzelm@49424
|
162 |
def execute_env(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*): Process =
|
wenzelm@39847
|
163 |
{
|
wenzelm@39847
|
164 |
val cmdline =
|
wenzelm@44532
|
165 |
if (Platform.is_windows) List(standard_system.platform_root + "\\bin\\env.exe") ++ args
|
wenzelm@39847
|
166 |
else args
|
wenzelm@49368
|
167 |
val env1 = if (env == null) settings else settings ++ env
|
wenzelm@49368
|
168 |
Standard_System.raw_execute(cwd, env1, redirect, cmdline: _*)
|
wenzelm@39847
|
169 |
}
|
wenzelm@39847
|
170 |
|
wenzelm@49368
|
171 |
def execute(redirect: Boolean, args: String*): Process =
|
wenzelm@49368
|
172 |
execute_env(null, null, redirect, args: _*)
|
wenzelm@49368
|
173 |
|
wenzelm@39847
|
174 |
|
wenzelm@39847
|
175 |
/* managed process */
|
wenzelm@39847
|
176 |
|
wenzelm@49424
|
177 |
class Managed_Process(cwd: JFile, env: Map[String, String], redirect: Boolean, args: String*)
|
wenzelm@39847
|
178 |
{
|
wenzelm@39847
|
179 |
private val params =
|
wenzelm@44479
|
180 |
List(standard_path(Path.explode("~~/lib/scripts/process")), "group", "-", "no_script")
|
wenzelm@49368
|
181 |
private val proc = execute_env(cwd, env, redirect, (params ++ args):_*)
|
wenzelm@39847
|
182 |
|
wenzelm@39847
|
183 |
|
wenzelm@39847
|
184 |
// channels
|
wenzelm@39847
|
185 |
|
wenzelm@39847
|
186 |
val stdin: BufferedWriter =
|
wenzelm@39847
|
187 |
new BufferedWriter(new OutputStreamWriter(proc.getOutputStream, Standard_System.charset))
|
wenzelm@39847
|
188 |
|
wenzelm@39847
|
189 |
val stdout: BufferedReader =
|
wenzelm@39847
|
190 |
new BufferedReader(new InputStreamReader(proc.getInputStream, Standard_System.charset))
|
wenzelm@39847
|
191 |
|
wenzelm@39847
|
192 |
val stderr: BufferedReader =
|
wenzelm@39847
|
193 |
new BufferedReader(new InputStreamReader(proc.getErrorStream, Standard_System.charset))
|
wenzelm@39847
|
194 |
|
wenzelm@39847
|
195 |
|
wenzelm@39847
|
196 |
// signals
|
wenzelm@39847
|
197 |
|
wenzelm@39847
|
198 |
private val pid = stdout.readLine
|
wenzelm@39847
|
199 |
|
wenzelm@39847
|
200 |
private def kill(signal: String): Boolean =
|
wenzelm@39848
|
201 |
{
|
wenzelm@39848
|
202 |
execute(true, "kill", "-" + signal, "-" + pid).waitFor
|
wenzelm@39848
|
203 |
execute(true, "kill", "-0", "-" + pid).waitFor == 0
|
wenzelm@39848
|
204 |
}
|
wenzelm@39847
|
205 |
|
wenzelm@39847
|
206 |
private def multi_kill(signal: String): Boolean =
|
wenzelm@39847
|
207 |
{
|
wenzelm@39847
|
208 |
var running = true
|
wenzelm@39847
|
209 |
var count = 10
|
wenzelm@39847
|
210 |
while (running && count > 0) {
|
wenzelm@39847
|
211 |
if (kill(signal)) {
|
wenzelm@39847
|
212 |
Thread.sleep(100)
|
wenzelm@39847
|
213 |
count -= 1
|
wenzelm@39847
|
214 |
}
|
wenzelm@39847
|
215 |
else running = false
|
wenzelm@39847
|
216 |
}
|
wenzelm@39847
|
217 |
running
|
wenzelm@39847
|
218 |
}
|
wenzelm@39847
|
219 |
|
wenzelm@39847
|
220 |
def interrupt() { multi_kill("INT") }
|
wenzelm@39847
|
221 |
def terminate() { multi_kill("INT") && multi_kill("TERM") && kill("KILL"); proc.destroy }
|
wenzelm@39847
|
222 |
|
wenzelm@39847
|
223 |
|
wenzelm@39847
|
224 |
// JVM shutdown hook
|
wenzelm@39847
|
225 |
|
wenzelm@39847
|
226 |
private val shutdown_hook = new Thread { override def run = terminate() }
|
wenzelm@39847
|
227 |
|
wenzelm@39847
|
228 |
try { Runtime.getRuntime.addShutdownHook(shutdown_hook) }
|
wenzelm@39847
|
229 |
catch { case _: IllegalStateException => }
|
wenzelm@39847
|
230 |
|
wenzelm@39847
|
231 |
private def cleanup() =
|
wenzelm@39847
|
232 |
try { Runtime.getRuntime.removeShutdownHook(shutdown_hook) }
|
wenzelm@39847
|
233 |
catch { case _: IllegalStateException => }
|
wenzelm@39847
|
234 |
|
wenzelm@39847
|
235 |
|
wenzelm@39847
|
236 |
/* result */
|
wenzelm@39847
|
237 |
|
wenzelm@39847
|
238 |
def join: Int = { val rc = proc.waitFor; cleanup(); rc }
|
wenzelm@39847
|
239 |
}
|
wenzelm@39847
|
240 |
|
wenzelm@39847
|
241 |
|
wenzelm@39847
|
242 |
/* bash */
|
wenzelm@31799
|
243 |
|
wenzelm@49424
|
244 |
def bash_env(cwd: JFile, env: Map[String, String], script: String): (String, String, Int) =
|
wenzelm@34198
|
245 |
{
|
wenzelm@49426
|
246 |
File.with_tmp_file("isabelle_script") { script_file =>
|
wenzelm@49426
|
247 |
File.write(script_file, script)
|
wenzelm@49368
|
248 |
val proc = new Managed_Process(cwd, env, false, "bash", posix_path(script_file.getPath))
|
wenzelm@34198
|
249 |
|
wenzelm@39847
|
250 |
proc.stdin.close
|
wenzelm@49426
|
251 |
val (_, stdout) = Simple_Thread.future("bash_stdout") { File.read(proc.stdout) }
|
wenzelm@49426
|
252 |
val (_, stderr) = Simple_Thread.future("bash_stderr") { File.read(proc.stderr) }
|
wenzelm@34198
|
253 |
|
wenzelm@39845
|
254 |
val rc =
|
wenzelm@39847
|
255 |
try { proc.join }
|
wenzelm@39847
|
256 |
catch { case e: InterruptedException => Thread.interrupted; proc.terminate; 130 }
|
wenzelm@39845
|
257 |
(stdout.join, stderr.join, rc)
|
wenzelm@34198
|
258 |
}
|
wenzelm@34198
|
259 |
}
|
wenzelm@34198
|
260 |
|
wenzelm@49368
|
261 |
def bash(script: String): (String, String, Int) = bash_env(null, null, script)
|
wenzelm@49368
|
262 |
|
wenzelm@39847
|
263 |
|
wenzelm@39847
|
264 |
/* system tools */
|
wenzelm@39847
|
265 |
|
wenzelm@31818
|
266 |
def isabelle_tool(name: String, args: String*): (String, Int) =
|
wenzelm@31484
|
267 |
{
|
wenzelm@44544
|
268 |
Path.split(getenv_strict("ISABELLE_TOOLS")).find { dir =>
|
wenzelm@49388
|
269 |
val file = (dir + Path.basic(name)).file
|
wenzelm@42995
|
270 |
try {
|
wenzelm@42995
|
271 |
file.isFile && file.canRead && file.canExecute &&
|
wenzelm@42995
|
272 |
!name.endsWith("~") && !name.endsWith(".orig")
|
wenzelm@42995
|
273 |
}
|
wenzelm@31818
|
274 |
catch { case _: SecurityException => false }
|
wenzelm@34200
|
275 |
} match {
|
wenzelm@31818
|
276 |
case Some(dir) =>
|
wenzelm@44544
|
277 |
val file = standard_path(dir + Path.basic(name))
|
wenzelm@44479
|
278 |
Standard_System.process_output(execute(true, (List(file) ++ args): _*))
|
wenzelm@31818
|
279 |
case None => ("Unknown Isabelle tool: " + name, 2)
|
wenzelm@31818
|
280 |
}
|
wenzelm@28063
|
281 |
}
|
wenzelm@28063
|
282 |
|
wenzelm@28063
|
283 |
|
wenzelm@32450
|
284 |
|
wenzelm@31799
|
285 |
/** Isabelle resources **/
|
wenzelm@31799
|
286 |
|
wenzelm@32328
|
287 |
/* components */
|
wenzelm@32328
|
288 |
|
wenzelm@44544
|
289 |
def components(): List[Path] =
|
wenzelm@44544
|
290 |
Path.split(getenv_strict("ISABELLE_COMPONENTS"))
|
wenzelm@32328
|
291 |
|
wenzelm@32328
|
292 |
|
wenzelm@29152
|
293 |
/* find logics */
|
wenzelm@29152
|
294 |
|
wenzelm@31484
|
295 |
def find_logics(): List[String] =
|
wenzelm@31484
|
296 |
{
|
wenzelm@29152
|
297 |
val ml_ident = getenv_strict("ML_IDENTIFIER")
|
wenzelm@34170
|
298 |
val logics = new mutable.ListBuffer[String]
|
wenzelm@44544
|
299 |
for (dir <- Path.split(getenv_strict("ISABELLE_PATH"))) {
|
wenzelm@49388
|
300 |
val files = (dir + Path.explode(ml_ident)).file.listFiles()
|
wenzelm@29152
|
301 |
if (files != null) {
|
wenzelm@29152
|
302 |
for (file <- files if file.isFile) logics += file.getName
|
wenzelm@29152
|
303 |
}
|
wenzelm@29152
|
304 |
}
|
wenzelm@46773
|
305 |
logics.toList.sorted
|
wenzelm@29152
|
306 |
}
|
wenzelm@29570
|
307 |
|
wenzelm@29570
|
308 |
|
wenzelm@34024
|
309 |
/* fonts */
|
wenzelm@34024
|
310 |
|
wenzelm@44355
|
311 |
def get_font(family: String = "IsabelleText", size: Int = 1, bold: Boolean = false): Font =
|
wenzelm@44355
|
312 |
new Font(family, if (bold) Font.BOLD else Font.PLAIN, size)
|
wenzelm@37468
|
313 |
|
wenzelm@34049
|
314 |
def install_fonts()
|
wenzelm@34049
|
315 |
{
|
wenzelm@37468
|
316 |
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
|
wenzelm@44544
|
317 |
for (font <- Path.split(getenv_strict("ISABELLE_FONTS")))
|
wenzelm@49388
|
318 |
ge.registerFont(Font.createFont(Font.TRUETYPE_FONT, font.file))
|
wenzelm@34024
|
319 |
}
|
wenzelm@27919
|
320 |
}
|