idf-build-apps 2.6.4__tar.gz → 2.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.github/workflows/test-build-docs.yml +1 -1
  2. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.pre-commit-config.yaml +1 -0
  3. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/CHANGELOG.md +6 -0
  4. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/PKG-INFO +2 -1
  5. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/__init__.py +1 -1
  6. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/app.py +32 -54
  7. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/constants.py +0 -11
  8. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/junit/report.py +2 -2
  9. idf_build_apps-2.7.0/idf_build_apps/log.py +119 -0
  10. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/main.py +15 -9
  11. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/pyproject.toml +4 -2
  12. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/setup.py +3 -2
  13. idf_build_apps-2.6.4/idf_build_apps/log.py +0 -88
  14. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.editorconfig +0 -0
  15. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.git-blame-ignore-revs +0 -0
  16. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.gitattributes +0 -0
  17. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.github/dependabot.yml +0 -0
  18. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.github/workflows/check-pre-commit.yml +0 -0
  19. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.github/workflows/publish-pypi.yml +0 -0
  20. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.github/workflows/sync-jira.yml +0 -0
  21. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.github/workflows/test-build-idf-apps.yml +0 -0
  22. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.gitignore +0 -0
  23. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/.readthedocs.yml +0 -0
  24. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/CONTRIBUTING.md +0 -0
  25. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/LICENSE +0 -0
  26. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/README.md +0 -0
  27. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/_apidoc_templates/module.rst_t +0 -0
  28. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/_apidoc_templates/package.rst_t +0 -0
  29. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/_apidoc_templates/toc.rst_t +0 -0
  30. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/_static/espressif-logo.svg +0 -0
  31. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/_static/theme_overrides.css +0 -0
  32. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/_templates/layout.html +0 -0
  33. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/conf_common.py +0 -0
  34. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/Makefile +0 -0
  35. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/conf.py +0 -0
  36. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/explanations/build.rst +0 -0
  37. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/explanations/config_rules.rst +0 -0
  38. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/explanations/dependency_driven_build.rst +0 -0
  39. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/explanations/find.rst +0 -0
  40. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/guides/1.x_to_2.x.md +0 -0
  41. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/index.rst +0 -0
  42. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/others/CHANGELOG.md +0 -0
  43. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/others/CONTRIBUTING.md +0 -0
  44. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/references/cli.rst +0 -0
  45. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/references/config_file.rst +0 -0
  46. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/docs/en/references/manifest.rst +0 -0
  47. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/__main__.py +0 -0
  48. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/args.py +0 -0
  49. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/autocompletions.py +0 -0
  50. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/finder.py +0 -0
  51. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/junit/__init__.py +0 -0
  52. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/junit/utils.py +0 -0
  53. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/manifest/__init__.py +0 -0
  54. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/manifest/manifest.py +0 -0
  55. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/manifest/soc_header.py +0 -0
  56. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/py.typed +0 -0
  57. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/session_args.py +0 -0
  58. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/utils.py +0 -0
  59. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/vendors/__init__.py +0 -0
  60. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/vendors/pydantic_sources.py +0 -0
  61. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/yaml/__init__.py +0 -0
  62. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/idf_build_apps/yaml/parser.py +0 -0
  63. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/license_header.txt +0 -0
  64. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/conftest.py +0 -0
  65. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/test_app.py +0 -0
  66. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/test_args.py +0 -0
  67. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/test_build.py +0 -0
  68. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/test_cmd.py +0 -0
  69. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/test_finder.py +0 -0
  70. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/test_manifest.py +0 -0
  71. {idf_build_apps-2.6.4 → idf_build_apps-2.7.0}/tests/test_utils.py +0 -0
@@ -22,4 +22,4 @@ jobs:
22
22
  cd docs
23
23
  pushd en && make html && popd
24
24
  - name: markdown-link-check
25
- uses: gaurav-nelson/github-action-markdown-link-check@1.0.15
25
+ uses: gaurav-nelson/github-action-markdown-link-check@1.0.16
@@ -38,6 +38,7 @@ repos:
38
38
  - pytest
39
39
  - argcomplete>=3
40
40
  - esp-bool-parser>=0.1.2,<1
41
+ - rich
41
42
  - repo: https://github.com/hfudev/rstfmt
42
43
  rev: v0.1.4
