idf-build-apps 2.0.0b5__py3-none-any.whl → 2.0.0rc1__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 +4 -3
- idf_build_apps/app.py +131 -134
- idf_build_apps/config.py +10 -10
- idf_build_apps/constants.py +8 -11
- idf_build_apps/finder.py +3 -2
- idf_build_apps/main.py +45 -7
- idf_build_apps/manifest/manifest.py +21 -24
- idf_build_apps/manifest/soc_header.py +12 -12
- idf_build_apps/session_args.py +17 -9
- idf_build_apps/utils.py +50 -24
- {idf_build_apps-2.0.0b5.dist-info → idf_build_apps-2.0.0rc1.dist-info}/METADATA +1 -1
- idf_build_apps-2.0.0rc1.dist-info/RECORD +23 -0
- idf_build_apps-2.0.0b5.dist-info/RECORD +0 -23
- {idf_build_apps-2.0.0b5.dist-info → idf_build_apps-2.0.0rc1.dist-info}/LICENSE +0 -0
- {idf_build_apps-2.0.0b5.dist-info → idf_build_apps-2.0.0rc1.dist-info}/WHEEL +0 -0
- {idf_build_apps-2.0.0b5.dist-info → idf_build_apps-2.0.0rc1.dist-info}/entry_points.txt +0 -0
idf_build_apps/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2022-
|
|
1
|
+
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
"""
|
|
@@ -8,7 +8,7 @@ Tools for building ESP-IDF related apps.
|
|
|
8
8
|
# ruff: noqa: E402
|
|
9
9
|
# avoid circular imports
|
|
10
10
|
|
|
11
|
-
__version__ = '2.0.
|
|
11
|
+
__version__ = '2.0.0rc1'
|
|
12
12
|
|
|
13
13
|
from .session_args import (
|
|
14
14
|
SessionArgs,
|
|
@@ -28,6 +28,7 @@ from .log import (
|
|
|
28
28
|
from .main import (
|
|
29
29
|
build_apps,
|
|
30
30
|
find_apps,
|
|
31
|
+
json_to_app,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
__all__ = [
|
|
@@ -37,6 +38,6 @@ __all__ = [
|
|
|
37
38
|
'MakeApp',
|
|
38
39
|
'find_apps',
|
|
39
40
|
'build_apps',
|
|
41
|
+
'json_to_app',
|
|
40
42
|
'setup_logging',
|
|
41
|
-
'SESSION_ARGS',
|
|
42
43
|
]
|
idf_build_apps/app.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2022-
|
|
1
|
+
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import functools
|
|
@@ -8,17 +8,10 @@ import os
|
|
|
8
8
|
import re
|
|
9
9
|
import shutil
|
|
10
10
|
import sys
|
|
11
|
-
import tempfile
|
|
12
11
|
import typing as t
|
|
13
|
-
from copy import (
|
|
14
|
-
deepcopy,
|
|
15
|
-
)
|
|
16
12
|
from datetime import (
|
|
17
13
|
datetime,
|
|
18
14
|
)
|
|
19
|
-
from functools import (
|
|
20
|
-
lru_cache,
|
|
21
|
-
)
|
|
22
15
|
from pathlib import (
|
|
23
16
|
Path,
|
|
24
17
|
)
|
|
@@ -31,10 +24,9 @@ from pydantic import (
|
|
|
31
24
|
computed_field,
|
|
32
25
|
)
|
|
33
26
|
|
|
34
|
-
from
|
|
27
|
+
from . import (
|
|
35
28
|
SESSION_ARGS,
|
|
36
29
|
)
|
|
37
|
-
|
|
38
30
|
from .build_apps_args import (
|
|
39
31
|
BuildAppsArgs,
|
|
40
32
|
)
|
|
@@ -92,8 +84,8 @@ class App(BaseModel):
|
|
|
92
84
|
WILDCARD_PLACEHOLDER: t.ClassVar[str] = '@w' # replace it with the wildcard, usually the sdkconfig
|
|
93
85
|
NAME_PLACEHOLDER: t.ClassVar[str] = '@n' # replace it with self.name
|
|
94
86
|
FULL_NAME_PLACEHOLDER: t.ClassVar[str] = '@f' # replace it with escaped self.app_dir
|
|
95
|
-
INDEX_PLACEHOLDER: t.ClassVar[str] = '@i' # replace it with the build index
|
|
96
87
|
IDF_VERSION_PLACEHOLDER: t.ClassVar[str] = '@v' # replace it with the IDF version
|
|
88
|
+
INDEX_PLACEHOLDER: t.ClassVar[str] = '@i' # replace it with the build index (while build_apps)
|
|
97
89
|
|
|
98
90
|
SDKCONFIG_LINE_REGEX: t.ClassVar[t.Pattern] = re.compile(r"^([^=]+)=\"?([^\"\n]*)\"?\n*$")
|
|
99
91
|
|
|
@@ -115,8 +107,7 @@ class App(BaseModel):
|
|
|
115
107
|
target: str
|
|
116
108
|
sdkconfig_path: t.Optional[str] = None
|
|
117
109
|
config_name: t.Optional[str] = None
|
|
118
|
-
|
|
119
|
-
build_status: BuildStatus = BuildStatus.UNKNOWN
|
|
110
|
+
sdkconfig_defaults_str: t.Optional[str] = None
|
|
120
111
|
|
|
121
112
|
# Attrs that support placeholders
|
|
122
113
|
_work_dir: t.Optional[str] = None
|
|
@@ -125,48 +116,46 @@ class App(BaseModel):
|
|
|
125
116
|
_build_log_filename: t.Optional[str] = None
|
|
126
117
|
_size_json_filename: t.Optional[str] = None
|
|
127
118
|
|
|
128
|
-
# Build related
|
|
129
119
|
dry_run: bool = False
|
|
130
|
-
index: t.Union[int, None] = None
|
|
131
120
|
verbose: bool = False
|
|
132
121
|
check_warnings: bool = False
|
|
133
122
|
preserve: bool = True
|
|
134
123
|
|
|
135
|
-
#
|
|
136
|
-
build_apps_args: t.Optional[BuildAppsArgs] =
|
|
124
|
+
# build_apps() related
|
|
125
|
+
build_apps_args: t.Optional[BuildAppsArgs] = None
|
|
126
|
+
index: t.Optional[int] = None
|
|
127
|
+
|
|
128
|
+
# build status related
|
|
129
|
+
build_status: BuildStatus = BuildStatus.UNKNOWN
|
|
130
|
+
build_comment: t.Optional[str] = None
|
|
137
131
|
|
|
138
|
-
_build_comment: t.Optional[str] = None
|
|
139
132
|
_build_stage: t.Optional[BuildStage] = None
|
|
140
133
|
_build_duration: float = 0
|
|
141
134
|
_build_timestamp: t.Optional[datetime] = None
|
|
142
135
|
|
|
136
|
+
__EQ_IGNORE_FIELDS__ = [
|
|
137
|
+
'build_comment',
|
|
138
|
+
]
|
|
139
|
+
|
|
143
140
|
def __init__(
|
|
144
141
|
self,
|
|
145
142
|
app_dir: str,
|
|
146
143
|
target: str,
|
|
147
144
|
*,
|
|
148
|
-
sdkconfig_path: t.Optional[str] = None,
|
|
149
|
-
config_name: t.Optional[str] = None,
|
|
150
145
|
work_dir: t.Optional[str] = None,
|
|
151
146
|
build_dir: str = 'build',
|
|
152
147
|
build_log_filename: t.Optional[str] = None,
|
|
153
148
|
size_json_filename: t.Optional[str] = None,
|
|
154
|
-
check_warnings: bool = False,
|
|
155
|
-
preserve: bool = True,
|
|
156
|
-
sdkconfig_defaults_str: t.Optional[str] = None,
|
|
157
149
|
**kwargs: t.Any,
|
|
158
150
|
) -> None:
|
|
159
151
|
kwargs.update(
|
|
160
152
|
{
|
|
161
153
|
'app_dir': app_dir,
|
|
162
154
|
'target': target,
|
|
163
|
-
'sdkconfig_path': sdkconfig_path,
|
|
164
|
-
'config_name': config_name,
|
|
165
|
-
'check_warnings': check_warnings,
|
|
166
|
-
'preserve': preserve,
|
|
167
155
|
}
|
|
168
156
|
)
|
|
169
157
|
super().__init__(**kwargs)
|
|
158
|
+
|
|
170
159
|
# These internal variables store the paths with environment variables and placeholders;
|
|
171
160
|
# Public properties with similar names use the _expand method to get the actual paths.
|
|
172
161
|
self._work_dir = work_dir or app_dir
|
|
@@ -174,31 +163,26 @@ class App(BaseModel):
|
|
|
174
163
|
|
|
175
164
|
self._build_log_filename = build_log_filename
|
|
176
165
|
self._size_json_filename = size_json_filename
|
|
177
|
-
|
|
178
|
-
# should be built or not
|
|
179
|
-
self._checked_should_build = False
|
|
180
|
-
|
|
181
|
-
# sdkconfig attrs, use properties instead
|
|
182
|
-
self._sdkconfig_defaults = self._get_sdkconfig_defaults(sdkconfig_defaults_str)
|
|
183
|
-
self._sdkconfig_files: t.List[str] = None # type: ignore
|
|
184
|
-
self._sdkconfig_files_defined_target: str = None # type: ignore
|
|
166
|
+
self._is_build_log_path_temp = not bool(build_log_filename)
|
|
185
167
|
|
|
186
168
|
# pass all parameters to initialize hook method
|
|
187
169
|
kwargs.update(
|
|
188
170
|
{
|
|
189
|
-
'work_dir':
|
|
190
|
-
'build_dir':
|
|
171
|
+
'work_dir': self._work_dir,
|
|
172
|
+
'build_dir': self._build_dir,
|
|
191
173
|
'build_log_filename': build_log_filename,
|
|
192
174
|
'size_json_filename': size_json_filename,
|
|
193
|
-
'sdkconfig_defaults_str': sdkconfig_defaults_str,
|
|
194
175
|
}
|
|
195
176
|
)
|
|
196
177
|
self._initialize_hook(**kwargs)
|
|
197
178
|
|
|
179
|
+
# private attrs, won't be dumped to json
|
|
180
|
+
self._checked_should_build = False
|
|
181
|
+
|
|
198
182
|
self._logger = logging.getLogger(f'{__name__}.{hash(self)}')
|
|
199
183
|
self._logger.addFilter(_AppBuildStageFilter(app=self))
|
|
200
184
|
|
|
201
|
-
self._process_sdkconfig_files()
|
|
185
|
+
self._sdkconfig_files, self._sdkconfig_files_defined_target = self._process_sdkconfig_files()
|
|
202
186
|
|
|
203
187
|
def _initialize_hook(self, **kwargs):
|
|
204
188
|
"""
|
|
@@ -231,16 +215,15 @@ class App(BaseModel):
|
|
|
231
215
|
|
|
232
216
|
return default_fmt.format(*default_args)
|
|
233
217
|
|
|
234
|
-
@
|
|
235
|
-
def
|
|
236
|
-
if sdkconfig_defaults_str is not None:
|
|
237
|
-
|
|
238
|
-
elif os.getenv('SDKCONFIG_DEFAULTS', None) is not None:
|
|
239
|
-
candidates = os.getenv('SDKCONFIG_DEFAULTS', '').split(';')
|
|
240
|
-
else:
|
|
241
|
-
candidates = [DEFAULT_SDKCONFIG]
|
|
218
|
+
@property
|
|
219
|
+
def sdkconfig_defaults_candidates(self) -> t.List[str]:
|
|
220
|
+
if self.sdkconfig_defaults_str is not None:
|
|
221
|
+
return self.sdkconfig_defaults_str.split(';')
|
|
242
222
|
|
|
243
|
-
|
|
223
|
+
if os.getenv('SDKCONFIG_DEFAULTS', None) is not None:
|
|
224
|
+
return os.getenv('SDKCONFIG_DEFAULTS', '').split(';')
|
|
225
|
+
|
|
226
|
+
return [DEFAULT_SDKCONFIG]
|
|
244
227
|
|
|
245
228
|
@t.overload
|
|
246
229
|
def _expand(self, path: None) -> None:
|
|
@@ -259,7 +242,8 @@ class App(BaseModel):
|
|
|
259
242
|
|
|
260
243
|
if self.index is not None:
|
|
261
244
|
path = path.replace(self.INDEX_PLACEHOLDER, str(self.index))
|
|
262
|
-
|
|
245
|
+
if self.build_apps_args:
|
|
246
|
+
path = self.build_apps_args.expand(path)
|
|
263
247
|
path = path.replace(
|
|
264
248
|
self.IDF_VERSION_PLACEHOLDER, f'{IDF_VERSION_MAJOR}_{IDF_VERSION_MINOR}_{IDF_VERSION_PATCH}'
|
|
265
249
|
)
|
|
@@ -283,7 +267,12 @@ class App(BaseModel):
|
|
|
283
267
|
|
|
284
268
|
@property
|
|
285
269
|
def name(self) -> str:
|
|
286
|
-
|
|
270
|
+
base_name = os.path.basename(self.app_dir)
|
|
271
|
+
# '.' for relative path like '.'
|
|
272
|
+
# '' for path endswith '/'
|
|
273
|
+
if base_name in ['.', '']:
|
|
274
|
+
return os.path.basename(os.path.abspath(self.app_dir))
|
|
275
|
+
return base_name
|
|
287
276
|
|
|
288
277
|
@computed_field # type: ignore
|
|
289
278
|
@property
|
|
@@ -308,25 +297,18 @@ class App(BaseModel):
|
|
|
308
297
|
|
|
309
298
|
return os.path.join(self.work_dir, self.build_dir)
|
|
310
299
|
|
|
311
|
-
@property
|
|
312
|
-
def build_comment(self) -> str:
|
|
313
|
-
return self._build_comment or ''
|
|
314
|
-
|
|
315
|
-
@build_comment.setter
|
|
316
|
-
def build_comment(self, value: str) -> None:
|
|
317
|
-
self._build_comment = value
|
|
318
|
-
|
|
319
300
|
@computed_field # type: ignore
|
|
320
301
|
@property
|
|
321
302
|
def build_log_filename(self) -> t.Optional[str]:
|
|
322
303
|
return self._expand(self._build_log_filename)
|
|
323
304
|
|
|
324
305
|
@property
|
|
325
|
-
def build_log_path(self) ->
|
|
306
|
+
def build_log_path(self) -> str:
|
|
326
307
|
if self.build_log_filename:
|
|
327
308
|
return os.path.join(self.build_path, self.build_log_filename)
|
|
328
309
|
|
|
329
|
-
|
|
310
|
+
# use a temp file if build log path is not specified
|
|
311
|
+
return os.path.join(self.build_path, f'.temp.build.{hash(self)}.log')
|
|
330
312
|
|
|
331
313
|
@computed_field # type: ignore
|
|
332
314
|
@property
|
|
@@ -344,28 +326,28 @@ class App(BaseModel):
|
|
|
344
326
|
|
|
345
327
|
return None
|
|
346
328
|
|
|
347
|
-
|
|
348
|
-
@property
|
|
349
|
-
def config(self) -> t.Optional[str]:
|
|
350
|
-
return self.config_name
|
|
351
|
-
|
|
352
|
-
def _process_sdkconfig_files(self):
|
|
329
|
+
def _process_sdkconfig_files(self) -> t.Tuple[t.List[str], t.Optional[str]]:
|
|
353
330
|
"""
|
|
354
331
|
Expand environment variables in default sdkconfig files and remove some CI related settings.
|
|
355
332
|
"""
|
|
356
|
-
|
|
333
|
+
real_sdkconfig_files: t.List[str] = []
|
|
334
|
+
sdkconfig_files_defined_target: t.Optional[str] = None
|
|
357
335
|
|
|
336
|
+
# put the expanded variable files in a temporary directory
|
|
337
|
+
# will remove if the content is the same as the original one
|
|
358
338
|
expanded_dir = os.path.join(self.work_dir, 'expanded_sdkconfig_files', os.path.basename(self.build_dir))
|
|
359
339
|
if not os.path.isdir(expanded_dir):
|
|
360
340
|
os.makedirs(expanded_dir)
|
|
361
341
|
|
|
362
|
-
for f in self.
|
|
363
|
-
if
|
|
364
|
-
f = os.path.join(self.work_dir, f)
|
|
365
|
-
|
|
342
|
+
for f in self.sdkconfig_defaults_candidates + ([self.sdkconfig_path] if self.sdkconfig_path else []):
|
|
343
|
+
# use filepath if abs/rel already point to itself
|
|
366
344
|
if not os.path.isfile(f):
|
|
367
|
-
|
|
368
|
-
|
|
345
|
+
# find it in the app_dir
|
|
346
|
+
self._logger.debug('sdkconfig file %s not found, checking under app_dir...', f)
|
|
347
|
+
f = os.path.join(self.app_dir, f)
|
|
348
|
+
if not os.path.isfile(f):
|
|
349
|
+
self._logger.debug('sdkconfig file %s not found, skipping...', f)
|
|
350
|
+
continue
|
|
369
351
|
|
|
370
352
|
expanded_fp = os.path.join(expanded_dir, os.path.basename(f))
|
|
371
353
|
with open(f) as fr:
|
|
@@ -377,7 +359,7 @@ class App(BaseModel):
|
|
|
377
359
|
if m:
|
|
378
360
|
key = m.group(1)
|
|
379
361
|
if key == 'CONFIG_IDF_TARGET':
|
|
380
|
-
|
|
362
|
+
sdkconfig_files_defined_target = m.group(2)
|
|
381
363
|
|
|
382
364
|
if isinstance(self, CMakeApp):
|
|
383
365
|
if key in self.SDKCONFIG_TEST_OPTS:
|
|
@@ -397,12 +379,16 @@ class App(BaseModel):
|
|
|
397
379
|
os.unlink(expanded_fp)
|
|
398
380
|
except OSError:
|
|
399
381
|
self._logger.debug('Failed to remove file %s', expanded_fp)
|
|
400
|
-
|
|
382
|
+
real_sdkconfig_files.append(f)
|
|
401
383
|
else:
|
|
402
384
|
self._logger.debug('Expand sdkconfig file %s to %s', f, expanded_fp)
|
|
403
|
-
|
|
385
|
+
real_sdkconfig_files.append(expanded_fp)
|
|
404
386
|
# copy the related target-specific sdkconfig files
|
|
405
|
-
|
|
387
|
+
par_dir = os.path.abspath(os.path.join(f, '..'))
|
|
388
|
+
for target_specific_file in (
|
|
389
|
+
os.path.join(par_dir, str(p))
|
|
390
|
+
for p in Path(par_dir).glob(os.path.basename(f) + f'.{self.target}')
|
|
391
|
+
):
|
|
406
392
|
self._logger.debug(
|
|
407
393
|
'Copy target-specific sdkconfig file %s to %s', target_specific_file, expanded_dir
|
|
408
394
|
)
|
|
@@ -419,24 +405,20 @@ class App(BaseModel):
|
|
|
419
405
|
except OSError:
|
|
420
406
|
pass
|
|
421
407
|
|
|
422
|
-
if SESSION_ARGS.
|
|
423
|
-
|
|
408
|
+
if SESSION_ARGS.override_sdkconfig_file_path:
|
|
409
|
+
real_sdkconfig_files.append(SESSION_ARGS.override_sdkconfig_file_path)
|
|
424
410
|
if 'CONFIG_IDF_TARGET' in SESSION_ARGS.override_sdkconfig_items:
|
|
425
|
-
|
|
411
|
+
sdkconfig_files_defined_target = SESSION_ARGS.override_sdkconfig_items['CONFIG_IDF_TARGET']
|
|
426
412
|
|
|
427
|
-
|
|
413
|
+
return real_sdkconfig_files, sdkconfig_files_defined_target
|
|
428
414
|
|
|
429
415
|
@property
|
|
430
|
-
@lru_cache()
|
|
431
|
-
# @cached_property requires python 3.8
|
|
432
416
|
def sdkconfig_files_defined_idf_target(self) -> t.Optional[str]:
|
|
433
417
|
return self._sdkconfig_files_defined_target
|
|
434
418
|
|
|
435
419
|
@property
|
|
436
|
-
@lru_cache()
|
|
437
|
-
# @cached_property requires python 3.8
|
|
438
420
|
def sdkconfig_files(self) -> t.List[str]:
|
|
439
|
-
return [os.path.
|
|
421
|
+
return [os.path.abspath(file) for file in self._sdkconfig_files]
|
|
440
422
|
|
|
441
423
|
@property
|
|
442
424
|
def depends_components(self) -> t.List[str]:
|
|
@@ -484,14 +466,7 @@ class App(BaseModel):
|
|
|
484
466
|
|
|
485
467
|
return wrapper
|
|
486
468
|
|
|
487
|
-
|
|
488
|
-
def build(
|
|
489
|
-
self,
|
|
490
|
-
manifest_rootpath: t.Optional[str] = None,
|
|
491
|
-
modified_components: t.Union[t.List[str], str, None] = None,
|
|
492
|
-
modified_files: t.Union[t.List[str], str, None] = None,
|
|
493
|
-
check_app_dependencies: bool = False,
|
|
494
|
-
) -> None:
|
|
469
|
+
def _pre_build(self) -> None:
|
|
495
470
|
if self.dry_run:
|
|
496
471
|
self._build_stage = BuildStage.DRY_RUN
|
|
497
472
|
else:
|
|
@@ -533,26 +508,32 @@ class App(BaseModel):
|
|
|
533
508
|
if not self.dry_run:
|
|
534
509
|
os.unlink(sdkconfig_file)
|
|
535
510
|
|
|
536
|
-
if self.build_log_path:
|
|
537
|
-
self._logger.
|
|
511
|
+
if os.path.isfile(self.build_log_path):
|
|
512
|
+
self._logger.debug('Removed existing build log file: %s', self.build_log_path)
|
|
513
|
+
if not self.dry_run:
|
|
514
|
+
os.unlink(self.build_log_path)
|
|
515
|
+
elif not self.dry_run:
|
|
516
|
+
os.makedirs(os.path.dirname(self.build_log_path), exist_ok=True)
|
|
517
|
+
self._logger.info('Writing build log to %s', self.build_log_path)
|
|
538
518
|
|
|
539
519
|
if self.dry_run:
|
|
540
520
|
self.build_status = BuildStatus.SKIPPED
|
|
541
521
|
self.build_comment = 'dry run'
|
|
542
522
|
return
|
|
543
523
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
524
|
+
@record_build_duration # type: ignore
|
|
525
|
+
def build(
|
|
526
|
+
self,
|
|
527
|
+
*,
|
|
528
|
+
manifest_rootpath: t.Optional[str] = None,
|
|
529
|
+
modified_components: t.Union[t.List[str], str, None] = None,
|
|
530
|
+
modified_files: t.Union[t.List[str], str, None] = None,
|
|
531
|
+
check_app_dependencies: bool = False,
|
|
532
|
+
) -> None:
|
|
533
|
+
self._pre_build()
|
|
551
534
|
|
|
552
|
-
self._build_stage = BuildStage.BUILD
|
|
553
535
|
try:
|
|
554
536
|
self._build(
|
|
555
|
-
logfile=logfile,
|
|
556
537
|
manifest_rootpath=manifest_rootpath,
|
|
557
538
|
modified_components=to_list(modified_components),
|
|
558
539
|
modified_files=to_list(modified_files),
|
|
@@ -561,12 +542,17 @@ class App(BaseModel):
|
|
|
561
542
|
except BuildError as e:
|
|
562
543
|
self.build_status = BuildStatus.FAILED
|
|
563
544
|
self.build_comment = str(e)
|
|
564
|
-
finally:
|
|
565
|
-
logfile.close()
|
|
566
545
|
|
|
546
|
+
self._post_build()
|
|
547
|
+
|
|
548
|
+
def _post_build(self) -> None:
|
|
567
549
|
self._build_stage = BuildStage.POST_BUILD
|
|
550
|
+
|
|
551
|
+
if not os.path.isfile(self.build_log_path):
|
|
552
|
+
return
|
|
553
|
+
|
|
568
554
|
has_unignored_warning = False
|
|
569
|
-
with open(
|
|
555
|
+
with open(self.build_log_path) as fr:
|
|
570
556
|
lines = [line.rstrip() for line in fr.readlines() if line.rstrip()]
|
|
571
557
|
for line in lines:
|
|
572
558
|
is_error_or_warning, ignored = self.is_error_or_warning(line)
|
|
@@ -582,15 +568,14 @@ class App(BaseModel):
|
|
|
582
568
|
self._logger.error(
|
|
583
569
|
'Last %s lines from the build log "%s":',
|
|
584
570
|
self.LOG_DEBUG_LINES,
|
|
585
|
-
|
|
571
|
+
self.build_log_path,
|
|
586
572
|
)
|
|
587
573
|
for line in lines[-self.LOG_DEBUG_LINES :]:
|
|
588
574
|
self._logger.error('%s', line)
|
|
589
575
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
self._logger.debug('Removed temporary build log file: %s', logfile.name)
|
|
576
|
+
if self._is_build_log_path_temp and self.build_status == BuildStatus.SUCCESS:
|
|
577
|
+
os.unlink(self.build_log_path)
|
|
578
|
+
self._logger.debug('Removed success build temporary log file: %s', self.build_log_path)
|
|
594
579
|
|
|
595
580
|
# Generate Size Files
|
|
596
581
|
if self.build_status == BuildStatus.SUCCESS:
|
|
@@ -601,8 +586,7 @@ class App(BaseModel):
|
|
|
601
586
|
exclude_list = []
|
|
602
587
|
if self.size_json_path:
|
|
603
588
|
exclude_list.append(os.path.basename(self.size_json_path))
|
|
604
|
-
|
|
605
|
-
exclude_list.append(os.path.basename(self.build_log_path))
|
|
589
|
+
exclude_list.append(os.path.basename(self.build_log_path))
|
|
606
590
|
|
|
607
591
|
rmdir(
|
|
608
592
|
self.build_path,
|
|
@@ -619,13 +603,13 @@ class App(BaseModel):
|
|
|
619
603
|
|
|
620
604
|
def _build(
|
|
621
605
|
self,
|
|
622
|
-
|
|
606
|
+
*,
|
|
623
607
|
manifest_rootpath: t.Optional[str] = None,
|
|
624
608
|
modified_components: t.Optional[t.List[str]] = None,
|
|
625
609
|
modified_files: t.Optional[t.List[str]] = None,
|
|
626
610
|
check_app_dependencies: bool = False,
|
|
627
611
|
) -> None:
|
|
628
|
-
|
|
612
|
+
self._build_stage = BuildStage.BUILD
|
|
629
613
|
|
|
630
614
|
def _write_size_json(self) -> None:
|
|
631
615
|
if not self.size_json_path:
|
|
@@ -701,10 +685,10 @@ class App(BaseModel):
|
|
|
701
685
|
if modified_files:
|
|
702
686
|
for f in modified_files:
|
|
703
687
|
_f_fullpath = to_absolute_path(f)
|
|
704
|
-
if _f_fullpath.
|
|
688
|
+
if os.path.basename(_f_fullpath).endswith('.md'):
|
|
705
689
|
continue
|
|
706
690
|
|
|
707
|
-
if
|
|
691
|
+
if _f_fullpath.startswith(_app_dir_fullpath):
|
|
708
692
|
return True
|
|
709
693
|
|
|
710
694
|
return False
|
|
@@ -794,12 +778,19 @@ class MakeApp(App):
|
|
|
794
778
|
|
|
795
779
|
def _build(
|
|
796
780
|
self,
|
|
797
|
-
|
|
781
|
+
*,
|
|
798
782
|
manifest_rootpath: t.Optional[str] = None,
|
|
799
783
|
modified_components: t.Optional[t.List[str]] = None,
|
|
800
784
|
modified_files: t.Optional[t.List[str]] = None,
|
|
801
785
|
check_app_dependencies: bool = False,
|
|
802
786
|
) -> None:
|
|
787
|
+
super()._build(
|
|
788
|
+
manifest_rootpath=manifest_rootpath,
|
|
789
|
+
modified_components=modified_components,
|
|
790
|
+
modified_files=modified_files,
|
|
791
|
+
check_app_dependencies=check_app_dependencies,
|
|
792
|
+
)
|
|
793
|
+
|
|
803
794
|
# additional env variables
|
|
804
795
|
additional_env_dict = {
|
|
805
796
|
'IDF_TARGET': self.target,
|
|
@@ -816,8 +807,8 @@ class MakeApp(App):
|
|
|
816
807
|
for cmd in commands:
|
|
817
808
|
subprocess_run(
|
|
818
809
|
cmd,
|
|
819
|
-
log_terminal=
|
|
820
|
-
log_fs=
|
|
810
|
+
log_terminal=self._is_build_log_path_temp,
|
|
811
|
+
log_fs=self.build_log_path,
|
|
821
812
|
check=True,
|
|
822
813
|
additional_env_dict=additional_env_dict,
|
|
823
814
|
cwd=self.work_dir,
|
|
@@ -861,12 +852,19 @@ class CMakeApp(App):
|
|
|
861
852
|
|
|
862
853
|
def _build(
|
|
863
854
|
self,
|
|
864
|
-
|
|
855
|
+
*,
|
|
865
856
|
manifest_rootpath: t.Optional[str] = None,
|
|
866
857
|
modified_components: t.Optional[t.List[str]] = None,
|
|
867
858
|
modified_files: t.Optional[t.List[str]] = None,
|
|
868
859
|
check_app_dependencies: bool = False,
|
|
869
860
|
) -> None:
|
|
861
|
+
super()._build(
|
|
862
|
+
manifest_rootpath=manifest_rootpath,
|
|
863
|
+
modified_components=modified_components,
|
|
864
|
+
modified_files=modified_files,
|
|
865
|
+
check_app_dependencies=check_app_dependencies,
|
|
866
|
+
)
|
|
867
|
+
|
|
870
868
|
if not self._checked_should_build:
|
|
871
869
|
self._check_should_build(
|
|
872
870
|
manifest_rootpath=manifest_rootpath,
|
|
@@ -898,8 +896,8 @@ class CMakeApp(App):
|
|
|
898
896
|
if modified_components is not None and check_app_dependencies and self.build_status == BuildStatus.UNKNOWN:
|
|
899
897
|
subprocess_run(
|
|
900
898
|
common_args + ['reconfigure'],
|
|
901
|
-
log_terminal=
|
|
902
|
-
log_fs=
|
|
899
|
+
log_terminal=self._is_build_log_path_temp,
|
|
900
|
+
log_fs=self.build_log_path,
|
|
903
901
|
check=True,
|
|
904
902
|
additional_env_dict=additional_env_dict,
|
|
905
903
|
)
|
|
@@ -920,23 +918,22 @@ class CMakeApp(App):
|
|
|
920
918
|
return
|
|
921
919
|
|
|
922
920
|
# idf.py build
|
|
923
|
-
build_args = deepcopy(common_args)
|
|
924
921
|
if self.cmake_vars:
|
|
925
922
|
for key, val in self.cmake_vars.items():
|
|
926
|
-
|
|
923
|
+
common_args.append(f'-D{key}={val}')
|
|
927
924
|
if 'TEST_EXCLUDE_COMPONENTS' in self.cmake_vars and 'TEST_COMPONENTS' not in self.cmake_vars:
|
|
928
|
-
|
|
925
|
+
common_args.append('-DTESTS_ALL=1')
|
|
929
926
|
if 'CONFIG_APP_BUILD_BOOTLOADER' in self.cmake_vars:
|
|
930
927
|
# In case if secure_boot is enabled then for bootloader build need to add `bootloader` cmd
|
|
931
|
-
|
|
932
|
-
|
|
928
|
+
common_args.append('bootloader')
|
|
929
|
+
common_args.append('build')
|
|
933
930
|
if self.verbose:
|
|
934
|
-
|
|
931
|
+
common_args.append('-v')
|
|
935
932
|
|
|
936
933
|
subprocess_run(
|
|
937
|
-
|
|
938
|
-
log_terminal=
|
|
939
|
-
log_fs=
|
|
934
|
+
common_args,
|
|
935
|
+
log_terminal=self._is_build_log_path_temp,
|
|
936
|
+
log_fs=self.build_log_path,
|
|
940
937
|
check=True,
|
|
941
938
|
additional_env_dict=additional_env_dict,
|
|
942
939
|
)
|
idf_build_apps/config.py
CHANGED
|
@@ -39,10 +39,10 @@ def load_toml(filepath: t.Union[str, Path]) -> dict:
|
|
|
39
39
|
raise InvalidTomlError(filepath, str(e))
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
def _get_config_from_file(filepath:
|
|
42
|
+
def _get_config_from_file(filepath: str) -> t.Tuple[t.Optional[dict], str]:
|
|
43
43
|
config = None
|
|
44
|
-
if
|
|
45
|
-
if filepath
|
|
44
|
+
if os.path.isfile(filepath):
|
|
45
|
+
if os.path.basename(filepath) == PYPROJECT_TOML_FN:
|
|
46
46
|
tool = load_toml(filepath).get('tool', None)
|
|
47
47
|
if tool:
|
|
48
48
|
config = tool.get('idf-build-apps', None)
|
|
@@ -52,14 +52,14 @@ def _get_config_from_file(filepath: Path) -> t.Tuple[t.Optional[dict], Path]:
|
|
|
52
52
|
return config, filepath
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
def _get_config_from_path(dirpath:
|
|
55
|
+
def _get_config_from_path(dirpath: str) -> t.Tuple[t.Optional[dict], str]:
|
|
56
56
|
config = None
|
|
57
57
|
filepath = dirpath
|
|
58
|
-
if (dirpath
|
|
59
|
-
config, filepath = _get_config_from_file(dirpath
|
|
58
|
+
if os.path.isfile(os.path.join(dirpath, PYPROJECT_TOML_FN)):
|
|
59
|
+
config, filepath = _get_config_from_file(os.path.join(dirpath, PYPROJECT_TOML_FN))
|
|
60
60
|
|
|
61
|
-
if config is None and (dirpath
|
|
62
|
-
config, filepath = _get_config_from_file(dirpath
|
|
61
|
+
if config is None and os.path.isfile(os.path.join(dirpath, IDF_BUILD_APPS_TOML_FN)):
|
|
62
|
+
config, filepath = _get_config_from_file(os.path.join(dirpath, IDF_BUILD_APPS_TOML_FN))
|
|
63
63
|
|
|
64
64
|
return config, filepath
|
|
65
65
|
|
|
@@ -83,9 +83,9 @@ def get_valid_config(starts_from: str = os.getcwd(), custom_path: t.Optional[str
|
|
|
83
83
|
print(f'Using config file: {filepath}')
|
|
84
84
|
return config
|
|
85
85
|
|
|
86
|
-
if (cur_dir
|
|
86
|
+
if os.path.exists(os.path.join(cur_dir, '.git')):
|
|
87
87
|
break
|
|
88
88
|
|
|
89
|
-
cur_dir = cur_dir
|
|
89
|
+
cur_dir = os.path.abspath(os.path.join(cur_dir, '..'))
|
|
90
90
|
|
|
91
91
|
return None
|
idf_build_apps/constants.py
CHANGED
|
@@ -9,9 +9,6 @@ import re
|
|
|
9
9
|
import sys
|
|
10
10
|
import tempfile
|
|
11
11
|
import typing as t
|
|
12
|
-
from pathlib import (
|
|
13
|
-
Path,
|
|
14
|
-
)
|
|
15
12
|
|
|
16
13
|
from .utils import (
|
|
17
14
|
to_version,
|
|
@@ -27,19 +24,19 @@ if _BUILDING_DOCS:
|
|
|
27
24
|
if _BUILDING_DOCS:
|
|
28
25
|
_idf_env = tempfile.gettempdir()
|
|
29
26
|
else:
|
|
30
|
-
_idf_env = os.getenv('IDF_PATH'
|
|
31
|
-
if not
|
|
32
|
-
|
|
27
|
+
_idf_env = os.getenv('IDF_PATH') or ''
|
|
28
|
+
if not _idf_env:
|
|
29
|
+
raise SystemExit('environment variable IDF_PATH must be set')
|
|
33
30
|
|
|
34
31
|
|
|
35
|
-
IDF_PATH =
|
|
36
|
-
IDF_PY = IDF_PATH
|
|
37
|
-
IDF_SIZE_PY = IDF_PATH
|
|
32
|
+
IDF_PATH = os.path.abspath(_idf_env)
|
|
33
|
+
IDF_PY = os.path.join(IDF_PATH, 'tools', 'idf.py')
|
|
34
|
+
IDF_SIZE_PY = os.path.join(IDF_PATH, 'tools', 'idf_size.py')
|
|
38
35
|
PROJECT_DESCRIPTION_JSON = 'project_description.json'
|
|
39
36
|
DEFAULT_SDKCONFIG = 'sdkconfig.defaults'
|
|
40
37
|
|
|
41
38
|
|
|
42
|
-
sys.path.append(
|
|
39
|
+
sys.path.append(os.path.join(IDF_PATH, 'tools', 'idf_py_actions'))
|
|
43
40
|
if _BUILDING_DOCS:
|
|
44
41
|
_idf_py_constant_py = object()
|
|
45
42
|
else:
|
|
@@ -54,7 +51,7 @@ ALL_TARGETS = SUPPORTED_TARGETS + PREVIEW_TARGETS
|
|
|
54
51
|
|
|
55
52
|
|
|
56
53
|
def _idf_version_from_cmake() -> t.Tuple[int, int, int]:
|
|
57
|
-
version_path =
|
|
54
|
+
version_path = os.path.join(IDF_PATH, 'tools', 'cmake', 'version.cmake')
|
|
58
55
|
if not os.path.isfile(version_path):
|
|
59
56
|
raise ValueError(f'File {version_path} does not exist')
|
|
60
57
|
|
idf_build_apps/finder.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import logging
|
|
5
5
|
import os
|
|
6
|
+
import os.path
|
|
6
7
|
import re
|
|
7
8
|
import typing as t
|
|
8
9
|
from pathlib import (
|
|
@@ -80,7 +81,7 @@ def _get_apps_from_path(
|
|
|
80
81
|
default_config_name = rule.config_name
|
|
81
82
|
continue
|
|
82
83
|
|
|
83
|
-
sdkconfig_paths = sorted([str(p.
|
|
84
|
+
sdkconfig_paths = sorted([str(p.resolve()) for p in Path(path).glob(rule.file_name)])
|
|
84
85
|
|
|
85
86
|
if sdkconfig_paths:
|
|
86
87
|
sdkconfig_paths_matched = True # skip the next block for no wildcard config rules
|
|
@@ -178,7 +179,7 @@ def _find_apps(
|
|
|
178
179
|
del dirs[:]
|
|
179
180
|
continue
|
|
180
181
|
|
|
181
|
-
if root_path
|
|
182
|
+
if os.path.basename(root_path) == 'managed_components': # idf-component-manager
|
|
182
183
|
LOGGER.debug('=> Skipping %s (managed components)', root_path)
|
|
183
184
|
del dirs[:]
|
|
184
185
|
continue
|
idf_build_apps/main.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2022-
|
|
1
|
+
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import argparse
|
|
@@ -10,8 +10,10 @@ import shutil
|
|
|
10
10
|
import sys
|
|
11
11
|
import textwrap
|
|
12
12
|
import typing as t
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
from pydantic import (
|
|
15
|
+
Field,
|
|
16
|
+
create_model,
|
|
15
17
|
)
|
|
16
18
|
|
|
17
19
|
from . import (
|
|
@@ -19,6 +21,7 @@ from . import (
|
|
|
19
21
|
)
|
|
20
22
|
from .app import (
|
|
21
23
|
App,
|
|
24
|
+
AppDeserializer,
|
|
22
25
|
CMakeApp,
|
|
23
26
|
MakeApp,
|
|
24
27
|
)
|
|
@@ -181,6 +184,7 @@ def find_apps(
|
|
|
181
184
|
|
|
182
185
|
for target in targets:
|
|
183
186
|
for path in to_list(paths):
|
|
187
|
+
path = path.strip()
|
|
184
188
|
apps.extend(
|
|
185
189
|
_find_apps(
|
|
186
190
|
path,
|
|
@@ -295,7 +299,6 @@ def build_apps(
|
|
|
295
299
|
if f and os.path.isfile(f):
|
|
296
300
|
os.remove(f)
|
|
297
301
|
LOGGER.debug('Remove existing collect file %s', f)
|
|
298
|
-
Path(f).touch()
|
|
299
302
|
|
|
300
303
|
exit_code = 0
|
|
301
304
|
for i, app in enumerate(apps):
|
|
@@ -428,8 +431,9 @@ def get_parser() -> argparse.ArgumentParser:
|
|
|
428
431
|
'- @w: would be replaced by the wildcard, usually the sdkconfig\n'
|
|
429
432
|
'- @n: would be replaced by the app name\n'
|
|
430
433
|
'- @f: would be replaced by the escaped app path (replaced "/" to "_")\n'
|
|
431
|
-
'- @
|
|
432
|
-
'- @
|
|
434
|
+
'- @v: Would be replaced by the ESP-IDF version like `5_3_0`\n'
|
|
435
|
+
'- @i: would be replaced by the build index (only available in `build` command)\n'
|
|
436
|
+
'- @p: would be replaced by the parallel index (only available in `build` command)',
|
|
433
437
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
434
438
|
)
|
|
435
439
|
actions = parser.add_subparsers(dest='action')
|
|
@@ -743,7 +747,7 @@ def main():
|
|
|
743
747
|
os.makedirs(os.path.dirname(os.path.realpath(args.output)), exist_ok=True)
|
|
744
748
|
with open(args.output, 'w') as fw:
|
|
745
749
|
for app in apps:
|
|
746
|
-
fw.write(app.
|
|
750
|
+
fw.write(app.to_json() + '\n')
|
|
747
751
|
else:
|
|
748
752
|
for app in apps:
|
|
749
753
|
print(app)
|
|
@@ -792,3 +796,37 @@ def main():
|
|
|
792
796
|
print(f' {app}')
|
|
793
797
|
|
|
794
798
|
sys.exit(res)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def json_to_app(json_str: str, extra_classes: t.Optional[t.List[t.Type[App]]] = None) -> App:
|
|
802
|
+
"""
|
|
803
|
+
Deserialize json string to App object
|
|
804
|
+
|
|
805
|
+
.. note::
|
|
806
|
+
|
|
807
|
+
You can pass extra_cls to support custom App class. A custom App class must be a subclass of App, and have a
|
|
808
|
+
different value of `build_system`. For example, a custom CMake app
|
|
809
|
+
|
|
810
|
+
>>> class CustomApp(CMakeApp):
|
|
811
|
+
>>> build_system: Literal['custom_cmake'] = 'custom_cmake'
|
|
812
|
+
|
|
813
|
+
Then you can pass the CustomApp class to the `extra_cls` argument
|
|
814
|
+
|
|
815
|
+
>>> json_str = CustomApp('.', 'esp32').to_json()
|
|
816
|
+
>>> json_to_app(json_str, extra_classes=[CustomApp])
|
|
817
|
+
|
|
818
|
+
:param json_str: json string
|
|
819
|
+
:param extra_classes: extra App class
|
|
820
|
+
:return: App object
|
|
821
|
+
"""
|
|
822
|
+
types = [App, CMakeApp, MakeApp]
|
|
823
|
+
if extra_classes:
|
|
824
|
+
types.extend(extra_classes)
|
|
825
|
+
|
|
826
|
+
custom_deserializer = create_model(
|
|
827
|
+
'_CustomDeserializer',
|
|
828
|
+
app=(t.Union[tuple(types)], Field(discriminator='build_system')),
|
|
829
|
+
__base__=AppDeserializer,
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
return custom_deserializer.from_json(json_str)
|
|
@@ -5,9 +5,6 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
import typing as t
|
|
7
7
|
import warnings
|
|
8
|
-
from pathlib import (
|
|
9
|
-
Path,
|
|
10
|
-
)
|
|
11
8
|
|
|
12
9
|
import yaml
|
|
13
10
|
from pyparsing import (
|
|
@@ -52,24 +49,26 @@ class FolderRule:
|
|
|
52
49
|
|
|
53
50
|
def __init__(
|
|
54
51
|
self,
|
|
55
|
-
folder:
|
|
52
|
+
folder: str,
|
|
56
53
|
enable: t.Optional[t.List[t.Dict[str, t.Any]]] = None,
|
|
57
54
|
disable: t.Optional[t.List[t.Dict[str, t.Any]]] = None,
|
|
58
55
|
disable_test: t.Optional[t.List[t.Dict[str, t.Any]]] = None,
|
|
59
56
|
depends_components: t.Optional[t.List[str]] = None,
|
|
60
57
|
depends_filepatterns: t.Optional[t.List[str]] = None,
|
|
61
58
|
) -> None:
|
|
62
|
-
self.folder =
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.
|
|
59
|
+
self.folder = os.path.abspath(folder)
|
|
60
|
+
|
|
61
|
+
def _clause_to_if_clause(clause: t.Dict[str, t.Any]) -> IfClause:
|
|
62
|
+
_kwargs = {'stmt': clause['if']}
|
|
63
|
+
if 'temporary' in clause:
|
|
64
|
+
_kwargs['temporary'] = clause['temporary']
|
|
65
|
+
if 'reason' in clause:
|
|
66
|
+
_kwargs['reason'] = clause['reason']
|
|
67
|
+
return IfClause(**_kwargs)
|
|
68
|
+
|
|
69
|
+
self.enable = [_clause_to_if_clause(clause) for clause in enable] if enable else []
|
|
70
|
+
self.disable = [_clause_to_if_clause(clause) for clause in disable] if disable else []
|
|
71
|
+
self.disable_test = [_clause_to_if_clause(clause) for clause in disable_test] if disable_test else []
|
|
73
72
|
self.depends_components = depends_components or []
|
|
74
73
|
self.depends_filepatterns = depends_filepatterns or []
|
|
75
74
|
|
|
@@ -148,13 +147,13 @@ class FolderRule:
|
|
|
148
147
|
|
|
149
148
|
|
|
150
149
|
class DefaultRule(FolderRule):
|
|
151
|
-
def __init__(self, folder:
|
|
150
|
+
def __init__(self, folder: str) -> None:
|
|
152
151
|
super().__init__(folder)
|
|
153
152
|
|
|
154
153
|
|
|
155
154
|
class Manifest:
|
|
156
155
|
# could be reassigned later
|
|
157
|
-
ROOTPATH =
|
|
156
|
+
ROOTPATH = os.curdir
|
|
158
157
|
CHECK_MANIFEST_RULES = False
|
|
159
158
|
|
|
160
159
|
def __init__(
|
|
@@ -174,12 +173,10 @@ class Manifest:
|
|
|
174
173
|
if folder.startswith('.'):
|
|
175
174
|
continue
|
|
176
175
|
|
|
177
|
-
if os.path.isabs(folder):
|
|
178
|
-
folder =
|
|
179
|
-
else:
|
|
180
|
-
folder = Path(cls.ROOTPATH, folder)
|
|
176
|
+
if not os.path.isabs(folder):
|
|
177
|
+
folder = os.path.join(cls.ROOTPATH, folder)
|
|
181
178
|
|
|
182
|
-
if not
|
|
179
|
+
if not os.path.exists(folder):
|
|
183
180
|
msg = f'Folder "{folder}" does not exist. Please check your manifest file {path}'
|
|
184
181
|
if cls.CHECK_MANIFEST_RULES:
|
|
185
182
|
raise InvalidManifest(msg)
|
|
@@ -194,9 +191,9 @@ class Manifest:
|
|
|
194
191
|
return Manifest(rules)
|
|
195
192
|
|
|
196
193
|
def _most_suitable_rule(self, _folder: str) -> FolderRule:
|
|
197
|
-
folder =
|
|
194
|
+
folder = os.path.abspath(_folder)
|
|
198
195
|
for rule in self.rules[::-1]:
|
|
199
|
-
if rule.folder == folder or rule.folder
|
|
196
|
+
if rule.folder == folder or folder.startswith(rule.folder):
|
|
200
197
|
return rule
|
|
201
198
|
|
|
202
199
|
return DefaultRule(folder)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
|
|
4
3
|
import logging
|
|
4
|
+
import os.path
|
|
5
5
|
import typing as t
|
|
6
6
|
from pathlib import (
|
|
7
7
|
Path,
|
|
@@ -50,10 +50,10 @@ _value = Optional('(').suppress() + MatchFirst([_hex_value, _str_value, _int_val
|
|
|
50
50
|
_define_expr = '#define' + Optional(_name)('name') + Optional(_value)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
def get_defines(header_path:
|
|
53
|
+
def get_defines(header_path: str) -> t.List[str]:
|
|
54
54
|
defines = []
|
|
55
55
|
LOGGER.debug('Reading macros from %s...', header_path)
|
|
56
|
-
with open(
|
|
56
|
+
with open(header_path) as f:
|
|
57
57
|
output = f.read()
|
|
58
58
|
|
|
59
59
|
for line in output.split('\n'):
|
|
@@ -82,10 +82,10 @@ class SocHeader(dict):
|
|
|
82
82
|
super().__init__(**soc_header_dict)
|
|
83
83
|
|
|
84
84
|
@staticmethod
|
|
85
|
-
def _get_dir_from_candidates(candidates: t.List[
|
|
85
|
+
def _get_dir_from_candidates(candidates: t.List[str]) -> t.Optional[str]:
|
|
86
86
|
for d in candidates:
|
|
87
|
-
if not
|
|
88
|
-
LOGGER.debug('folder "%s" not found. Skipping...',
|
|
87
|
+
if not os.path.isdir(d):
|
|
88
|
+
LOGGER.debug('folder "%s" not found. Skipping...', os.path.abspath(d))
|
|
89
89
|
else:
|
|
90
90
|
return d
|
|
91
91
|
|
|
@@ -96,22 +96,22 @@ class SocHeader(dict):
|
|
|
96
96
|
soc_headers_dir = cls._get_dir_from_candidates(
|
|
97
97
|
[
|
|
98
98
|
# other branches
|
|
99
|
-
IDF_PATH
|
|
99
|
+
os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', target, 'include', 'soc')),
|
|
100
100
|
# release/v4.2
|
|
101
|
-
IDF_PATH
|
|
101
|
+
os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', 'soc', target, 'include', 'soc')),
|
|
102
102
|
]
|
|
103
103
|
)
|
|
104
104
|
esp_rom_headers_dir = cls._get_dir_from_candidates(
|
|
105
105
|
[
|
|
106
|
-
IDF_PATH
|
|
106
|
+
os.path.join(IDF_PATH, 'components', 'esp_rom', target),
|
|
107
107
|
]
|
|
108
108
|
)
|
|
109
109
|
|
|
110
|
-
header_files = []
|
|
110
|
+
header_files: t.List[str] = []
|
|
111
111
|
if soc_headers_dir:
|
|
112
|
-
header_files +=
|
|
112
|
+
header_files += [str(p.resolve()) for p in Path(soc_headers_dir).glob(cls.CAPS_HEADER_FILEPATTERN)]
|
|
113
113
|
if esp_rom_headers_dir:
|
|
114
|
-
header_files +=
|
|
114
|
+
header_files += [str(p.resolve()) for p in Path(esp_rom_headers_dir).glob(cls.CAPS_HEADER_FILEPATTERN)]
|
|
115
115
|
|
|
116
116
|
output_dict = {}
|
|
117
117
|
for f in header_files:
|
idf_build_apps/session_args.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
import logging
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
6
7
|
import typing as t
|
|
7
8
|
|
|
9
|
+
LOGGER = logging.getLogger(__name__)
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
class SessionArgs:
|
|
10
13
|
workdir: str = os.getcwd()
|
|
@@ -35,27 +38,32 @@ class SessionArgs:
|
|
|
35
38
|
self.override_sdkconfig_file_path = override_sdkconfig_merged_file
|
|
36
39
|
|
|
37
40
|
def _get_override_sdkconfig_files_items(self, override_sdkconfig_files: t.Tuple[str]) -> t.Dict:
|
|
38
|
-
|
|
41
|
+
d = {}
|
|
39
42
|
for f in override_sdkconfig_files:
|
|
40
|
-
if
|
|
41
|
-
f = os.path.join(self.workdir, f)
|
|
43
|
+
# use filepath if abs/rel already point to itself
|
|
42
44
|
if not os.path.isfile(f):
|
|
43
|
-
|
|
45
|
+
# find it in the workdir
|
|
46
|
+
LOGGER.debug('override sdkconfig file %s not found, checking under app_dir...', f)
|
|
47
|
+
f = os.path.join(self.workdir, f)
|
|
48
|
+
if not os.path.isfile(f):
|
|
49
|
+
LOGGER.debug('override sdkconfig file %s not found, skipping...', f)
|
|
50
|
+
continue
|
|
51
|
+
|
|
44
52
|
with open(f) as fr:
|
|
45
53
|
for line in fr:
|
|
46
54
|
m = re.compile(r"^([^=]+)=\"?([^\"\n]*)\"?\n*$").match(line)
|
|
47
55
|
if not m:
|
|
48
56
|
continue
|
|
49
|
-
|
|
50
|
-
return
|
|
57
|
+
d[m.group(1)] = m.group(2)
|
|
58
|
+
return d
|
|
51
59
|
|
|
52
60
|
def _get_override_sdkconfig_items(self, override_sdkconfig_items: t.Tuple[str]) -> t.Dict:
|
|
53
|
-
|
|
61
|
+
d = {}
|
|
54
62
|
for line in override_sdkconfig_items:
|
|
55
63
|
m = re.compile(r"^([^=]+)=\"?([^\"\n]*)\"?\n*$").match(line)
|
|
56
64
|
if m:
|
|
57
|
-
|
|
58
|
-
return
|
|
65
|
+
d[m.group(1)] = m.group(2)
|
|
66
|
+
return d
|
|
59
67
|
|
|
60
68
|
def _create_override_sdkconfig_merged_file(self, override_sdkconfig_merged_items) -> t.Optional[str]:
|
|
61
69
|
if not override_sdkconfig_merged_items:
|
idf_build_apps/utils.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
# SPDX-FileCopyrightText: 2022-
|
|
1
|
+
# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import fnmatch
|
|
5
|
+
import functools
|
|
5
6
|
import glob
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
@@ -12,9 +13,6 @@ import typing as t
|
|
|
12
13
|
from copy import (
|
|
13
14
|
deepcopy,
|
|
14
15
|
)
|
|
15
|
-
from pathlib import (
|
|
16
|
-
Path,
|
|
17
|
-
)
|
|
18
16
|
|
|
19
17
|
from packaging.version import (
|
|
20
18
|
Version,
|
|
@@ -137,7 +135,7 @@ def find_first_match(pattern: str, path: str) -> t.Optional[str]:
|
|
|
137
135
|
def subprocess_run(
|
|
138
136
|
cmd: t.List[str],
|
|
139
137
|
log_terminal: bool = True,
|
|
140
|
-
log_fs: t.
|
|
138
|
+
log_fs: t.Union[t.IO[str], str, None] = None,
|
|
141
139
|
check: bool = False,
|
|
142
140
|
additional_env_dict: t.Optional[t.Dict[str, str]] = None,
|
|
143
141
|
**kwargs,
|
|
@@ -160,16 +158,26 @@ def subprocess_run(
|
|
|
160
158
|
subprocess_env.update(additional_env_dict)
|
|
161
159
|
|
|
162
160
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=subprocess_env, **kwargs)
|
|
163
|
-
if p.stdout:
|
|
164
|
-
for line in p.stdout:
|
|
165
|
-
if isinstance(line, bytes):
|
|
166
|
-
line = line.decode('utf-8')
|
|
167
161
|
|
|
168
|
-
|
|
169
|
-
|
|
162
|
+
def _log_stdout(fs: t.Optional[t.IO[str]] = None):
|
|
163
|
+
if p.stdout:
|
|
164
|
+
for line in p.stdout:
|
|
165
|
+
if isinstance(line, bytes):
|
|
166
|
+
line = line.decode('utf-8')
|
|
170
167
|
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
if log_terminal:
|
|
169
|
+
sys.stdout.write(line)
|
|
170
|
+
|
|
171
|
+
if fs:
|
|
172
|
+
fs.write(line)
|
|
173
|
+
|
|
174
|
+
if p.stdout:
|
|
175
|
+
if log_fs:
|
|
176
|
+
if isinstance(log_fs, str):
|
|
177
|
+
with open(log_fs, 'a') as fa:
|
|
178
|
+
_log_stdout(fa)
|
|
179
|
+
else:
|
|
180
|
+
_log_stdout(log_fs)
|
|
173
181
|
|
|
174
182
|
returncode = p.wait()
|
|
175
183
|
if check and returncode != 0:
|
|
@@ -270,14 +278,14 @@ def semicolon_separated_str_to_list(s: t.Optional[str]) -> t.Optional[t.List[str
|
|
|
270
278
|
return [p.strip() for p in s.strip().split(';') if p.strip()]
|
|
271
279
|
|
|
272
280
|
|
|
273
|
-
def to_absolute_path(s: str, rootpath: t.Optional[str] = None) ->
|
|
274
|
-
rp =
|
|
281
|
+
def to_absolute_path(s: str, rootpath: t.Optional[str] = None) -> str:
|
|
282
|
+
rp = os.path.abspath(os.path.expanduser(rootpath or '.'))
|
|
275
283
|
|
|
276
|
-
sp =
|
|
277
|
-
if
|
|
278
|
-
return sp
|
|
284
|
+
sp = os.path.expanduser(s)
|
|
285
|
+
if os.path.isabs(sp):
|
|
286
|
+
return sp
|
|
279
287
|
else:
|
|
280
|
-
return (rp
|
|
288
|
+
return os.path.abspath(os.path.join(rp, sp))
|
|
281
289
|
|
|
282
290
|
|
|
283
291
|
def to_version(s: t.Any) -> Version:
|
|
@@ -308,31 +316,49 @@ def files_matches_patterns(
|
|
|
308
316
|
return False
|
|
309
317
|
|
|
310
318
|
|
|
319
|
+
@functools.total_ordering
|
|
311
320
|
class BaseModel(_BaseModel):
|
|
312
321
|
"""
|
|
313
322
|
BaseModel that is hashable
|
|
314
323
|
"""
|
|
315
324
|
|
|
325
|
+
__EQ_IGNORE_FIELDS__: t.List[str] = []
|
|
326
|
+
|
|
316
327
|
def __lt__(self, other: t.Any) -> bool:
|
|
317
328
|
if isinstance(other, self.__class__):
|
|
318
329
|
for k in self.model_dump():
|
|
319
|
-
if
|
|
320
|
-
return getattr(self, k) < getattr(other, k)
|
|
321
|
-
else:
|
|
330
|
+
if k in self.__EQ_IGNORE_FIELDS__:
|
|
322
331
|
continue
|
|
323
332
|
|
|
333
|
+
self_attr = getattr(self, k, '') or ''
|
|
334
|
+
other_attr = getattr(other, k, '') or ''
|
|
335
|
+
|
|
336
|
+
if self_attr != other_attr:
|
|
337
|
+
return self_attr < other_attr
|
|
338
|
+
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
return False
|
|
342
|
+
|
|
324
343
|
return NotImplemented
|
|
325
344
|
|
|
326
345
|
def __eq__(self, other: t.Any) -> bool:
|
|
327
346
|
if isinstance(other, self.__class__):
|
|
328
347
|
# we only care the public attributes
|
|
329
|
-
|
|
348
|
+
self_model_dump = self.model_dump()
|
|
349
|
+
other_model_dump = other.model_dump()
|
|
350
|
+
|
|
351
|
+
for _field in self.__EQ_IGNORE_FIELDS__:
|
|
352
|
+
self_model_dump.pop(_field, None)
|
|
353
|
+
other_model_dump.pop(_field, None)
|
|
354
|
+
|
|
355
|
+
return self_model_dump == other_model_dump
|
|
330
356
|
|
|
331
357
|
return NotImplemented
|
|
332
358
|
|
|
333
359
|
def __hash__(self) -> int:
|
|
334
360
|
hash_list = []
|
|
335
|
-
for v in self.
|
|
361
|
+
for v in self.model_dump().values():
|
|
336
362
|
if isinstance(v, list):
|
|
337
363
|
hash_list.append(tuple(v))
|
|
338
364
|
elif isinstance(v, dict):
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
idf_build_apps/__init__.py,sha256=WAwRTUCnGjoWIT8bl_n9qCOk7xmA8JdvjZfnYWNs1Qs,653
|
|
2
|
+
idf_build_apps/__main__.py,sha256=8E-5xHm2MlRun0L88XJleNh5U50dpE0Q1nK5KqomA7I,182
|
|
3
|
+
idf_build_apps/app.py,sha256=KwZ7xrxUs_cVqEaDxtrPE7B4bo-_-RCJAEUX3iHR9A4,34537
|
|
4
|
+
idf_build_apps/build_apps_args.py,sha256=r6VCJDdCzE873X8OTputYkCBZPgECaKoNlAejfcamJk,1644
|
|
5
|
+
idf_build_apps/config.py,sha256=I75uOQGarCWVKGi16ZYpo0qTVU25BUP4eh6-RWCtbvw,2924
|
|
6
|
+
idf_build_apps/constants.py,sha256=xqclwUpWE5dEByL4kxdg2HaHjbAfkJtxodFfLZuAk8A,2818
|
|
7
|
+
idf_build_apps/finder.py,sha256=qw5moNq7U5mHSsR0CCfGkKE9p4QsWYNcfkxzeQ73HgM,6252
|
|
8
|
+
idf_build_apps/log.py,sha256=4P4q8EqV68iQw0LCoNI6ibBBpAkNgetDxZ0Cgbhp0Bo,2570
|
|
9
|
+
idf_build_apps/main.py,sha256=jh2mZFK75OpjEeWzupNk-ZQFCIXFfyoL6-XyKi2hbnk,32896
|
|
10
|
+
idf_build_apps/session_args.py,sha256=LYgibgUGjHm0iAd6gBYcT9rpTCcdKfcblFMysAlv7UE,2996
|
|
11
|
+
idf_build_apps/utils.py,sha256=-o0yyYHAhwVD2ihRupUJdKEu0sAIOaGg4DnIRy6iJNA,9669
|
|
12
|
+
idf_build_apps/junit/__init__.py,sha256=GRyhJfZet00iWxe2PvUAG72CXhnhOZaV4B7Z7txKVAk,226
|
|
13
|
+
idf_build_apps/junit/report.py,sha256=2sBLEL5EnGW6h0ku2W-BOyEFMgQcu0OO-WcFyXH_Aqg,6341
|
|
14
|
+
idf_build_apps/junit/utils.py,sha256=gtibRs8WTE8IXTIAS73QR_k_jrJlOjCl2y-9KiP5_Nk,1304
|
|
15
|
+
idf_build_apps/manifest/__init__.py,sha256=Q2-cb3ngNjnl6_zWhUfzZZB10f_-Rv2JYNck3Lk7UkQ,133
|
|
16
|
+
idf_build_apps/manifest/if_parser.py,sha256=hS3khcDeFsoqfDLdRG931h91SU4bTALACxjcEiiDIBQ,6379
|
|
17
|
+
idf_build_apps/manifest/manifest.py,sha256=oRy3BlR_JXfQ7avJQnAZ8__ZCcuhGM_wnFDqHYCqkIQ,7316
|
|
18
|
+
idf_build_apps/manifest/soc_header.py,sha256=TVT7wxzcxdeIdX8WAtPYr8bFV7EqTWkVo4h18MWsD8Q,4028
|
|
19
|
+
idf_build_apps-2.0.0rc1.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
|
|
20
|
+
idf_build_apps-2.0.0rc1.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
21
|
+
idf_build_apps-2.0.0rc1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
22
|
+
idf_build_apps-2.0.0rc1.dist-info/METADATA,sha256=EU1KK7_Meebf2ZdJ6mpyB2hS_VJj4HLjXVWri1q0RwU,4453
|
|
23
|
+
idf_build_apps-2.0.0rc1.dist-info/RECORD,,
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
idf_build_apps/__init__.py,sha256=BcyNvVaRq_d-XR_wSvY4kaO2XLD4sy2OajweqWTc7F8,636
|
|
2
|
-
idf_build_apps/__main__.py,sha256=8E-5xHm2MlRun0L88XJleNh5U50dpE0Q1nK5KqomA7I,182
|
|
3
|
-
idf_build_apps/app.py,sha256=uDo_UInS4ogLf_R44N9ZbG_-NMlofUD1rvHe0ohjd4E,34102
|
|
4
|
-
idf_build_apps/build_apps_args.py,sha256=r6VCJDdCzE873X8OTputYkCBZPgECaKoNlAejfcamJk,1644
|
|
5
|
-
idf_build_apps/config.py,sha256=zRmFgka3S3HKxPeJXj5ebj6I0DN4U2UNQnNFAD-u_2Y,2806
|
|
6
|
-
idf_build_apps/constants.py,sha256=CweC0HwaACuHkrjY2zaPPrjM9rgnacesuu-FGw0SOqk,2817
|
|
7
|
-
idf_build_apps/finder.py,sha256=0qJ0jQdTJH9HwFe9vrt_fbI8RjZmDcqzS7WMI5G_k1o,6237
|
|
8
|
-
idf_build_apps/log.py,sha256=4P4q8EqV68iQw0LCoNI6ibBBpAkNgetDxZ0Cgbhp0Bo,2570
|
|
9
|
-
idf_build_apps/main.py,sha256=VQjsoKvT8GPXVI4S-xByWyea6JWSHjblxoJt_CgaUnc,31615
|
|
10
|
-
idf_build_apps/session_args.py,sha256=UOPqYPlqQBd2p0pBDkgypjqnpwFGF1YYndOoWGpBjLI,2654
|
|
11
|
-
idf_build_apps/utils.py,sha256=V-8Wfk3Wg7ML-xpfV_aQc9HsFbpSHYLL-EN39UvUnNg,8883
|
|
12
|
-
idf_build_apps/junit/__init__.py,sha256=GRyhJfZet00iWxe2PvUAG72CXhnhOZaV4B7Z7txKVAk,226
|
|
13
|
-
idf_build_apps/junit/report.py,sha256=2sBLEL5EnGW6h0ku2W-BOyEFMgQcu0OO-WcFyXH_Aqg,6341
|
|
14
|
-
idf_build_apps/junit/utils.py,sha256=gtibRs8WTE8IXTIAS73QR_k_jrJlOjCl2y-9KiP5_Nk,1304
|
|
15
|
-
idf_build_apps/manifest/__init__.py,sha256=Q2-cb3ngNjnl6_zWhUfzZZB10f_-Rv2JYNck3Lk7UkQ,133
|
|
16
|
-
idf_build_apps/manifest/if_parser.py,sha256=hS3khcDeFsoqfDLdRG931h91SU4bTALACxjcEiiDIBQ,6379
|
|
17
|
-
idf_build_apps/manifest/manifest.py,sha256=X0bgE0QokqBuH9zeJo1oEzJAC4ebKhTcvMTF3RDbHm4,7215
|
|
18
|
-
idf_build_apps/manifest/soc_header.py,sha256=MAEOUQgP6VJERbfFtVF9lnkbDAZbEkzGduHCA_eH7Pc,3879
|
|
19
|
-
idf_build_apps-2.0.0b5.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
|
|
20
|
-
idf_build_apps-2.0.0b5.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
|
21
|
-
idf_build_apps-2.0.0b5.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
22
|
-
idf_build_apps-2.0.0b5.dist-info/METADATA,sha256=ZBOLysnM320fDM9DkYtllmIc9PLMqjAILyFm5e6Yqe4,4452
|
|
23
|
-
idf_build_apps-2.0.0b5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|