-
-
Notifications
You must be signed in to change notification settings - Fork 22
Expand file tree
/
Copy pathPluginFrontend.scala
More file actions
143 lines (126 loc) · 4.37 KB
/
PluginFrontend.scala
File metadata and controls
143 lines (126 loc) · 4.37 KB
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
package protocbridge.frontend
import java.io.{ByteArrayOutputStream, InputStream, PrintWriter, StringWriter}
import java.nio.file.{Files, Path}
import protocbridge.{ProtocCodeGenerator, ExtraEnv}
/** A PluginFrontend instance provides a platform-dependent way for protoc to
* communicate with a JVM based ProtocCodeGenerator.
*
* protoc is able to launch plugins. Plugins are executables that take a
* serialized CodeGenerationRequest via stdin and serialize a
* CodeGenerationRequest to stdout. The idea in PluginFrontend is to create a
* minimal plugin that wires its stdin/stdout to this JVM.
*
* The two-way communication always goes as follows:
*
* 1. protoc writes a request to the stdin of a plugin 2. plugin writes the
* data to the channel 3. this JVM reads it, interprets it as
* CodeGenerationRequest and process it. 4. this JVM writes a
* CodeGenerationResponse to the channel 5. this JVM closes the channel.
* 6. the plugin reads the data and writes it to standard out. 7. protoc
* handles the CodeGenerationResponse (creates Scala sources)
*/
trait PluginFrontend {
type InternalState
// Notifies the frontend to set up a protoc plugin that runs the given generator. It returns
// the system path of the executable and an arbitary internal state object that is passed
// later. Useful for cleanup.
def prepare(plugin: ProtocCodeGenerator, env: ExtraEnv): (Path, InternalState)
def cleanup(state: InternalState): Unit
}
object PluginFrontend {
private def getStackTrace(e: Throwable): String = {
val stringWriter = new StringWriter
val printWriter = new PrintWriter(stringWriter)
e.printStackTrace(printWriter)
stringWriter.toString
}
def runWithBytes(
gen: ProtocCodeGenerator,
request: Array[Byte]
): Array[Byte] = {
gen.run(request)
}
def createCodeGeneratorResponseWithError(error: String): Array[Byte] = {
val b = Array.newBuilder[Byte]
def addRawVarint32(value0: Int): Unit = {
var value = value0
while (true) {
if ((value & ~0x7f) == 0) {
b += value.toByte
return
} else {
b += ((value & 0x7f) | 0x80).toByte
value >>>= 7
}
}
}
b += 10
val errorBytes = error.getBytes(java.nio.charset.Charset.forName("UTF-8"))
var length = errorBytes.length
addRawVarint32(length)
b ++= errorBytes
b.result()
}
@deprecated("This method is going to be removed.", "0.9.0")
def readInputStreamToByteArray(fsin: InputStream): Array[Byte] = {
val b = new ByteArrayOutputStream()
val buffer = new Array[Byte](4096)
var count = 0
while (count != -1) {
count = fsin.read(buffer)
if (count > 0) {
b.write(buffer, 0, count)
}
}
b.toByteArray
}
private[protocbridge] def readInputStreamToByteArrayWithEnv(
fsin: InputStream,
env: ExtraEnv
): Array[Byte] = {
val b = new ByteArrayOutputStream()
val buffer = new Array[Byte](4096)
var count = 0
while (count != -1) {
count = fsin.read(buffer)
if (count > 0) {
b.write(buffer, 0, count)
}
}
val envBytes = env.toByteArrayAsField
b.write(envBytes, 0, envBytes.length)
b.toByteArray
}
def runWithInputStream(
gen: ProtocCodeGenerator,
fsin: InputStream,
env: ExtraEnv
): Array[Byte] = try {
val bytes = readInputStreamToByteArrayWithEnv(fsin, env)
runWithBytes(gen, bytes)
} catch {
// This covers all Throwable including OutOfMemoryError, StackOverflowError, etc.
// We need to make a best effort to return a response to protoc,
// otherwise protoc can hang indefinitely.
case throwable: Throwable =>
createCodeGeneratorResponseWithError(
throwable.toString + "\n" + getStackTrace(throwable)
)
}
def createTempFile(extension: String, content: String): Path = {
val fileName = Files.createTempFile("protocbridge", extension)
val os = Files.newOutputStream(fileName)
os.write(content.getBytes("UTF-8"))
os.close()
fileName
}
def isWindows: Boolean = sys.props("os.name").startsWith("Windows")
def isMac: Boolean = sys.props("os.name").startsWith("Mac") || sys
.props("os.name")
.startsWith("Darwin")
def newInstance: PluginFrontend = {
if (isWindows) WindowsPluginFrontend
else if (isMac) MacPluginFrontend
else PosixPluginFrontend
}
}