-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpackageManager.py
205 lines (180 loc) · 7.95 KB
/
packageManager.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import os
import sys
import subprocess
from qgis.PyQt.QtWidgets import QMessageBox
from qgis.core import QgsApplication
from datetime import datetime
class PackageManager:
def __init__(self, dependencies):
"""
Initialize with a list of required dependencies.
:param dependencies: List of module names as strings.
"""
self.dependencies = dependencies
self.missingDependencies = []
def checkDependencies(self):
"""
Check for missing dependencies and prompt the user to install them.
"""
self.missingDependencies = [dep for dep in self.dependencies if not self._isModuleInstalled(dep)]
if self.missingDependencies:
self._promptInstallation()
def _isModuleInstalled(self, moduleName):
"""
Check if a Python module is installed.
:param module_name: Name of the module to check.
:return: True if installed, False otherwise.
"""
try:
__import__(moduleName)
return True
except ImportError:
return False
def _promptInstallation(self):
"""
Prompt the user to install missing dependencies.
"""
reply = QMessageBox.question(
None,
'Missing Dependencies',
f'The following modules are required but not installed:\n\n'
f'{", ".join(self.missingDependencies)}\n\n'
'Do you want to install them now?',
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
if reply == QMessageBox.Yes:
self._installDependencies()
else:
QMessageBox.warning(
None,
'Dependencies Not Installed',
'The plugin may not function correctly without the required dependencies. '
'Please install them manually.'
)
def _installDependencies(self):
"""
Attempt to install missing dependencies using pip.
"""
scriptDir = os.path.dirname(os.path.abspath(__file__))
requirementsPath = os.path.join(scriptDir, 'requirements.txt')
try:
from pip._internal import main as pip_main
except ImportError:
from pip import main as pip_main # Fallback for older versions
try:
pip_main(['install', '-r', requirementsPath])
missingDependencies = [dep for dep in self.dependencies if not self._isModuleInstalled(dep)]
if not missingDependencies:
return
except Exception as e:
self._logError(e)
os.system(f"python -m pip install -r {requirementsPath}")
missingDependencies = [dep for dep in self.dependencies if not self._isModuleInstalled(dep)]
if not missingDependencies:
return
try:
# Determine the path to the QGIS Python interpreter
qgisPython = sys.executable
# Install each missing dependency
output = subprocess.check_output(
[qgisPython, "-m", "pip", "install", "-r", requirementsPath],
stderr=subprocess.STDOUT
)
QMessageBox.information(
None,
'Installation Successful',
'All required modules have been installed successfully. Please restart QGIS to apply changes.'
)
except subprocess.CalledProcessError as e:
self._logError(e)
reply = QMessageBox.question(
None,
'Installation Failed',
'Failed to install the required modules. Would you like to attempt a forced installation? '
'If you choose "No," you can manually install the dependencies by running `pip install -r'
'requirements.txt` in the console located in the plugin folder. To locate the plugin folder, navigate'
'within QGIS to "Settings" > "User Profile" > "Open Active Profile Folder," then open the "IntelliGeo"'
'folder.'
,
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
self._forceInstallDependencies()
else:
# No action needed if the user selects No
pass
def _forceInstallDependencies(self):
"""
Attempt to force install missing dependencies using pip.
"""
scriptDir = os.path.dirname(os.path.abspath(__file__))
requirementsPath = os.path.join(scriptDir, 'requirements.txt')
try:
from pip._internal import main as pip_main
except ImportError:
from pip import main as pip_main # Fallback for older versions
try:
pip_main(['install', '-r', requirementsPath])
missingDependencies = [dep for dep in self.dependencies if not self._isModuleInstalled(dep)]
if not missingDependencies:
return
except Exception as e:
self._logError(e)
os.system(f"python -m pip install --no-deps --force-reinstall --use-deprecated=legacy-resolver "
f"-r {requirementsPath}")
missingDependencies = [dep for dep in self.dependencies if not self._isModuleInstalled(dep)]
if not missingDependencies:
return
try:
# Determine the path to the QGIS Python interpreter
qgisPython = sys.executable
# Install each missing dependency
output = subprocess.check_output(
[qgisPython, "-m", "pip", "install", "--no-deps", "--force-reinstall",
"--use-deprecated=legacy-resolver", "-r", requirementsPath],
stderr=subprocess.STDOUT
)
QMessageBox.information(
None,
'Installation Successful',
'All required modules have been installed successfully. Please restart QGIS to apply changes.'
)
except subprocess.CalledProcessError as e:
self._logError(e)
QMessageBox.critical(
None,
'Installation Failed',
'Failed to install the required modules. Please install them manually or consult the error log'
'for details.'
)
def _logError(self, error):
"""
Log installation errors to a file.
:param error: The exception object containing error details.
"""
errorLogDir = os.path.expanduser("~/Documents/QGIS_IntelliGeo")
os.makedirs(errorLogDir, exist_ok=True)
errorLogPath = os.path.join(errorLogDir, "error_log.txt")
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(errorLogPath, "a", encoding="utf-8") as f:
f.write(f"\n[{timestamp}]\n")
f.write(f"Error Type: {type(error).__name__}\n")
# Handle subprocess.CalledProcessError specifically
if hasattr(error, 'returncode'):
f.write(f"Command failed with exit code: {error.returncode}\n")
# Log error message
f.write(f"Error Message: {str(error)}\n")
# Handle output if available
if hasattr(error, 'output'):
f.write("=== Output and Error ===\n")
if isinstance(error.output, bytes):
f.write(error.output.decode("utf-8", errors="replace"))
else:
f.write(str(error.output))
# Include traceback for more context
import traceback
f.write("\n=== Traceback ===\n")
f.write(traceback.format_exc())
f.write("\n" + "-" * 50 + "\n") # Add separator between entries