Addon Manager: Add support for <pythonmin> tag

This commit is contained in:
Chris Hennes
2022-09-25 15:02:46 -05:00
parent 1d11c5805a
commit 552d6ab895
6 changed files with 48 additions and 50 deletions

View File

@@ -114,7 +114,7 @@ class Addon:
self.blockers = [] # A list of Addons
self.replaces = [] # A list of Addons
self.internal_workbenches: Set[str] = set() # Required internal workbenches
self.python_required: Set[str] = set()
self.python_requires: Set[str] = set()
self.python_optional: Set[str] = set()
self.python_min_version = {"major": 3, "minor": 0}
@@ -335,27 +335,22 @@ class Addon:
if not self.version_is_ok(metadata):
return
if metadata.PythonMin != "0.0.0":
split_version_string = metadata.PythonMin.split(".")
if len(split_version_string) >= 2:
try:
self.python_min_version["major"] = int(split_version_string[0])
self.python_min_version["minor"] = int(split_version_string[1])
FreeCAD.Console.PrintLog(
f"Package {self.name}: Requires Python {split_version_string[0]}.{split_version_string[1]} or greater\n"
)
except ValueError:
FreeCAD.Console.PrintWarning(
f"Package {self.name}: Invalid Python version requirement specified\n"
)
for dep in metadata.Depend:
if dep["package"].lower() == "python":
# We only support the "version_gte" attribute for Python itself
if "version_gte" in dep and "." in dep["version_gte"]:
split_version_string = dep["version_gte"].split(".")
if len(split_version_string) >= 2:
try:
self.python_min_version["major"] = int(
split_version_string[0]
)
self.python_min_version["minor"] = int(
split_version_string[1]
)
FreeCAD.Console.PrintLog(
f"Package {self.name}: Requires Python {split_version_string[0]}.{split_version_string[1]} or greater\n"
)
except ValueError:
FreeCAD.Console.PrintWarning(
f"Package {self.name}: Invalid Python version requirement specified\n"
)
elif "type" in dep:
if "type" in dep:
if dep["type"] == "internal":
if dep["package"] in INTERNAL_WORKBENCHES:
self.requires.add(dep["package"])
@@ -369,10 +364,10 @@ class Addon:
elif dep["type"] == "addon":
self.requires.add(dep["package"])
elif dep["type"] == "python":
if "optional" in dep and dep["optional"].lower() == "true":
if "optional" in dep and dep["optional"]:
self.python_optional.add(dep["package"])
else:
self.python_required.add(dep["package"])
self.python_requires.add(dep["package"])
else:
# Automatic resolution happens later, once we have a complete list of Addons
self.requires.add(dep["package"])
@@ -517,7 +512,7 @@ class Addon:
information that may be needed.
"""
deps.python_required |= self.python_requires
deps.python_requires |= self.python_requires
deps.python_optional |= self.python_optional
deps.python_min_version["major"] = max(
@@ -548,7 +543,7 @@ class Addon:
deps.internal_workbenches.add(INTERNAL_WORKBENCHES[real_name])
else:
# Assume it's a Python requirement of some kind:
deps.python_required.add(dep)
deps.python_requires.add(dep)
for dep in self.blocks:
if dep in all_repos:

View File

@@ -1043,7 +1043,7 @@ class CommandAddonManager:
"""Encapsulates a group of four types of dependencies:
* Internal workbenches -> wbs
* External addons -> external_addons
* Required Python packages -> python_required
* Required Python packages -> python_requires
* Optional Python packages -> python_optional
"""
@@ -1081,13 +1081,13 @@ class CommandAddonManager:
# Check the Python dependencies:
self.python_min_version = deps.python_min_version
self.python_required = []
for py_dep in deps.python_required:
if py_dep not in self.python_required:
self.python_requires = []
for py_dep in deps.python_requires:
if py_dep not in self.python_requires:
try:
__import__(py_dep)
except ImportError:
self.python_required.append(py_dep)
self.python_requires.append(py_dep)
self.python_optional = []
for py_dep in deps.python_optional:
@@ -1098,12 +1098,12 @@ class CommandAddonManager:
self.wbs.sort()
self.external_addons.sort()
self.python_required.sort()
self.python_requires.sort()
self.python_optional.sort()
self.python_optional = [
option
for option in self.python_optional
if option not in self.python_required
if option not in self.python_requires
]
def update_allowed_packages_list(self) -> None:
@@ -1128,19 +1128,19 @@ class CommandAddonManager:
"Could not fetch remote ALLOWED_PYTHON_PACKAGES.txt, using local copy\n"
)
def handle_disallowed_python(self, python_required: List[str]) -> bool:
def handle_disallowed_python(self, python_requires: List[str]) -> bool:
"""Determine if we are missing any required Python packages that are not in the allowed
packages list. If so, display a message to the user, and return True. Otherwise return
False."""
bad_packages = []
# self.update_allowed_packages_list()
for dep in python_required:
for dep in python_requires:
if dep not in self.allowed_packages:
bad_packages.append(dep)
for dep in bad_packages:
python_required.remove(dep)
python_requires.remove(dep)
if bad_packages:
message = (
@@ -1225,7 +1225,7 @@ class CommandAddonManager:
for addon in missing.external_addons:
self.dependency_dialog.listWidgetAddons.addItem(addon)
for mod in missing.python_required:
for mod in missing.python_requires:
self.dependency_dialog.listWidgetPythonRequired.addItem(mod)
for mod in missing.python_optional:
item = QtWidgets.QListWidgetItem(mod)
@@ -1251,7 +1251,7 @@ class CommandAddonManager:
return
missing = CommandAddonManager.MissingDependencies(repo, self.item_model.repos)
if self.handle_disallowed_python(missing.python_required):
if self.handle_disallowed_python(missing.python_requires):
return
# For now only look at the minor version, since major is always Python 3
@@ -1292,7 +1292,7 @@ class CommandAddonManager:
return
if (
missing.external_addons
or missing.python_required
or missing.python_requires
or missing.python_optional
):
# Recoverable: ask the user if they want to install the missing deps
@@ -1311,10 +1311,10 @@ class CommandAddonManager:
if repo.name == name or repo.display_name == name:
addons.append(repo)
python_required = []
python_requires = []
for row in range(self.dependency_dialog.listWidgetPythonRequired.count()):
item = self.dependency_dialog.listWidgetPythonRequired.item(row)
python_required.append(item.text())
python_requires.append(item.text())
python_optional = []
for row in range(self.dependency_dialog.listWidgetPythonOptional.count()):
@@ -1323,7 +1323,7 @@ class CommandAddonManager:
python_optional.append(item.text())
self.dependency_installation_worker = DependencyInstallationWorker(
addons, python_required, python_optional
addons, python_requires, python_optional
)
self.dependency_installation_worker.no_python_exe.connect(
functools.partial(self.no_python_exe, installing_repo)

View File

@@ -20,7 +20,7 @@
# * *
# ***************************************************************************
""" Contains a class for adding a single content item, as well as auxilliary classes for
""" Contains a class for adding a single content item, as well as auxiliary classes for
its dependent dialog boxes. """
import os
@@ -547,7 +547,7 @@ class EditDependency:
if dep_type and dep_name:
index = self.dialog.typeComboBox.findData(dep_type)
if index == -1:
raise RuntimeError(f"Invaid dependency type {dep_type}")
raise RuntimeError(f"Invalid dependency type {dep_type}")
self.dialog.typeComboBox.setCurrentIndex(index)
index = self.dialog.dependencyComboBox.findData(dep_name)
if index == -1:

