idf-build-apps 2.3.0__py3-none-any.whl → 2.4.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.
@@ -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.3.0'
11
+ __version__ = '2.4.0'
12
12
 
13
13
  from .session_args import (
14
14
  SessionArgs,
idf_build_apps/app.py CHANGED
@@ -124,6 +124,7 @@ class App(BaseModel):
124
124
  verbose: bool = False
125
125
  check_warnings: bool = False
126
126
  preserve: bool = True
127
+ copy_sdkconfig: bool = False
127
128
 
128
129
  # build_apps() related
129
130
  build_apps_args: t.Optional[BuildAppsArgs] = None
@@ -561,6 +562,17 @@ class App(BaseModel):
561
562
  def _post_build(self) -> None:
562
563
  self._build_stage = BuildStage.POST_BUILD
563
564
 
565
+ if self.copy_sdkconfig:
566
+ try:
567
+ shutil.copy(
568
+ os.path.join(self.work_dir, 'sdkconfig'),
569
+ os.path.join(self.build_path, 'sdkconfig'),
570
+ )
571
+ except Exception as e:
572
+ self._logger.warning('Copy sdkconfig file from failed: %s', e)
573
+ else:
574
+ self._logger.debug('Copied sdkconfig file from %s to %s', self.work_dir, self.build_path)
575
+
564
576
  if not os.path.isfile(self.build_log_path):
565
577
  return
566
578
 
@@ -0,0 +1,54 @@
1
+ # SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ import os
5
+ from typing import Optional
6
+
7
+ from .utils import AutocompleteActivationError
8
+
9
+
10
+ def append_to_file(file_path: str, content: str) -> None:
11
+ """Add commands to shell configuration file
12
+
13
+ :param file_path: path to shell configurations file
14
+ :param content: commands to add
15
+ """
16
+ if os.path.exists(file_path):
17
+ with open(file_path) as file:
18
+ if content.strip() in file.read():
19
+ print(f'Autocompletion already set up in {file_path}')
20
+ return
21
+ with open(file_path, 'a') as file:
22
+ file.write(f'\n# Begin added by idf-build-apps \n{content} \n# End added by idf-build-apps')
23
+ print(f'Autocompletion added to {file_path}')
24
+
25
+
26
+ def activate_completions(shell_type: Optional[str]) -> None:
27
+ """Activates autocompletion for supported shells.
28
+
29
+ :raises AutocompleteActivationError: if the $SHELL env variable is empty, or if the detected shell is unsupported.
30
+ """
31
+ supported_shells = ['bash', 'zsh', 'fish']
32
+
33
+ if shell_type == 'auto':
34
+ shell_type = os.path.basename(os.environ.get('SHELL', ''))
35
+
36
+ if not shell_type:
37
+ raise AutocompleteActivationError('$SHELL is empty. Please provide your shell type with the `--shell` option')
38
+
39
+ if shell_type not in supported_shells:
40
+ raise AutocompleteActivationError('Unsupported shell. Autocompletion is supported for bash, zsh and fish.')
41
+
42
+ if shell_type == 'bash':
43
+ completion_command = 'eval "$(register-python-argcomplete idf-build-apps)"'
44
+ elif shell_type == 'zsh':
45
+ completion_command = (
46
+ 'autoload -U bashcompinit && bashcompinit && eval "$(register-python-argcomplete idf-build-apps)"'
47
+ )
48
+ elif shell_type == 'fish':
49
+ completion_command = 'register-python-argcomplete --shell fish idf-build-apps | source'
50
+
51
+ rc_file = {'bash': '~/.bashrc', 'zsh': '~/.zshrc', 'fish': '~/.config/fish/completions/idf-build-apps.fish'}
52
+
53
+ shell_rc = os.path.expanduser(rc_file[shell_type])
54
+ append_to_file(shell_rc, completion_command)
@@ -1,4 +1,4 @@
1
- # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
1
+ # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import enum
@@ -95,3 +95,37 @@ class BuildStage(str, enum.Enum):
95
95
  @classmethod
96
96
  def max_length(cls) -> int:
97
97
  return max(len(v.value) for v in cls.__members__.values())
98
+
99
+
100
+ completion_instructions = """
101
+ With `--activate` option detect your shell type and add the appropriate commands to your shell's config file
102
+ so that it is run on startup. You will likely have to restart
103
+ or re-login for the autocompletion to start working.
104
+
105
+ You can also specify your shell by the `--shell` option.
106
+
107
+ If you do not want automaticall modifying your shell config file
108
+ you can manually add commands provided below to activate autocompletion
109
+ or run them in your current terminal session for one-time activation.
110
+
111
+ Once again, you will likely have to restart
112
+ or re-login for the autocompletion to start working.
113
+
114
+ bash:
115
+ eval "$(register-python-argcomplete idf-build-apps)"
116
+
117
+ zsh:
118
+ To activate completions in zsh, first make sure compinit is marked for
119
+ autoload and run autoload:
120
+
121
+ autoload -U compinit
122
+ compinit
123
+
124
+ Afterwards you can enable completions for idf-build-apps:
125
+
126
+ eval "$(register-python-argcomplete idf-build-apps)"
127
+
128
+ fish:
129
+ # Not required to be in the config file, only run once
130
+ register-python-argcomplete --shell fish idf-build-apps >~/.config/fish/completions/idf-build-apps.fish
131
+ """
idf_build_apps/main.py CHANGED
@@ -1,3 +1,5 @@
1
+ # PYTHON_ARGCOMPLETE_OK
2
+
1
3
  # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2
4
  # SPDX-License-Identifier: Apache-2.0
3
5
 
@@ -6,11 +8,11 @@ import json
6
8
  import logging
7
9
  import os
8
10
  import re
9
- import shutil
10
11
  import sys
11
12
  import textwrap
12
13
  import typing as t
13
14
 
15
+ import argcomplete
14
16
  from pydantic import (
15
17
  Field,
16
18
  create_model,
@@ -25,16 +27,14 @@ from .app import (
25
27
  CMakeApp,
26
28
  MakeApp,
27
29
  )
30
+ from .autocompletions import activate_completions
28
31
  from .build_apps_args import (
29
32
  BuildAppsArgs,
30
33
  )
31
34
  from .config import (
32
35
  get_valid_config,
33
36
  )
34
- from .constants import (
35
- ALL_TARGETS,
36
- BuildStatus,
37
- )
37
+ from .constants import ALL_TARGETS, BuildStatus, completion_instructions
38
38
  from .finder import (
39
39
  _find_apps,
40
40
  )
@@ -51,6 +51,7 @@ from .manifest.manifest import (
51
51
  Manifest,
52
52
  )
53
53
  from .utils import (
54
+ AutocompleteActivationError,
54
55
  InvalidCommand,
55
56
  files_matches_patterns,
56
57
  get_parallel_start_stop,
@@ -324,6 +325,7 @@ def build_apps(
324
325
  app.dry_run = dry_run
325
326
  app.index = index
326
327
  app.verbose = build_verbose
328
+ app.copy_sdkconfig = copy_sdkconfig
327
329
 
328
330
  LOGGER.info('(%s/%s) Building app: %s', index, len(apps), app)
329
331
 
@@ -353,17 +355,6 @@ def build_apps(
353
355
  fw.write(app.to_json() + '\n')
354
356
  LOGGER.debug('Recorded app info in %s', build_apps_args.collect_app_info)
355
357
 
356
- if copy_sdkconfig:
357
- try:
358
- shutil.copy(
359
- os.path.join(app.work_dir, 'sdkconfig'),
360
- os.path.join(app.build_path, 'sdkconfig'),
361
- )
362
- except Exception as e:
363
- LOGGER.warning('Copy sdkconfig file from failed: %s', e)
364
- else:
365
- LOGGER.debug('Copied sdkconfig file from %s to %s', app.work_dir, app.build_path)
366
-
367
358
  if app.build_status == BuildStatus.FAILED:
368
359
  if not keep_going:
369
360
  return 1
@@ -617,10 +608,27 @@ def get_parser() -> argparse.ArgumentParser:
617
608
  help='enable colored output by default on UNIX-like systems. enable this flag to make the logs uncolored.',
618
609
  )
619
610
 
620
- find_parser = actions.add_parser('find', parents=[common_args], formatter_class=IdfBuildAppsCliFormatter)
611
+ find_parser = actions.add_parser(
612
+ 'find',
613
+ help='Find the buildable applications. Run `idf-build-apps find --help` for more information on a command',
614
+ description='Find the buildable applications in the given path or paths for specified chips. '
615
+ '`--path` and `--target` options must be provided. '
616
+ 'By default, print the found apps in stdout. '
617
+ 'To find apps for all chips use the `--target` option with the `all` argument.',
618
+ parents=[common_args],
619
+ formatter_class=IdfBuildAppsCliFormatter,
620
+ )
621
+
621
622
  find_parser.add_argument('-o', '--output', help='Print the found apps to the specified file instead of stdout')
622
623
 
623
- build_parser = actions.add_parser('build', parents=[common_args], formatter_class=IdfBuildAppsCliFormatter)
624
+ build_parser = actions.add_parser(
625
+ 'build',
626
+ help='Build the found applications. Run `idf-build-apps build --help` for more information on a command',
627
+ description='Build the application in the given path or paths for specified chips. '
628
+ '`--path` and `--target` options must be provided.',
629
+ parents=[common_args],
630
+ formatter_class=IdfBuildAppsCliFormatter,
631
+ )
624
632
  build_parser.add_argument(
625
633
  '--build-verbose',
626
634
  action='store_true',
@@ -685,14 +693,48 @@ def get_parser() -> argparse.ArgumentParser:
685
693
  help='Path to the junitxml file. If specified, the junitxml file will be generated. Can expand placeholder @p',
686
694
  )
687
695
 
696
+ completions_parser = actions.add_parser(
697
+ 'completions',
698
+ help='Add the autocompletion activation script to the shell rc file. '
699
+ 'Run `idf-build-apps complations --help` for more information on a command',
700
+ description='Without `--activate` option print instructions for manual activation. '
701
+ 'With `--activate` option add the autocompletion activation script to the shell rc file '
702
+ 'for bash, zsh, or fish. Other shells are not supported. '
703
+ 'The `--shell` option used only with the `--activate` option, '
704
+ 'if provided, add the autocompletion activation script to the given shell; '
705
+ 'without this argument, will detect shell type automatically. '
706
+ 'May need to restart or re-login for the autocompletion to start working',
707
+ formatter_class=IdfBuildAppsCliFormatter,
708
+ )
709
+ completions_parser.add_argument(
710
+ '-a', '--activate', action='store_true', help='Activate autocompletion automatically and permanently'
711
+ )
712
+ completions_parser.add_argument(
713
+ '-s',
714
+ '--shell',
715
+ choices=['bash', 'zsh', 'fish'],
716
+ help='Specify the shell type for the autocomplite activation script. ',
717
+ )
718
+
688
719
  return parser
689
720
 
690
721
 
722
+ def handle_completions(args: argparse.Namespace) -> None:
723
+ if args.activate:
724
+ if not args.shell:
725
+ args.shell = 'auto'
726
+ activate_completions(args.shell)
727
+ elif not args.activate and args.shell:
728
+ raise AutocompleteActivationError('The --shell option can only be used with the --activate option.')
729
+ else:
730
+ print(completion_instructions)
731
+
732
+
691
733
  def validate_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> None:
692
734
  # validate cli subcommands
693
- if args.action not in ['find', 'build']:
735
+ if args.action not in ['find', 'build', 'completions']:
694
736
  parser.print_help()
695
- raise InvalidCommand('subcommand is required. {find, build}')
737
+ raise InvalidCommand('subcommand is required. {find, build, completions}')
696
738
 
697
739
  if not args.paths:
698
740
  raise InvalidCommand(
@@ -739,8 +781,13 @@ def apply_config_args(args: argparse.Namespace) -> None:
739
781
 
740
782
  def main():
741
783
  parser = get_parser()
784
+ argcomplete.autocomplete(parser)
742
785
  args = parser.parse_args()
743
786
 
787
+ if args.action == 'completions':
788
+ handle_completions(args)
789
+ sys.exit(0)
790
+
744
791
  apply_config_args(args)
745
792
  validate_args(parser, args)
746
793
 
@@ -1,5 +1,6 @@
1
1
  # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
2
2
  # SPDX-License-Identifier: Apache-2.0
3
+ import glob
3
4
  import logging
4
5
  import os.path
5
6
  import typing as t
@@ -82,35 +83,47 @@ class SocHeader(dict):
82
83
  super().__init__(**soc_header_dict)
83
84
 
84
85
  @staticmethod
85
- def _get_dir_from_candidates(candidates: t.List[str]) -> t.Optional[str]:
86
+ def _get_dirs_from_candidates(candidates: t.List[str]) -> t.List[str]:
87
+ dirs = []
86
88
  for d in candidates:
87
89
  if not os.path.isdir(d):
88
90
  LOGGER.debug('folder "%s" not found. Skipping...', os.path.abspath(d))
89
91
  else:
90
- return d
92
+ dirs.append(d)
91
93
 
92
- return None
94
+ return dirs
95
+
96
+ @staticmethod
97
+ def _find_candidates_from_pattern(pattern: str) -> t.List[str]:
98
+ # get dirs from pattern
99
+ return [d for d in glob.glob(pattern) if os.path.isdir(d)]
93
100
 
94
101
  @classmethod
95
102
  def _parse_soc_header(cls, target: str) -> t.Dict[str, t.Any]:
96
- soc_headers_dir = cls._get_dir_from_candidates([
103
+ soc_headers_dirs = cls._get_dirs_from_candidates([
104
+ # master c5
105
+ *cls._find_candidates_from_pattern(
106
+ os.path.join(IDF_PATH, 'components', 'soc', target, '*', 'include', 'soc')
107
+ ),
97
108
  # other branches
98
109
  os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', target, 'include', 'soc')),
99
110
  # release/v4.2
100
111
  os.path.abspath(os.path.join(IDF_PATH, 'components', 'soc', 'soc', target, 'include', 'soc')),
101
112
  ])
102
- esp_rom_headers_dir = cls._get_dir_from_candidates([
113
+ esp_rom_headers_dirs = cls._get_dirs_from_candidates([
114
+ # master c5
115
+ *cls._find_candidates_from_pattern(os.path.join(IDF_PATH, 'components', 'esp_rom', target, '*', target)),
103
116
  os.path.join(IDF_PATH, 'components', 'esp_rom', target),
104
117
  ])
105
118
 
106
119
  header_files: t.List[str] = []
107
- if soc_headers_dir:
108
- header_files += [str(p.resolve()) for p in Path(soc_headers_dir).glob(cls.CAPS_HEADER_FILEPATTERN)]
109
- if esp_rom_headers_dir:
110
- header_files += [str(p.resolve()) for p in Path(esp_rom_headers_dir).glob(cls.CAPS_HEADER_FILEPATTERN)]
120
+ for d in [*soc_headers_dirs, *esp_rom_headers_dirs]:
121
+ LOGGER.debug('Checking dir %s', d)
122
+ header_files += [str(p.resolve()) for p in Path(d).glob(cls.CAPS_HEADER_FILEPATTERN)]
111
123
 
112
124
  output_dict = {}
113
125
  for f in header_files:
126
+ LOGGER.debug('Checking header file %s', f)
114
127
  for line in get_defines(f):
115
128
  try:
116
129
  res = parse_define(line)
idf_build_apps/utils.py CHANGED
@@ -85,6 +85,10 @@ class BuildError(RuntimeError):
85
85
  pass
86
86
 
87
87
 
88
+ class AutocompleteActivationError(SystemExit):
89
+ pass
90
+
91
+
88
92
  class InvalidCommand(SystemExit):
89
93
  def __init__(self, msg: str) -> None:
90
94
  super().__init__('Invalid Command: ' + msg.strip())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: idf-build-apps
3
- Version: 2.3.0
3
+ Version: 2.4.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,6 +18,7 @@ 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: argcomplete>=3
21
22
  Requires-Dist: typing-extensions ; extra == "dev" and ( python_version < '3.8')
22
23
  Requires-Dist: sphinx ; extra == "doc"
23
24
  Requires-Dist: sphinx-rtd-theme ; extra == "doc"
@@ -68,10 +69,11 @@ pipx install idf-build-apps
68
69
 
69
70
  `idf-build-apps` is a python package that could be used as a library or a CLI tool.
70
71
 
71
- As a CLI tool, it contains two sub-commands.
72
+ As a CLI tool, it contains three sub-commands.
72
73
 
73
74
  - `find` to find the buildable applications
74
75
  - `build` to build the found applications
76
+ - `completions` to activate autocompletions or print instructions for manual activation
75
77
 
76
78
  For detailed explanation to all CLI options, you may run
77
79
 
@@ -79,6 +81,7 @@ For detailed explanation to all CLI options, you may run
79
81
  idf-build-apps -h
80
82
  idf-build-apps find -h
81
83
  idf-build-apps build -h
84
+ idf-build-apps completions -h
82
85
  ```
83
86
 
84
87
  As a library, you may check the [API documentation][api-doc] for more information. Overall it provides
@@ -1,25 +1,26 @@
1
- idf_build_apps/__init__.py,sha256=Bc43K0Sz7VlWyK8keQVKSeQIm8AVRyT0Ou9P1oTszW4,650
1
+ idf_build_apps/__init__.py,sha256=C3lZ5AdMBO7eTuVsdBtoQ6npF65DDzpgHgBXhP3-AAA,650
2
2
  idf_build_apps/__main__.py,sha256=8E-5xHm2MlRun0L88XJleNh5U50dpE0Q1nK5KqomA7I,182
3
- idf_build_apps/app.py,sha256=vThFVR70Oleddt_nTVmO-3VBJLMrm5NmoZbC48zolTo,35393
3
+ idf_build_apps/app.py,sha256=z6i55q_XaMjea34_nbAKgNTU-qOEkO_qTHCYSeQNUFw,35887
4
+ idf_build_apps/autocompletions.py,sha256=g-bx0pzXoFKI0VQqftkHyGVWN6MLjuFOdozeuAf45yo,2138
4
5
  idf_build_apps/build_apps_args.py,sha256=r6VCJDdCzE873X8OTputYkCBZPgECaKoNlAejfcamJk,1644
5
6
  idf_build_apps/config.py,sha256=I75uOQGarCWVKGi16ZYpo0qTVU25BUP4eh6-RWCtbvw,2924
6
- idf_build_apps/constants.py,sha256=xqclwUpWE5dEByL4kxdg2HaHjbAfkJtxodFfLZuAk8A,2818
7
+ idf_build_apps/constants.py,sha256=s-b-wAKjrIjF4tAspJBkXaDVC6HMX3Hv1mM2vGuLrB8,3952
7
8
  idf_build_apps/finder.py,sha256=qw5moNq7U5mHSsR0CCfGkKE9p4QsWYNcfkxzeQ73HgM,6252
8
9
  idf_build_apps/log.py,sha256=JysogBHoompfW9E9w0U4wZH7tt8dBdfoh-stPXQz1hw,2573
9
- idf_build_apps/main.py,sha256=WNQUsFAm5IOT2SM6jMs3oNPhTOz0qBwwmLwQZ31utQE,35061
10
+ idf_build_apps/main.py,sha256=17w4VaIjmdbosBqU7EF4-0gziM6veslQOBPH_JXK75U,37293
10
11
  idf_build_apps/session_args.py,sha256=2WDTy40IFAc0KQ57HaeBcYj_k10eUXRKkDOWLrFCaHY,2985
11
- idf_build_apps/utils.py,sha256=n9PNJEDDsA9hbbM0SNPdTFsO2_E8alNrX7CNYzaFHik,9644
12
+ idf_build_apps/utils.py,sha256=dYxNXPe7FRWxzUFlTzOb3G-LpKpRUrNWdQrsO5MXAB8,9702
12
13
  idf_build_apps/junit/__init__.py,sha256=IxvdaS6eSXp7kZxRuXqyZyGxuA_A1nOW1jF1HMi8Gns,231
13
14
  idf_build_apps/junit/report.py,sha256=vHE5RcBa7Bj4a1p2lXaTh0m9eJIO_uTGrOnJg8NEi4I,6430
14
15
  idf_build_apps/junit/utils.py,sha256=gtibRs8WTE8IXTIAS73QR_k_jrJlOjCl2y-9KiP5_Nk,1304
15
16
  idf_build_apps/manifest/__init__.py,sha256=Q2-cb3ngNjnl6_zWhUfzZZB10f_-Rv2JYNck3Lk7UkQ,133
16
17
  idf_build_apps/manifest/if_parser.py,sha256=r0pivV9gmniPn3Ia6sTMbW5tFAKInhOXk-Lfd6GokqE,6381
17
18
  idf_build_apps/manifest/manifest.py,sha256=P5ZaUd72A_HOVF6iuwap__Bw-w7WI72ugiTURm9PNNQ,10708
18
- idf_build_apps/manifest/soc_header.py,sha256=ULLad1TI8mGfdJhB7LJ4T9A_8BugF1TmckgdO7jO7Fg,3956
19
+ idf_build_apps/manifest/soc_header.py,sha256=uAS944qDN3z9lvFHXE7du7bsQXQbgCcf2Lt90iiDano,4472
19
20
  idf_build_apps/yaml/__init__.py,sha256=W-3z5no07RQ6eYKGyOAPA8Z2CLiMPob8DD91I4URjrA,162
20
21
  idf_build_apps/yaml/parser.py,sha256=Y2OyB4g1DCC7C7jrvpIyZV9lgeCB_XvuB75iGmqiTaM,2093
21
- idf_build_apps-2.3.0.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
22
- idf_build_apps-2.3.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
23
- idf_build_apps-2.3.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
24
- idf_build_apps-2.3.0.dist-info/METADATA,sha256=UAXe094NGW0HOdwYBr9X9QoIX-S-uoP-DWAloHU-5Ek,4458
25
- idf_build_apps-2.3.0.dist-info/RECORD,,
22
+ idf_build_apps-2.4.0.dist-info/entry_points.txt,sha256=3pVUirUEsb6jsDRikkQWNUt4hqLK2ci1HvW_Vf8b6uE,59
23
+ idf_build_apps-2.4.0.dist-info/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
24
+ idf_build_apps-2.4.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
25
+ idf_build_apps-2.4.0.dist-info/METADATA,sha256=WN1SIhaqA3rl5ZOWe_SzfQlkwDrW5iQFDkOe0FrKaug,4608
26
+ idf_build_apps-2.4.0.dist-info/RECORD,,