idf-build-apps 2.4.2__py3-none-any.whl → 2.5.0__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.
- idf_build_apps/__init__.py +1 -1
- idf_build_apps/app.py +31 -27
- idf_build_apps/args.py +846 -0
- idf_build_apps/constants.py +18 -28
- idf_build_apps/finder.py +34 -54
- idf_build_apps/log.py +2 -0
- idf_build_apps/main.py +159 -622
- idf_build_apps/manifest/if_parser.py +14 -13
- idf_build_apps/manifest/manifest.py +124 -28
- idf_build_apps/utils.py +27 -1
- idf_build_apps/vendors/__init__.py +0 -0
- idf_build_apps/vendors/pydantic_sources.py +120 -0
- idf_build_apps/yaml/parser.py +3 -1
- {idf_build_apps-2.4.2.dist-info → idf_build_apps-2.5.0.dist-info}/METADATA +4 -4
- idf_build_apps-2.5.0.dist-info/RECORD +27 -0
- idf_build_apps/build_apps_args.py +0 -64
- idf_build_apps/config.py +0 -91
- idf_build_apps-2.4.2.dist-info/RECORD +0 -26
- {idf_build_apps-2.4.2.dist-info → idf_build_apps-2.5.0.dist-info}/LICENSE +0 -0
- {idf_build_apps-2.4.2.dist-info → idf_build_apps-2.5.0.dist-info}/WHEEL +0 -0
- {idf_build_apps-2.4.2.dist-info → idf_build_apps-2.5.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
3
|
import operator
|
|
5
4
|
import os
|
|
6
5
|
from ast import (
|
|
@@ -16,6 +15,7 @@ from packaging.version import (
|
|
|
16
15
|
from pyparsing import (
|
|
17
16
|
Keyword,
|
|
18
17
|
Literal,
|
|
18
|
+
MatchFirst,
|
|
19
19
|
ParseResults,
|
|
20
20
|
QuotedString,
|
|
21
21
|
Suppress,
|
|
@@ -35,6 +35,7 @@ from ..constants import (
|
|
|
35
35
|
IDF_VERSION_PATCH,
|
|
36
36
|
)
|
|
37
37
|
from ..utils import (
|
|
38
|
+
InvalidIfClause,
|
|
38
39
|
InvalidInput,
|
|
39
40
|
to_version,
|
|
40
41
|
)
|
|
@@ -177,22 +178,23 @@ class BoolExpr(Stmt):
|
|
|
177
178
|
pass
|
|
178
179
|
|
|
179
180
|
|
|
180
|
-
class
|
|
181
|
+
class BoolOrAnd(BoolExpr):
|
|
181
182
|
def __init__(self, t: ParseResults):
|
|
183
|
+
if len(t[0]) > 3:
|
|
184
|
+
raise InvalidIfClause(
|
|
185
|
+
'Chaining "and"/"or" is not allowed. Please use paratheses instead. '
|
|
186
|
+
'For example: "a and b and c" should be "(a and b) and c".'
|
|
187
|
+
)
|
|
182
188
|
self.left: BoolStmt = t[0][0]
|
|
183
189
|
self.right: BoolStmt = t[0][2]
|
|
184
190
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
class BoolOr(BoolExpr):
|
|
190
|
-
def __init__(self, t: ParseResults):
|
|
191
|
-
self.left: BoolStmt = t[0][0]
|
|
192
|
-
self.right: BoolStmt = t[0][2]
|
|
191
|
+
if t[0][1] == 'and':
|
|
192
|
+
self.operation = lambda l, r: l and r # noqa: E741
|
|
193
|
+
if t[0][1] == 'or':
|
|
194
|
+
self.operation = lambda l, r: l or r # noqa: E741
|
|
193
195
|
|
|
194
196
|
def get_value(self, target: str, config_name: str) -> Any:
|
|
195
|
-
return self.left.get_value(target, config_name)
|
|
197
|
+
return self.operation(self.left.get_value(target, config_name), self.right.get_value(target, config_name))
|
|
196
198
|
|
|
197
199
|
|
|
198
200
|
CAP_WORD = Word(alphas.upper(), nums + alphas.upper() + '_').setParseAction(ChipAttr)
|
|
@@ -225,7 +227,6 @@ OR = Keyword('or')
|
|
|
225
227
|
BOOL_EXPR = infixNotation(
|
|
226
228
|
BOOL_STMT,
|
|
227
229
|
[
|
|
228
|
-
(AND, 2, opAssoc.LEFT,
|
|
229
|
-
(OR, 2, opAssoc.LEFT, BoolOr),
|
|
230
|
+
(MatchFirst((AND, OR)), 2, opAssoc.LEFT, BoolOrAnd),
|
|
230
231
|
],
|
|
231
232
|
)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
3
|
import logging
|
|
5
4
|
import os
|
|
5
|
+
import pickle
|
|
6
6
|
import typing as t
|
|
7
|
-
import
|
|
7
|
+
from hashlib import sha512
|
|
8
8
|
|
|
9
9
|
from pyparsing import (
|
|
10
10
|
ParseException,
|
|
@@ -17,6 +17,8 @@ from ..constants import (
|
|
|
17
17
|
from ..utils import (
|
|
18
18
|
InvalidIfClause,
|
|
19
19
|
InvalidManifest,
|
|
20
|
+
PathLike,
|
|
21
|
+
to_absolute_path,
|
|
20
22
|
)
|
|
21
23
|
from ..yaml import (
|
|
22
24
|
parse,
|
|
@@ -33,14 +35,21 @@ class IfClause:
|
|
|
33
35
|
def __init__(self, stmt: str, temporary: bool = False, reason: t.Optional[str] = None) -> None:
|
|
34
36
|
try:
|
|
35
37
|
self.stmt: BoolStmt = BOOL_EXPR.parseString(stmt)[0]
|
|
36
|
-
except ParseException:
|
|
37
|
-
raise InvalidIfClause(f'Invalid if
|
|
38
|
+
except (ParseException, InvalidIfClause) as ex:
|
|
39
|
+
raise InvalidIfClause(f'Invalid if clause: {stmt}. {ex}')
|
|
38
40
|
|
|
39
41
|
self.temporary = temporary
|
|
40
42
|
self.reason = reason
|
|
41
43
|
|
|
42
44
|
if self.temporary is True and not self.reason:
|
|
43
|
-
raise InvalidIfClause(
|
|
45
|
+
raise InvalidIfClause(
|
|
46
|
+
f'Invalid if clause "{stmt}". '
|
|
47
|
+
f'"reason" must be set when "temporary: true". '
|
|
48
|
+
f'For example:\n'
|
|
49
|
+
f' - if: {stmt}\n'
|
|
50
|
+
f' temporary: true\n'
|
|
51
|
+
f' reason: lack of ci runners'
|
|
52
|
+
)
|
|
44
53
|
|
|
45
54
|
def get_value(self, target: str, config_name: str) -> t.Any:
|
|
46
55
|
return self.stmt.get_value(target, config_name)
|
|
@@ -127,7 +136,26 @@ class FolderRule:
|
|
|
127
136
|
self.depends_filepatterns = _clause_to_switch_or_list(depends_filepatterns)
|
|
128
137
|
|
|
129
138
|
def __hash__(self) -> int:
|
|
130
|
-
return hash(self.
|
|
139
|
+
return hash(self.sha)
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def sha(self) -> str:
|
|
143
|
+
"""
|
|
144
|
+
SHA of the FolderRule instance
|
|
145
|
+
|
|
146
|
+
:return: SHA value
|
|
147
|
+
"""
|
|
148
|
+
sha = sha512()
|
|
149
|
+
for obj in [
|
|
150
|
+
self.enable,
|
|
151
|
+
self.disable,
|
|
152
|
+
self.disable_test,
|
|
153
|
+
self.depends_components,
|
|
154
|
+
self.depends_filepatterns,
|
|
155
|
+
]:
|
|
156
|
+
sha.update(pickle.dumps(obj, protocol=4)) # protocol 4 by default is set in Python 3.8
|
|
157
|
+
|
|
158
|
+
return sha.hexdigest()
|
|
131
159
|
|
|
132
160
|
def __repr__(self) -> str:
|
|
133
161
|
return f'FolderRule({self.folder})'
|
|
@@ -207,23 +235,28 @@ class DefaultRule(FolderRule):
|
|
|
207
235
|
|
|
208
236
|
class Manifest:
|
|
209
237
|
# could be reassigned later
|
|
210
|
-
ROOTPATH = os.curdir
|
|
211
238
|
CHECK_MANIFEST_RULES = False
|
|
212
239
|
|
|
213
|
-
def __init__(
|
|
214
|
-
self,
|
|
215
|
-
rules: t.Iterable[FolderRule],
|
|
216
|
-
) -> None:
|
|
240
|
+
def __init__(self, rules: t.Iterable[FolderRule], *, root_path: str = os.curdir) -> None:
|
|
217
241
|
self.rules = sorted(rules, key=lambda x: x.folder)
|
|
218
242
|
|
|
243
|
+
self._root_path = to_absolute_path(root_path)
|
|
244
|
+
|
|
219
245
|
@classmethod
|
|
220
|
-
def from_files(cls, paths: t.
|
|
221
|
-
|
|
222
|
-
|
|
246
|
+
def from_files(cls, paths: t.Iterable[PathLike], *, root_path: str = os.curdir) -> 'Manifest':
|
|
247
|
+
"""
|
|
248
|
+
Create a Manifest instance from multiple manifest files
|
|
249
|
+
|
|
250
|
+
:param paths: manifest file paths
|
|
251
|
+
:param root_path: root path for relative paths in manifest files
|
|
252
|
+
:return: Manifest instance
|
|
253
|
+
"""
|
|
254
|
+
# folder, defined as dict
|
|
255
|
+
_known_folders: t.Dict[str, PathLike] = dict()
|
|
223
256
|
|
|
224
257
|
rules: t.List[FolderRule] = []
|
|
225
258
|
for path in paths:
|
|
226
|
-
_manifest = cls.from_file(path)
|
|
259
|
+
_manifest = cls.from_file(path, root_path=root_path)
|
|
227
260
|
|
|
228
261
|
for rule in _manifest.rules:
|
|
229
262
|
if rule.folder in _known_folders:
|
|
@@ -231,43 +264,106 @@ class Manifest:
|
|
|
231
264
|
if cls.CHECK_MANIFEST_RULES:
|
|
232
265
|
raise InvalidManifest(msg)
|
|
233
266
|
else:
|
|
234
|
-
|
|
267
|
+
LOGGER.warning(msg)
|
|
235
268
|
|
|
236
269
|
_known_folders[rule.folder] = path
|
|
237
270
|
|
|
238
271
|
rules.extend(_manifest.rules)
|
|
239
272
|
|
|
240
|
-
return Manifest(rules)
|
|
273
|
+
return Manifest(rules, root_path=root_path)
|
|
241
274
|
|
|
242
275
|
@classmethod
|
|
243
|
-
def from_file(cls, path: str) -> 'Manifest':
|
|
276
|
+
def from_file(cls, path: PathLike, *, root_path: str = os.curdir) -> 'Manifest':
|
|
277
|
+
"""
|
|
278
|
+
Create a Manifest instance from a manifest file
|
|
279
|
+
|
|
280
|
+
:param path: path to the manifest file
|
|
281
|
+
:param root_path: root path for relative paths in manifest file
|
|
282
|
+
:return: Manifest instance
|
|
283
|
+
"""
|
|
244
284
|
manifest_dict = parse(path)
|
|
245
285
|
|
|
246
286
|
rules: t.List[FolderRule] = []
|
|
247
287
|
for folder, folder_rule in manifest_dict.items():
|
|
248
|
-
# not a folder, but
|
|
288
|
+
# not a folder, but an anchor
|
|
249
289
|
if folder.startswith('.'):
|
|
250
290
|
continue
|
|
251
291
|
|
|
252
292
|
if not os.path.isabs(folder):
|
|
253
|
-
folder = os.path.join(
|
|
293
|
+
folder = os.path.join(root_path, folder)
|
|
254
294
|
|
|
255
295
|
if not os.path.exists(folder):
|
|
256
296
|
msg = f'Folder "{folder}" does not exist. Please check your manifest file {path}'
|
|
257
297
|
if cls.CHECK_MANIFEST_RULES:
|
|
258
298
|
raise InvalidManifest(msg)
|
|
259
299
|
else:
|
|
260
|
-
|
|
300
|
+
LOGGER.warning(msg)
|
|
261
301
|
|
|
262
302
|
try:
|
|
263
303
|
rules.append(FolderRule(folder, **folder_rule if folder_rule else {}))
|
|
264
304
|
except InvalidIfClause as e:
|
|
265
305
|
raise InvalidManifest(f'Invalid manifest file {path}: {e}')
|
|
266
306
|
|
|
267
|
-
return Manifest(rules)
|
|
307
|
+
return Manifest(rules, root_path=root_path)
|
|
308
|
+
|
|
309
|
+
def dump_sha_values(self, sha_filepath: str) -> None:
|
|
310
|
+
"""
|
|
311
|
+
Dump the (relative path of the folder, SHA of the FolderRule instance) pairs
|
|
312
|
+
for all rules to the file in format: ``<relative_path>:<SHA>``
|
|
313
|
+
|
|
314
|
+
:param sha_filepath: output file path
|
|
315
|
+
:return: None
|
|
316
|
+
"""
|
|
317
|
+
with open(sha_filepath, 'w') as fw:
|
|
318
|
+
for rule in self.rules:
|
|
319
|
+
fw.write(f'{os.path.relpath(rule.folder, self._root_path)}:{rule.sha}\n')
|
|
320
|
+
|
|
321
|
+
def diff_sha_with_filepath(self, sha_filepath: str, use_abspath: bool = False) -> t.Set[str]:
|
|
322
|
+
"""
|
|
323
|
+
Compare the SHA recorded in the file with the current Manifest instance.
|
|
324
|
+
|
|
325
|
+
:param sha_filepath: dumped SHA file path
|
|
326
|
+
:param use_abspath: whether to return the absolute path of the folders
|
|
327
|
+
:return: Set of folders that have different SHA values
|
|
328
|
+
"""
|
|
329
|
+
recorded__rel_folder__sha__dict = dict()
|
|
330
|
+
with open(sha_filepath) as fr:
|
|
331
|
+
for line in fr:
|
|
332
|
+
line = line.strip()
|
|
333
|
+
if line:
|
|
334
|
+
try:
|
|
335
|
+
folder, sha_value = line.strip().rsplit(':', maxsplit=1)
|
|
336
|
+
except ValueError:
|
|
337
|
+
raise InvalidManifest(f'Invalid line in SHA file: {line}. Expected format: <folder>:<SHA>')
|
|
338
|
+
|
|
339
|
+
recorded__rel_folder__sha__dict[folder] = sha_value
|
|
340
|
+
|
|
341
|
+
self__rel_folder__sha__dict = {os.path.relpath(rule.folder, self._root_path): rule.sha for rule in self.rules}
|
|
342
|
+
|
|
343
|
+
diff_folders = set()
|
|
344
|
+
if use_abspath:
|
|
345
|
+
|
|
346
|
+
def _path(x):
|
|
347
|
+
return os.path.join(self._root_path, x)
|
|
348
|
+
else:
|
|
349
|
+
|
|
350
|
+
def _path(x):
|
|
351
|
+
return x
|
|
352
|
+
|
|
353
|
+
for folder, sha_value in recorded__rel_folder__sha__dict.items():
|
|
354
|
+
if folder not in self__rel_folder__sha__dict:
|
|
355
|
+
diff_folders.add(_path(folder))
|
|
356
|
+
elif sha_value != self__rel_folder__sha__dict[folder]:
|
|
357
|
+
diff_folders.add(_path(folder))
|
|
358
|
+
|
|
359
|
+
for folder in self__rel_folder__sha__dict:
|
|
360
|
+
if folder not in recorded__rel_folder__sha__dict:
|
|
361
|
+
diff_folders.add(_path(folder))
|
|
362
|
+
|
|
363
|
+
return diff_folders
|
|
268
364
|
|
|
269
|
-
def
|
|
270
|
-
folder =
|
|
365
|
+
def most_suitable_rule(self, _folder: str) -> FolderRule:
|
|
366
|
+
folder = to_absolute_path(_folder)
|
|
271
367
|
for rule in self.rules[::-1]:
|
|
272
368
|
if os.path.commonpath([folder, rule.folder]) == rule.folder:
|
|
273
369
|
return rule
|
|
@@ -277,17 +373,17 @@ class Manifest:
|
|
|
277
373
|
def enable_build_targets(
|
|
278
374
|
self, folder: str, default_sdkconfig_target: t.Optional[str] = None, config_name: t.Optional[str] = None
|
|
279
375
|
) -> t.List[str]:
|
|
280
|
-
return self.
|
|
376
|
+
return self.most_suitable_rule(folder).enable_build_targets(default_sdkconfig_target, config_name)
|
|
281
377
|
|
|
282
378
|
def enable_test_targets(
|
|
283
379
|
self, folder: str, default_sdkconfig_target: t.Optional[str] = None, config_name: t.Optional[str] = None
|
|
284
380
|
) -> t.List[str]:
|
|
285
|
-
return self.
|
|
381
|
+
return self.most_suitable_rule(folder).enable_test_targets(default_sdkconfig_target, config_name)
|
|
286
382
|
|
|
287
383
|
def depends_components(
|
|
288
384
|
self, folder: str, default_sdkconfig_target: t.Optional[str] = None, config_name: t.Optional[str] = None
|
|
289
385
|
) -> t.List[str]:
|
|
290
|
-
res = self.
|
|
386
|
+
res = self.most_suitable_rule(folder).depends_components
|
|
291
387
|
if isinstance(res, list):
|
|
292
388
|
return res
|
|
293
389
|
return res.get_value(default_sdkconfig_target or '', config_name or '')
|
|
@@ -295,7 +391,7 @@ class Manifest:
|
|
|
295
391
|
def depends_filepatterns(
|
|
296
392
|
self, folder: str, default_sdkconfig_target: t.Optional[str] = None, config_name: t.Optional[str] = None
|
|
297
393
|
) -> t.List[str]:
|
|
298
|
-
res = self.
|
|
394
|
+
res = self.most_suitable_rule(folder).depends_filepatterns
|
|
299
395
|
if isinstance(res, list):
|
|
300
396
|
return res
|
|
301
397
|
return res.get_value(default_sdkconfig_target or '', config_name or '')
|
idf_build_apps/utils.py
CHANGED
|
@@ -13,6 +13,7 @@ import typing as t
|
|
|
13
13
|
from copy import (
|
|
14
14
|
deepcopy,
|
|
15
15
|
)
|
|
16
|
+
from pathlib import Path
|
|
16
17
|
|
|
17
18
|
from packaging.version import (
|
|
18
19
|
Version,
|
|
@@ -21,6 +22,24 @@ from pydantic import BaseModel as _BaseModel
|
|
|
21
22
|
|
|
22
23
|
LOGGER = logging.getLogger(__name__)
|
|
23
24
|
|
|
25
|
+
if sys.version_info < (3, 8):
|
|
26
|
+
from typing_extensions import (
|
|
27
|
+
Literal,
|
|
28
|
+
)
|
|
29
|
+
else:
|
|
30
|
+
from typing import (
|
|
31
|
+
Literal, # noqa
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if sys.version_info < (3, 11):
|
|
35
|
+
from typing_extensions import (
|
|
36
|
+
Self,
|
|
37
|
+
)
|
|
38
|
+
else:
|
|
39
|
+
from typing import (
|
|
40
|
+
Self, # noqa
|
|
41
|
+
)
|
|
42
|
+
|
|
24
43
|
|
|
25
44
|
class ConfigRule:
|
|
26
45
|
def __init__(self, file_name: str, config_name: str = '') -> None:
|
|
@@ -106,7 +125,7 @@ class InvalidManifest(SystemExit):
|
|
|
106
125
|
"""Invalid manifest file"""
|
|
107
126
|
|
|
108
127
|
|
|
109
|
-
def rmdir(path: str, exclude_file_patterns: t.Union[t.List[str], str, None] = None) -> None:
|
|
128
|
+
def rmdir(path: t.Union[Path, str], exclude_file_patterns: t.Union[t.List[str], str, None] = None) -> None:
|
|
110
129
|
if not exclude_file_patterns:
|
|
111
130
|
shutil.rmtree(path, ignore_errors=True)
|
|
112
131
|
return
|
|
@@ -365,3 +384,10 @@ class BaseModel(_BaseModel):
|
|
|
365
384
|
hash_list.append(v)
|
|
366
385
|
|
|
367
386
|
return hash((type(self), *tuple(hash_list)))
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def drop_none_kwargs(d: dict) -> dict:
|
|
390
|
+
return {k: v for k, v in d.items() if v is not None}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
PathLike = t.Union[str, Path]
|
|
File without changes
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2022 Samuel Colvin and other contributors
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Partially copied from https://github.com/pydantic/pydantic-settings v2.5.2
|
|
6
|
+
since python 3.7 version got dropped at pydantic-settings 2.1.0
|
|
7
|
+
but the feature we need introduced in 2.2.0
|
|
8
|
+
|
|
9
|
+
For contributing history please refer to the original github page
|
|
10
|
+
For the full license text refer to
|
|
11
|
+
https://github.com/pydantic/pydantic-settings/blob/9b73e924cab136d876907af0c6836dcca09ac35c/LICENSE
|
|
12
|
+
|
|
13
|
+
Modifications:
|
|
14
|
+
- use toml instead of tomli when python < 3.11
|
|
15
|
+
- stop using global variables
|
|
16
|
+
- fix some warnings
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from abc import ABC, abstractmethod
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
|
24
|
+
|
|
25
|
+
from pydantic_settings import InitSettingsSource
|
|
26
|
+
from pydantic_settings.main import BaseSettings
|
|
27
|
+
|
|
28
|
+
PathType = Union[Path, str, List[Union[Path, str]], Tuple[Union[Path, str], ...]]
|
|
29
|
+
DEFAULT_PATH: PathType = Path('')
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConfigFileSourceMixin(ABC):
|
|
33
|
+
def _read_files(self, files: Optional[PathType]) -> Dict[str, Any]:
|
|
34
|
+
if files is None:
|
|
35
|
+
return {}
|
|
36
|
+
if isinstance(files, (str, os.PathLike)):
|
|
37
|
+
files = [files]
|
|
38
|
+
kwargs: Dict[str, Any] = {}
|
|
39
|
+
for file in files:
|
|
40
|
+
file_path = Path(file).expanduser()
|
|
41
|
+
if file_path.is_file():
|
|
42
|
+
kwargs.update(self._read_file(file_path))
|
|
43
|
+
return kwargs
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def _read_file(self, path: Path) -> Dict[str, Any]:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TomlConfigSettingsSource(InitSettingsSource, ConfigFileSourceMixin):
|
|
51
|
+
"""
|
|
52
|
+
A source class that loads variables from a TOML file
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
settings_cls: Type[BaseSettings],
|
|
58
|
+
toml_file: Optional[PathType] = DEFAULT_PATH,
|
|
59
|
+
):
|
|
60
|
+
self.toml_file_path = toml_file if toml_file != DEFAULT_PATH else settings_cls.model_config.get('toml_file')
|
|
61
|
+
self.toml_data = self._read_files(self.toml_file_path)
|
|
62
|
+
super().__init__(settings_cls, self.toml_data)
|
|
63
|
+
|
|
64
|
+
def _read_file(self, file_path: Path) -> Dict[str, Any]:
|
|
65
|
+
if sys.version_info < (3, 11):
|
|
66
|
+
import toml
|
|
67
|
+
|
|
68
|
+
with open(file_path) as toml_file:
|
|
69
|
+
return toml.load(toml_file)
|
|
70
|
+
else:
|
|
71
|
+
import tomllib
|
|
72
|
+
|
|
73
|
+
with open(file_path, 'rb') as toml_file:
|
|
74
|
+
return tomllib.load(toml_file)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class PyprojectTomlConfigSettingsSource(TomlConfigSettingsSource):
|
|
78
|
+
"""
|
|
79
|
+
A source class that loads variables from a `pyproject.toml` file.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
settings_cls: Type[BaseSettings],
|
|
85
|
+
toml_file: Optional[Path] = None,
|
|
86
|
+
) -> None:
|
|
87
|
+
self.toml_file_path = self._pick_pyproject_toml_file(
|
|
88
|
+
toml_file, settings_cls.model_config.get('pyproject_toml_depth', 0)
|
|
89
|
+
)
|
|
90
|
+
self.toml_table_header: Tuple[str, ...] = settings_cls.model_config.get(
|
|
91
|
+
'pyproject_toml_table_header', ('tool', 'pydantic-settings')
|
|
92
|
+
)
|
|
93
|
+
self.toml_data = self._read_files(self.toml_file_path)
|
|
94
|
+
for key in self.toml_table_header:
|
|
95
|
+
self.toml_data = self.toml_data.get(key, {})
|
|
96
|
+
super(TomlConfigSettingsSource, self).__init__(settings_cls, self.toml_data)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _pick_pyproject_toml_file(provided: Optional[Path], depth: int) -> Path:
|
|
100
|
+
"""Pick a `pyproject.toml` file path to use.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
provided: Explicit path provided when instantiating this class.
|
|
104
|
+
depth: Number of directories up the tree to check of a pyproject.toml.
|
|
105
|
+
|
|
106
|
+
"""
|
|
107
|
+
if provided:
|
|
108
|
+
return provided.resolve()
|
|
109
|
+
rv = Path.cwd() / 'pyproject.toml'
|
|
110
|
+
count = 0
|
|
111
|
+
if not rv.is_file():
|
|
112
|
+
child = rv.parent.parent / 'pyproject.toml'
|
|
113
|
+
while count < depth:
|
|
114
|
+
if child.is_file():
|
|
115
|
+
return child
|
|
116
|
+
if str(child.parent) == rv.root:
|
|
117
|
+
break # end discovery after checking system root once
|
|
118
|
+
child = child.parent.parent / 'pyproject.toml'
|
|
119
|
+
count += 1
|
|
120
|
+
return rv
|
idf_build_apps/yaml/parser.py
CHANGED
|
@@ -5,6 +5,8 @@ import typing as t
|
|
|
5
5
|
|
|
6
6
|
import yaml
|
|
7
7
|
|
|
8
|
+
from ..utils import PathLike
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
def parse_postfixes(manifest_dict: t.Dict):
|
|
10
12
|
for folder, folder_rule in manifest_dict.items():
|
|
@@ -60,7 +62,7 @@ def parse_postfixes(manifest_dict: t.Dict):
|
|
|
60
62
|
manifest_dict[folder] = updated_folder
|
|
61
63
|
|
|
62
64
|
|
|
63
|
-
def parse(path:
|
|
65
|
+
def parse(path: PathLike) -> t.Dict:
|
|
64
66
|
with open(path) as f:
|
|
65
67
|
manifest_dict = yaml.safe_load(f) or {}
|
|
66
68
|
parse_postfixes(manifest_dict)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: idf-build-apps
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Tools for building ESP-IDF related apps.
|
|
5
5
|
Author-email: Fu Hanxi <fuhanxi@espressif.com>
|
|
6
6
|
Requires-Python: >=3.7
|
|
@@ -18,8 +18,9 @@ Requires-Dist: pyyaml
|
|
|
18
18
|
Requires-Dist: packaging
|
|
19
19
|
Requires-Dist: toml; python_version < '3.11'
|
|
20
20
|
Requires-Dist: pydantic~=2.0
|
|
21
|
+
Requires-Dist: pydantic_settings
|
|
21
22
|
Requires-Dist: argcomplete>=3
|
|
22
|
-
Requires-Dist: typing-extensions
|
|
23
|
+
Requires-Dist: typing-extensions; python_version < '3.11'
|
|
23
24
|
Requires-Dist: sphinx ; extra == "doc"
|
|
24
25
|
Requires-Dist: sphinx-rtd-theme ; extra == "doc"
|
|
25
26
|
Requires-Dist: sphinx_copybutton ; extra == "doc"
|
|
@@ -32,7 +33,6 @@ Project-URL: changelog, https://github.com/espressif/idf-build-apps/blob/master/
|
|
|
32
33
|
Project-URL: documentation, https://docs.espressif.com/projects/idf-build-apps
|
|
33
34
|
Project-URL: homepage, https://github.com/espressif/idf-build-apps
|
|
34
35
|
Project-URL: repository, https://github.com/espressif/idf-build-apps
|
|
35
|
-
Provides-Extra: dev
|
|
36
36
|
Provides-Extra: doc
|
|
37
37
|
Provides-Extra: test
|
|
38
38
|
|
|
@@ -113,5 +113,5 @@ Thanks for your contribution! Please refer to our [Contributing Guide](CONTRIBUT
|
|
|
113
113
|
[hello-world]: https://github.com/espressif/esp-idf/tree/master/examples/get-started/hello_world
|
|
114
114
|
[supported-targets]: https://github.com/espressif/esp-idf/tree/v5.0#esp-idf-release-and-soc-compatibility
|
|
115
115
|
[doc]: https://docs.espressif.com/projects/idf-build-apps/en/latest/
|
|
116
|
-
[api-doc]: https://docs.espressif.com/projects/idf-build-apps/en/latest/api/modules.html
|
|
116
|
+
[api-doc]: https://docs.espressif.com/projects/idf-build-apps/en/latest/references/api/modules.html
|
|
117
117
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
idf_build_apps/__init__.py,sha256=1tAmsSMb7Trrua4-819N1AqwwqDEfk9YN4iWCmwUGOU,650
|
|
2
|
+
idf_build_apps/__main__.py,sha256=8E-5xHm2MlRun0L88XJleNh5U50dpE0Q1nK5KqomA7I,182
|
|
3
|
+
idf_build_apps/app.py,sha256=F-MKOsaz7cJ0H2wsEE4gpO4kkkEdkyFmIZBHDoM2qgs,37359
|
|
4
|
+
idf_build_apps/args.py,sha256=Y4Wwoi8XYLjf8lWmCUm197NSzSBa4eC95QAyx4q7-6Q,31668
|
|
5
|
+
idf_build_apps/autocompletions.py,sha256=g-bx0pzXoFKI0VQqftkHyGVWN6MLjuFOdozeuAf45yo,2138
|
|
6
|
+
idf_build_apps/constants.py,sha256=07ve2FtWuLBuc_6LFzbs1XncB1VNi9HJUqGjQQauRNM,3952
|
|
7
|
+
idf_build_apps/finder.py,sha256=kfZaGWJfPUwWAbaOj_W3Fu97SIIFEsv1R_dJucjbFHw,5691
|
|
8
|
+
idf_build_apps/log.py,sha256=pyvT7N4MWzGjIXph5mThQCGBiSt53RNPW0WrFfLr0Kw,2650
|
|
9
|
+
idf_build_apps/main.py,sha256=Z_hetbOavgCJZQPaP01_jx57fR1w0DefiFz0J2cCwp0,16498
|
|
10
|
+
idf_build_apps/session_args.py,sha256=2WDTy40IFAc0KQ57HaeBcYj_k10eUXRKkDOWLrFCaHY,2985
|
|
11
|
+
idf_build_apps/utils.py,sha256=s4D8P7QA17XcaCUQ_EoiNOW_VpU3cPQgiZVV9KQ8I30,10171
|
|
12
|
+
idf_build_apps/junit/__init__.py,sha256=IxvdaS6eSXp7kZxRuXqyZyGxuA_A1nOW1jF1HMi8Gns,231
|
|
13
|
+
idf_build_apps/junit/report.py,sha256=T7dVU3Sz5tqjfbcFW7wjsb65PDH6C2HFf73ePJqBhMs,6555
|
|
14
|
+
idf_build_apps/junit/utils.py,sha256=j0PYhFTZjXtTwkENdeL4bFJcX24ktf1CsOOVXz65yNo,1297
|
|
15
|
+
idf_build_apps/manifest/__init__.py,sha256=Q2-cb3ngNjnl6_zWhUfzZZB10f_-Rv2JYNck3Lk7UkQ,133
|
|
16
|
+
idf_build_apps/manifest/if_parser.py,sha256=XUXN-lemfvR93XOUedO8JybSgLCD71EdkDBGuOE8jnQ,6526
|
|
17
|
+
idf_build_apps/manifest/manifest.py,sha256=1RpD6k-u-51dJIKl4PT3JtQd_3_vKBTwrK4f2a6zFf0,14360
|
|
18
|
+
idf_build_apps/manifest/soc_header.py,sha256=PzJ37xFspt5f0AXWvAFNA_avHZA9fMXHBrwDYLi3qEI,4344
|
|
19
|
+
idf_build_apps/vendors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
+
idf_build_apps/vendors/pydantic_sources.py,sha256=2IN6opo6qjCwaqhERFbgA4PtEwqKaTtEkccy5_fYAT0,4130
|
|
21
|
+
idf_build_apps/yaml/__init__.py,sha256=W-3z5no07RQ6eYKGyOAPA8Z2CLiMPob8DD91I4URjrA,162
|
|
22
|
+
idf_build_apps/yaml/parser.py,sha256=b3LvogO6do-eJPRsYzT-8xk8AT2MnXpLCzQutJqyC7M,2128
|
|
23
|
+
idf_build_apps-2.5.0.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
|
|
24
|
+
idf_build_apps-2.5.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
25
|
+
idf_build_apps-2.5.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
26
|
+
idf_build_apps-2.5.0.dist-info/METADATA,sha256=1qLRKtlKQmat-zA054RMia_mMilScx-nClcbMZPdtfg,4610
|
|
27
|
+
idf_build_apps-2.5.0.dist-info/RECORD,,
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
|
2
|
-
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
|
-
import typing as t
|
|
5
|
-
|
|
6
|
-
from pydantic import (
|
|
7
|
-
computed_field,
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
from .utils import (
|
|
11
|
-
BaseModel,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class BuildAppsArgs(BaseModel):
|
|
16
|
-
PARALLEL_INDEX_PLACEHOLDER: t.ClassVar[str] = '@p' # replace it with the parallel index
|
|
17
|
-
|
|
18
|
-
parallel_index: int = 1
|
|
19
|
-
parallel_count: int = 1
|
|
20
|
-
|
|
21
|
-
_junitxml: t.Optional[str] = None
|
|
22
|
-
_collect_app_info: t.Optional[str] = None
|
|
23
|
-
_collect_size_info: t.Optional[str] = None
|
|
24
|
-
|
|
25
|
-
def __init__(
|
|
26
|
-
self,
|
|
27
|
-
*,
|
|
28
|
-
collect_app_info: t.Optional[str] = None,
|
|
29
|
-
collect_size_info: t.Optional[str] = None,
|
|
30
|
-
junitxml: t.Optional[str] = None,
|
|
31
|
-
**kwargs,
|
|
32
|
-
):
|
|
33
|
-
super().__init__(**kwargs)
|
|
34
|
-
|
|
35
|
-
self._junitxml = junitxml
|
|
36
|
-
self._collect_app_info = collect_app_info
|
|
37
|
-
self._collect_size_info = collect_size_info
|
|
38
|
-
|
|
39
|
-
@computed_field # type: ignore
|
|
40
|
-
@property
|
|
41
|
-
def collect_app_info(self) -> t.Optional[str]:
|
|
42
|
-
if self._collect_app_info:
|
|
43
|
-
return self.expand(self._collect_app_info)
|
|
44
|
-
|
|
45
|
-
return None
|
|
46
|
-
|
|
47
|
-
@computed_field # type: ignore
|
|
48
|
-
@property
|
|
49
|
-
def collect_size_info(self) -> t.Optional[str]:
|
|
50
|
-
if self._collect_size_info:
|
|
51
|
-
return self.expand(self._collect_size_info)
|
|
52
|
-
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
|
-
@computed_field # type: ignore
|
|
56
|
-
@property
|
|
57
|
-
def junitxml(self) -> t.Optional[str]:
|
|
58
|
-
if self._junitxml:
|
|
59
|
-
return self.expand(self._junitxml)
|
|
60
|
-
|
|
61
|
-
return None
|
|
62
|
-
|
|
63
|
-
def expand(self, path):
|
|
64
|
-
return path.replace(self.PARALLEL_INDEX_PLACEHOLDER, str(self.parallel_index))
|