43
44
  hooks:
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v2.7.0 (2025-02-18)
6
+
7
+ ### Feat
8
+
9
+ - improve debug info with rich
10
+
5
11
  ## v2.6.4 (2025-02-14)
6
12
 
7
13
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: idf-build-apps
3
- Version: 2.6.4
3
+ Version: 2.7.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
@@ -22,6 +22,7 @@ Requires-Dist: pydantic_settings
22
22
  Requires-Dist: argcomplete>=3
23
23
  Requires-Dist: typing-extensions; python_version < '3.11'
24
24
  Requires-Dist: esp-bool-parser>=0.1.2,<1
25
+ Requires-Dist: rich
25
26
  Requires-Dist: sphinx ; extra == "doc"
26
27
  Requires-Dist: sphinx-rtd-theme ; extra == "doc"
27
28
  Requires-Dist: sphinx_copybutton ; extra == "doc"
@@ -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.6.4'
11
+ __version__ = '2.7.0'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
@@ -36,7 +36,6 @@ from .constants import (
36
36
  IDF_VERSION_PATCH,
37
37
  PREVIEW_TARGETS,
38
38
  PROJECT_DESCRIPTION_JSON,
39
- BuildStage,
40
39
  BuildStatus,
41
40
  )
42
41
  from .manifest.manifest import (
@@ -55,17 +54,7 @@ from .utils import (
55
54
  to_list,
56
55
  )
57
56
 
58
-
59
- class _AppBuildStageFilter(logging.Filter):
60
- def __init__(self, *args, app, **kwargs):
61
- super().__init__(*args, **kwargs)
62
- self.app = app
63
-
64
- def filter(self, record: logging.LogRecord) -> bool:
65
- if self.app._build_stage:
66
- record.build_stage = self.app._build_stage.value
67
-
68
- return True
57
+ LOGGER = logging.getLogger(__name__)
69
58
 
70
59
 
71
60
  class App(BaseModel):
@@ -120,7 +109,6 @@ class App(BaseModel):
120
109
  build_status: BuildStatus = BuildStatus.UNKNOWN
121
110
  build_comment: t.Optional[str] = None
122
111
 
123
- _build_stage: t.Optional[BuildStage] = None
124
112
  _build_duration: float = 0
125
113
  _build_timestamp: t.Optional[datetime] = None
126
114
 
@@ -176,9 +164,6 @@ class App(BaseModel):
176
164
  # private attrs, won't be dumped to json
177
165
  self._checked_should_build = False
178
166
 
179
- self._logger = logging.getLogger(f'{__name__}.{hash(self)}')
180
- self._logger.addFilter(_AppBuildStageFilter(app=self))
181
-
182
167
  self._sdkconfig_files, self._sdkconfig_files_defined_target = self._process_sdkconfig_files()
183
168
 
184
169
  @classmethod
@@ -346,10 +331,10 @@ class App(BaseModel):
346
331
  # use filepath if abs/rel already point to itself
347
332
  if not os.path.isfile(f):
348
333
  # find it in the app_dir
349
- self._logger.debug('sdkconfig file %s not found, checking under app_dir...', f)
334
+ LOGGER.debug('sdkconfig file %s not found, checking under app_dir...', f)
350
335
  f = os.path.join(self.app_dir, f)
351
336
  if not os.path.isfile(f):
352
- self._logger.debug('sdkconfig file %s not found, skipping...', f)
337
+ LOGGER.debug('sdkconfig file %s not found, skipping...', f)
353
338
  continue
354
339
 
355
340
  expanded_fp = os.path.join(expanded_dir, os.path.basename(f))
@@ -377,14 +362,14 @@ class App(BaseModel):
377
362
  with open(f) as fr:
378
363
  with open(expanded_fp) as new_fr:
379
364
  if fr.read() == new_fr.read():
380
- self._logger.debug('Use sdkconfig file %s', f)
365
+ LOGGER.debug('Use sdkconfig file %s', f)
381
366
  try:
382
367
  os.unlink(expanded_fp)
383
368
  except OSError:
384
- self._logger.debug('Failed to remove file %s', expanded_fp)
369
+ LOGGER.debug('Failed to remove file %s', expanded_fp)
385
370
  real_sdkconfig_files.append(f)
386
371
  else:
387
- self._logger.debug('Expand sdkconfig file %s to %s', f, expanded_fp)
372
+ LOGGER.debug('Expand sdkconfig file %s to %s', f, expanded_fp)
388
373
  real_sdkconfig_files.append(expanded_fp)
389
374
  # copy the related target-specific sdkconfig files
390
375
  par_dir = os.path.abspath(os.path.join(f, '..'))
@@ -392,7 +377,7 @@ class App(BaseModel):
392
377
  os.path.join(par_dir, str(p))
393
378
  for p in Path(par_dir).glob(os.path.basename(f) + f'.{self.target}')
394
379
  ):
