lib-installer 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. lib_installer/__init__.py +51 -0
  2. lib_installer/__main__.py +8 -0
  3. lib_installer/arduino/__init__.py +1 -0
  4. lib_installer/arduino/arduino.py +334 -0
  5. lib_installer/arduino/properties.py +168 -0
  6. lib_installer/arduino/sketchbook.py +133 -0
  7. lib_installer/cli.py +38 -0
  8. lib_installer/core/__init__.py +8 -0
  9. lib_installer/core/blocks.py +373 -0
  10. lib_installer/core/cli.py +1432 -0
  11. lib_installer/core/console.py +132 -0
  12. lib_installer/core/download.py +781 -0
  13. lib_installer/core/files.py +35 -0
  14. lib_installer/core/installer.py +1222 -0
  15. lib_installer/core/manifest.py +1487 -0
  16. lib_installer/core/steps.py +222 -0
  17. lib_installer/core/target.py +261 -0
  18. lib_installer/core/updates.py +120 -0
  19. lib_installer/core/usercode.py +193 -0
  20. lib_installer/core/yamlreader.py +417 -0
  21. lib_installer/esp32/__init__.py +1 -0
  22. lib_installer/esp32/component.py +354 -0
  23. lib_installer/esp32/esp32.py +491 -0
  24. lib_installer/esp32/project.py +768 -0
  25. lib_installer/pio/__init__.py +1 -0
  26. lib_installer/pio/ini.py +304 -0
  27. lib_installer/pio/library_json.py +161 -0
  28. lib_installer/pio/pio.py +208 -0
  29. lib_installer/stm32/__init__.py +1 -0
  30. lib_installer/stm32/checks.py +204 -0
  31. lib_installer/stm32/ide/__init__.py +110 -0
  32. lib_installer/stm32/ide/base.py +269 -0
  33. lib_installer/stm32/ide/cmake.py +299 -0
  34. lib_installer/stm32/ide/cubeide.py +344 -0
  35. lib_installer/stm32/ide/iar.py +509 -0
  36. lib_installer/stm32/ide/keil.py +419 -0
  37. lib_installer/stm32/ide/makefile.py +376 -0
  38. lib_installer/stm32/stm32.py +101 -0
  39. lib_installer/zephyr/__init__.py +1 -0
  40. lib_installer/zephyr/app.py +648 -0
  41. lib_installer/zephyr/module.py +375 -0
  42. lib_installer/zephyr/zephyr.py +620 -0
  43. lib_installer-3.0.1.dist-info/METADATA +583 -0
  44. lib_installer-3.0.1.dist-info/RECORD +48 -0
  45. lib_installer-3.0.1.dist-info/WHEEL +5 -0
  46. lib_installer-3.0.1.dist-info/entry_points.txt +2 -0
  47. lib_installer-3.0.1.dist-info/licenses/LICENSE +201 -0
  48. lib_installer-3.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,51 @@