View File

@@ -345,7 +345,7 @@ class Macro:
def clean_icon(self):
"""Downloads the macro's icon from whatever source is specified and stores a local
copy, potentially updating the interal icon location to that local storage."""
copy, potentially updating the internal icon location to that local storage."""
if self.icon.startswith("http://") or self.icon.startswith("https://"):
FreeCAD.Console.PrintLog(
f"Attempting to fetch macro icon from {self.icon}\n"

View File

@@ -377,7 +377,7 @@ class DependencyInstallationWorker(QtCore.QThread):
def __init__(
self,
addons: List[Addon],
python_required: List[str],
python_requires: List[str],
python_optional: List[str],
location: os.PathLike = None,
):
@@ -387,7 +387,7 @@ class DependencyInstallationWorker(QtCore.QThread):
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_requires = python_requires
self.python_optional = python_optional
self.location = location
@@ -395,7 +395,7 @@ class DependencyInstallationWorker(QtCore.QThread):
"""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:
if self.python_requires or self.python_optional:
self._install_python_packages()
self.success.emit()
@@ -464,7 +464,7 @@ class DependencyInstallationWorker(QtCore.QThread):
"""Install the required Python package dependencies. If any fail a failure signal is
emitted and the function exits without proceeding with any additional installs."""
python_exe = utils.get_python_exe()
for pymod in self.python_required:
for pymod in self.python_requires:
if QtCore.QThread.currentThread().isInterruptionRequested():
return
proc = subprocess.run(

View File

@@ -331,7 +331,9 @@ class CreateAddonListWorker(QtCore.QThread):
+ "\n"
)
try:
os.chdir(os.path.join(macro_cache_location,"..")) # Make sure we are not IN this directory
os.chdir(
os.path.join(macro_cache_location, "..")
) # Make sure we are not IN this directory
shutil.rmtree(macro_cache_location, onerror=self._remove_readonly)
self.git_manager.clone(
"https://github.com/FreeCAD/FreeCAD-macros.git",
@@ -449,10 +451,11 @@ class LoadPackagesFromCacheWorker(QtCore.QThread):
repo.updated_timestamp = os.path.getmtime(
repo_metadata_cache_path
)
except Exception:
except Exception as e:
FreeCAD.Console.PrintLog(
f"Failed loading {repo_metadata_cache_path}\n"
)
FreeCAD.Console.PrintLog(str(e) + "\n")
self.addon_repo.emit(repo)