395
- self._logger.debug(
380
+ LOGGER.debug(
396
381
  'Copy target-specific sdkconfig file %s to %s', target_specific_file, expanded_dir
397
382
  )
398
383
  shutil.copy(target_specific_file, expanded_dir)
@@ -474,21 +459,16 @@ class App(BaseModel):
474
459
  return wrapper
475
460
 
476
461
  def _pre_build(self) -> None:
477
- if self.dry_run:
478
- self._build_stage = BuildStage.DRY_RUN
479
- else:
480
- self._build_stage = BuildStage.PRE_BUILD
481
-
482
462
  if self.build_status == BuildStatus.SKIPPED:
483
463
  return
484
464
 
485
465
  if self.work_dir != self.app_dir:
486
466
  if os.path.exists(self.work_dir):
487
- self._logger.debug('Removed existing work dir: %s', self.work_dir)
467
+ LOGGER.debug('Removed existing work dir: %s', self.work_dir)
488
468
  if not self.dry_run:
489
469
  shutil.rmtree(self.work_dir)
490
470
 
491
- self._logger.debug('Copied app from %s to %s', self.app_dir, self.work_dir)
471
+ LOGGER.debug('Copied app from %s to %s', self.app_dir, self.work_dir)
492
472
  if not self.dry_run:
493
473
  # if the new directory inside the original directory,
494
474
  # make sure not to go into recursion.
@@ -502,7 +482,7 @@ class App(BaseModel):
502
482
  shutil.copytree(self.app_dir, self.work_dir, ignore=ignore, symlinks=True)
503
483
 
504
484
  if os.path.exists(self.build_path):
505
- self._logger.debug('Removed existing build dir: %s', self.build_path)
485
+ LOGGER.debug('Removed existing build dir: %s', self.build_path)
506
486
  if not self.dry_run:
507
487
  shutil.rmtree(self.build_path)
508
488
 
@@ -511,17 +491,17 @@ class App(BaseModel):
511
491
 
512
492
  sdkconfig_file = os.path.join(self.work_dir, 'sdkconfig')
513
493
  if os.path.exists(sdkconfig_file):
514
- self._logger.debug('Removed existing sdkconfig file: %s', sdkconfig_file)
494
+ LOGGER.debug('Removed existing sdkconfig file: %s', sdkconfig_file)
515
495
  if not self.dry_run:
516
496
  os.unlink(sdkconfig_file)
517
497
 
518
498
  if os.path.isfile(self.build_log_path):
519
- self._logger.debug('Removed existing build log file: %s', self.build_log_path)
499
+ LOGGER.debug('Removed existing build log file: %s', self.build_log_path)
520
500
  if not self.dry_run:
521
501
  os.unlink(self.build_log_path)
522
502
  elif not self.dry_run:
523
503
  os.makedirs(os.path.dirname(self.build_log_path), exist_ok=True)
524
- self._logger.info('Writing build log to %s', self.build_log_path)
504
+ LOGGER.info('Writing build log to %s', self.build_log_path)
525
505
 
526
506
  if self.dry_run:
527
507
  self.build_status = BuildStatus.SKIPPED
@@ -570,8 +550,6 @@ class App(BaseModel):
570
550
  ):
571
551
  return
572
552
 
573
- self._build_stage = BuildStage.POST_BUILD
574
-
575
553
  # both status applied
576
554
  if self.copy_sdkconfig:
577
555
  try:
@@ -580,9 +558,9 @@ class App(BaseModel):
580
558
  os.path.join(self.build_path, 'sdkconfig'),
581
559
  )
582
560
  except Exception as e:
583
- self._logger.warning('Copy sdkconfig file from failed: %s', e)
561
+ LOGGER.warning('Copy sdkconfig file from failed: %s', e)
584
562
  else:
