|
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() if board_line.startswith("Board ID:") else "" |
|
139 ) |
|
140 except FileNotFoundError: |
|
141 EricMessageBox.critical( |
|
142 None, |
|
143 QCoreApplication.translate("CircupFunctions", "Download Bundle"), |
|
144 QCoreApplication.translate( |
|
145 "CircupFunctions", |
|
146 """<p>Missing file <b>boot_out.txt</b> on the device: wrong path or""" |
|
147 """ drive corrupted.</p>""", |
|
148 ), |
|
149 ) |
|
150 circuit_python, board_id = "", "" |
|
151 |
|
152 return (circuit_python, board_id) |
|
153 |
|
154 |
|
155 def install_module(device_path, device_modules, name, py, mod_names): |
|
156 """ |
|
157 Function to find a connected device and install a given module name. |
|
158 |
|
159 Installation is done if it is available in the current module bundle and is not |
|
160 already installed on the device. |
|
161 |
|
162 @param device_path path to the connected board |
|
163 @type str |
|
164 @param device_modules list of module metadata from the device |
|
165 @type list of dict |
|
166 @param name name of the module to be installed |
|
167 @type str |
|
168 @param py flag indicating if the module should be installed from source or |
|
169 from a pre-compiled module |
|
170 @type bool |
|
171 @param mod_names dictionary containing metadata from modules that can be generated |
|
172 with circup.get_bundle_versions() |
|
173 @type dict |
|
174 @return flag indicating success |
|
175 @rtype bool |
|
176 """ |
|
177 if not name: |
|
178 return False |
|
179 elif name in mod_names: |
|
180 library_path = os.path.join(device_path, "lib") |
|
181 if not os.path.exists(library_path): # pragma: no cover |
|
182 os.makedirs(library_path) |
|
183 metadata = mod_names[name] |
|
184 bundle = metadata["bundle"] |
|
185 # Grab device modules to check if module already installed |
|
186 if name in device_modules: |
|
187 # ignore silently |
|
188 return False |
|
189 if py: |
|
190 # Use Python source for module. |
|
191 source_path = metadata["path"] # Path to Python source version. |
|
192 if os.path.isdir(source_path): |
|
193 target = os.path.basename(os.path.dirname(source_path)) |
|
194 target_path = os.path.join(library_path, target) |
|
195 # Copy the directory. |
|
196 shutil.copytree(source_path, target_path) |
|
197 return True |
|
198 else: |
|
199 target = os.path.basename(source_path) |
|
200 target_path = os.path.join(library_path, target) |
|
201 # Copy file. |
|
202 shutil.copyfile(source_path, target_path) |
|
203 return True |
|
204 else: |
|
205 # Use pre-compiled mpy modules. |
|
206 module_name = os.path.basename(metadata["path"]).replace(".py", ".mpy") |
|
207 if not module_name: |
|
208 # Must be a directory based module. |
|
209 module_name = os.path.basename(os.path.dirname(metadata["path"])) |
|
210 major_version = circup.CPY_VERSION.split(".")[0] |
|
211 bundle_platform = "{0}mpy".format(major_version) |
|
212 bundle_path = os.path.join(bundle.lib_dir(bundle_platform), module_name) |
|
213 if os.path.isdir(bundle_path): |
|
214 target_path = os.path.join(library_path, module_name) |
|
215 # Copy the directory. |
|
216 shutil.copytree(bundle_path, target_path) |
|
217 return True |
|
218 elif os.path.isfile(bundle_path): |
|
219 target = os.path.basename(bundle_path) |
|
220 target_path = os.path.join(library_path, target) |
|
221 # Copy file. |
|
222 shutil.copyfile(bundle_path, target_path) |
|
223 return True |
|
224 else: |
|
225 EricMessageBox.critical( |
|
226 None, |
|
227 QCoreApplication.translate("CircupFunctions", "Install Modules"), |
|
228 QCoreApplication.translate( |
|
229 "CircupFunctions", |
|
230 """<p>The compiled version of module <b>{0}</b> cannot be""" |
|
231 """ found.</p>""", |
|
232 ).format(name), |
|
233 ) |
|
234 return False |
|
235 else: |
|
236 EricMessageBox.critical( |
|
237 None, |
|
238 QCoreApplication.translate("CircupFunctions", "Install Modules"), |
|
239 QCoreApplication.translate( |
|
240 "CircupFunctions", """<p>The module name <b>{0}</b> is not known.</p>""" |
|
241 ).format(name), |
|
242 ) |
|
243 return False |
|
244 |
|
245 |
|
246 def patch_circup(): |
|
247 """ |
|
248 Function to patch 'circup' to use our functions adapted to the use within the |
|
249 eric-ide. |
|
250 """ |
|
251 circup.ensure_latest_bundle = ensure_latest_bundle |
|
252 circup.find_modules = find_modules |
|
253 circup.get_circuitpython_version = get_circuitpython_version |
|
254 circup.install_module = install_module |