|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing an interface to the 'circup' package. |
|
8 """ |
|
9 |
|
10 import importlib |
|
11 import logging |
|
12 import os |
|
13 import re |
|
14 import shutil |
|
15 |
|
16 import requests |
|
17 |
|
18 from PyQt6.QtCore import QObject, pyqtSlot |
|
19 from PyQt6.QtWidgets import QDialog, QInputDialog, QLineEdit, QMenu |
|
20 |
|
21 from eric7 import Preferences |
|
22 from eric7.EricGui.EricOverrideCursor import EricOverrideCursor |
|
23 from eric7.EricWidgets import EricFileDialog, EricMessageBox |
|
24 from eric7.EricWidgets.EricApplication import ericApp |
|
25 from eric7.EricWidgets.EricListSelectionDialog import EricListSelectionDialog |
|
26 from eric7.SystemUtilities import PythonUtilities |
|
27 |
|
28 try: |
|
29 import circup |
|
30 |
|
31 circup.logger.setLevel(logging.WARNING) |
|
32 except ImportError: |
|
33 circup = None |
|
34 |
|
35 |
|
36 class CircuitPythonUpdaterInterface(QObject): |
|
37 """ |
|
38 Class implementing an interface to the 'circup' package. |
|
39 """ |
|
40 |
|
41 def __init__(self, device, parent=None): |
|
42 """ |
|
43 Constructor |
|
44 |
|
45 @param device reference to the CircuitPython device interface |
|
46 @type CircuitPythonDevice |
|
47 @param parent reference to the parent object (defaults to None) |
|
48 @type QObject (optional) |
|
49 """ |
|
50 super().__init__(parent) |
|
51 |
|
52 self.__device = device |
|
53 |
|
54 self.__installMenu = QMenu(self.tr("Install Modules")) |
|
55 self.__installMenu.setTearOffEnabled(True) |
|
56 self.__installMenu.addAction( |
|
57 self.tr("Select from Available Modules"), self.__installFromAvailable |
|
58 ) |
|
59 self.__installMenu.addAction( |
|
60 self.tr("Install Requirements"), self.__installRequirements |
|
61 ) |
|
62 self.__installMenu.addAction( |
|
63 self.tr("Install based on 'code.py'"), self.__installFromCode |
|
64 ) |
|
65 self.__installMenu.addSeparator() |
|
66 self.__installPyAct = self.__installMenu.addAction( |
|
67 self.tr("Install Python Source") |
|
68 ) |
|
69 self.__installPyAct.setCheckable(True) |
|
70 self.__installPyAct.setChecked(False) |
|
71 # kind of hack to make this action not hide the menu |
|
72 # Note: parent menus are hidden nevertheless |
|
73 self.__installPyAct.toggled.connect(self.__installMenu.show) |
|
74 |
|
75 def populateMenu(self, menu): |
|
76 """ |
|
77 Public method to populate the 'circup' menu. |
|
78 |
|
79 @param menu reference to the menu to be populated |
|
80 @type QMenu |
|
81 """ |
|
82 from .CircupFunctions import patch_circup |
|
83 |
|
84 patch_circup() |
|
85 isMounted = self.__device.supportsLocalFileAccess() |
|
86 |
|
87 act = menu.addAction(self.tr("circup"), self.__aboutCircup) |
|
88 font = act.font() |
|
89 font.setBold(True) |
|
90 act.setFont(font) |
|
91 menu.addSeparator() |
|
92 menu.addAction( |
|
93 self.tr("List Outdated Modules"), self.__listOutdatedModules |
|
94 ).setEnabled(isMounted) |
|
95 menu.addAction(self.tr("Update Modules"), self.__updateModules).setEnabled( |
|
96 isMounted |
|
97 ) |
|
98 menu.addAction( |
|
99 self.tr("Update All Modules"), self.__updateAllModules |
|
100 ).setEnabled(isMounted) |
|
101 menu.addSeparator() |
|
102 menu.addAction(self.tr("Show Available Modules"), self.__showAvailableModules) |
|
103 menu.addAction( |
|
104 self.tr("Show Installed Modules"), self.__showInstalledModules |
|
105 ).setEnabled(isMounted) |
|
106 menu.addMenu(self.__installMenu).setEnabled(isMounted) |
|
107 menu.addAction( |
|
108 self.tr("Uninstall Modules"), self.__uninstallModules |
|
109 ).setEnabled(isMounted) |
|
110 menu.addSeparator() |
|
111 menu.addAction( |
|
112 self.tr("Generate Requirements ..."), self.__generateRequirements |
|
113 ).setEnabled(isMounted) |
|
114 menu.addSeparator() |
|
115 menu.addAction(self.tr("Show Bundles"), self.__showBundles) |
|
116 menu.addAction(self.tr("Show Bundles with Modules"), self.__showBundlesModules) |
|
117 menu.addSeparator() |
|
118 menu.addAction(self.tr("Add Bundle"), self.__addBundle) |
|
119 menu.addAction(self.tr("Remove Bundles"), self.__removeBundle) |
|
120 |
|
121 @pyqtSlot() |
|
122 def __aboutCircup(self): |
|
123 """ |
|
124 Private slot to show some info about 'circup'. |
|
125 """ |
|
126 version = circup.get_circup_version() |
|
127 if version is None: |
|
128 version = self.tr("unknown") |
|
129 |
|
130 EricMessageBox.information( |
|
131 None, |
|
132 self.tr("About circup"), |
|
133 self.tr( |
|
134 """<p><b>circup Version {0}</b></p>""" |
|
135 """<p><i>circup</i> is a tool to manage and update libraries on a""" |
|
136 """ CircuitPython device.</p>""", |
|
137 ).format(version), |
|
138 ) |
|
139 |
|
140 @pyqtSlot() |
|
141 def installCircup(self): |
|
142 """ |
|
143 Public slot to install the 'circup' package via pip. |
|
144 """ |
|
145 global circup |
|
146 |
|
147 pip = ericApp().getObject("Pip") |
|
148 pip.installPackages( |
|
149 ["circup"], interpreter=PythonUtilities.getPythonExecutable() |
|
150 ) |
|
151 |
|
152 circup = importlib.import_module("circup") |
|
153 circup.logger.setLevel(logging.WARNING) |
|
154 |
|
155 @pyqtSlot() |
|
156 def __showBundles(self, withModules=False): |
|
157 """ |
|
158 Private slot to show the available bundles (default and local). |
|
159 |
|
160 @param withModules flag indicating to list the modules and their version |
|
161 (defaults to False) |
|
162 @type bool (optional) |
|
163 """ |
|
164 from .ShowBundlesDialog import ShowBundlesDialog |
|
165 |
|
166 with EricOverrideCursor(): |
|
167 dlg = ShowBundlesDialog(withModules=withModules) |
|
168 dlg.exec() |
|
169 |
|
170 @pyqtSlot() |
|
171 def __showBundlesModules(self): |
|
172 """ |
|
173 Private slot to show the available bundles (default and local) with their |
|
174 modules. |
|
175 """ |
|
176 self.__showBundles(withModules=True) |
|
177 |
|
178 @pyqtSlot() |
|
179 def __addBundle(self): |
|
180 """ |
|
181 Private slot to add a bundle to the local bundles list, by "user/repo" github |
|
182 string. |
|
183 """ |
|
184 bundle, ok = QInputDialog.getText( |
|
185 None, |
|
186 self.tr("Add Bundle"), |
|
187 self.tr("Enter Bundle by 'User/Repo' Github String:"), |
|
188 QLineEdit.EchoMode.Normal, |
|
189 ) |
|
190 if ok and bundle: |
|
191 bundles = circup.get_bundles_local_dict() |
|
192 modified = False |
|
193 |
|
194 # do some cleanup |
|
195 bundle = re.sub(r"https?://github.com/([^/]+/[^/]+)(/.*)?", r"\1", bundle) |
|
196 if bundle in bundles: |
|
197 EricMessageBox.information( |
|
198 None, |
|
199 self.tr("Add Bundle"), |
|
200 self.tr( |
|
201 """<p>The bundle <b>{0}</b> is already in the list.</p>""" |
|
202 ).format(bundle), |
|
203 ) |
|
204 return |
|
205 |
|
206 try: |
|
207 cBundle = circup.Bundle(bundle) |
|
208 except ValueError: |
|
209 EricMessageBox.critical( |
|
210 None, |
|
211 self.tr("Add Bundle"), |
|
212 self.tr( |
|
213 """<p>The bundle string is invalid, expecting github URL""" |
|
214 """ or 'user/repository' string.</p>""" |
|
215 ), |
|
216 ) |
|
217 return |
|
218 |
|
219 result = requests.head("https://github.com/" + bundle) |
|
220 if result.status_code == requests.codes.NOT_FOUND: |
|
221 EricMessageBox.critical( |
|
222 None, |
|
223 self.tr("Add Bundle"), |
|
224 self.tr( |
|
225 """<p>The bundle string is invalid. The repository doesn't""" |
|
226 """ exist (error code 404).</p>""" |
|
227 ), |
|
228 ) |
|
229 return |
|
230 |
|
231 if not cBundle.validate(): |
|
232 EricMessageBox.critical( |
|
233 None, |
|
234 self.tr("Add Bundle"), |
|
235 self.tr( |
|
236 """<p>The bundle string is invalid. Is the repository a valid""" |
|
237 """ circup bundle?</p>""" |
|
238 ), |
|
239 ) |
|
240 return |
|
241 |
|
242 # Use the bundle string as the dictionary key for uniqueness |
|
243 bundles[bundle] = bundle |
|
244 modified = True |
|
245 EricMessageBox.information( |
|
246 None, |
|
247 self.tr("Add Bundle"), |
|
248 self.tr("""<p>Added bundle <b>{0}</b> ({1}).</p>""").format( |
|
249 bundle, cBundle.url |
|
250 ), |
|
251 ) |
|
252 |
|
253 if modified: |
|
254 # save the bundles list |
|
255 circup.save_local_bundles(bundles) |
|
256 # update and get the new bundle for the first time |
|
257 circup.get_bundle_versions(circup.get_bundles_list()) |
|
258 |
|
259 @pyqtSlot() |
|
260 def __removeBundle(self): |
|
261 """ |
|
262 Private slot to remove one or more bundles from the local bundles list. |
|
263 """ |
|
264 localBundles = circup.get_bundles_local_dict() |
|
265 dlg = EricListSelectionDialog( |
|
266 sorted(localBundles.keys()), |
|
267 title=self.tr("Remove Bundles"), |
|
268 message=self.tr("Select the bundles to be removed:"), |
|
269 checkBoxSelection=True, |
|
270 ) |
|
271 modified = False |
|
272 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
273 bundles = dlg.getSelection() |
|
274 for bundle in bundles: |
|
275 del localBundles[bundle] |
|
276 modified = True |
|
277 |
|
278 if modified: |
|
279 circup.save_local_bundles(localBundles) |
|
280 EricMessageBox.information( |
|
281 None, |
|
282 self.tr("Remove Bundles"), |
|
283 self.tr( |
|
284 """<p>These bundles were removed from the local bundles list.{0}""" |
|
285 """</p>""" |
|
286 ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(bundles))), |
|
287 ) |
|
288 |
|
289 @pyqtSlot() |
|
290 def __listOutdatedModules(self): |
|
291 """ |
|
292 Private slot to list the outdated modules of the connected device. |
|
293 """ |
|
294 from .ShowOutdatedDialog import ShowOutdatedDialog |
|
295 |
|
296 devicePath = self.__device.getWorkspace() |
|
297 |
|
298 cpyVersion, board_id = circup.get_circuitpython_version(devicePath) |
|
299 circup.CPY_VERSION = cpyVersion |
|
300 |
|
301 with EricOverrideCursor(): |
|
302 dlg = ShowOutdatedDialog(devicePath=devicePath) |
|
303 dlg.exec() |
|
304 |
|
305 @pyqtSlot() |
|
306 def __updateModules(self): |
|
307 """ |
|
308 Private slot to update the modules of the connected device. |
|
309 """ |
|
310 from .ShowOutdatedDialog import ShowOutdatedDialog |
|
311 |
|
312 devicePath = self.__device.getWorkspace() |
|
313 |
|
314 cpyVersion, board_id = circup.get_circuitpython_version(devicePath) |
|
315 circup.CPY_VERSION = cpyVersion |
|
316 |
|
317 with EricOverrideCursor(): |
|
318 dlg = ShowOutdatedDialog(devicePath=devicePath, selectionMode=True) |
|
319 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
320 modules = dlg.getSelection() |
|
321 self.__doUpdateModules(modules) |
|
322 |
|
323 @pyqtSlot() |
|
324 def __updateAllModules(self): |
|
325 """ |
|
326 Private slot to update all modules of the connected device. |
|
327 """ |
|
328 devicePath = self.__device.getWorkspace() |
|
329 |
|
330 cpyVersion, board_id = circup.get_circuitpython_version(devicePath) |
|
331 circup.CPY_VERSION = cpyVersion |
|
332 |
|
333 with EricOverrideCursor(): |
|
334 modules = [ |
|
335 m |
|
336 for m in circup.find_modules(devicePath, circup.get_bundles_list()) |
|
337 if m.outofdate |
|
338 ] |
|
339 if modules: |
|
340 self.__doUpdateModules(modules) |
|
341 else: |
|
342 EricMessageBox.information( |
|
343 None, |
|
344 self.tr("Update Modules"), |
|
345 self.tr("All modules are already up-to-date."), |
|
346 ) |
|
347 |
|
348 def __doUpdateModules(self, modules): |
|
349 """ |
|
350 Private method to perform the update of a list of modules. |
|
351 |
|
352 @param modules list of modules to be updated |
|
353 @type circup.Module |
|
354 """ |
|
355 updatedModules = [] |
|
356 for module in modules: |
|
357 try: |
|
358 module.update() |
|
359 updatedModules.append(module.name) |
|
360 except Exception as ex: |
|
361 EricMessageBox.critical( |
|
362 None, |
|
363 self.tr("Update Modules"), |
|
364 self.tr( |
|
365 """<p>There was an error updating <b>{0}</b>.</p>""" |
|
366 """<p>Error: {1}</p>""" |
|
367 ).format(module.name, str(ex)), |
|
368 ) |
|
369 |
|
370 if updatedModules: |
|
371 EricMessageBox.information( |
|
372 None, |
|
373 self.tr("Update Modules"), |
|
374 self.tr( |
|
375 """<p>These modules were updated on the connected device.{0}</p>""" |
|
376 ).format( |
|
377 """<ul><li>{0}</li></ul>""".format("</li><li>".join(updatedModules)) |
|
378 ), |
|
379 ) |
|
380 else: |
|
381 EricMessageBox.information( |
|
382 None, |
|
383 self.tr("Update Modules"), |
|
384 self.tr("No modules could be updated."), |
|
385 ) |
|
386 |
|
387 @pyqtSlot() |
|
388 def __showAvailableModules(self): |
|
389 """ |
|
390 Private slot to show the available modules. |
|
391 |
|
392 These are modules which could be installed on the device. |
|
393 """ |
|
394 from ..ShowModulesDialog import ShowModulesDialog |
|
395 |
|
396 with EricOverrideCursor(): |
|
397 availableModules = circup.get_bundle_versions(circup.get_bundles_list()) |
|
398 moduleNames = [m.replace(".py", "") for m in availableModules] |
|
399 |
|
400 dlg = ShowModulesDialog(moduleNames) |
|
401 dlg.exec() |
|
402 |
|
403 @pyqtSlot() |
|
404 def __showInstalledModules(self): |
|
405 """ |
|
406 Private slot to show the modules installed on the connected device. |
|
407 """ |
|
408 from .ShowInstalledDialog import ShowInstalledDialog |
|
409 |
|
410 devicePath = self.__device.getWorkspace() |
|
411 |
|
412 with EricOverrideCursor(): |
|
413 dlg = ShowInstalledDialog(devicePath=devicePath) |
|
414 dlg.exec() |
|
415 |
|
416 @pyqtSlot() |
|
417 def __installFromAvailable(self): |
|
418 """ |
|
419 Private slot to install modules onto the connected device. |
|
420 """ |
|
421 from ..ShowModulesDialog import ShowModulesDialog |
|
422 |
|
423 with EricOverrideCursor(): |
|
424 availableModules = circup.get_bundle_versions(circup.get_bundles_list()) |
|
425 moduleNames = [m.replace(".py", "") for m in availableModules] |
|
426 |
|
427 dlg = ShowModulesDialog(moduleNames, selectionMode=True) |
|
428 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
429 modules = dlg.getSelection() |
|
430 self.__installModules(modules) |
|
431 |
|
432 @pyqtSlot() |
|
433 def __installRequirements(self): |
|
434 """ |
|
435 Private slot to install modules determined by a requirements file. |
|
436 """ |
|
437 homeDir = ( |
|
438 Preferences.getMicroPython("MpyWorkspace") |
|
439 or Preferences.getMultiProject("Workspace") |
|
440 or os.path.expanduser("~") |
|
441 ) |
|
442 reqFile = EricFileDialog.getOpenFileName( |
|
443 None, |
|
444 self.tr("Install Modules"), |
|
445 homeDir, |
|
446 self.tr("Text Files (*.txt);;All Files (*)"), |
|
447 ) |
|
448 if reqFile: |
|
449 if os.path.exists(reqFile): |
|
450 with open(reqFile, "r") as fp: |
|
451 requirementsText = fp.read() |
|
452 modules = circup.libraries_from_requirements(requirementsText) |
|
453 if modules: |
|
454 self.__installModules(modules) |
|
455 else: |
|
456 EricMessageBox.critical( |
|
457 None, |
|
458 self.tr("Install Modules"), |
|
459 self.tr( |
|
460 """<p>The given requirements file <b>{0}</b> does not""" |
|
461 """ contain valid modules.</p>""" |
|
462 ).format(reqFile), |
|
463 ) |
|
464 else: |
|
465 EricMessageBox.critical( |
|
466 None, |
|
467 self.tr("Install Modules"), |
|
468 self.tr( |
|
469 """<p>The given requirements file <b>{0}</b> does not exist.""" |
|
470 """</p>""" |
|
471 ).format(reqFile), |
|
472 ) |
|
473 |
|
474 @pyqtSlot() |
|
475 def __installFromCode(self): |
|
476 """ |
|
477 Private slot to install modules based on the 'code.py' file of the |
|
478 connected device. |
|
479 """ |
|
480 devicePath = self.__device.getWorkspace() |
|
481 |
|
482 codeFile = EricFileDialog.getOpenFileName( |
|
483 None, |
|
484 self.tr("Install Modules"), |
|
485 os.path.join(devicePath, "code.py"), |
|
486 self.tr("Python Files (*.py);;All Files (*)"), |
|
487 ) |
|
488 if codeFile: |
|
489 if os.path.exists(codeFile): |
|
490 with EricOverrideCursor(): |
|
491 availableModules = circup.get_bundle_versions( |
|
492 circup.get_bundles_list() |
|
493 ) |
|
494 moduleNames = {} |
|
495 for module, metadata in availableModules.items(): |
|
496 moduleNames[module.replace(".py", "")] = metadata |
|
497 |
|
498 modules = circup.libraries_from_imports(codeFile, moduleNames) |
|
499 if modules: |
|
500 self.__installModules(modules) |
|
501 else: |
|
502 EricMessageBox.critical( |
|
503 None, |
|
504 self.tr("Install Modules"), |
|
505 self.tr( |
|
506 """<p>The given code file <b>{0}</b> does not""" |
|
507 """ contain valid import statements or does not import""" |
|
508 """ external modules.</p>""" |
|
509 ).format(codeFile), |
|
510 ) |
|
511 else: |
|
512 EricMessageBox.critical( |
|
513 None, |
|
514 self.tr("Install Modules"), |
|
515 self.tr( |
|
516 """<p>The given code file <b>{0}</b> does not exist.</p>""" |
|
517 ).format(codeFile), |
|
518 ) |
|
519 |
|
520 def __installModules(self, installs): |
|
521 """ |
|
522 Private method to install the given list of modules. |
|
523 |
|
524 @param installs list of module names to be installed |
|
525 @type list of str |
|
526 """ |
|
527 devicePath = self.__device.getWorkspace() |
|
528 |
|
529 cpyVersion, board_id = circup.get_circuitpython_version(devicePath) |
|
530 circup.CPY_VERSION = cpyVersion |
|
531 |
|
532 with EricOverrideCursor(): |
|
533 availableModules = circup.get_bundle_versions(circup.get_bundles_list()) |
|
534 moduleNames = {} |
|
535 for module, metadata in availableModules.items(): |
|
536 moduleNames[module.replace(".py", "")] = metadata |
|
537 toBeInstalled = circup.get_dependencies(installs, mod_names=moduleNames) |
|
538 deviceModules = circup.get_device_versions(devicePath) |
|
539 if toBeInstalled is not None: |
|
540 dependencies = [m for m in toBeInstalled if m not in installs] |
|
541 ok = EricMessageBox.yesNo( |
|
542 None, |
|
543 self.tr("Install Modules"), |
|
544 self.tr("""<p>Ready to install these modules?{0}{1}</p>""").format( |
|
545 """<ul><li>{0}</li></ul>""".format( |
|
546 "</li><li>".join(sorted(installs)) |
|
547 ), |
|
548 self.tr("Dependencies:") |
|
549 + """<ul><li>{0}</li></ul>""".format( |
|
550 "</li><li>".join(sorted(dependencies)) |
|
551 ) |
|
552 if dependencies |
|
553 else "", |
|
554 ), |
|
555 yesDefault=True, |
|
556 ) |
|
557 if ok: |
|
558 installedModules = [] |
|
559 with EricOverrideCursor(): |
|
560 for library in toBeInstalled: |
|
561 success = circup.install_module( |
|
562 devicePath, |
|
563 deviceModules, |
|
564 library, |
|
565 self.__installPyAct.isChecked(), |
|
566 moduleNames, |
|
567 ) |
|
568 if success: |
|
569 installedModules.append(library) |
|
570 |
|
571 if installedModules: |
|
572 EricMessageBox.information( |
|
573 None, |
|
574 self.tr("Install Modules"), |
|
575 self.tr( |
|
576 "<p>Installation complete. These modules were installed" |
|
577 " successfully.{0}</p>" |
|
578 ).format( |
|
579 """<ul><li>{0}</li></ul>""".format( |
|
580 "</li><li>".join(sorted(installedModules)) |
|
581 ), |
|
582 ), |
|
583 ) |
|
584 else: |
|
585 EricMessageBox.information( |
|
586 None, |
|
587 self.tr("Install Modules"), |
|
588 self.tr( |
|
589 "<p>Installation complete. No modules were installed.</p>" |
|
590 ), |
|
591 ) |
|
592 else: |
|
593 EricMessageBox.information( |
|
594 None, |
|
595 self.tr("Install Modules"), |
|
596 self.tr("<p>No modules installation is required.</p>"), |
|
597 ) |
|
598 |
|
599 @pyqtSlot() |
|
600 def __uninstallModules(self): |
|
601 """ |
|
602 Private slot to uninstall modules from the connected device. |
|
603 """ |
|
604 devicePath = self.__device.getWorkspace() |
|
605 libraryPath = os.path.join(devicePath, "lib") |
|
606 |
|
607 with EricOverrideCursor(): |
|
608 deviceModules = circup.get_device_versions(devicePath) |
|
609 modNames = {} |
|
610 for moduleItem, metadata in deviceModules.items(): |
|
611 modNames[moduleItem.replace(".py", "").lower()] = metadata |
|
612 |
|
613 dlg = EricListSelectionDialog( |
|
614 sorted(modNames.keys()), |
|
615 title=self.tr("Uninstall Modules"), |
|
616 message=self.tr("Select the modules/packages to be uninstalled:"), |
|
617 checkBoxSelection=True, |
|
618 ) |
|
619 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
620 names = dlg.getSelection() |
|
621 for name in names: |
|
622 modulePath = modNames[name]["path"] |
|
623 if os.path.isdir(modulePath): |
|
624 target = os.path.basename(os.path.dirname(modulePath)) |
|
625 targetPath = os.path.join(libraryPath, target) |
|
626 # Remove the package directory. |
|
627 shutil.rmtree(targetPath) |
|
628 else: |
|
629 target = os.path.basename(modulePath) |
|
630 targetPath = os.path.join(libraryPath, target) |
|
631 # Remove the module file |
|
632 os.remove(targetPath) |
|
633 |
|
634 EricMessageBox.information( |
|
635 None, |
|
636 self.tr("Uninstall Modules"), |
|
637 self.tr( |
|
638 """<p>These modules/packages were uninstalled from the connected""" |
|
639 """ device.{0}</p>""" |
|
640 ).format("""<ul><li>{0}</li></ul>""".format("</li><li>".join(names))), |
|
641 ) |
|
642 |
|
643 @pyqtSlot() |
|
644 def __generateRequirements(self): |
|
645 """ |
|
646 Private slot to generate requirements for the connected device. |
|
647 """ |
|
648 from .RequirementsDialog import RequirementsDialog |
|
649 |
|
650 devicePath = self.__device.getWorkspace() |
|
651 |
|
652 cpyVersion, board_id = circup.get_circuitpython_version(devicePath) |
|
653 circup.CPY_VERSION = cpyVersion |
|
654 |
|
655 dlg = RequirementsDialog(devicePath=devicePath) |
|
656 dlg.exec() |
|
657 |
|
658 |
|
659 def isCircupAvailable(): |
|
660 """ |
|
661 Function to check for the availability of 'circup'. |
|
662 |
|
663 @return flag indicating the availability of 'circup' |
|
664 @rtype bool |
|
665 """ |
|
666 global circup |
|
667 |
|
668 return circup is not None |