585
- self._logger.debug('Copied sdkconfig file from %s to %s', self.work_dir, self.build_path)
563
+ LOGGER.debug('Copied sdkconfig file from %s to %s', self.work_dir, self.build_path)
586
564
 
587
565
  # for originally success builds generate size.json if enabled
588
566
  #
@@ -592,7 +570,7 @@ class App(BaseModel):
592
570
  self.write_size_json()
593
571
 
594
572
  if not os.path.isfile(self.build_log_path):
595
- self._logger.warning(f'{self.build_log_path} does not exist. Skipping post build actions...')
573
+ LOGGER.warning(f'{self.build_log_path} does not exist. Skipping post build actions...')
596
574
  return
597
575
 
598
576
  # check warnings
@@ -603,21 +581,21 @@ class App(BaseModel):
603
581
  is_error_or_warning, ignored = self.is_error_or_warning(line)
604
582
  if is_error_or_warning:
605
583
  if ignored:
606
- self._logger.info('[Ignored warning] %s', line)
584
+ LOGGER.info('[Ignored warning] %s', line)
607
585
  else:
608
- self._logger.warning('%s', line)
586
+ LOGGER.warning('%s', line)
609
587
  has_unignored_warning = True
610
588
 
611
589
  # for failed builds, print last few lines to help debug
612
590
  if self.build_status == BuildStatus.FAILED:
613
591
  # print last few lines to help debug
614
- self._logger.error(
592
+ LOGGER.error(
615
593
  'Last %s lines from the build log "%s":',
616
594
  self.LOG_DEBUG_LINES,
617
595
  self.build_log_path,
618
596
  )
619
597
  for line in lines[-self.LOG_DEBUG_LINES :]:
620
- self._logger.error('%s', line)
598
+ LOGGER.error('%s', line)
621
599
  # correct build status for originally successful builds
622
600
  elif self.build_status == BuildStatus.SUCCESS:
623
601
  if self.check_warnings and has_unignored_warning:
@@ -636,7 +614,7 @@ class App(BaseModel):
636
614
  # remove temp log file
637
615
  if self._is_build_log_path_temp:
638
616
  os.unlink(self.build_log_path)
639
- self._logger.debug('Removed success build temporary log file: %s', self.build_log_path)
617
+ LOGGER.debug('Removed success build temporary log file: %s', self.build_log_path)
640
618
 
641
619
  # Cleanup build directory if not preserving
642
620
  if not self.preserve:
@@ -649,17 +627,17 @@ class App(BaseModel):
649
627
  self.build_path,
650
628
  exclude_file_patterns=exclude_list,
651
629
  )
652
- self._logger.debug('Removed built binaries under: %s', self.build_path)
630
+ LOGGER.debug('Removed built binaries under: %s', self.build_path)
653
631
 
654
632
  def _build(
655
633
  self,
656
634
  *,
657
- manifest_rootpath: t.Optional[str] = None, # noqa: ARG002
658
- modified_components: t.Optional[t.List[str]] = None, # noqa: ARG002
659
- modified_files: t.Optional[t.List[str]] = None, # noqa: ARG002
660
- check_app_dependencies: bool = False, # noqa: ARG002
635
+ manifest_rootpath: t.Optional[str] = None,
636
+ modified_components: t.Optional[t.List[str]] = None,
637
+ modified_files: t.Optional[t.List[str]] = None,
638
+ check_app_dependencies: bool = False,
661
639
  ) -> None:
662
- self._build_stage = BuildStage.BUILD
640
+ pass
663
641
 
664
642
  def _write_size_json(self) -> None:
665
643
  if not self.size_json_path:
@@ -667,7 +645,7 @@ class App(BaseModel):
667
645
 
668
646
  map_file = find_first_match('*.map', self.build_path)
669
647
  if not map_file:
670
- self._logger.warning(
648
+ LOGGER.warning(
671
649
  '.map file not found. Cannot write size json to file: %s',
672
650
  self.size_json_path,
673
651
  )
@@ -703,18 +681,18 @@ class App(BaseModel):
703
681
  check=True,
704
682
  )
705
683
 
706
- self._logger.debug('Generated size info to %s', self.size_json_path)
684
+ LOGGER.debug('Generated size info to %s', self.size_json_path)
707
685
 
708
686
  def write_size_json(self) -> None:
709
687
  if self.target in PREVIEW_TARGETS:
