Addon Manager: Add support for <pythonmin> tag
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user