1
+ """
2
+ Install embedded C libraries into STM32, Zephyr, ESP-IDF, Arduino and
3
+ PlatformIO projects.
4
+
5
+ Installed with pip, it is the lib-installer command. It takes a library from
6
+ GitHub, from a downloaded zip, or from a folder. Which platform the project is
7
+ comes from the library's installer.yml, or from --platform when it names none.
8
+
9
+ The work that is the same on every platform is in core/. What a project of
10
+ each platform is, is in stm32/, zephyr/, esp32/, arduino/ and pio/.
11
+
12
+ See https://github.com/nimaltd/lib-installer
13
+ """
14
+
15
+ __version__ = "3.0.1"
16
+
17
+ from .core.installer import InstallError, Result, install_in_place, install_to # noqa: E402
18
+ from .core.manifest import Manifest, ManifestError, load # noqa: E402
19
+
20
+ __all__ = [
21
+ "InstallError",
22
+ "Manifest",
23
+ "ManifestError",
24
+ "Result",
25
+ "install_in_place",
26
+ "install_to",
27
+ "load",
28
+ "main",
29
+ "use",
30
+ ]
31
+
32
+
33
+ def use(platform):
34
+ """
35
+ Choose the platform the functions above work for, such as "zephyr", before
36
+ calling them. The command chooses it by itself.
37
+ """
38
+ from .cli import PLATFORMS
39
+ from .core import target
40
+
41
+ if platform not in PLATFORMS:
42
+ raise ValueError(f"{platform!r} is not a platform. It is one of {', '.join(PLATFORMS)}.")
43
+
44
+ target.use(PLATFORMS[platform]())
45
+
46
+
47
+ def main(argv=None):
48
+ """Run the command line interface. Imported lazily to keep start-up cheap."""
49
+ from .cli import main as _main
50
+
51
+ return _main(argv=argv)
@@ -0,0 +1,8 @@
1
+ """Lets the package be run directly: python -m lib_installer owner/example"""
2
+
3
+ import sys
4
+
5
+ from .cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
@@ -0,0 +1 @@
1
+ """Arduino: the sketchbook's libraries folder, or one sketch's src folder."""
@@ -0,0 +1,334 @@
1
+ """
2
+ What lib-installer knows about Arduino: where Arduino finds a library.
3
+
4
+ Arduino has two places a library can go, and the user chooses:
5
+
6
+ the libraries folder of the sketchbook, where Library Manager installs.
7
+ Every sketch sees the library, and a library that
8
+ includes another finds it. The library gets a
9
+ library.properties, which Arduino knows it by.
10
+ a sketch's src folder for that sketch only, with its own settings. Arduino
11
+ compiles everything in it, but puts none of it on
12
+ the include path: the sketch includes the library by
13
+ its path, "src/example/example.h", and one library
14
+ there cannot find another.
15
+
16
+ So what a library needs always goes into the libraries folder, where any
17
+ library finds it, a library installed into a sketch included.
18
+ """
19
+
20
+ import re
21
+ from pathlib import Path
22
+
23
+ from . import properties, sketchbook
24
+ from ..core import console, installer
25
+ from ..core.installer import InstallError
26
+ from ..core.manifest import ManifestError
27
+ from ..core.target import ALREADY, INFO, OK, WARN, Finding, Outcome, Platform
28
+
29
+ LIBRARIES = "libraries"
30
+ SKETCH = "sketch"
31
+
32
+ # An architecture as boards name it in library.properties: avr, esp32,
33
+ # mbed_nano, or * for any.
34
+ ARCHITECTURE = re.compile(r"^(?:\*|[A-Za-z0-9_.-]+)$")
35
+
36
+ # A library of Library Manager's, with a version when one is needed:
37
+ # "Adafruit BusIO" or "ArduinoJson (>=6.0.0)".
38
+ DEPENDS = re.compile(r"^[A-Za-z0-9][A-Za-z0-9 _.-]*(?: \([<>=!]*[0-9][0-9.]*\))?$")
39
+
40
+
41
+ def _text(data, key, where):
42
+ """One line of text from the arduino section, or None."""
43
+ value = data.get(key)
44
+
45
+ if value is None:
46
+ return None
47
+
48
+ text = str(value).strip()
49
+
50
+ if "\n" in text or "\r" in text:
51
+ raise ManifestError(f"{where} is one line of text.")
52
+
53
+ return text or None
54
+
55
+
56
+ class ArduinoPart:
57
+ """The arduino section of installer.yml, with what it leaves out filled in."""
58
+
59
+ def __init__(self, data):
60
+ if data is None:
61
+ data = {}
62
+
63
+ if not isinstance(data, dict):
64
+ raise ManifestError("arduino should hold architectures, category, author, maintainer and depends.")
65
+
66
+ architectures = data.get("architectures") or ["*"]
67
+
68
+ if isinstance(architectures, str):
69
+ architectures = [architectures]
70
+
71
+ if not isinstance(architectures, list) or not all(ARCHITECTURE.match(str(a)) for a in architectures):
72
+ raise ManifestError(
73
+ "arduino.architectures is a list of the architectures it runs on, such as [avr, esp32], "
74
+ "or [\"*\"] for any."
75
+ )
76
+
77
+ self.architectures = [str(a) for a in architectures]
78
+ self.category = _text(data, "category", "arduino.category")
79
+
80
+ if self.category is not None and self.category not in properties.CATEGORIES:
81
+ raise ManifestError(
82
+ f"arduino.category has {self.category!r}. Use one of Arduino's: {', '.join(properties.CATEGORIES)}."
83
+ )
84
+
85
+ self.author = _text(data, "author", "arduino.author")
86
+ self.maintainer = _text(data, "maintainer", "arduino.maintainer")
87
+
88
+ depends = data.get("depends") or []
89
+
90
+ if not isinstance(depends, list) or not all(DEPENDS.match(str(d).strip()) for d in depends):
91
+ raise ManifestError(
92
+ "arduino.depends is a list of Library Manager libraries by their name, such as "
93
+ '["Adafruit BusIO"], with a version when one is needed: "ArduinoJson (>=6.0.0)".'
94
+ )
95
+
96
+ self.depends = [str(d).strip() for d in depends]
97
+
98
+
99
+ class Arduino(Platform):
100
+ """Arduino: the sketchbook's libraries folder, or one sketch's src folder."""
101
+
102
+ platform = "arduino"
103
+ legacy = "arduino-installer"
104
+ label = "Arduino"
105
+ project = "Arduino sketch"
106
+ no_project = "Nothing was registered."
107
+ no_project_hint = "Arduino finds a library in the libraries folder by itself."
108
+
109
+ def __init__(self, version):
110
+ self.version = version
111
+ self.into = None
112
+ self.sketchbook_given = None
113
+ self.uninstalling = None
114
+ self.ask = None
115
+ # Settled by project_root() at the start of each run.
116
+ self.libraries = None
117
+ self.sketch = None
118
+
119
+ # The command line
120
+
121
+ def add_arguments(self, parser):
122
+ parser.add_argument(
123
+ "--into",
124
+ choices=[LIBRARIES, SKETCH],
125
+ default=None,
126
+ help="where the libraries go: libraries, the sketchbook's libraries folder, for every "
127
+ "sketch, or sketch, the src folder of the sketch this is run from, for it only. Asked "
128
+ "when run from a sketch, libraries otherwise.",
129
+ )
130
+ parser.add_argument(
131
+ "--sketchbook",
132
+ default=None,
133
+ metavar="FOLDER",
134
+ help="the sketchbook folder, the one holding libraries/. Found the way Arduino finds it "
135
+ "when not given.",
136
+ )
137
+
138
+ def take_arguments(self, args, ask):
139
+ self.into = getattr(args, "into", None)
140
+ self.sketchbook_given = getattr(args, "sketchbook", None)
141
+ self.uninstalling = getattr(args, "uninstall", None)
142
+ self.ask = ask
143
+ self.sketch = None
144
+
145
+ # Where the run works
146
+
147
+ def legacy_roots(self, given):
148
+ """
149
+ The sketch run from and the sketchbook's libraries folder, since
150
+ arduino-installer kept a record in whichever it installed into.
151
+ """
152
+ found, _ = sketchbook.sketchbook(self.sketchbook_given)
153
+ libraries = found / "libraries"
154
+
155
+ return [given] if libraries == given else [given, libraries]
156
+
157
+ def _ask_where(self, sketch, libraries, default):
158
+ """Ask which of the two places the libraries go into. Returns LIBRARIES or SKETCH."""
159
+ if self.ask is None:
160
+ return default
161
+
162
+ first = "1" if default == LIBRARIES else "2"
163
+ print()
164
+ print(console.heading(f"This is the sketch {sketch.name}. Where do the libraries go?"))
165
+ print(f" 1 the libraries folder, for every sketch: {libraries.as_posix()}")
166
+ print(f" 2 this sketch's src folder, for this sketch only")
167
+ answer = (self.ask(console.strong(f"Install into [{first}]: ")) or first).strip().lower()
168
+
169
+ return SKETCH if answer in ("2", "s", SKETCH, "src") else LIBRARIES
170
+
171
+ def _recorded_where(self, given, names):
172
+ """Where the libraries to take out are recorded: SKETCH, LIBRARIES, or None."""
173
+ in_sketch = set(installer.installed_libraries(given)) if sketchbook.is_sketch(given) else set()
174
+ in_libraries = set(installer.installed_libraries(self.libraries))
175
+ names = set(names)
176
+
177
+ if names <= in_sketch:
178
+ return SKETCH
179
+
180
+ if names <= in_libraries:
181
+ return LIBRARIES
182
+
183
+ if names & in_sketch:
184
+ raise InstallError(
185
+ "Some of these are in this sketch and some in the libraries folder. Take them out "
186
+ "in two runs, with --into sketch and --into libraries."
187
+ )
188
+
189
+ return None
190
+
191
+ def project_root(self, given):
192
+ """
193
+ The libraries folder, or the sketch run from, as the user chose.
194
+
195
+ Run from a sketch, the user is asked, and in a sketch the installer
196
+ has put libraries into before, its src folder is the answer offered.
197
+ Anywhere else it is the libraries folder, without a question.
198
+ """
199
+ found, how = sketchbook.sketchbook(self.sketchbook_given)
200
+ self.libraries = found / "libraries"
201
+ is_sketch = sketchbook.is_sketch(given)
202
+ where = self.into
203
+
204
+ if where is None and self.uninstalling:
205
+ where = self._recorded_where(given, self.uninstalling)
206
+
207
+ if where == SKETCH and not is_sketch:
208
+ raise InstallError(
209
+ f"{given.as_posix()} is not a sketch: it has no {given.name}.ino. Run this from "
210
+ "the sketch's folder, or give it with --project."
211
+ )
212
+
213
+ if where is None and is_sketch and not self.uninstalling:
214
+ used_before = bool(installer.installed_libraries(given))
215
+ where = self._ask_where(given, self.libraries, SKETCH if used_before else LIBRARIES)
216
+
217
+ if where == SKETCH:
218
+ self.sketch = given
219
+ return given
220
+
221
+ print(console.note(f"Libraries folder: {self.libraries.as_posix()} (from {how})"))
222
+ self.libraries.mkdir(parents=True, exist_ok=True)
223
+
224
+ return self.libraries
225
+
226
+ def dependency_root(self, project_root):
227
+ """What a library needs goes into the libraries folder, where any library finds it."""
228
+ return self.libraries
229
+
230
+ no_once_root = ("The libraries folder is shared by every sketch, so it has no place for them. "
231
+ "Install it into a sketch, with --into sketch, to get them.")
232
+
233
+ def once_root(self, project_root):
234
+ """
235
+ The sketch, for a library installed into one. The libraries folder is no
236
+ sketch's own, so there a once file, or a run step, has nowhere to go.
237
+ """
238
+ return None if self._in_libraries(project_root) else project_root
239
+
240
+ def managed_files(self, project_root, library):
241
+ """The sketch's own .ino files, which a once file must never be."""
242
+ return [self.record_name, "*.ino"]
243
+
244
+ def _in_libraries(self, project_root):
245
+ return Path(project_root).resolve() == Path(self.libraries).resolve()
246
+
247
+ def folder_for(self, library, project_root):
248
+ """Its name in the libraries folder, src/<name> in a sketch."""
249
+ return library.name if self._in_libraries(project_root) else f"src/{library.name}"
250
+
251
+ def include_line(self, library, destination, project_root):
252
+ """In a sketch, the header by its path from the sketch, which is how a sketch finds it."""
253
+ if self._in_libraries(project_root) or not library.headers:
254
+ return None
255
+
256
+ header = Path(destination) / library.headers[0].destination
257
+
258
+ try:
259
+ return header.resolve().relative_to(Path(project_root).resolve()).as_posix()
260
+ except ValueError:
261
+ return None
262
+
263
+ # installer.yml
264
+
265
+ def read_manifest(self, manifest, data):
266
+ manifest.arduino = ArduinoPart(data.get("arduino"))
267
+
268
+ def read_requirements(self, requirements, data):
269
+ """Hardware the library uses, named to the user: I2C, or I2C, 400 kHz."""
270
+ found = []
271
+
272
+ for entry in data.get("peripherals") or []:
273
+ if isinstance(entry, dict):
274
+ note = entry.get("note")
275
+ found.append(str(entry.get("type", "")).upper() + (f", {note}" if note else ""))
276
+ else:
277
+ found.append(str(entry).upper())
278
+
279
+ requirements.peripherals = found
280
+
281
+ def requirements_empty(self, requirements):
282
+ return not requirements.peripherals
283
+
284
+ # Before, during and after an install
285
+
286
+ def check(self, library, project_root):
287
+ """What Arduino cannot do for this library, said before it goes in. Never stops an install."""
288
+ findings = []
289
+ in_libraries = self._in_libraries(project_root)
290
+
291
+ if library.defines:
292
+ findings.append(Finding(
293
+ WARN, f"Arduino has no defines for a whole build, so these are not set: {', '.join(library.defines)}",
294
+ f"Set them in {library.name}'s configuration header, if it has one.",
295
+ ))
296
+
297
+ if not in_libraries:
298
+ for dependency in library.dependencies:
299
+ findings.append(Finding(
300
+ INFO, f"{dependency.name}, which it needs, goes into the libraries folder",
301
+ "In a sketch's src folder one library cannot find another. In the libraries "
302
+ "folder every library and sketch can.",
303
+ ))
304
+
305
+ for name in library.arduino.depends:
306
+ plain = name.split(" (")[0]
307
+
308
+ if (Path(self.libraries) / plain).is_dir() or (Path(self.libraries) / plain.replace(" ", "_")).is_dir():
309
+ findings.append(Finding(OK, f"{plain}, from Library Manager, is installed"))
310
+ else:
311
+ findings.append(Finding(WARN, f"Needs {plain}, from Library Manager",
312
+ "Install it with the Library Manager, or arduino-cli lib install."))
313
+
314
+ for peripheral in library.requires.peripherals:
315
+ findings.append(Finding(INFO, f"Needs {peripheral}", "Wire it up and set it up in your sketch."))
316
+
317
+ return findings
318
+
319
+ def integrate(self, project_root, library, destination, only=None, dropped=(), dropped_defines=()):
320
+ """In the libraries folder, a library.properties. In a sketch, nothing: Arduino builds src/ itself."""
321
+ if self._in_libraries(project_root):
322
+ return [properties.write(library, destination)]
323
+
324
+ return [Outcome(
325
+ "Sketch", ALREADY,
326
+ "Arduino builds every file in the sketch's src folder, so there is nothing to register.",
327
+ )]
328
+
329
+ def uninstall(self, project_root, library):
330
+ """The library.properties written for it goes, before its files do."""
331
+ if library.destination is not None:
332
+ properties.remove(library.destination)
333
+
334
+ return []
@@ -0,0 +1,168 @@
1
+ """
2
+ library.properties: how a library in the libraries folder introduces itself to Arduino.
3
+
4
+ The Arduino IDE and arduino-cli read it for the library's name, version and
5
+ the boards it runs on, and show it in Library Manager's list of what is
6
+ installed. A library written for any project has none, so it is written here,
7
+ from installer.yml, into the library's folder, and written again on every
8
+ install and update.
9
+
10
+ A library made for Arduino ships its own, which knows the library better than
11
+ this does, and is left alone. Its first line tells the two apart: this one
12
+ starts with a comment, which Arduino skips.
13
+ """
14
+
15
+ import re
16
+ from pathlib import Path, PurePosixPath
17
+
18
+ from ..core.target import ALREADY, CHANGED, MANUAL, Outcome
19
+
20
+ NAME = "library.properties"
21
+
22
+ MARK = "# Generated by lib-installer"
23
+
24
+ # Every first line taken as this one's: MARK, and what arduino-installer
25
+ # wrote before 3.0.0, so a file it wrote is still written again, not refused.
26
+ MARKS = (MARK, "# Generated by arduino-installer")
27
+
28
+ # Arduino's own categories, the only ones Library Manager takes.
29
+ CATEGORIES = (
30
+ "Display", "Communication", "Signal Input/Output", "Sensors", "Device Control",
31
+ "Timing", "Data Storage", "Data Processing", "Other",
32
+ )
33
+
34
+ # installer.yml's category, as the nearest of Arduino's.
35
+ FROM_CATEGORY = {
36
+ "sensor": "Sensors",
37
+ "storage": "Data Storage",
38
+ "display": "Display",
39
+ "communication": "Communication",
40
+ "wireless": "Communication",
41
+ "timing": "Timing",
42
+ "power": "Device Control",
43
+ "input": "Signal Input/Output",
44
+ "math": "Data Processing",
45
+ }
46
+
47
+ _OWNER = re.compile(r"github\.com[/:]([^/\s]+)/", re.IGNORECASE)
48
+
49
+
50
+ def _label(library):
51
+ return f"{library.name} {library.version}".strip()
52
+
53
+
54
+ def author(library):
55
+ """The author: as installer.yml gives it, or the owner of the repository it comes from."""
56
+ if library.arduino.author:
57
+ return library.arduino.author
58
+
59
+ found = _OWNER.search(library.repository or "")
60
+
61
+ return found.group(1) if found else "Unknown"
62
+
63
+
64
+ def category(library):
65
+ """Arduino's category for the library: as given, or the nearest to its own category."""
66
+ return library.arduino.category or FROM_CATEGORY.get(library.category, "Other")
67
+
68
+
69
+ def includes(library):
70
+ """The header a sketch includes, the way the include folder makes it."""
71
+ if not library.headers:
72
+ return None
73
+
74
+ landed = PurePosixPath(library.headers[0].destination)
75
+
76
+ for folder in sorted(library.include_dirs, key=len, reverse=True):
77
+ if folder in (".", ""):
78
+ continue
79
+
80
+ try:
81
+ return landed.relative_to(folder).as_posix()
82
+ except ValueError:
83
+ continue
84
+
85
+ return landed.as_posix()
86
+
87
+
88
+ def one_line(text):
89
+ """A value for one line of the file: no line breaks."""
90
+ return " ".join(str(text).split())
91
+
92
+
93
+ def properties(library):
94
+ """The text of library.properties for this library."""
95
+ name = author(library)
96
+ lines = [
97
+ f"{MARK} for {_label(library)}. Written again on",
98
+ "# every install and update, so a change made here does not last.",
99
+ f"name={library.name}",
100
+ ]
101
+
102
+ if library.version:
103
+ lines.append(f"version={library.version}")
104
+
105
+ lines += [
106
+ f"author={one_line(name)}",
107
+ f"maintainer={one_line(library.arduino.maintainer or name)}",
108
+ f"sentence={one_line(library.description or library.name)}",
109
+ "paragraph=",
110
+ f"category={category(library)}",
111
+ f"url={one_line(library.repository or '')}",
112
+ f"architectures={','.join(library.arduino.architectures)}",
113
+ ]
114
+
115
+ if library.arduino.depends:
116
+ lines.append(f"depends={','.join(library.arduino.depends)}")
117
+
118
+ header = includes(library)
119
+
120
+ if header:
121
+ lines.append(f"includes={header}")
122
+
123
+ return "\n".join(lines) + "\n"
124
+
125
+
126
+ def _ours(path):
127
+ try:
128
+ return path.read_text(encoding="utf-8").startswith(MARKS)
129
+ except (OSError, UnicodeDecodeError):
130
+ return False
131
+
132
+
133
+ def ships_its_own(destination):
134
+ """Whether the library brought a library.properties of its own."""
135
+ path = Path(destination) / NAME
136
+
137
+ return path.is_file() and not _ours(path)
138
+
139
+
140
+ def write(library, destination):
141
+ """Write library.properties, only when it changed. Returns an Outcome."""
142
+ path = Path(destination) / NAME
143
+
144
+ if ships_its_own(destination):
145
+ return Outcome(NAME, ALREADY, f"{library.name} has its own library.properties, used as it is.")
146
+
147
+ data = properties(library).encode("utf-8")
148
+
149
+ try:
150
+ if path.is_file() and path.read_bytes() == data:
151
+ return Outcome(NAME, ALREADY, f"{library.name}'s library.properties is up to date.")
152
+
153
+ path.write_bytes(data)
154
+ except OSError as error:
155
+ return Outcome(NAME, MANUAL, f"Could not write {path}: {error}")
156
+
157
+ return Outcome(NAME, CHANGED, f"Wrote {library.name}'s library.properties, which Arduino knows it by.")
158
+
159
+
160
+ def remove(destination):
161
+ """Delete the library.properties write() made. One of the library's own is not touched."""
162
+ path = Path(destination) / NAME
163
+
164
+ if path.is_file() and _ours(path):
165
+ try:
166
+ path.unlink()
167
+ except OSError:
168
+ pass