diff -r 88f10deec960 -r 7328efba128b src/eric7/Project/Project.py --- a/src/eric7/Project/Project.py Thu Dec 01 10:18:07 2022 +0100 +++ b/src/eric7/Project/Project.py Mon Jan 02 11:16:03 2023 +0100 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# Copyright (c) 2002 - 2023 Detlev Offenbach <detlev@die-offenbachs.de> # """ @@ -33,7 +33,7 @@ from PyQt6.QtGui import QAction, QKeySequence from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu, QToolBar -from eric7 import Globals, Preferences, Utilities +from eric7 import Preferences, Utilities from eric7.CodeFormatting.BlackFormattingAction import BlackFormattingAction from eric7.CodeFormatting.BlackUtilities import aboutBlack from eric7.CodeFormatting.IsortFormattingAction import IsortFormattingAction @@ -52,6 +52,12 @@ from eric7.EricXML.UserProjectReader import UserProjectReader from eric7.Globals import recentNameProject from eric7.Sessions.SessionFile import SessionFile +from eric7.SystemUtilities import ( + FileSystemUtilities, + OSUtilities, + PythonUtilities, + QtUtilities, +) from eric7.Tasks.TasksFile import TasksFile from eric7.UI import Config from eric7.UI.NotificationWidget import NotificationTypes @@ -179,7 +185,7 @@ self.__dbgFilters = { "Python3": self.tr( - "Python3 Files (*.py *.py3);;" "Python3 GUI Files (*.pyw *.pyw3);;" + "Python3 Files (*.py *.py3);;Python3 GUI Files (*.pyw *.pyw3);;" ), } @@ -347,12 +353,12 @@ "JavaScript": ["Other"], } - if Utilities.checkPyside(variant=2): + if QtUtilities.checkPyside(variant=2): self.__projectTypes["PySide2"] = self.tr("PySide2 GUI") self.__projectTypes["PySide2C"] = self.tr("PySide2 Console") self.__projectProgLanguages["Python3"].extend(["PySide2", "PySide2C"]) - if Utilities.checkPyside(variant=6): + if QtUtilities.checkPyside(variant=6): self.__projectTypes["PySide6"] = self.tr("PySide6 GUI") self.__projectTypes["PySide6C"] = self.tr("PySide6 Console") self.__projectProgLanguages["Python3"].extend(["PySide6", "PySide6C"]) @@ -792,10 +798,30 @@ def initFileTypes(self): """ - Public method to initialize the filetype associations with default + Public method to initialize the file type associations with default values. """ - self.__pdata["FILETYPES"] = { + self.__pdata["FILETYPES"] = self.defaultFileTypes( + self.__pdata["PROGLANGUAGE"], + self.__pdata["MIXEDLANGUAGE"], + self.__pdata["PROJECTTYPE"], + ) + self.setDirty(True) + + def defaultFileTypes(self, progLanguage, isMixed, projectType): + """ + Public method to get a dictionary containing the default file type associations. + + @param progLanguage programming language (main language) + @type str + @param isMixed flag indicating a project with multiple programming languages + @type bool + @param projectType type of the project + @type str + @return dictionary containing the default file type associations + @rtype dict + """ + fileTypesDict = { "*.txt": "OTHERS", "*.md": "OTHERS", "*.rst": "OTHERS", @@ -806,33 +832,31 @@ "GNUmakefile": "OTHERS", "makefile": "OTHERS", "Makefile": "OTHERS", + "*.ini": "OTHERS", + "*.cfg": "OTHERS", + "*.toml": "OTHERS", + "*.json": "OTHERS", + "*.yml": "OTHERS", + "*.yaml": "OTHERS", } # Sources - sourceKey = ( - "Mixed" if self.__pdata["MIXEDLANGUAGE"] else self.__pdata["PROGLANGUAGE"] - ) + sourceKey = "Mixed" if isMixed else progLanguage for ext in self.__sourceExtensions(sourceKey): - self.__pdata["FILETYPES"]["*{0}".format(ext)] = "SOURCES" - - # IDL interfaces - self.__pdata["FILETYPES"]["*.idl"] = "INTERFACES" - - # Protobuf Files - self.__pdata["FILETYPES"]["*.proto"] = "PROTOCOLS" + fileTypesDict["*{0}".format(ext)] = "SOURCES" # Forms - if self.__pdata["PROJECTTYPE"] in [ + if projectType in [ "E7Plugin", "PyQt5", "PyQt6", "PySide2", "PySide6", ]: - self.__pdata["FILETYPES"]["*.ui"] = "FORMS" + fileTypesDict["*.ui"] = "FORMS" # Resources - if self.__pdata["PROJECTTYPE"] in [ + if projectType in [ "PyQt5", "PyQt5C", "PySide2", @@ -840,10 +864,10 @@ "PySide6", "PySide6C", ]: - self.__pdata["FILETYPES"]["*.qrc"] = "RESOURCES" + fileTypesDict["*.qrc"] = "RESOURCES" # Translations - if self.__pdata["PROJECTTYPE"] in [ + if projectType in [ "E7Plugin", "PyQt5", "PyQt5C", @@ -854,16 +878,26 @@ "PySide6", "PySide6C", ]: - self.__pdata["FILETYPES"]["*.ts"] = "TRANSLATIONS" - self.__pdata["FILETYPES"]["*.qm"] = "TRANSLATIONS" - + fileTypesDict["*.ts"] = "TRANSLATIONS" + fileTypesDict["*.qm"] = "TRANSLATIONS" + + # File categories handled by activated plugin project browsers + for fileCategory in [ + f + for f in self.__fileCategoriesRepository.keys() + if f not in ["SOURCES", "FORMS", "RESOURCES", "TRANSLATIONS", "OTHERS"] + ]: + for ext in self.__fileCategoriesRepository[ + fileCategory + ].fileCategoryExtensions: + fileTypesDict[ext] = fileCategory # Project type specific ones with contextlib.suppress(KeyError): - if self.__fileTypeCallbacks[self.__pdata["PROJECTTYPE"]] is not None: - ftypes = self.__fileTypeCallbacks[self.__pdata["PROJECTTYPE"]]() - self.__pdata["FILETYPES"].update(ftypes) - - self.setDirty(True) + if self.__fileTypeCallbacks[projectType] is not None: + ftypes = self.__fileTypeCallbacks[projectType]() + fileTypesDict.update(ftypes) + + return fileTypesDict def updateFileTypes(self): """ @@ -951,6 +985,15 @@ """ return self.vcs + def isVcsControlled(self): + """ + Public method to check, if the project is controlled by a VCS. + + @return flag indicating a VCS controlled project + @rtype bool + """ + return self.vcs is not None + def handlePreferencesChanged(self): """ Public slot used to handle the preferencesChanged signal. @@ -1055,7 +1098,7 @@ self.ui, self.tr("Read Project File"), self.tr( - "<p>The project file <b>{0}</b> could not be read." "</p>" + "<p>The project file <b>{0}</b> could not be read.</p>" ).format(fn), ) res = False @@ -1338,7 +1381,7 @@ self.ui, self.tr("Read Tasks"), self.tr( - "<p>The tasks file <b>{0}</b> could not be read." "</p>" + "<p>The tasks file <b>{0}</b> could not be read.</p>" ).format(fn), ) @@ -1730,18 +1773,13 @@ @param langFile the translation file to be removed (string) """ - try: - from send2trash import send2trash as s2t # __IGNORE_WARNING_I10__ - except ImportError: - s2t = os.remove - langFile = self.getRelativePath(langFile) qmFile = self.__binaryTranslationFile(langFile) try: fn = os.path.join(self.ppath, langFile) if os.path.exists(fn): - s2t(fn) + os.remove(fn) except OSError as err: EricMessageBox.critical( self.ui, @@ -1767,7 +1805,7 @@ ) fn = os.path.join(self.ppath, qmFile) if os.path.exists(fn): - s2t(fn) + os.remove(fn) except OSError as err: EricMessageBox.critical( self.ui, @@ -1868,7 +1906,7 @@ if target != "": for fn in fnames: targetfile = os.path.join(target, os.path.basename(fn)) - if not Utilities.samepath(os.path.dirname(fn), target): + if not FileSystemUtilities.samepath(os.path.dirname(fn), target): try: if not os.path.isdir(target): os.makedirs(target) @@ -1943,7 +1981,9 @@ ) return - if not Utilities.samepath(target, source) and not os.path.isdir(target): + if not FileSystemUtilities.samepath(target, source) and not os.path.isdir( + target + ): try: os.makedirs(target) except OSError as why: @@ -1963,7 +2003,7 @@ continue targetfile = os.path.join(target, os.path.basename(file)) - if not Utilities.samepath(target, source): + if not FileSystemUtilities.samepath(target, source): try: if os.path.exists(targetfile): res = EricMessageBox.yesNo( @@ -1999,25 +2039,29 @@ self.__addSingleDirectory(filetype, source, target, True) ignore_patterns = [ + ".svn", + ".hg", + ".git", + ".ropeproject", + ".eric7project", + ".jedi", + "__pycache__", + ] + [ pattern for pattern, filetype in self.__pdata["FILETYPES"].items() if filetype == "__IGNORE__" ] # now recurse into subdirectories - for name in os.listdir(source): - ns = os.path.join(source, name) - if os.path.isdir(ns): - skip = False - for ignore_pattern in ignore_patterns: - if fnmatch.fnmatch(name, ignore_pattern): - skip = True - break - if skip: - continue - - nt = os.path.join(target, name) - self.__addRecursiveDirectory(filetype, ns, nt) + with os.scandir(source) as dirEntriesIterator: + for dirEntry in dirEntriesIterator: + if dirEntry.is_dir() and not any( + fnmatch.fnmatch(dirEntry.name, ignore_pattern) + for ignore_pattern in ignore_patterns + ): + self.__addRecursiveDirectory( + filetype, dirEntry.path, os.path.join(target, dirEntry.name) + ) @pyqtSlot() def addDirectory(self, fileTypeFilter=None, startdir=None): @@ -2121,7 +2165,7 @@ ) if not newfn: return False - newfn = Utilities.toNativeSeparators(newfn) + newfn = FileSystemUtilities.toNativeSeparators(newfn) if os.path.exists(newfn): res = EricMessageBox.yesNo( @@ -2221,15 +2265,6 @@ if reorganized: # copy the reorganized files back to the project - ##for key in [ - ##"SOURCES", - ##"FORMS", - ##"INTERFACES", - ##"PROTOCOLS", - ##"RESOURCES", - ##"OTHERS", - ##"TRANSLATIONS", - ##]: for fileCategory in self.getFileCategories(): self.__pdata[fileCategory] = newPdata[fileCategory][:] @@ -2246,14 +2281,6 @@ """ olddn = self.getRelativePath(olddn) newdn = self.getRelativePath(newdn) - ##for key in [ - ##"SOURCES", - ##"FORMS", - ##"INTERFACES", - ##"PROTOCOLS", - ##"RESOURCES", - ##"OTHERS", - ##]: for fileCategory in [ c for c in self.getFileCategories() if c != "TRANSLATIONS" ]: @@ -2275,14 +2302,6 @@ olddn = self.getRelativePath(olddn) newdn = self.getRelativePath(newdn) typeStrings = [] - ##for key in [ - ##"SOURCES", - ##"FORMS", - ##"INTERFACES", - ##"PROTOCOLS", - ##"RESOURCES", - ##"OTHERS", - ##]: for fileCategory in [ c for c in self.getFileCategories() if c != "TRANSLATIONS" ]: @@ -2363,27 +2382,22 @@ @return flag indicating success (boolean) """ try: - from send2trash import send2trash as s2t # __IGNORE_WARNING_I10__ - except ImportError: - s2t = os.remove - - try: - s2t(os.path.join(self.ppath, fn)) + os.remove(os.path.join(self.ppath, fn)) path, ext = os.path.splitext(fn) if ext == ".ui": fn2 = os.path.join(self.ppath, "{0}.h".format(fn)) if os.path.isfile(fn2): - s2t(fn2) + os.remove(fn2) head, tail = os.path.split(path) for ext in [".pyc", ".pyo"]: fn2 = os.path.join(self.ppath, path + ext) if os.path.isfile(fn2): - s2t(fn2) + os.remove(fn2) pat = os.path.join( self.ppath, head, "__pycache__", "{0}.*{1}".format(tail, ext) ) for f in glob.glob(pat): - s2t(f) + os.remove(f) except OSError as err: EricMessageBox.critical( self.ui, @@ -2410,12 +2424,7 @@ if not os.path.isabs(dn): dn = os.path.join(self.ppath, dn) try: - try: - from send2trash import send2trash # __IGNORE_WARNING_I10__ - - send2trash(dn) - except ImportError: - shutil.rmtree(dn, True) + shutil.rmtree(dn, True) except OSError as err: EricMessageBox.critical( self.ui, @@ -2458,7 +2467,7 @@ if not self.checkDirty(): return - dlg = PropertiesDialog(self, True) + dlg = PropertiesDialog(self, new=True) if dlg.exec() == QDialog.DialogCode.Accepted: self.closeProject() @@ -2803,6 +2812,17 @@ self.__createEmbeddedEnvironment() self.menuEnvironmentAct.setEnabled(self.__pdata["EMBEDDED_VENV"]) + self.projectOpenedHooks.emit() + self.projectOpened.emit() + + # open the main script + if self.__pdata["MAINSCRIPT"]: + if not os.path.isabs(self.__pdata["MAINSCRIPT"]): + ms = os.path.join(self.ppath, self.__pdata["MAINSCRIPT"]) + else: + ms = self.__pdata["MAINSCRIPT"] + self.sourceFile.emit(ms) + def newProjectAddFiles(self, mainscript): """ Public method to add files to a new project. @@ -2812,11 +2832,22 @@ # Show the file type associations for the user to change self.__showFiletypeAssociations() + ignoreList = [] + if self.__pdata["EMBEDDED_VENV"]: + environmentPath = self.__findEmbeddedEnvironment() + if environmentPath: + # there is already an embeded venv; ignore thie whenn adding files + ignoreList = [os.path.split(environmentPath)[-1]] with EricOverrideCursor(): # search the project directory for files with known extensions filespecs = list(self.__pdata["FILETYPES"].keys()) for filespec in filespecs: - files = Utilities.direntries(self.ppath, True, filespec) + files = FileSystemUtilities.direntries( + self.ppath, + filesonly=True, + pattern=filespec, + ignore=ignoreList, + ) for file in files: self.appendFile(file) @@ -2836,10 +2867,10 @@ tpd = self.__pdata["TRANSLATIONPATTERN"].split("%language%")[0] else: pattern = "*.ts" - tslist.extend(Utilities.direntries(tpd, True, pattern)) + tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern)) pattern = self.__binaryTranslationFile(pattern) if pattern: - tslist.extend(Utilities.direntries(tpd, True, pattern)) + tslist.extend(FileSystemUtilities.direntries(tpd, True, pattern)) if tslist: if "_" in os.path.basename(tslist[0]): # the first entry determines the mainscript name @@ -2888,7 +2919,7 @@ self.__pdata["TRANSLATIONPATTERN"] ).replace("%language%", "*") pattern = self.__binaryTranslationFile(pattern) - qmlist = Utilities.direntries(tpd, True, pattern) + qmlist = FileSystemUtilities.direntries(tpd, True, pattern) for qm in qmlist: self.__pdata["TRANSLATIONS"].append(qm) self.projectFileAdded.emit(qm, "TRANSLATIONS") @@ -2905,9 +2936,9 @@ """ from .PropertiesDialog import PropertiesDialog - dlg = PropertiesDialog(self, False) + dlg = PropertiesDialog(self, new=False) if dlg.exec() == QDialog.DialogCode.Accepted: - projectType = self.__pdata["PROJECTTYPE"] + fileTypesDict = copy.copy(self.__pdata["FILETYPES"]) dlg.storeData() self.setDirty(True) if self.__pdata["MAINSCRIPT"]: @@ -2940,10 +2971,6 @@ ) self.appendFile(mf) - if self.__pdata["PROJECTTYPE"] != projectType: - # reinitialize filetype associations - self.initFileTypes() - if self.translationsRoot: tp = os.path.join(self.ppath, self.translationsRoot) if not self.translationsRoot.endswith(os.sep): @@ -2967,7 +2994,7 @@ self.__model.projectPropertiesChanged() self.projectPropertiesChanged.emit() - if self.__pdata["PROJECTTYPE"] != projectType: + if self.__pdata["FILETYPES"] != fileTypesDict: self.__reorganizeFiles() if self.__pdata["EMBEDDED_VENV"] and not self.__findEmbeddedEnvironment(): @@ -3020,9 +3047,10 @@ """ from .FiletypeAssociationDialog import FiletypeAssociationDialog - dlg = FiletypeAssociationDialog(self) + dlg = FiletypeAssociationDialog(self, self.getProjectData(dataKey="FILETYPES")) if dlg.exec() == QDialog.DialogCode.Accepted: - dlg.transferData() + fileTypes = dlg.getData() + self.setProjectData(fileTypes, dataKey="FILETYPES") self.setDirty(True) self.__reorganizeFiles() @@ -3105,7 +3133,7 @@ fn = EricFileDialog.getOpenFileName( self.parent(), self.tr("Open project"), - Preferences.getMultiProject("Workspace") or Utilities.getHomeDir(), + Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir(), self.tr("Project Files (*.epj);;XML Project Files (*.e4p)"), ) @@ -3155,7 +3183,7 @@ res, vcs_ok = QInputDialog.getItem( None, self.tr("New Project"), - self.tr("Select Version Control" " System"), + self.tr("Select Version Control System"), vcsList, 0, False, @@ -3261,6 +3289,8 @@ # start the VCS monitor thread self.__vcsConnectStatusMonitor() + else: + self.__initData() # delete all invalid data read def reopenProject(self): """ @@ -3301,7 +3331,7 @@ defaultPath = ( self.ppath if self.ppath - else (Preferences.getMultiProject("Workspace") or Utilities.getHomeDir()) + else (Preferences.getMultiProject("Workspace") or OSUtilities.getHomeDir()) ) fn, selectedFilter = EricFileDialog.getSaveFileNameAndFilter( self.parent(), @@ -3739,8 +3769,12 @@ """ return bool(self.ppath) and ( path == self.ppath - or Utilities.normcasepath(Utilities.toNativeSeparators(path)).startswith( - Utilities.normcasepath(Utilities.toNativeSeparators(self.ppath + "/")) + or FileSystemUtilities.normcasepath( + FileSystemUtilities.toNativeSeparators(path) + ).startswith( + FileSystemUtilities.normcasepath( + FileSystemUtilities.toNativeSeparators(self.ppath + "/") + ) ) ) @@ -3816,7 +3850,7 @@ @return project relative path or unchanged path, if path doesn't belong to the project (string) """ - return Utilities.fromNativeSeparators(self.getRelativePath(path)) + return FileSystemUtilities.fromNativeSeparators(self.getRelativePath(path)) def getAbsolutePath(self, fn): """ @@ -3839,7 +3873,7 @@ @return absolute path (string) """ if not os.path.isabs(fn): - fn = os.path.join(self.ppath, Utilities.toNativeSeparators(fn)) + fn = os.path.join(self.ppath, FileSystemUtilities.toNativeSeparators(fn)) return fn def getEolString(self): @@ -3956,7 +3990,7 @@ .getVirtualenvInterpreter(venvName) ) if not interpreter and resolveGlobal: - interpreter = Globals.getPythonExecutable() + interpreter = PythonUtilities.getPythonExecutable() return interpreter @@ -4051,7 +4085,7 @@ ): return True - if Utilities.isWindowsPlatform(): + if OSUtilities.isWindowsPlatform(): # try the above case-insensitive newfn = newfn.lower() if any(entry.lower() == newfn for entry in self.__pdata[group]): @@ -4279,9 +4313,8 @@ act.setWhatsThis( self.tr( """<b>Search new files...</b>""" - """<p>This searches for new files (sources, *.ui, *.idl,""" - """ *.proto) in the project directory and registered""" - """ subdirectories.</p>""" + """<p>This searches for new files (sources, forms, ...) in the""" + """ project directory and registered subdirectories.</p>""" ) ) act.triggered.connect(self.__searchNewFiles) @@ -5141,7 +5174,9 @@ " is cleared first.</p>" ) ) - self.recreateVenvAct.triggered.connect(self.__createEmbeddedEnvironment) + self.recreateVenvAct.triggered.connect( + lambda: self.__createEmbeddedEnvironment(force=True) + ) self.actions.append(self.recreateVenvAct) self.reloadAct.setEnabled(False) @@ -5380,7 +5415,7 @@ with the central store. """ for recent in self.recent[:]: - if Utilities.samepath(self.pfile, recent): + if FileSystemUtilities.samepath(self.pfile, recent): self.recent.remove(recent) self.recent.insert(0, self.pfile) maxRecent = Preferences.getProject("RecentNumber") @@ -5400,7 +5435,7 @@ formatStr = "&{0:d}. {1}" if idx < 10 else "{0:d}. {1}" act = self.recentMenu.addAction( formatStr.format( - idx, Utilities.compactPath(rp, self.ui.maxMenuFilePathLen) + idx, FileSystemUtilities.compactPath(rp, self.ui.maxMenuFilePathLen) ) ) act.setData(rp) @@ -5510,7 +5545,7 @@ if ns.startswith("."): continue if ( - Utilities.isWindowsPlatform() + OSUtilities.isWindowsPlatform() and os.path.isdir(os.path.join(curpath, ns)) and ns.startswith("_") ): @@ -6129,7 +6164,9 @@ lst.extend( [ self.getRelativePath(p) - for p in Utilities.direntries(self.getAbsolutePath(entry), True) + for p in FileSystemUtilities.direntries( + self.getAbsolutePath(entry), True + ) ] ) continue @@ -6153,7 +6190,9 @@ with open(pkglist, "w", encoding="utf-8", newline=newline) as pkglistFile: pkglistFile.write("\n".join(header) + "\n") pkglistFile.write( - "\n".join([Utilities.fromNativeSeparators(f) for f in lst]) + "\n".join( + [FileSystemUtilities.fromNativeSeparators(f) for f in lst] + ) ) pkglistFile.write("\n") # ensure the file ends with an empty line @@ -6364,7 +6403,7 @@ EricPixmapCache.getPixmap("pluginArchive48"), self.tr("Create Plugin Archive"), self.tr( - "<p>The eric plugin archive files were " "created successfully.</p>" + "<p>The eric plugin archive files were created successfully.</p>" ), ) @@ -6667,24 +6706,6 @@ self.__makeProcess = None ######################################################################### - ## Below are methods implementing some 'IDL' support functions - ######################################################################### - - def hasDefaultIdlCompilerParameters(self): - """ - Public method to test, if the project contains the default IDL compiler - parameters. - - @return flag indicating default parameter set - @rtype bool - """ - return self.__pdata["IDLPARAMS"] == { - "IncludeDirs": [], - "DefinedNames": [], - "UndefinedNames": [], - } - - ######################################################################### ## Below are methods implementing some 'UIC' support functions ######################################################################### @@ -6782,7 +6803,7 @@ """ Private slot to create a SBOM file of the project dependencies. """ - import CycloneDXInterface # __IGNORE_WARNING_I102__ + from eric7 import CycloneDXInterface CycloneDXInterface.createCycloneDXFile("<project>") @@ -6914,6 +6935,15 @@ @return path of the embedded virtual environment (empty if not found) @rtype str """ + with os.scandir(self.getProjectPath()) as ppathDirEntriesIterator: + for dirEntry in ppathDirEntriesIterator: + # potential venv directory; check for 'pyvenv.cfg' + if dirEntry.is_dir() and os.path.exists( + os.path.join(dirEntry.path, "pyvenv.cfg") + ): + return dirEntry.path + + # check for some common names in case 'pyvenv.cfg' is missing for venvPathName in (".venv", "venv", ".env", "env"): venvPath = os.path.join(self.getProjectPath(), venvPathName) if os.path.isdir(venvPath): @@ -6943,12 +6973,14 @@ "system_site_packages": False, } - def __createEmbeddedEnvironment(self, upgrade=False): + def __createEmbeddedEnvironment(self, upgrade=False, force=False): """ Private method to create the embedded virtual environment. @param upgrade flag indicating an upgrade operation (defaults to False) @type bool (optional) + @param force flag indicating to force the creation (defaults to False) + @type bool (optional) """ from eric7.VirtualEnv.VirtualenvExecDialog import VirtualenvExecDialog @@ -6956,52 +6988,60 @@ ProjectVenvCreationParametersDialog, ) - dlg = ProjectVenvCreationParametersDialog( - withSystemSitePackages=self.__venvConfiguration["system_site_packages"] - ) - if dlg.exec() != QDialog.DialogCode.Accepted: - # user canceled the environment creation - self.__setEmbeddedEnvironmentProjectConfig(False) - return - - pythonPath, withSystemSitePackages = dlg.getData() - configuration = { - "envType": "pyvenv", - "targetDirectory": os.path.join(self.getProjectPath(), ".venv"), - "openTarget": False, - "createLog": True, - "createScript": True, - "logicalName": self.__venvConfiguration["name"], - "pythonExe": pythonPath, - } - - args = [] - if upgrade: - args.append("--upgrade") - else: - if os.path.exists(os.path.join(self.getProjectPath(), ".venv")): - args.append("--clear") - if withSystemSitePackages: - args.append("--system-site-packages") - args.append(configuration["targetDirectory"]) - dia = VirtualenvExecDialog(configuration, None) - dia.show() - dia.start(args) - dia.exec() - - self.__venvConfiguration["system_site_packages"] = withSystemSitePackages - - self.__configureEnvironment() - if not self.__venvConfiguration["interpreter"]: - # user canceled the environment creation, delete the created directory - shutil.rmtree(configuration["targetDirectory"], True) - self.__setEmbeddedEnvironmentProjectConfig(False) - return - - if upgrade and not withSystemSitePackages: - # re-install the project into the upgraded environment - # Note: seems to fail on some systems with access to system site-packages - self.__installProjectIntoEnvironment() + environmentPath = self.__findEmbeddedEnvironment() + if force or upgrade or not environmentPath: + dlg = ProjectVenvCreationParametersDialog( + withSystemSitePackages=self.__venvConfiguration["system_site_packages"] + ) + if dlg.exec() != QDialog.DialogCode.Accepted: + # user canceled the environment creation + self.__setEmbeddedEnvironmentProjectConfig(False) + return + + pythonPath, withSystemSitePackages = dlg.getData() + configuration = { + "envType": "pyvenv", + "targetDirectory": os.path.join(self.getProjectPath(), ".venv"), + "openTarget": False, + "createLog": True, + "createScript": True, + "logicalName": self.__venvConfiguration["name"], + "pythonExe": pythonPath, + } + + args = [] + if upgrade: + args.append("--upgrade") + else: + if os.path.exists(os.path.join(self.getProjectPath(), ".venv")): + args.append("--clear") + if withSystemSitePackages: + args.append("--system-site-packages") + args.append(configuration["targetDirectory"]) + dia = VirtualenvExecDialog(configuration, None) + dia.show() + dia.start(args) + dia.exec() + + self.__venvConfiguration["system_site_packages"] = withSystemSitePackages + + self.__configureEnvironment() + if not self.__venvConfiguration["interpreter"]: + # user canceled the environment creation, delete the created directory + shutil.rmtree(configuration["targetDirectory"], True) + self.__setEmbeddedEnvironmentProjectConfig(False) + return + + if upgrade and not withSystemSitePackages: + # re-install the project into the upgraded environment + # Note: seems to fail on some systems with access to system + # site-packages + self.__installProjectIntoEnvironment() + + if environmentPath and not self.__venvConfiguration["interpreter"].startswith( + environmentPath + ): + self.__configureEnvironment(environmentPath) @pyqtSlot() def __configureEnvironment(self, environmentPath=""): @@ -7014,7 +7054,9 @@ from .ProjectVenvConfigurationDialog import ProjectVenvConfigurationDialog if not environmentPath: - environmentPath = os.path.join(self.getProjectPath(), ".venv") + environmentPath = self.__findEmbeddedEnvironment() + if not environmentPath: + environmentPath = os.path.join(self.getProjectPath(), ".venv") dlg = ProjectVenvConfigurationDialog( self.__venvConfiguration["name"],