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(

View File

@@ -339,6 +339,7 @@ def get_python_exe() -> str:
if not python_exe or not os.path.exists(python_exe):
return ""
python_exe = python_exe.replace("/",os.path.sep)
prefs.SetString("PythonExecutableForPip", python_exe)
return python_exe

View File

@@ -54,95 +54,6 @@
<string>Metadata</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="12" column="0">
<widget class="QLabel" name="labelIcon">
<property name="text">
<string>Icon</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="labelReadmeURL">
<property name="text">
<string>README URL</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="displayNameLineEdit">
<property name="toolTip">
<string>Displayed in the Addon Manager's list of Addons. Should not include the word &quot;FreeCAD&quot;, and must be a valid directory name on all support operating systems.</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPlainTextEdit" name="descriptionTextEdit">
<property name="toolTip">
<string>Explanation of what this Addon provides. Displayed in the Addon Manager. It is not necessary for this to state that this is a FreeCAD Addon.</string>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>TIP: Since this is displayed within FreeCAD, in the Addon Manager, it is not necessary to take up space saying things like &quot;This is a FreeCAD Addon...&quot; -- just say what it does.</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="readmeURLLineEdit">
<property name="placeholderText">
<string>(Recommended)</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="websiteURLLineEdit">
<property name="placeholderText">
<string>(Optional)</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="bugtrackerURLLineEdit">
<property name="placeholderText">
<string>(Optional)</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="peopleAndLicenseshorizontalLayout"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelDisplayName">
<property name="toolTip">
<string>Displayed in the Addon Manager's list of Addons. Should not include the word &quot;FreeCAD&quot;, and must be a valid directory name on all support operating systems.</string>
</property>
<property name="text">
<string>Addon Name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelVersion">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="labelDocumentationURL">
<property name="text">
<string>Documentation URL</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="labelRepoURL">
<property name="text">
<string>Repository URL</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
@@ -160,13 +71,6 @@
</item>
</layout>
</item>
<item row="11" column="0">
<widget class="QLabel" name="labelDiscssionURL">
<property name="text">
<string>Discussion URL</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelDescription">
<property name="toolTip">
@@ -180,17 +84,17 @@
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLineEdit" name="documentationURLLineEdit">
<property name="placeholderText">
<string>(Optional)</string>
<item row="11" column="0">
<widget class="QLabel" name="labelDiscssionURL">
<property name="text">
<string>Discussion URL</string>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="labelWebsiteURL">
<item row="13" column="0">
<widget class="QLabel" name="labelIcon">
<property name="text">
<string>Website URL</string>
<string>Icon</string>
</property>
</widget>
</item>
@@ -219,7 +123,65 @@
</item>
</layout>
</item>
<item row="12" column="1">
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="peopleAndLicenseshorizontalLayout"/>
</item>
<item row="11" column="1">
<widget class="QLineEdit" name="discussionURLLineEdit">
<property name="placeholderText">
<string>(Optional)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="displayNameLineEdit">
<property name="toolTip">
<string>Displayed in the Addon Manager's list of Addons. Should not include the word &quot;FreeCAD&quot;, and must be a valid directory name on all support operating systems.</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLineEdit" name="documentationURLLineEdit">
<property name="placeholderText">
<string>(Optional)</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="labelReadmeURL">
<property name="text">
<string>README URL</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPlainTextEdit" name="descriptionTextEdit">
<property name="toolTip">
<string>Explanation of what this Addon provides. Displayed in the Addon Manager. It is not necessary for this to state that this is a FreeCAD Addon.</string>
</property>
<property name="tabChangesFocus">
<bool>true</bool>
</property>
<property name="placeholderText">
<string>TIP: Since this is displayed within FreeCAD, in the Addon Manager, it is not necessary to take up space saying things like &quot;This is a FreeCAD Addon...&quot; -- just say what it does.</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="labelRepoURL">
<property name="text">
<string>Repository URL</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="websiteURLLineEdit">
<property name="placeholderText">
<string>(Optional)</string>
</property>
</widget>
</item>
<item row="13" column="1">
<layout class="QHBoxLayout" name="iconHorizontalLayout">
<item>
<widget class="QLabel" name="iconDisplayLabel"/>
@@ -236,13 +198,76 @@
</item>
</layout>
</item>
<item row="11" column="1">
<widget class="QLineEdit" name="discussionURLLineEdit">
<item row="8" column="0">
<widget class="QLabel" name="labelWebsiteURL">
<property name="text">
<string>Website URL</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="labelDocumentationURL">
<property name="text">
<string>Documentation URL</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="bugtrackerURLLineEdit">
<property name="placeholderText">
<string>(Optional)</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="labelDisplayName">
<property name="toolTip">
<string>Displayed in the Addon Manager's list of Addons. Should not include the word &quot;FreeCAD&quot;, and must be a valid directory name on all support operating systems.</string>
</property>
<property name="text">
<string>Addon Name</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelVersion">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="readmeURLLineEdit">
<property name="placeholderText">
<string>(Recommended)</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Minimum Python</string>
</property>
</widget>
</item>
<item row="12" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="minPythonLineEdit">
<property name="placeholderText">
<string>(Optional, only 3.x version supported)</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="detectMinPythonButton">
<property name="text">
<string>Detect...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>