|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2023 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing variants of some 'circup' functions suitable for 'eric-ide' |
|
8 integration. |
|
9 """ |
|
10 |
|
11 # |
|
12 # Copyright of the original sources: |
|
13 # Copyright (c) 2019 Adafruit Industries |
|
14 # |
|
15 |
|
16 import os |
|
17 import shutil |
|
18 |
|
19 import circup |
|
20 import requests |
|
21 |
|
22 from PyQt6.QtCore import QCoreApplication |
|
23 |
|
24 from eric7.EricWidgets import EricMessageBox |
|
25 |
|
26 |
|
27 def find_modules(device_path, bundles_list): |
|
28 """ |
|
29 Function to extract metadata from the connected device and available bundles and |
|
30 returns this as a list of Module instances representing the modules on the device. |
|
31 |
|
32 @param device_path path to the connected board |
|
33 @type str |
|
34 @param bundles_list list of supported bundles |
|
35 @type list of circup.Bundle |
|
36 @return list of Module instances describing the current state of the |
|
37 modules on the connected device |
|
38 @rtype list of circup.Module |
|
39 """ |
|
40 result = [] |
|
41 try: |
|
42 device_modules = circup.get_device_versions(device_path) |
|
43 bundle_modules = circup.get_bundle_versions(bundles_list) |
|
44 for name, device_metadata in device_modules.items(): |
|
45 if name in bundle_modules: |
|
46 path = device_metadata["path"] |
|
47 bundle_metadata = bundle_modules[name] |
|
48 repo = bundle_metadata.get("__repo__") |
|
49 bundle = bundle_metadata.get("bundle") |
|
50 device_version = device_metadata.get("__version__") |
|
51 bundle_version = bundle_metadata.get("__version__") |
|
52 mpy = device_metadata["mpy"] |
|
53 compatibility = device_metadata.get("compatibility", (None, None)) |
|
54 result.append( |
|
55 circup.Module( |
|
56 path, |
|
57 repo, |
|
58 device_version, |
|
59 bundle_version, |
|
60 mpy, |
|
61 bundle, |
|
62 compatibility, |
|
63 ) |
|
64 ) |
|
65 except Exception as ex: |
|
66 # If it's not possible to get the device and bundle metadata, bail out |
|
67 # with a friendly message and indication of what's gone wrong. |
|
68 EricMessageBox.critical( |
|
69 None, |
|
70 QCoreApplication.translate("CircupFunctions", "Find Modules"), |
|
71 QCoreApplication.translate( |
|
72 "CircupFunctions", """<p>There was an error: {0}</p>""" |
|
73 ).format(str(ex)), |
|
74 ) |
|
75 |
|
76 return result |
|
77 |
|
78 |
|
79 def ensure_latest_bundle(bundle): |
|
80 """ |
|
81 Function to ensure that there's a copy of the latest library bundle available so |
|
82 circup can check the metadata contained therein. |
|
83 |
|
84 @param bundle reference to the target Bundle object. |
|
85 @type circup.Bundle |
|
86 """ |
|
87 tag = bundle.latest_tag |
|
88 do_update = False |
|
89 if tag == bundle.current_tag: |
|
90 for platform in circup.PLATFORMS: |
|
91 # missing directories (new platform added on an existing install |
|
92 # or side effect of pytest or network errors) |
|
93 do_update = do_update or not os.path.isdir(bundle.lib_dir(platform)) |
|
94 else: |
|
95 do_update = True |
|
96 |
|
97 if do_update: |
|
98 try: |
|
99 circup.get_bundle(bundle, tag) |
|
100 circup.tags_data_save_tag(bundle.key, tag) |
|
101 except requests.exceptions.HTTPError as ex: |
|
102 EricMessageBox.critical( |
|
103 None, |
|
104 QCoreApplication.translate("CircupFunctions", "Download Bundle"), |
|
105 QCoreApplication.translate( |
|
106 "CircupFunctions", |
|
107 """<p>There was a problem downloading the bundle. Please try""" |
|
108 """ again in a moment.</p><p>Error: {0}</p>""", |
|
109 ).format(str(ex)), |
|
110 ) |
|
111 |
|
112 |
|
113 def get_circuitpython_version(device_path): |
|
114 """ |
|
115 Function to return the version number of CircuitPython running on the board |
|
116 connected via ``device_path``, along with the board ID. |
|
117 |
|
118 This is obtained from the 'boot_out.txt' file on the device, whose first line |
|
119 will start with something like this: |
|
120 |
|
121 Adafruit CircuitPython 4.1.0 on 2019-08-02; |
|
122 |
|
123 While the second line is: |
|
124 |
|
125 Board ID:raspberry_pi_pico |
|
126 |
|
127 @param device_path path to the connected board. |
|
128 @type str |
|
129 @return tuple with the version string for CircuitPython and the board ID string |
|
130 @rtype tuple of (str, str) |
|
131 """ |
|
132 try: |
|
133 with open(os.path.join(device_path, "boot_out.txt")) as boot: |
|
134 version_line = boot.readline() |
|
135 circuit_python = version_line.split(";")[0].split(" ")[-3] |
|
136 board_line = boot.readline() |
|
137 board_id = ( |
|
138 board_line[9:].strip() |
|
139 if board_line.startswith("Board ID:") |
|
140 else "" |
|
141 ) |
|
142 except FileNotFoundError: |
|
143 EricMessageBox.critical( |
|
144 None, |
|
145 QCoreApplication.translate("CircupFunctions", "Download Bundle"), |
|
146 QCoreApplication.translate( |
|
147 "CircupFunctions", |
|
148 """<p>Missing file <b>boot_out.txt</b> on the device: wrong path or""" |
|
149 """ drive corrupted.</p>""", |
|
150 ), |
|
151 ) |
|
152 circuit_python, board_id = "", "" |
|
153 |
|
154 return (circuit_python, board_id) |
|
155 |
|
156 |
|
157 def install_module(device_path, device_modules, name, py, mod_names): |
|
158 """ |
|
159 Function to find a connected device and install a given module name. |
|
160 |
|
161 Installation is done if it is available in the current module bundle and is not |
|
162 already installed on the device. |
|
163 |
|
164 @param device_path path to the connected board |
|
165 @type str |
|
166 @param device_modules list of module metadata from the device |
|
167 @type list of dict |
|
168 @param name name of the module to be installed |
|
169 @type str |
|
170 @param py flag indicating if the module should be installed from source or |
|
171 from a pre-compiled module |
|
172 @type bool |
|
173 @param mod_names dictionary containing metadata from modules that can be generated |
|
174 with circup.get_bundle_versions() |
|
175 @type dict |
|
176 @return flag indicating success |
|
177 @rtype bool |
|
178 """ |
|
179 if not name: |
|
180 return False |
|
181 elif name in mod_names: |
|
182 library_path = os.path.join(device_path, "lib") |
|
183 if not os.path.exists(library_path): # pragma: no cover |
|
184 os.makedirs(library_path) |
|
185 metadata = mod_names[name] |
|
186 bundle = metadata["bundle"] |
|
187 # Grab device modules to check if module already installed |
|
188 if name in device_modules: |
|
189 # ignore silently |
|
190 return False |
|
191 if py: |
|
192 # Use Python source for module. |
|
193 source_path = metadata["path"] # Path to Python source version. |
|
194 if os.path.isdir(source_path): |
|
195 target = os.path.basename(os.path.dirname(source_path)) |
|
196 target_path = os.path.join(library_path, target) |
|
197 # Copy the directory. |
|
198 shutil.copytree(source_path, target_path) |
|
199 return True |
|
200 else: |
|
201 target = os.path.basename(source_path) |
|
202 target_path = os.path.join(library_path, target) |
|
203 # Copy file. |
|
204 shutil.copyfile(source_path, target_path) |
|
205 return True |
|
206 else: |
|
207 # Use pre-compiled mpy modules. |
|
208 module_name = os.path.basename(metadata["path"]).replace(".py", ".mpy") |
|
209 if not module_name: |
|
210 # Must be a directory based module. |
|
211 module_name = os.path.basename(os.path.dirname(metadata["path"])) |
|
212 major_version = circup.CPY_VERSION.split(".")[0] |
|
213 bundle_platform = "{0}mpy".format(major_version) |
|
214 bundle_path = os.path.join(bundle.lib_dir(bundle_platform), module_name) |
|
215 if os.path.isdir(bundle_path): |
|
216 target_path = os.path.join(library_path, module_name) |
|
217 # Copy the directory. |
|
218 shutil.copytree(bundle_path, target_path) |
|
219 return True |
|
220 elif os.path.isfile(bundle_path): |
|
221 target = os.path.basename(bundle_path) |
|
222 target_path = os.path.join(library_path, target) |
|
223 # Copy file. |
|
224 shutil.copyfile(bundle_path, target_path) |
|
225 return True |
|
226 else: |
|
227 EricMessageBox.critical( |
|
228 None, |
|
229 QCoreApplication.translate("CircupFunctions", "Install Modules"), |
|
230 QCoreApplication.translate( |
|
231 "CircupFunctions", |
|
232 """<p>The compiled version of module <b>{0}</b> cannot be""" |
|
233 """ found.</p>""", |
|
234 ).format(name), |
|
235 ) |
|
236 return False |
|
237 else: |
|
238 EricMessageBox.critical( |
|
239 None, |
|
240 QCoreApplication.translate("CircupFunctions", "Install Modules"), |
|
241 QCoreApplication.translate( |
|
242 "CircupFunctions", """<p>The module name <b>{0}</b> is not known.</p>""" |
|
243 ).format(name), |
|
244 ) |
|
245 return False |
|
246 |
|
247 |
|
248 def patch_circup(): |
|
249 """ |
|
250 Function to patch 'circup' to use our functions adapted to the use within the |
|
251 eric-ide. |
|
252 """ |
|
253 circup.ensure_latest_bundle = ensure_latest_bundle |
|
254 circup.find_modules = find_modules |
|
255 circup.get_circuitpython_version = get_circuitpython_version |
|
256 circup.install_module = install_module |