From f8567b0975118322116d536b737d022c4064258d Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Wed, 24 Aug 2022 12:14:20 -0500 Subject: [PATCH] AddonManager: Refactoring of installer --- .../AddonManagerTest/data/TestWorkbench.zip | Bin 0 -> 25149 bytes src/Mod/AddonManager/CMakeLists.txt | 1 + .../addonmanager_workers_installation.py | 215 +++++++++++------- 3 files changed, 135 insertions(+), 81 deletions(-) create mode 100644 src/Mod/AddonManager/AddonManagerTest/data/TestWorkbench.zip diff --git a/src/Mod/AddonManager/AddonManagerTest/data/TestWorkbench.zip b/src/Mod/AddonManager/AddonManagerTest/data/TestWorkbench.zip new file mode 100644 index 0000000000000000000000000000000000000000..187c9b6c34ce610c7802c155071ddb80f280484f GIT binary patch literal 25149 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 None: - """ Periodically emit the progress of the git download, for asynchronous operations """ + """Periodically emit the progress of the git download, for asynchronous operations""" if hasattr(self, "git_progress") and self.isRunning(): self.progress_made.emit(self.git_progress.current, self.git_progress.total) self.status_message.emit(self.git_progress.message) def run_git(self, clonedir: str) -> None: - """ Clone or update the addon using git. Exits if git is disabled. """ + """Clone or update the addon using git. Exits if git is disabled.""" if not self.git_manager: FreeCAD.Console.PrintLog( @@ -148,8 +143,8 @@ class InstallWorkbenchWorker(QtCore.QThread): self.run_git_clone(clonedir) def run_git_update(self, clonedir: str) -> None: - """ Runs git update operation: normally a fetch and pull, but if something goew wrong it - will revert to a clean clone. """ + """Runs git update operation: normally a fetch and pull, but if something goew wrong it + will revert to a clean clone.""" self.status_message.emit("Updating module...") with self.repo.git_lock: if not os.path.exists(clonedir + os.sep + ".git"): @@ -157,6 +152,7 @@ class InstallWorkbenchWorker(QtCore.QThread): try: self.git_manager.update(clonedir) if self.repo.contains_workbench(): + # pylint: disable=line-too-long answer = translate( "AddonsInstaller", "Workbench successfully updated. Please restart FreeCAD to apply the changes.", @@ -181,7 +177,7 @@ class InstallWorkbenchWorker(QtCore.QThread): self.success.emit(self.repo, answer) def run_git_clone(self, clonedir: str) -> None: - """ Clones a repo using git """ + """Clones a repo using git""" self.status_message.emit("Cloning module...") current_thread = QtCore.QThread.currentThread() @@ -193,8 +189,7 @@ class InstallWorkbenchWorker(QtCore.QThread): "Timeout waiting for a lock on the git process, failed to clone repo\n" ) return - else: - self.repo.git_lock.release() + self.repo.git_lock.release() with self.repo.git_lock: FreeCAD.Console.PrintMessage("Lock acquired...\n") @@ -232,13 +227,15 @@ class InstallWorkbenchWorker(QtCore.QThread): os.path.join(clonedir, f), os.path.join(macro_dir, f) ) except OSError: - # If the symlink failed (e.g. for a non-admin user on Windows), copy the macro instead + # If the symlink failed (e.g. for a non-admin user on Windows), copy + # the macro instead shutil.copy( os.path.join(clonedir, f), os.path.join(macro_dir, f) ) FreeCAD.ParamGet( "User parameter:Plugins/" + self.repo.name ).SetString("destination", clonedir) + # pylint: disable=line-too-long answer += "\n\n" + translate( "AddonsInstaller", "A macro has been installed and is available under Macro -> Macros menu", @@ -248,7 +245,7 @@ class InstallWorkbenchWorker(QtCore.QThread): self.success.emit(self.repo, answer) def launch_zip(self, zipdir: str) -> None: - """ Downloads and unzip a zip version from a git repo """ + """Downloads and unzip a zip version from a git repo""" bakdir = None if os.path.exists(zipdir): @@ -277,8 +274,8 @@ class InstallWorkbenchWorker(QtCore.QThread): ) def update_zip_status(self, index: int, bytes_read: int, data_size: int): - """ Called periodically when downloading a zip file, emits a signal to display the - download progress. """ + """Called periodically when downloading a zip file, emits a signal to display the + download progress.""" if index == self.zip_download_index: locale = QtCore.QLocale() if data_size > 10 * 1024 * 1024: # To avoid overflows, show MB instead @@ -323,8 +320,8 @@ class InstallWorkbenchWorker(QtCore.QThread): ).format(bytes_str=bytes_str) ) - def finish_zip(self, index: int, response_code: int, filename: os.PathLike): - """ Once the zip download is finished, unzip it into the correct location. """ + def finish_zip(self, _index: int, response_code: int, filename: os.PathLike): + """Once the zip download is finished, unzip it into the correct location.""" self.zip_complete = True if response_code != 200: self.failure.emit( @@ -339,14 +336,14 @@ class InstallWorkbenchWorker(QtCore.QThread): with zipfile.ZipFile(filename, "r") as zfile: master = zfile.namelist()[0] # github will put everything in a subfolder self.status_message.emit( - translate("AddonsInstaller", f"Download complete. Unzipping file...") + translate("AddonsInstaller", "Download complete. Unzipping file...") ) QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents) zfile.extractall(self.zipdir) - for filename in os.listdir(self.zipdir + os.sep + master): + for extracted_filename in os.listdir(self.zipdir + os.sep + master): shutil.move( - self.zipdir + os.sep + master + os.sep + filename, - self.zipdir + os.sep + filename, + self.zipdir + os.sep + master + os.sep + extracted_filename, + self.zipdir + os.sep + extracted_filename, ) os.rmdir(self.zipdir + os.sep + master) if self.bakdir: @@ -361,7 +358,7 @@ class InstallWorkbenchWorker(QtCore.QThread): ) def update_metadata(self): - """ Loads the package metadata from the Addon's downloaded package.xml file. """ + """Loads the package metadata from the Addon's downloaded package.xml file.""" basedir = FreeCAD.getUserAppDataDir() package_xml = os.path.join(basedir, "Mod", self.repo.name, "package.xml") if os.path.isfile(package_xml): @@ -378,20 +375,40 @@ class DependencyInstallationWorker(QtCore.QThread): failure = QtCore.Signal(str, str) # Short message, detailed message success = QtCore.Signal() - def __init__(self, addons, python_required, python_optional): + def __init__( + self, + addons: List[Addon], + python_required: List[str], + python_optional: List[str], + location: os.PathLike = None, + ): + """Install the various types of dependencies that might be specified. If an optional + dependency fails this is non-fatal, but other failures are considered fatal. If location + is specified it overrides the FreeCAD user base directory setting: this is used mostly + for testing purposes and shouldn't be set by normal code in most circumstances.""" QtCore.QThread.__init__(self) self.addons = addons self.python_required = python_required self.python_optional = python_optional + self.location = location def run(self): - """ Normally not called directly: create the object and call start() to launch it + """Normally not called directly: create the object and call start() to launch it in its own thread. Installs dependencies for the Addon.""" + self._install_required_addons() + if self.python_required or self.python_optional: + self._install_python_packages() + self.success.emit() + def _install_required_addons(self): + """Install whatever FreeCAD Addons were set as required.""" for repo in self.addons: if QtCore.QThread.currentThread().isInterruptionRequested(): return - worker = InstallWorkbenchWorker(repo) + location = self.location + if location: + location = os.path.join(location, "Mod") + worker = InstallWorkbenchWorker(repo, location=location) worker.start() while worker.isRunning(): if QtCore.QThread.currentThread().isInterruptionRequested(): @@ -401,33 +418,52 @@ class DependencyInstallationWorker(QtCore.QThread): time.sleep(0.1) QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents, 50) - if self.python_required or self.python_optional: - python_exe = utils.get_python_exe() - pip_failed = False - if python_exe: - try: - proc = subprocess.run( - [python_exe, "-m", "pip", "--version"], stdout=subprocess.PIPE - ) - except subprocess.CalledProcessError as e: - pip_failed = True - if proc.returncode != 0: - pip_failed = True - else: - pip_failed = True - if pip_failed: - self.no_pip.emit(f"{python_exe} -m pip --version") - return - FreeCAD.Console.PrintMessage(proc.stdout) - FreeCAD.Console.PrintWarning(proc.stderr) - result = proc.stdout - FreeCAD.Console.PrintMessage(result.decode()) + def _install_python_packages(self): + """Install required and optional Python dependencies using pip.""" + if not self._verify_pip(): + return + + if self.location: + vendor_path = os.path.join(self.location, "AdditionalPythonPackages") + else: vendor_path = os.path.join( FreeCAD.getUserAppDataDir(), "AdditionalPythonPackages" ) - if not os.path.exists(vendor_path): - os.makedirs(vendor_path) + if not os.path.exists(vendor_path): + os.makedirs(vendor_path) + self._install_required(vendor_path) + self._install_optional(vendor_path) + + def _verify_pip(self) -> bool: + """Ensure that pip is working -- returns True if it is, or False if not. Also emits the + no_pip signal if pip cannot execute.""" + python_exe = utils.get_python_exe() + pip_failed = False + if python_exe: + try: + proc = subprocess.run( + [python_exe, "-m", "pip", "--version"], + stdout=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError: + pip_failed = True + if proc.returncode != 0: + pip_failed = True + else: + pip_failed = True + if pip_failed: + self.no_pip.emit(f"{python_exe} -m pip --version") + FreeCAD.Console.PrintMessage(proc.stdout) + FreeCAD.Console.PrintWarning(proc.stderr) + result = proc.stdout + FreeCAD.Console.PrintMessage(result.decode()) + return not pip_failed + + def _install_required(self, vendor_path: os.PathLike): + """Install the required Python package dependencies. If any fail a failure signal is + emitted and the function exits without proceeding with any additional installs.""" for pymod in self.python_required: if QtCore.QThread.currentThread().isInterruptionRequested(): return @@ -444,9 +480,8 @@ class DependencyInstallationWorker(QtCore.QThread): ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=True, ) - # Note to self: how to list installed packages - # ./python.exe -m pip list --path ~/AppData/Roaming/FreeCAD/AdditionalPythonPackages FreeCAD.Console.PrintMessage(proc.stdout.decode()) if proc.returncode != 0: self.failure.emit( @@ -458,37 +493,46 @@ class DependencyInstallationWorker(QtCore.QThread): ) return + def _install_optional(self, vendor_path: os.PathLike): + """Install the optional Python package dependencies. If any fail a message is printed to + the console, but installation of the others continues.""" for pymod in self.python_optional: if QtCore.QThread.currentThread().isInterruptionRequested(): return - proc = subprocess.run( - [python_exe, "-m", "pip", "install", "--target", vendor_path, pymod], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) + try: + proc = subprocess.run( + [ + python_exe, + "-m", + "pip", + "install", + "--target", + vendor_path, + pymod, + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as e: + FreeCAD.Console.PrintError(str(e)) + continue FreeCAD.Console.PrintMessage(proc.stdout.decode()) if proc.returncode != 0: - self.failure.emit( - translate( - "AddonsInstaller", - "Installation of Python package {} failed", - ).format(pymod), - proc.stderr, - ) - return - - self.success.emit() + FreeCAD.Console.PrintError(proc.stderr.decode()) class UpdateMetadataCacheWorker(QtCore.QThread): - "Scan through all available packages and see if our local copy of package.xml needs to be updated" + """Scan through all available packages and see if our local copy of package.xml needs to be + updated""" status_message = QtCore.Signal(str) progress_made = QtCore.Signal(int, int) package_updated = QtCore.Signal(Addon) class RequestType(Enum): - """ The type of item being downloaded. """ + """The type of item being downloaded.""" + PACKAGE_XML = auto() METADATA_TXT = auto() REQUIREMENTS_TXT = auto() @@ -562,6 +606,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): def download_completed( self, index: int, code: int, data: QtCore.QByteArray ) -> None: + """Callback for handling a completed metadata file download.""" if index in self.requests: self.requests_completed += 1 self.progress_made.emit(self.requests_completed, self.total_requests) @@ -580,6 +625,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): self.process_icon(request[0], data) def process_package_xml(self, repo: Addon, data: QtCore.QByteArray): + """Process the package.xml metadata file""" repo.repo_type = Addon.Kind.PACKAGE # By definition package_cache_directory = os.path.join(self.store, repo.name) if not os.path.exists(package_cache_directory): @@ -619,6 +665,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): self.total_requests += 1 def process_metadata_txt(self, repo: Addon, data: QtCore.QByteArray): + """Process the metadata.txt metadata file""" self.status_message.emit( translate("AddonsInstaller", "Downloaded metadata.txt for {}").format( repo.display_name @@ -656,7 +703,8 @@ class UpdateMetadataCacheWorker(QtCore.QThread): if dep: repo.python_optional.add(dep) FreeCAD.Console.PrintLog( - f"{repo.display_name} optionally imports python package '{pl.strip()}'\n" + f"{repo.display_name} optionally imports python package" + + f" '{pl.strip()}'\n" ) # For review and debugging purposes, store the file locally package_cache_directory = os.path.join(self.store, repo.name) @@ -667,6 +715,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): f.write(data.data()) def process_requirements_txt(self, repo: Addon, data: QtCore.QByteArray): + """Process the requirements.txt metadata file""" self.status_message.emit( translate( "AddonsInstaller", @@ -693,6 +742,7 @@ class UpdateMetadataCacheWorker(QtCore.QThread): f.write(data.data()) def process_icon(self, repo: Addon, data: QtCore.QByteArray): + """Convert icon data into a valid icon file and store it""" self.status_message.emit( translate("AddonsInstaller", "Downloaded icon for {}").format( repo.display_name @@ -712,7 +762,8 @@ class UpdateAllWorker(QtCore.QThread): success = QtCore.Signal(Addon) failure = QtCore.Signal(Addon) - # TODO: This should be re-written to be solidly single-threaded, some of the called code is not re-entrant + # TODO: This should be re-written to be solidly single-threaded, some of the called code is + # not re-entrant def __init__(self, repos): super().__init__() @@ -809,7 +860,8 @@ class UpdateSingleWorker(QtCore.QThread): self.update_package(repo) self.repo_queue.task_done() FreeCAD.Console.PrintLog( - f" UPDATER: Worker thread completed action for '{repo.name}' and reported result to main thread\n" + f" UPDATER: Worker thread completed action for '{repo.name}' and reported result " + + "to main thread\n" ) def update_macro(self, repo: Addon): @@ -831,7 +883,8 @@ class UpdateSingleWorker(QtCore.QThread): self.failure.emit(repo) def update_package(self, repo: Addon): - """Updating a package re-uses the package installation worker, so actually spawns another thread that we block on""" + """Updating a package re-uses the package installation worker, so actually spawns another + thread that we block on""" worker = InstallWorkbenchWorker(repo, location=self.location) worker.success.connect(lambda repo, _: self.success.emit(repo))