710
688
  # targets in preview may not yet have support in esp-idf-size
711
- self._logger.info('Skipping generation of size json for target %s as it is in preview.', self.target)
689
+ LOGGER.info('Skipping generation of size json for target %s as it is in preview.', self.target)
712
690
  return
713
691
 
714
692
  try:
715
693
  self._write_size_json()
716
694
  except Exception as e:
717
- self._logger.warning('Failed to generate size json: %s', e)
695
+ LOGGER.warning('Failed to generate size json: %s', e)
718
696
 
719
697
  def to_json(self) -> str:
720
698
  return self.model_dump_json()
@@ -969,7 +947,7 @@ class CMakeApp(App):
969
947
  check=True,
970
948
  additional_env_dict=additional_env_dict,
971
949
  )
972
- self._logger.debug('generated project_description.json to check app dependencies')
950
+ LOGGER.debug('generated project_description.json to check app dependencies')
973
951
 
974
952
  with open(os.path.join(self.build_path, PROJECT_DESCRIPTION_JSON)) as fr:
975
953
  build_components = {item for item in json.load(fr)['build_components'] if item}
@@ -31,17 +31,6 @@ class BuildStatus(str, enum.Enum):
31
31
  SUCCESS = 'build success'
32
32
 
33
33
 
34
- class BuildStage(str, enum.Enum):
35
- DRY_RUN = 'Dry Run'
36
- PRE_BUILD = 'Pre Build'
37
- BUILD = 'Build'
38
- POST_BUILD = 'Post Build'
39
-
40
- @classmethod
41
- def max_length(cls) -> int:
42
- return max(len(v.value) for v in cls.__members__.values())
43
-
44
-
45
34
  completion_instructions = """
46
35
  With the `--activate` option, detect your shell type and add the appropriate commands to your shell's config file
47
36
  so that it runs on startup. You will likely have to restart.
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
1
+ # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  """
@@ -211,4 +211,4 @@ class TestReport:
211
211
  xml.append(test_suite.to_xml_elem())
212
212
 
213
213
  ElementTree.ElementTree(xml).write(self.filepath, encoding='utf-8')
