--- 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()