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.
- lib_installer/__init__.py +51 -0
- lib_installer/__main__.py +8 -0
- lib_installer/arduino/__init__.py +1 -0
- lib_installer/arduino/arduino.py +334 -0
- lib_installer/arduino/properties.py +168 -0
- lib_installer/arduino/sketchbook.py +133 -0
- lib_installer/cli.py +38 -0
- lib_installer/core/__init__.py +8 -0
- lib_installer/core/blocks.py +373 -0
- lib_installer/core/cli.py +1432 -0
- lib_installer/core/console.py +132 -0
- lib_installer/core/download.py +781 -0
- lib_installer/core/files.py +35 -0
- lib_installer/core/installer.py +1222 -0
- lib_installer/core/manifest.py +1487 -0
- lib_installer/core/steps.py +222 -0
- lib_installer/core/target.py +261 -0
- lib_installer/core/updates.py +120 -0
- lib_installer/core/usercode.py +193 -0
- lib_installer/core/yamlreader.py +417 -0
- lib_installer/esp32/__init__.py +1 -0
- lib_installer/esp32/component.py +354 -0
- lib_installer/esp32/esp32.py +491 -0
- lib_installer/esp32/project.py +768 -0
- lib_installer/pio/__init__.py +1 -0
- lib_installer/pio/ini.py +304 -0
- lib_installer/pio/library_json.py +161 -0
- lib_installer/pio/pio.py +208 -0
- lib_installer/stm32/__init__.py +1 -0
- lib_installer/stm32/checks.py +204 -0
- lib_installer/stm32/ide/__init__.py +110 -0
- lib_installer/stm32/ide/base.py +269 -0
- lib_installer/stm32/ide/cmake.py +299 -0
- lib_installer/stm32/ide/cubeide.py +344 -0
- lib_installer/stm32/ide/iar.py +509 -0
- lib_installer/stm32/ide/keil.py +419 -0
- lib_installer/stm32/ide/makefile.py +376 -0
- lib_installer/stm32/stm32.py +101 -0
- lib_installer/zephyr/__init__.py +1 -0
- lib_installer/zephyr/app.py +648 -0
- lib_installer/zephyr/module.py +375 -0
- lib_installer/zephyr/zephyr.py +620 -0
- lib_installer-3.0.1.dist-info/METADATA +583 -0
- lib_installer-3.0.1.dist-info/RECORD +48 -0
- lib_installer-3.0.1.dist-info/WHEEL +5 -0
- lib_installer-3.0.1.dist-info/entry_points.txt +2 -0
- lib_installer-3.0.1.dist-info/licenses/LICENSE +201 -0
- 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 @@
|
|
|
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
|