This repository has been archived by the owner on Sep 25, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
Environment.scala
224 lines (175 loc) · 8.06 KB
/
Environment.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package info.hupel.isabelle.api
import java.net.URLClassLoader
import java.nio.charset.Charset
import java.nio.file._
import scala.util.control.ControlThrowable
import monix.execution.Scheduler
import org.log4s._
import shapeless.tag._
case class ExitTrap(rc: Int) extends ControlThrowable
object Environment {
private val logger = getLogger
private def getVersion(clazz: Class[_ <: Environment]): Version.Stable =
Option(clazz.getAnnotation(classOf[Implementation])) match {
case Some(annot) =>
Version.Stable(annot.identifier)
case _ =>
sys.error("malformed implementation")
}
val packageName: String = "info.hupel.isabelle.impl"
def instantiate(version: Version, classpath: List[Path], context: Context) = {
logger.debug(s"Creating environment with classpath ${classpath.mkString(":")} ...")
val classLoader = new URLClassLoader(classpath.map(_.toUri.toURL).toArray, getClass.getClassLoader)
val env = classLoader.loadClass(s"$packageName.Environment").asSubclass(classOf[Environment])
val actualVersion = getVersion(env)
if (actualVersion != version)
sys.error(s"expected version $version, got version $actualVersion")
val info = classLoader.loadClass(s"$packageName.BuildInfo").getDeclaredMethod("toString").invoke(null)
if (BuildInfo.toString != info.toString)
sys.error(s"build info does not match")
val constructor = env.getDeclaredConstructor(classOf[Environment.Context])
constructor.setAccessible(true)
import context.scheduler
constructor.newInstance(context.copy(options =
OptionKey.Bool("ML_statistics").set(false) :: context.options
))
}
/**
* Marker trait indicating raw (encoded) Isabelle symbol text.
*
* @see [[Environment#decode]]
* @see [[Environment#encode]]
*/
sealed trait Raw
/**
* Marker trait indicating Unicode (decoded) Isabelle symbol text.
*
* @see [[Environment#decode]]
* @see [[Environment#encode]]
*/
sealed trait Unicode
/** Bundles all requirements to instantiate an [[Environment environment]]. */
case class Context(home: Path, user: Path, components: List[Path], options: List[OptionKey.Update])(implicit val scheduler: Scheduler) {
def executorService = scheduler.toExecutorService
// FIXME move to different location?
def etc(version: Version) = version match {
case Version.Devel(_) => user.resolve(".isabelle").resolve("etc")
case Version.Stable(identifier) => user.resolve(".isabelle").resolve(s"Isabelle$identifier").resolve("etc")
}
def etcComponents(version: Version) = etc(version).resolve("components")
}
}
/**
* Abstract interface for an Isabelle environment of a particular
* [[Version version]] in a path with an underlying PIDE machinery.
*
* As opposed to a mere logic-less `[[info.hupel.isabelle.setup.Setup Setup]]`,
* an environment knows how to manage Isabelle processes. It can also manage
* multiple running processes at the same time.
*
* A subclass of this class is called ''implementation'' throughout
* `libisabelle`.
*
* It is highly recommended to use
* `[[info.hupel.isabelle.setup.Setup#makeEnvironment(resolver* Setup.makeEnvironment]]`
* to instantiate implementations.
*
* While implementations may be created freely by users, it is recommended to
* only use the bundled implementations for the supported Isabelle versions.
* By convention, they live in the package specified in
* `[[Environment.packageName]]`.
*
* ''Contract''
*
* - An implementation is a subclass of this class.
* - The class name of the implementation must be `Environment`. There must
* be a `BuildInfo` class in the same package.
* - Implementations must be final and provide a constructor with exactly one
* argument (of type `[[Environment.Context]]`). There must be no other
* constructors. The constructor should be `private`.
* - Implementations must be annotated with
* `[[info.hupel.isabelle.api.Implementation Implementation]]`, where the
* given [[info.hupel.isabelle.api.Implementation.identifier identifier]]
* corresponds to the [[Version version identifier]].
*
* ''Footnote''
*
* Due to name clashes in the underlying PIDE machinery (which is provided by
* Isabelle itself and is not under control of `libisabelle`), it is impossible
* to have multiple environments for different versions in the same class
* loader. This is the primary reason why this class exists in the first place,
* to enable seamless abstraction over multiple PIDEs.
*/
abstract class Environment protected(val context: Environment.Context, versionOverride: Option[Version] = None, checkClassLoader: Boolean = true) { self =>
if (checkClassLoader && getClass.getClassLoader == classOf[Environment].getClassLoader)
sys.error("Environment not loaded in an isolated class loader, this is unsupported")
protected final val logger = getLogger
protected final val evalCommand = "libisabelle_eval"
logger.debug(s"Instantiating environment at ${context.home} (with user storage ${context.user}) ...")
final val home = context.home.toAbsolutePath
final val user = context.user.toAbsolutePath
final implicit val scheduler: Scheduler = context.scheduler
final val version: Version = versionOverride.getOrElse(Environment.getVersion(getClass()))
final val variables: Map[String, String] = Map(
"LIBISABELLE_GIT" -> BuildInfo.gitHeadCommit.getOrElse(""),
"LIBISABELLE_VERSION" -> BuildInfo.version
) ++ (version match {
case Version.Devel(_) => Map()
case Version.Stable(identifier) => Map("ISABELLE_VERSION" -> identifier)
})
final val etc = context.etc(version)
final val etcComponents = context.etcComponents(version)
protected final def setEtcComponents(): Unit =
if (!context.components.isEmpty) {
logger.debug(s"Initializing components ...")
Files.createDirectories(etc)
if (Files.exists(etcComponents))
logger.error(s"Components catalog $etcComponents already exists")
val out = Files.newBufferedWriter(etcComponents, Charset.forName("UTF-8"), StandardOpenOption.CREATE_NEW)
context.components.foreach { c =>
val path = isabellePath(c.toAbsolutePath().toString)
logger.debug(s"Adding component $path ...")
out.write(path)
out.write("\n")
}
out.close()
}
protected final def cleanEtcComponents(): Unit = {
Files.deleteIfExists(etcComponents)
()
}
protected final def protocolTheory(loaded: Set[String]): Option[String] =
if (loaded.contains("Protocol.Protocol"))
None
else {
// this only works if resources are registered
val resourcesHome = isabelleSetting("LIBISABELLE_RESOURCES_HOME")
if (resourcesHome.isEmpty)
sys.error("protocol not loaded but component not registered")
logger.info("Protocol theory not contained in image, scheduling to be loaded ...")
if (loaded.contains("Main"))
Some(s"HOL-Protocol.Protocol_Main")
else
Some(s"Protocol.Protocol_Pure")
}
override def toString: String =
s"$version at $home"
protected[isabelle] def build(config: Configuration): Int
protected[isabelle] val functionTag: String
protected[isabelle] val protocolTag: String
protected[isabelle] val initTag: String
protected[isabelle] val exitTag: String
protected[isabelle] val printTags: Set[String]
protected[isabelle] type Session
protected[isabelle] def create(config: Configuration, consumer: (Markup, XML.Body) => Unit): (Session, Option[String])
protected[isabelle] def sendOptions(session: Session): Unit
protected[isabelle] def sendCommand(session: Session, name: String, args: List[String]): Unit
protected[isabelle] def dispose(session: Session): Unit
protected[isabelle] final def eval(session: Session, ml: String): Unit =
sendCommand(session, evalCommand, List(ml))
def isabelleSetting(name: String): String
def isabellePath(path: String): String
def decode(text: String @@ Environment.Raw): String @@ Environment.Unicode
def encode(text: String @@ Environment.Unicode): String @@ Environment.Raw
def exec(tool: String, args: List[String]): Int
}