Skip to content

Commit 816fefd

Browse files
authored
Python packaging defs (#247)
- Add pyproject.toml - Refactor paths for packaging: - precompiled compiler now in edg_core/resources - HDL interface server in its own package, to be runnable with `python -m` - Update documentation - Package uploaded to pip
1 parent 2932736 commit 816fefd

16 files changed

Lines changed: 165 additions & 123 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ BlinkyExample/
2323

2424
Pipfile.lock
2525

26+
# Python packaging
27+
build
28+
edg.egg-info
29+
dist
30+
2631
# mypy
2732
.mypy_cache/
2833
.dmypy.json

LibraryDump.py

Lines changed: 0 additions & 45 deletions
This file was deleted.

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include edg/edg-compiler-precompiled.jar

compiler/src/main/scala/edg/compiler/CompilerServerMain.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import java.io.{File, PrintWriter, StringWriter}
1414

1515
// a PythonInterface that uses the on-event hooks to forward stderr and stdout
1616
// without this, the compiler can freeze on large stdout/stderr data, possibly because of queue sizing
17-
class ForwardingPythonInterface(serverFile: File)
18-
extends PythonInterface(serverFile) {
17+
class ForwardingPythonInterface(serverFile: Option[File], pythonPaths: Seq[String])
18+
extends PythonInterface(serverFile, pythonPaths) {
1919
def forwardProcessOutput(): Unit = {
2020
StreamUtils.forAvailable(processOutputStream) { data =>
2121
System.out.print(new String(data))
@@ -79,7 +79,9 @@ object CompilerServerMain {
7979
}
8080

8181
def main(args: Array[String]): Unit = {
82-
val pyIf = new ForwardingPythonInterface(new File("HdlInterfaceService.py")) // use relative path
82+
val hdlServerOption = PythonInterface.serverFileOption(None) // local relative path
83+
hdlServerOption.foreach { serverFile => println(s"Using local $serverFile") }
84+
val pyIf = new ForwardingPythonInterface(hdlServerOption, Seq(new File(".").getAbsolutePath))
8385
val pyLib = new PythonInterfaceLibrary()
8486
pyLib.withPythonInterface(pyIf) {
8587
while (true) {

compiler/src/main/scala/edg/compiler/PythonInterface.scala

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@ object ProtobufStdioSubprocess {
2121
}
2222

2323

24-
class ProtobufStdioSubprocess
25-
[RequestType <: scalapb.GeneratedMessage, ResponseType <: scalapb.GeneratedMessage](
24+
class ProtobufStdioSubprocess[RequestType <: scalapb.GeneratedMessage, ResponseType <: scalapb.GeneratedMessage](
2625
responseType: scalapb.GeneratedMessageCompanion[ResponseType],
27-
args: Seq[String]) {
26+
pythonPaths: Seq[String], args: Seq[String]) {
2827
protected val process: Either[Process, Throwable] = try {
29-
Left(new ProcessBuilder(args: _*).start())
28+
val processBuilder = new ProcessBuilder(args: _*)
29+
if (pythonPaths.nonEmpty) {
30+
val env = processBuilder.environment()
31+
val pythonPathString = pythonPaths.mkString(";")
32+
Option(env.get("PYTHONPATH")) match { // merge existing PYTHONPATH if exists
33+
case None => env.put("PYTHONPATH", pythonPathString)
34+
case Some(envPythonPath) => env.put("PYTHONPATH", envPythonPath + ";" + pythonPathString)
35+
}
36+
}
37+
Left(processBuilder.start())
3038
} catch {
3139
case e: Throwable => Right(e) // if it fails store the exception to be thrown when we can
3240
}
@@ -113,14 +121,36 @@ class ProtobufStdioSubprocess
113121
}
114122

115123

124+
object PythonInterface {
125+
private val kHdlServerFilePath = "edg_hdl_server/__main__.py"
126+
// returns the HDL server Python script if it exists locally, otherwise returns None.
127+
def serverFileOption(root: Option[File] = None): Option[File] = {
128+
val hdlServerFile = root match {
129+
case Some(root) => new File(root, kHdlServerFilePath)
130+
case None => new File(kHdlServerFilePath)
131+
}
132+
if (hdlServerFile.exists()) {
133+
Some(hdlServerFile)
134+
} else {
135+
None
136+
}
137+
}
138+
}
139+
140+
116141
/** An interface to the Python HDL elaborator, which reads in Python HDL code and (partially) compiles
117142
* them down to IR.
118143
* The underlying Python HDL should not change while this is open. This will not reload updated Python HDL files.
144+
*
145+
* If the serverFile is specified, run that; otherwise use "python -m edg_hdl_server" for the global package.
119146
*/
120-
class PythonInterface(serverFile: File, pythonInterpreter: String = "python") {
147+
class PythonInterface(serverFile: Option[File], pythonPaths: Seq[String], pythonInterpreter: String = "python") {
148+
val command = serverFile match { // -u for unbuffered mode
149+
case Some(serverFile) => Seq(pythonInterpreter, "-u", serverFile.getAbsolutePath)
150+
case None => Seq(pythonInterpreter, "-u", "-m", "edg_hdl_server")
151+
}
121152
protected val process = new ProtobufStdioSubprocess[edgrpc.HdlRequest, edgrpc.HdlResponse](
122-
edgrpc.HdlResponse,
123-
Seq(pythonInterpreter, "-u", serverFile.getAbsolutePath)) // in unbuffered mode
153+
edgrpc.HdlResponse, pythonPaths, command)
124154
val processOutputStream: InputStream = process.outputStream
125155
val processErrorStream: InputStream = process.errorStream
126156

compiler/src/test/scala/edg/compiler/PythonInterfaceTest.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class PythonInterfaceTest extends AnyFlatSpec {
1313
val compiledDir = new File(getClass.getResource("").getPath)
1414
// above returns compiler/target/scala-2.xx/test-classes/edg/compiler, get the root repo dir
1515
val repoDir = compiledDir.getParentFile.getParentFile.getParentFile.getParentFile.getParentFile.getParentFile
16-
val pyIf = new PythonInterface(new File(repoDir, "HdlInterfaceService.py"))
16+
val pyIf = new PythonInterface(Some(new File(repoDir, "edg_hdl_server/__main__.py")),
17+
Seq(repoDir.getAbsolutePath))
1718
pyIf.indexModule("edg_core").getClass should equal(classOf[Errorable.Success[Seq[LibraryPath]]])
1819
pyIf.shutdown() should equal(0)
1920
}

edg_core/ScalaCompilerInterface.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ def append_values(self, values: List[Tuple[edgir.LocalPath, edgir.ValueLit]]):
5050

5151

5252
class ScalaCompilerInstance:
53-
PRECOMPIED_RELPATH = "compiler/edg-compiler-precompiled.jar"
54-
DEV_RELPATH = "compiler/target/scala-2.13/edg-compiler-assembly-0.1-SNAPSHOT.jar"
53+
DEV_RELPATH = "../compiler/target/scala-2.13/edg-compiler-assembly-0.1-SNAPSHOT.jar"
54+
PRECOMPIED_RELPATH = "resources/edg-compiler-precompiled.jar"
5555

5656
def __init__(self, *, suppress_stderr: bool = False):
5757
self.process: Optional[Any] = None
@@ -61,13 +61,15 @@ def __init__(self, *, suppress_stderr: bool = False):
6161

6262
def check_started(self) -> None:
6363
if self.process is None:
64-
if os.path.exists(self.DEV_RELPATH):
65-
jar_path = self.DEV_RELPATH
64+
dev_path = os.path.join(os.path.dirname(__file__), self.DEV_RELPATH)
65+
precompiled_path = os.path.join(os.path.dirname(__file__), self.PRECOMPIED_RELPATH)
66+
if os.path.exists(dev_path):
67+
jar_path = dev_path
6668
print("Using development JAR")
67-
elif os.path.exists(self.PRECOMPIED_RELPATH):
68-
jar_path = self.PRECOMPIED_RELPATH
69+
elif os.path.exists(precompiled_path):
70+
jar_path = precompiled_path
6971
else:
70-
raise ValueError("No EDG Compiler JAR found")
72+
raise ValueError(f"No EDG Compiler JAR found")
7173

7274
self.process = subprocess.Popen(
7375
['java', '-jar', jar_path],
12.6 MB
Binary file not shown.

edg_hdl_server/__init__.py

Whitespace-only changes.
Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
from edg_core.Core import NonLibraryProperty
1111

1212

13+
EDG_PROTO_VERSION = 1
14+
15+
1316
class LibraryElementIndexer:
1417
"""Indexer for libraries, recursively searches modules and their LibraryElements."""
1518
def __init__(self):
@@ -24,8 +27,8 @@ def index_module(self, module: ModuleType) -> Set[Type[LibraryElement]]:
2427
def _search_module(self, module: ModuleType) -> None:
2528
# avoid repeated work and re-indexing modules
2629
if (module.__name__ in sys.builtin_module_names
27-
or not hasattr(module, '__file__') # apparently load six.moves breaks
28-
or module in self.seen_modules):
30+
or not hasattr(module, '__file__') # apparently load six.moves breaks
31+
or module in self.seen_modules):
2932
return
3033
self.seen_modules.add(module)
3134

@@ -34,8 +37,8 @@ def _search_module(self, module: ModuleType) -> None:
3437
self._search_module(member)
3538

3639
if inspect.isclass(member) and issubclass(member, LibraryElement) and not issubclass(member, DesignTop) \
37-
and member not in self.seen_elements \
38-
and (member, NonLibraryProperty) not in member._elt_properties: # process elements
40+
and member not in self.seen_elements \
41+
and (member, NonLibraryProperty) not in member._elt_properties: # process elements
3942
self.seen_elements.add(member)
4043

4144
for mro in member.mro():
@@ -67,7 +70,7 @@ def elaborate_class(elt_cls: Type[LibraryElementType]) -> Tuple[LibraryElementTy
6770

6871
LibraryClassType = TypeVar('LibraryClassType')
6972
def class_from_library(elt: edgir.LibraryPath, expected_superclass: Type[LibraryClassType]) -> \
70-
Type[LibraryClassType]:
73+
Type[LibraryClassType]:
7174
elt_split = elt.target.name.split('.')
7275
elt_module = importlib.import_module('.'.join(elt_split[:-1]))
7376
assert inspect.ismodule(elt_module)
@@ -76,9 +79,7 @@ def class_from_library(elt: edgir.LibraryPath, expected_superclass: Type[Library
7679
return cls
7780

7881

79-
# In some cases stdout seems to buffer excessively, in which case starting python with -u seems to work
80-
# https://stackoverflow.com/a/35467658/5875811
81-
if __name__ == '__main__':
82+
def run_server():
8283
stdin_deserializer = BufferDeserializer(edgrpc.HdlRequest, sys.stdin.buffer)
8384
stdout_serializer = BufferSerializer[edgrpc.HdlResponse](sys.stdout.buffer)
8485

@@ -137,6 +138,8 @@ def class_from_library(elt: edgir.LibraryPath, expected_superclass: Type[Library
137138
response_result = response.run_backend.results.add()
138139
response_result.path.CopyFrom(path)
139140
response_result.text = backend_result
141+
elif request.HasField('get_proto_version'):
142+
response.get_proto_version = EDG_PROTO_VERSION
140143
else:
141144
raise RuntimeError(f"Unknown request {request}")
142145
except BaseException as e:
@@ -149,3 +152,9 @@ def class_from_library(elt: edgir.LibraryPath, expected_superclass: Type[Library
149152

150153
sys.stdout.buffer.write(stdin_deserializer.read_stdout())
151154
stdout_serializer.write(response)
155+
156+
157+
# In some cases stdout seems to buffer excessively, in which case starting python with -u seems to work
158+
# https://stackoverflow.com/a/35467658/5875811
159+
if __name__ == '__main__':
160+
run_server()

0 commit comments

Comments
 (0)