Addon Manager: Use Vermin to detect min Python

This commit is contained in:
Chris Hennes
2022-09-26 09:10:45 -05:00
parent f4bae7f9d3
commit b253f5acce
3 changed files with 281 additions and 109 deletions

View File

@@ -24,6 +24,7 @@
import os
import datetime
import subprocess
import FreeCAD
import FreeCADGui
@@ -33,6 +34,7 @@ from PySide2.QtWidgets import (
QListWidgetItem,
QDialog,
QSizePolicy,
QMessageBox,
)
from PySide2.QtGui import (
QIcon,
@@ -46,6 +48,7 @@ from addonmanager_devmode_validators import NameValidator, VersionValidator
from addonmanager_devmode_predictor import Predictor
from addonmanager_devmode_people_table import PeopleTable
from addonmanager_devmode_licenses_table import LicensesTable
import addonmanager_utilities as utils
translate = FreeCAD.Qt.translate
@@ -66,7 +69,9 @@ class AddonGitInterface:
try:
AddonGitInterface.git_manager = GitManager()
except NoGitFound:
FreeCAD.Console.PrintLog("No git found, Addon Manager Developer Mode disabled.")
FreeCAD.Console.PrintLog(
"No git found, Addon Manager Developer Mode disabled."
)
return
self.path = path
@@ -96,14 +101,15 @@ class AddonGitInterface:
return AddonGitInterface.git_manager.get_last_authors(self.path, 10)
return []
#pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-instance-attributes
class DeveloperMode:
"""The main Developer Mode dialog, for editing package.xml metadata graphically."""
def __init__(self):
# In the UI we want to show a translated string for the person type, but the underlying
# string must be the one expected by the metadata parser, in English
self.person_type_translation = {
@@ -137,6 +143,7 @@ class DeveloperMode:
self.dialog.displayNameLineEdit.setValidator(NameValidator())
self.dialog.versionLineEdit.setValidator(VersionValidator())
self.dialog.minPythonLineEdit.setValidator(VersionValidator())
self.dialog.addContentItemToolButton.setIcon(
QIcon.fromTheme("add", QIcon(":/icons/list-add.svg"))
@@ -210,6 +217,7 @@ class DeveloperMode:
self.dialog.displayNameLineEdit.setText(self.metadata.Name)
self.dialog.descriptionTextEdit.setPlainText(self.metadata.Description)
self.dialog.versionLineEdit.setText(self.metadata.Version)
self.dialog.minPythonLineEdit.setText(self.metadata.PythonMin)
self._populate_urls_from_metadata(self.metadata)
self._populate_contents_from_metadata(self.metadata)
@@ -342,6 +350,7 @@ class DeveloperMode:
self.dialog.readmeURLLineEdit.clear()
self.dialog.documentationURLLineEdit.clear()
self.dialog.discussionURLLineEdit.clear()
self.dialog.minPythonLineEdit.clear()
self.dialog.iconDisplayLabel.setPixmap(QPixmap())
self.dialog.iconPathLineEdit.clear()
@@ -354,6 +363,9 @@ class DeveloperMode:
self.dialog.pathToAddonComboBox.editTextChanged.connect(
self._addon_combo_text_changed
)
self.dialog.detectMinPythonButton.clicked.connect(
self._detect_min_python_clicked
)
self.dialog.iconBrowseButton.clicked.connect(self._browse_for_icon_clicked)
self.dialog.addContentItemToolButton.clicked.connect(self._add_content_clicked)
@@ -427,6 +439,11 @@ class DeveloperMode:
)
self.metadata.Urls = urls
if self.dialog.minPythonLineEdit.text():
self.metadata.PythonMin = self.dialog.minPythonLineEdit.text()
else:
self.metadata.PythonMin = "0.0.0" # Code for "unset"
# Content, people, and licenses should already be sync'ed
###############################################################################################
@@ -565,6 +582,135 @@ class DeveloperMode:
version_string = f"{year}.{month:>02}.{day:>02}"
self.dialog.versionLineEdit.setText(version_string)
def _detect_min_python_clicked(self):
if not self._ensure_vermin_loaded():
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller",
"No Vermin, cancelling operation.\n",
"'Vermin' is a Python package - do not translate",
)
)
return
FreeCAD.Console.PrintMessage(
translate(
"AddonsInstaller", "Scanning Addon for Python version compatibility"
)
+ "...\n"
)
#pylint: disable=import-outside-toplevel
import vermin
required_minor_version = 0
for dirpath, _, filenames in os.walk(self.current_mod):
for filename in filenames:
if filename.endswith(".py"):
with open(
os.path.join(dirpath, filename), "r", encoding="utf-8"
) as f:
contents = f.read()
version_strings = vermin.version_strings(
vermin.detect(contents)
)
version = version_strings.split(",")
if len(version) >= 2:
# Only care about Py3, and only if there is a dot in the version:
if "." in version[1]:
py3 = version[1].split(".")
major = int(py3[0].strip())
minor = int(py3[1].strip())
if major == 3:
FreeCAD.Console.PrintLog(
f"Detected Python 3.{minor} required by {filename}\n"
)
required_minor_version = max(
required_minor_version, minor
)
self.dialog.minPythonLineEdit.setText(f"3.{required_minor_version}")
QMessageBox.information(
self.dialog,
translate("AddonsInstaller", "Minimum Python Version Detected"),
translate(
"AddonsInstaller",
"Vermin auto-detected a required version of Python 3.{}",
).format(required_minor_version),
QMessageBox.Ok,
)
def _ensure_vermin_loaded(self) -> bool:
try:
#pylint: disable=import-outside-toplevel,unused-import
import vermin
except ImportError:
#pylint: disable=line-too-long
response = QMessageBox.question(
self.dialog,
translate("AddonsInstaller", "Install Vermin?"),
translate(
"AddonsInstaller",
"Autodetecting the required version of Python for this Addon requires Vermin (https://pypi.org/project/vermin/). OK to install?",
),
QMessageBox.Yes | QMessageBox.Cancel,
)
if response == QMessageBox.Cancel:
return False
FreeCAD.Console.PrintMessage(
translate("AddonsInstaller", "Attempting to install Vermin from PyPi")
+ "...\n"
)
python_exe = utils.get_python_exe()
vendor_path = os.path.join(
FreeCAD.getUserAppDataDir(), "AdditionalPythonPackages"
)
if not os.path.exists(vendor_path):
os.makedirs(vendor_path)
proc = subprocess.run(
[
python_exe,
"-m",
"pip",
"install",
"--disable-pip-version-check",
"--target",
vendor_path,
"vermin",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=True,
)
FreeCAD.Console.PrintMessage(proc.stdout.decode())
if proc.returncode != 0:
response = QMessageBox.critical(
self.dialog,
translate("AddonsInstaller", "Installation failed"),
translate(
"AddonsInstaller",
"Failed to install Vermin -- check Report View for details.",
"'Vermin' is the name of a Python package, do not translate",
),
QMessageBox.Cancel,
)
return False
try:
#pylint: disable=import-outside-toplevel
import vermin
except ImportError:
response = QMessageBox.critical(
self.dialog,
translate("AddonsInstaller", "Installation failed"),
translate(
"AddonsInstaller",
"Failed to import vermin after installation -- cannot scan Addon.",
"'vermin' is the name of a Python package, do not translate",
),
QMessageBox.Cancel,
)
return False
return True
def _browse_for_icon_clicked(self):
"""Callback: when the "Browse..." button for the icon field is clicked"""
new_icon_path, _ = QFileDialog.getOpenFileName(