Tue, 14 Nov 2017 19:13:28 +0100
Continued to add support for Google protobuf protocol files. Added support for gRPC.
--- a/Preferences/ConfigurationPages/ProtobufPage.py Mon Nov 13 20:20:06 2017 +0100 +++ b/Preferences/ConfigurationPages/ProtobufPage.py Tue Nov 14 19:13:28 2017 +0100 @@ -34,14 +34,21 @@ "Press to select the Protobuf compiler via a file selection" " dialog.")) + self.grpcPythonPicker.setMode(E5PathPickerModes.OpenFileMode) + self.grpcPythonPicker.setToolTip(self.tr( + "Press to select the Python interpreter containing the grpc" + " compiler via a file selection dialog.")) + # set initial values self.protocPicker.setText(Preferences.getProtobuf("protoc")) + self.grpcPythonPicker.setText(Preferences.getProtobuf("grpcPython")) def save(self): """ Public slot to save the protobuf configuration. """ Preferences.setProtobuf("protoc", self.protocPicker.text()) + Preferences.setProtobuf("grpcPython", self.grpcPythonPicker.text()) def create(dlg):
--- a/Preferences/ConfigurationPages/ProtobufPage.ui Mon Nov 13 20:20:06 2017 +0100 +++ b/Preferences/ConfigurationPages/ProtobufPage.ui Tue Nov 14 19:13:28 2017 +0100 @@ -10,7 +10,7 @@ <height>490</height> </rect> </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> + <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QLabel" name="headerLabel"> <property name="text"> @@ -58,6 +58,32 @@ </widget> </item> <item> + <widget class="QGroupBox" name="groupBox_3"> + <property name="title"> + <string>grpc Compiler</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="E5PathPicker" name="grpcPythonPicker" native="true"> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="toolTip"> + <string>Enter the path of the Python interpreter containing the grpc compiler.</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="textLabel1_6"> + <property name="text"> + <string><b>Note:</b> Leave this entry empty to use the Python interprter of eric.</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> <spacer> <property name="orientation"> <enum>Qt::Vertical</enum>
--- a/Preferences/ProgramsDialog.py Mon Nov 13 20:20:06 2017 +0100 +++ b/Preferences/ProgramsDialog.py Tue Nov 14 19:13:28 2017 +0100 @@ -15,6 +15,7 @@ import os import re +import sys from PyQt5.QtCore import pyqtSlot, Qt, QProcess from PyQt5.QtGui import QCursor @@ -214,6 +215,13 @@ exe += ".exe" self.__createProgramEntry( self.tr("Protobuf Compiler"), exe, '--version', 'libprotoc', -1) + # 5c. grpc + exe = Preferences.getProtobuf("grpcPython") + if not exe: + exe = sys.executable + self.__createProgramEntry( + self.tr("grpc Compiler"), exe, '--version', 'libprotoc', -1, + exeModule=['-m', 'grpc_tools.protoc']) # 6. do the spell checking entry try: @@ -253,6 +261,8 @@ pm = e5App().getObject("PluginManager") for info in pm.getPluginExeDisplayData(): if info["programEntry"]: + if "exeModule" not in info: + info["exeModule"] = None self.__createProgramEntry( info["header"], info["exe"], @@ -261,6 +271,7 @@ versionPosition=info["versionPosition"], version=info["version"], versionCleanup=info["versionCleanup"], + exeModule=info["exeModule"], ) else: self.__createEntry( @@ -277,14 +288,15 @@ def __createProgramEntry(self, description, exe, versionCommand="", versionStartsWith="", versionPosition=0, version="", - versionCleanup=None, versionRe=None): + versionCleanup=None, versionRe=None, + exeModule=None): """ Private method to generate a program entry. @param description descriptive text (string) @param exe name of the executable program (string) @param versionCommand command line switch to get the version info - (string) if this is empty, the given version will be shown. + (str). If this is empty, the given version will be shown. @param versionStartsWith start of line identifying version info (string) @param versionPosition index of part containing the version info @@ -294,6 +306,9 @@ start and stop for the version string (tuple of integers) @keyparam versionRe regexp to determine the line identifying version info (string). Takes precedence over versionStartsWith. + @keyparam exeModule list of command line parameters to execute a module + with the program given in exe (e.g. to execute a Python module) + (list of str) @return version string of detected or given version (string) """ itmList = self.programsList.findItems( @@ -320,7 +335,11 @@ versionPosition: proc = QProcess() proc.setProcessChannelMode(QProcess.MergedChannels) - proc.start(exe, [versionCommand]) + if exeModule: + args = exeModule[:] + [versionCommand] + else: + args = [versionCommand] + proc.start(exe, args) finished = proc.waitForFinished(10000) if finished: output = str(proc.readAllStandardOutput(), @@ -345,7 +364,12 @@ version = self.tr("(unknown)") else: version = self.tr("(not executable)") - QTreeWidgetItem(itm, [exe, version]) + if exeModule: + QTreeWidgetItem(itm, [ + "{0} {1}".format(exe, " ".join(exeModule)), + version]) + else: + QTreeWidgetItem(itm, [exe, version]) itm.setExpanded(True) else: itm.setText(1, self.tr("(not found)"))
--- a/Preferences/__init__.py Mon Nov 13 20:20:06 2017 +0100 +++ b/Preferences/__init__.py Tue Nov 14 19:13:28 2017 +0100 @@ -1284,7 +1284,8 @@ # defaults for protobuf related stuff protobufDefaults = { - "protoc": "" + "protoc": "", + "grpcPython": "", } # defaults for user related stuff
--- a/Project/ProjectProtocolsBrowser.py Mon Nov 13 20:20:06 2017 +0100 +++ b/Project/ProjectProtocolsBrowser.py Tue Nov 14 19:13:28 2017 +0100 @@ -16,6 +16,7 @@ import os import glob +import sys from PyQt5.QtCore import QThread, pyqtSignal, QProcess from PyQt5.QtWidgets import QDialog, QApplication, QMenu @@ -61,13 +62,6 @@ @param parent parent widget of this browser @type QWidget """ - self.__protoc = Preferences.getProtobuf("protoc") - if self.__protoc == "": - self.__protoc = Utilities.isWindowsPlatform() and \ - "protoc.exe" or "protoc" - if not Utilities.isinpath(self.__protoc): - self.__protoc = None - ProjectBaseBrowser.__init__(self, project, ProjectBrowserProtocolsType, parent) @@ -96,12 +90,19 @@ self.dirMultiMenuActions = [] self.sourceMenu = QMenu(self) - if self.__protoc is not None: - self.sourceMenu.addAction( - self.tr('Compile protocol'), self.__compileProtocol) - self.sourceMenu.addAction( - self.tr('Compile all protocols'), - self.__compileAllProtocols) + self.sourceMenu.addAction( + self.tr('Compile protocol'), self.__compileProtocol) + self.sourceMenu.addAction( + self.tr('Compile all protocols'), + self.__compileAllProtocols) + self.sourceMenu.addSeparator() + self.sourceMenu.addAction( + self.tr('Compile protocol as grpc'), + lambda: self.__compileProtocol(grpc=True)) + self.sourceMenu.addAction( + self.tr('Compile all protocols as grpc'), + lambda: self.__compileAllProtocols(grpc=True)) + self.sourceMenu.addSeparator() self.sourceMenu.addAction(self.tr('Open'), self._openItem) self.sourceMenu.addSeparator() act = self.sourceMenu.addAction( @@ -133,12 +134,19 @@ self.tr('Configure Protobuf...'), self.__configureProtobuf) self.menu = QMenu(self) - if self.__protoc is not None: - self.menu.addAction( - self.tr('Compile protocol'), self.__compileProtocol) - self.menu.addAction( - self.tr('Compile all protocols'), - self.__compileAllProtocols) + self.menu.addAction( + self.tr('Compile protocol'), self.__compileProtocol) + self.menu.addAction( + self.tr('Compile all protocols'), + self.__compileAllProtocols) + self.menu.addSeparator() + self.menu.addAction( + self.tr('Compile protocol as grpc'), + lambda: self.__compileProtocol(grpc=True)) + self.menu.addAction( + self.tr('Compile all protocols as grpc'), + lambda: self.__compileAllProtocols(grpc=True)) + self.menu.addSeparator() self.menu.addAction(self.tr('Open'), self._openItem) self.menu.addSeparator() self.menu.addAction( @@ -157,11 +165,14 @@ self.tr('Configure Protobuf...'), self.__configureProtobuf) self.backMenu = QMenu(self) - if self.__protoc is not None: - self.backMenu.addAction( - self.tr('Compile all protocols'), - self.__compileAllProtocols) - self.backMenu.addSeparator() + self.backMenu.addAction( + self.tr('Compile all protocols'), + self.__compileAllProtocols) + self.backMenu.addSeparator() + self.backMenu.addAction( + self.tr('Compile all protocols as grpc'), + lambda: self.__compileAllProtocols(grpc=True)) + self.backMenu.addSeparator() self.backMenu.addAction( self.tr('Add protocols...'), self.project.addProtoFiles) self.backMenu.addAction( @@ -179,10 +190,14 @@ # create the menu for multiple selected files self.multiMenu = QMenu(self) - if self.__protoc is not None: - self.multiMenu.addAction( - self.tr('Compile protocols'), - self.__compileSelectedProtocols) + self.multiMenu.addAction( + self.tr('Compile protocols'), + self.__compileSelectedProtocols) + self.multiMenu.addSeparator() + self.multiMenu.addAction( + self.tr('Compile protocols as grpc'), + lambda: self.__compileSelectedProtocols(grpc=True)) + self.multiMenu.addSeparator() self.multiMenu.addAction(self.tr('Open'), self._openItem) self.multiMenu.addSeparator() act = self.multiMenu.addAction( @@ -202,11 +217,13 @@ self.tr('Configure Protobuf...'), self.__configureProtobuf) self.dirMenu = QMenu(self) - if self.__protoc is not None: - self.dirMenu.addAction( - self.tr('Compile all protocols'), - self.__compileAllProtocols) - self.dirMenu.addSeparator() + self.dirMenu.addAction( + self.tr('Compile all protocols'), + self.__compileAllProtocols) + self.dirMenu.addSeparator() + self.dirMenu.addAction( + self.tr('Compile all protocols as grpc'), + lambda: self.__compileAllProtocols(grpc=True)) act = self.dirMenu.addAction( self.tr('Remove from project'), self._removeFile) self.dirMenuActions.append(act) @@ -233,11 +250,13 @@ self.tr('Configure Protobuf...'), self.__configureProtobuf) self.dirMultiMenu = QMenu(self) - if self.__protoc is not None: - self.dirMultiMenu.addAction( - self.tr('Compile all protocols'), - self.__compileAllProtocols) - self.dirMultiMenu.addSeparator() + self.dirMultiMenu.addAction( + self.tr('Compile all protocols'), + self.__compileAllProtocols) + self.dirMultiMenu.addSeparator() + self.dirMultiMenu.addAction( + self.tr('Compile all protocols as grpc'), + lambda: self.__compileAllProtocols(grpc=True)) self.dirMultiMenu.addAction( self.tr('Add protocols...'), self.project.addProtoFiles) self.dirMultiMenu.addAction( @@ -437,6 +456,33 @@ ## Methods to handle the various compile commands ########################################################################### + def __getCompilerCommand(self, grpc): + """ + Private method to get the compiler command. + + @param grpc flag indicating to get a grpc command + @type bool + @return tuple giving the executable and its parameter list + @rtype tuple of (str, list of str) + """ + exe = None + exeArgs = [] + + if grpc: + exe = Preferences.getProtobuf("grpcPython") + if exe == "": + exe = sys.executable + exeArgs = ['-m', 'grpc_tools.protoc'] + else: + exe = Preferences.getProtobuf("protoc") + if exe == "": + exe = Utilities.isWindowsPlatform() and \ + "protoc.exe" or "protoc" + if not Utilities.isinpath(exe): + exe = None + + return exe, exeArgs + def __readStdout(self): """ Private slot to handle the readyReadStandardOutput signal of the @@ -471,18 +517,24 @@ s += error self.appendStderr.emit(s) - def __compileProtocolDone(self, exitCode, exitStatus): + def __compileProtoDone(self, exitCode, exitStatus, grpc): """ Private slot to handle the finished signal of the protoc process. - @param exitCode exit code of the process (integer) - @param exitStatus exit status of the process (QProcess.ExitStatus) + @param exitCode exit code of the process + @type int + @param exitStatus exit status of the process + @type QProcess.ExitStatus + @param grpc flag indicating to compile as grpc files + @type bool """ self.__compileRunning = False ui = e5App().getObject("UserInterface") if exitStatus == QProcess.NormalExit and exitCode == 0: path = os.path.dirname(self.__protoFile) fileList = glob.glob(os.path.join(path, "*_pb2.py")) + if grpc: + fileList += glob.glob(os.path.join(path, "*_pb2_grpc.py")) for file in fileList: self.project.appendFile(file) if not self.noDialog and not ui.notificationsEnabled(): @@ -514,64 +566,87 @@ "The compilation of the protocol file failed.")) self.compileProc = None - def __compileProto(self, fn, noDialog=False, progress=None): + def __compileProto(self, fn, noDialog=False, progress=None, grpc=False): """ Private method to compile a .proto file to Python. - @param fn filename of the .proto file to be compiled (string) - @param noDialog flag indicating silent operations (boolean) - @param progress reference to the progress dialog (E5ProgressDialog) - @return reference to the compile process (QProcess) + @param fn filename of the .proto file to be compiled + @type str + @param noDialog flag indicating silent operations + @type bool + @param progress reference to the progress dialog + @type E5ProgressDialog + @param grpc flag indicating to compile as grpc files + @type bool + @return reference to the compile process + @rtype QProcess """ - self.compileProc = QProcess() - args = [] - - fn = os.path.join(self.project.ppath, fn) - self.__protoFile = fn - - srcPath = os.path.dirname(fn) - args.append("--proto_path={0}".format(srcPath)) - args.append("--python_out={0}".format(srcPath)) - args.append(fn) - - self.compileProc.finished.connect(self.__compileProtocolDone) - self.compileProc.readyReadStandardOutput.connect(self.__readStdout) - self.compileProc.readyReadStandardError.connect(self.__readStderr) - - self.noDialog = noDialog - self.compileProc.start(self.__protoc, args) - procStarted = self.compileProc.waitForStarted(5000) - if procStarted: - self.__compileRunning = True - return self.compileProc + exe, exeArgs = self.__getCompilerCommand(grpc) + if exe: + self.compileProc = QProcess() + args = [] + + fn = os.path.join(self.project.ppath, fn) + self.__protoFile = fn + + srcPath = os.path.dirname(fn) + args.append("--proto_path={0}".format(srcPath)) + args.append("--python_out={0}".format(srcPath)) + if grpc: + args.append("--grpc_python_out={0}".format(srcPath)) + args.append(fn) + + self.compileProc.finished.connect( + lambda c, s: self.__compileProtoDone(c, s, grpc)) + self.compileProc.readyReadStandardOutput.connect(self.__readStdout) + self.compileProc.readyReadStandardError.connect(self.__readStderr) + + self.noDialog = noDialog + self.compileProc.start(exe, exeArgs + args) + procStarted = self.compileProc.waitForStarted(5000) + if procStarted: + self.__compileRunning = True + return self.compileProc + else: + self.__compileRunning = False + if progress is not None: + progress.cancel() + E5MessageBox.critical( + self, + self.tr('Process Generation Error'), + self.tr( + '<p>Could not start {0}.<br>' + 'Ensure that it is in the search path.</p>' + ).format(exe)) + return None else: - self.__compileRunning = False - if progress is not None: - progress.cancel() E5MessageBox.critical( self, - self.tr('Process Generation Error'), - self.tr( - '<p>Could not start {0}.<br>' - 'Ensure that it is in the search path.</p>' - ).format(self.__protoc)) + self.tr('Compiler Invalid'), + self.tr('The configured compiler is invalid.')) return None - def __compileProtocol(self): + def __compileProtocol(self, grpc=False): """ Private method to compile a protocol to Python. + + @param grpc flag indicating to compile as grpc files + @type bool """ - if self.__protoc is not None: + if self.__getCompilerCommand(grpc)[0] is not None: itm = self.model().item(self.currentIndex()) fn2 = itm.fileName() fn = self.project.getRelativePath(fn2) - self.__compileProto(fn) + self.__compileProto(fn, grpc=grpc) - def __compileAllProtocols(self): + def __compileAllProtocols(self, grpc=False): """ Private method to compile all protocols to Python. + + @param grpc flag indicating to compile as grpc files + @type bool """ - if self.__protoc is not None: + if self.__getCompilerCommand(grpc)[0] is not None: numProtos = len(self.project.pdata["PROTOCOLS"]) progress = E5ProgressDialog( self.tr("Compiling Protocols..."), @@ -586,7 +661,7 @@ progress.setValue(i) if progress.wasCanceled(): break - proc = self.__compileProto(fn, True, progress) + proc = self.__compileProto(fn, True, progress, grpc=grpc) if proc is not None: while proc.state() == QProcess.Running: QApplication.processEvents() @@ -598,11 +673,14 @@ progress.setValue(numProtos) - def __compileSelectedProtocols(self): + def __compileSelectedProtocols(self, grpc=False): """ Private method to compile selected protocols to Python. + + @param grpc flag indicating to compile as grpc files + @type bool """ - if self.__protoc is not None: + if self.__getCompilerCommand(grpc)[0] is not None: items = self.getSelectedItems() files = [self.project.getRelativePath(itm.fileName()) @@ -621,7 +699,7 @@ progress.setValue(i) if progress.wasCanceled(): break - proc = self.__compileProto(fn, True, progress) + proc = self.__compileProto(fn, True, progress, grpc=grpc) if proc is not None: while proc.state() == QProcess.Running: QApplication.processEvents()