214
- LOGGER.info('Test report written to %s', self.filepath)
214
+ LOGGER.info('Generated build junit report at: %s', self.filepath)
@@ -0,0 +1,119 @@
1
+ # SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import logging
5
+ import typing as t
6
+ from datetime import datetime
7
+
8
+ from rich import get_console
9
+ from rich._log_render import LogRender
10
+ from rich.console import Console, ConsoleRenderable
11
+ from rich.containers import Renderables
12
+ from rich.logging import RichHandler
13
+ from rich.text import Text, TextType
14
+
15
+
16
+ class _OneLineLogRender(LogRender):
17
+ def __call__( # type: ignore # the original method returns Table instead of Text
18
+ self,
19
+ console: Console,
20
+ renderables: t.Iterable[ConsoleRenderable],
21
+ log_time: t.Optional[datetime] = None,
22
+ time_format: t.Optional[t.Union[str, t.Callable[[datetime], Text]]] = None,
23
+ level: TextType = '',
24
+ path: t.Optional[str] = None,
25
+ line_no: t.Optional[int] = None,
26
+ link_path: t.Optional[str] = None,
27
+ ) -> Text:
28
+ output = Text(no_wrap=True)
29
+ if self.show_time:
30
+ log_time = log_time or console.get_datetime()
31
+ time_format = time_format or self.time_format
32
+ if callable(time_format):
33
+ log_time_display = time_format(log_time)
34
+ else:
35
+ log_time_display = Text(log_time.strftime(time_format), style='log.time')
36
+ if log_time_display == self._last_time and self.omit_repeated_times:
37
+ output.append(' ' * len(log_time_display), style='log.time')
38
+ else:
39
+ output.append(log_time_display)
40
+ self._last_time = log_time_display
41
+ output.pad_right(1)
42
+
43
+ if self.show_level:
44
+ output.append(level)
45
+ if self.level_width:
46
+ output.pad_right(max(1, self.level_width - len(level)))
47
+ else:
48
+ output.pad_right(1)
49
+
50
+ for renderable in Renderables(renderables): # type: ignore
51
+ if isinstance(renderable, Text):
52
+ renderable.stylize('log.message')
53
+
54
+ output.append(renderable)
55
+ output.pad_right(1)
56
+
57
+ if self.show_path and path:
58
+ path_text = Text(style='log.path')
59
+ path_text.append(path, style=f'link file://{link_path}' if link_path else '')
60
+ if line_no:
61
+ path_text.append(':')
62
+ path_text.append(
63
+ f'{line_no}',
64
+ style=f'link file://{link_path}#{line_no}' if link_path else '',
65
+ )
66
+ output.append(path_text)
67
+ output.pad_right(1)
68
+
69
+ output.rstrip()
70
+ return output
71
+
72
+
73
+ def get_rich_log_handler(level: int = logging.WARNING, no_color: bool = False) -> RichHandler:
74
+ console = get_console()
75
+ console.soft_wrap = True
76
+ console.no_color = no_color
77
+ console.stderr = True
78
+
79
+ handler = RichHandler(
80
+ level,
81
+ console,
82
+ )
83
+ handler._log_render = _OneLineLogRender(
84
+ show_level=True,
85
+ show_path=False,
86
+ omit_repeated_times=False,
87
+ )
88
+
89
+ return handler
90
+
91
+
92
+ def setup_logging(verbose: int = 0, log_file: t.Optional[str] = None, colored: bool = True) -> None:
93
+ """
94
+ Setup logging stream handler
95
+
96
+ :param verbose: 0 - WARNING, 1 - INFO, 2 - DEBUG
97
+ :param log_file: log file path
98
+ :param colored: colored output or not
99
+ :return: None
100
+ """
101
+ if not verbose:
102
+ level = logging.WARNING
103
+ elif verbose == 1:
104
+ level = logging.INFO
105
+ else:
106
+ level = logging.DEBUG
107
+
108
+ package_logger = logging.getLogger(__package__)
109
+ package_logger.setLevel(level)
110
+
111
+ if log_file:
112
+ handler: logging.Handler = logging.FileHandler(log_file)
113
+ else:
114
+ handler = get_rich_log_handler(level, not colored)
115
+ if package_logger.hasHandlers():
116
+ package_logger.handlers.clear()
117
+ package_logger.addHandler(handler)
118
+
119
+ package_logger.propagate = False # don't propagate to root logger
@@ -76,7 +76,7 @@ def find_apps(
76
76
  ## `preserve`
77
77
  if 'preserve' in kwargs:
78
78
  LOGGER.warning(
79
- 'Passing "preserve" directly is deprecated. '
79
+ 'DEPRECATED: Passing "preserve" directly is deprecated. '
80
80
  'Pass "no_preserve" instead to disable preserving the build directory'
81
81
  )
82
82
  kwargs['no_preserve'] = not kwargs.pop('preserve')
@@ -103,11 +103,14 @@ def find_apps(
103
103
  apps: t.Set[App] = set()
104
104
  if find_arguments.target == 'all':
105
105
  targets = ALL_TARGETS
106
+ LOGGER.info('Searching for apps by all targets')
106
107
  else:
107
108
  targets = [find_arguments.target]
109
+ LOGGER.info('Searching for apps by target: %s', find_arguments.target)
108
110
 
109
111
  for _t in targets:
110
112
  for _p in find_arguments.paths:
113
+ LOGGER.debug('Searching for apps in path %s for target %s', _p, _t)
111
114
  apps.update(
112
115
  _find_apps(
113
116
  _p,
@@ -117,7 +120,7 @@ def find_apps(
117
120
  )
118
121
  )
119
122
 
120
- LOGGER.info(f'Found {len(apps)} apps in total')
123
+ LOGGER.info('Found %d apps in total', len(apps))
121
124
 
122
125
  return sorted(apps)
123
126
 
@@ -141,7 +144,7 @@ def build_apps(
141
144
  ## `check_app_dependencies`
142
145
  if 'check_app_dependencies' in kwargs:
143
146
  LOGGER.warning(
144
- 'Passing "check_app_dependencies" directly is deprecated. '
147
+ 'DEPRECATED: Passing "check_app_dependencies" directly is deprecated. '
145
148
  'Pass "modified_components" instead to enable dependency-driven build feature'
146
149
  )
147
150
  kwargs.pop('check_app_dependencies')
@@ -162,20 +165,22 @@ def build_apps(
162
165
  test_suite = TestSuite('build_apps')
163
166
 
164
167
  start, stop = get_parallel_start_stop(len(apps), build_arguments.parallel_count, build_arguments.parallel_index)
165
- LOGGER.info('Total %s apps. running build for app %s-%s', len(apps), start, stop)
168
+ LOGGER.info('Processing %d total apps: building apps %d-%d', len(apps), start, stop)
166
169
 
167
170
  # cleanup collect files if exists at this early-stage
168
171
  for f in (build_arguments.collect_app_info, build_arguments.collect_size_info, build_arguments.junitxml):
169
172
  if f and os.path.isfile(f):
173
+ LOGGER.debug('Removing existing collect file: %s', f)
170
174
  os.remove(f)
171
- LOGGER.debug('Remove existing collect file %s', f)
172
175
 
173
176
  exit_code = 0
174
177
 
175
178
  # create empty files, avoid no file when no app is built
176
179
  if build_arguments.collect_app_info:
180
+ LOGGER.debug('Creating empty app info file: %s', build_arguments.collect_app_info)
177
181
  Path(build_arguments.collect_app_info).touch()
178
182
  if build_arguments.collect_size_info:
183
+ LOGGER.debug('Creating empty size info file: %s', build_arguments.collect_size_info)
179
184
  Path(build_arguments.collect_size_info).touch()
180
185
 
181
186
  for i, app in enumerate(apps):
@@ -189,7 +194,7 @@ def build_apps(
189
194
  app.verbose = build_arguments.build_verbose
190
195
  app.copy_sdkconfig = build_arguments.copy_sdkconfig
191
196
 
192
- LOGGER.info('(%s/%s) Building app: %s', index, len(apps), app)
197
+ LOGGER.info('(%d/%d) Building app: %s', index, len(apps), app)
193
198
 
194
199
  app.build(
195
200
  manifest_rootpath=build_arguments.manifest_rootpath,
@@ -207,12 +212,14 @@ def build_apps(
207
212
  if build_arguments.collect_app_info:
208
213
  with open(build_arguments.collect_app_info, 'a') as fw:
209
214
  fw.write(app.to_json() + '\n')
210
- LOGGER.debug('Recorded app info in %s', build_arguments.collect_app_info)
215
+ LOGGER.debug('Recorded app info in file: %s', build_arguments.collect_app_info)
211
216
 
212
217
  if app.build_status == BuildStatus.FAILED:
213
218
  if not build_arguments.keep_going:
219
+ LOGGER.error('Build failed and keep_going=False, stopping build process')
214
220
  return 1
215
221
  else:
222
+ LOGGER.warning('Build failed but keep_going=True, continuing with next app')
216
223
  exit_code = 1
217
224
  elif app.build_status == BuildStatus.SUCCESS:
218
225
  if build_arguments.collect_size_info and app.size_json_path:
@@ -229,13 +236,12 @@ def build_apps(
229
236
  )
230
237
  + '\n'
231
238
  )
232
- LOGGER.debug('Recorded size info file path in %s', build_arguments.collect_size_info)
239
+ LOGGER.debug('Recorded size info file path: %s', build_arguments.collect_size_info)
233
240
 
234
241
  LOGGER.info('') # add one empty line for separating different builds
235
242
 
236
243
  if build_arguments.junitxml:
237
244
  TestReport([test_suite], build_arguments.junitxml).create_test_report()
238
- LOGGER.info('Generated junit report for build apps: %s', build_arguments.junitxml)
239
245
 
240
246
  return exit_code
241
247
 
@@ -31,7 +31,9 @@ dependencies = [
31
31
  "pydantic_settings",
32
32
  "argcomplete>=3",
33
33
  "typing-extensions; python_version < '3.11'",
34
- "esp-bool-parser>=0.1.2,<1"
34
+ "esp-bool-parser>=0.1.2,<1",
35
+ # debug/print
36
+ "rich",
35
37
  ]
36
38
 
37
39
  [project.optional-dependencies]
@@ -62,7 +64,7 @@ idf-build-apps = "idf_build_apps:main.main"
62
64
 
63
65
  [tool.commitizen]
64
66
  name = "cz_conventional_commits"
65
- version = "2.6.4"
67
+ version = "2.7.0"
66
68
  tag_format = "v$version"
67
69
  version_files = [
68
70
  "idf_build_apps/__init__.py",
@@ -20,7 +20,8 @@ install_requires = \
20
20
  'pydantic~=2.0',
21
21
  'pydantic_settings',
22
22
  'argcomplete>=3',
23
- 'esp-bool-parser>=0.1.2,<1']
23
+ 'esp-bool-parser>=0.1.2,<1',
24
+ 'rich']
24
25
 
25
26
  extras_require = \
26
27
  {":python_version < '3.11'": ['toml', 'typing-extensions'],
@@ -37,7 +38,7 @@ entry_points = \
37
38
  {'console_scripts': ['idf-build-apps = idf_build_apps:main.main']}
38
39
 
39
40
  setup(name='idf-build-apps',
40
- version='2.6.4',
41
+ version='2.7.0',
41
42
  description='Tools for building ESP-IDF related apps.',
42
43
  author=None,
43
44
  author_email='Fu Hanxi <fuhanxi@espressif.com>',
@@ -1,88 +0,0 @@
1
- # SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
2
- # SPDX-License-Identifier: Apache-2.0
3
-
4
- import logging
5
- import sys
6
- import typing as t
7
-
8
- from .constants import (
9
- BuildStage,
10
- )
11
-
12
-
13
- class ColoredFormatter(logging.Formatter):
14
- grey: str = '\x1b[37;20m'
15
- yellow: str = '\x1b[33;20m'
16
- red: str = '\x1b[31;20m'
17
- bold_red: str = '\x1b[31;1m'
18
-
19
- reset: str = '\x1b[0m'
20
-
21
- fmt: str = '%(asctime)s %(levelname)8s %(message)s'
22
- app_fmt: str = f'%(asctime)s %(levelname)8s [%(build_stage){BuildStage.max_length()}s] %(message)s'
23
-
24
- datefmt: str = '%Y-%m-%d %H:%M:%S'
25
-
26
- FORMATS: t.Dict[int, str] = {
27
- logging.DEBUG: f'{grey}{{}}{reset}',
28
- logging.INFO: '{}',
29
- logging.WARNING: f'{yellow}{{}}{reset}',
30
- logging.ERROR: f'{red}{{}}{reset}',
31
- logging.CRITICAL: f'{bold_red}{{}}{reset}',
32
- }
33
-
34
- def __init__(self, colored: bool = True) -> None:
35
- self.colored = colored
36
- if sys.platform == 'win32': # does not support it
37
- self.colored = False
38
-
39
- super().__init__(datefmt=self.datefmt)
40
-
41
- def format(self, record: logging.LogRecord) -> str:
42
- if getattr(record, 'build_stage', None):
43
- base_fmt = self.app_fmt
44
- else:
45
- base_fmt = self.fmt
46
-
47
- if self.colored:
48
- log_fmt = self.FORMATS[record.levelno].format(base_fmt)
49
- else:
50
- log_fmt = base_fmt
51
-
52
- if record.levelno in [logging.WARNING, logging.ERROR]:
53
- record.msg = '>>> ' + str(record.msg)
54
- elif record.levelno == logging.CRITICAL:
55
- record.msg = '!!! ' + str(record.msg)
56
-
57
- formatter = logging.Formatter(log_fmt, datefmt=self.datefmt)
58
- return formatter.format(record)
59
-
60
-
61
- def setup_logging(verbose: int = 0, log_file: t.Optional[str] = None, colored: bool = True) -> None:
62
- """
63
- Setup logging stream handler
64
-
65
- :param verbose: 0 - WARNING, 1 - INFO, 2 - DEBUG
66
- :param log_file: log file path
67
- :param colored: colored output or not
68
- :return: None
69
- """
70
- if not verbose:
71
- level = logging.WARNING
72
- elif verbose == 1:
73
- level = logging.INFO
74
- else:
75
- level = logging.DEBUG
76
-
77
- package_logger = logging.getLogger(__package__)
78
- package_logger.setLevel(level)
79
- if log_file:
80
- handler: logging.Handler = logging.FileHandler(log_file)
81
- else:
82
- handler = logging.StreamHandler(sys.stderr)
83
- handler.setFormatter(ColoredFormatter(colored))
84
-
85
- if package_logger.hasHandlers():
86
- package_logger.handlers.clear()
87
- package_logger.addHandler(handler)
88
- package_logger.propagate = False # don't propagate to root logger
File without changes
File without changes