Преглед на файлове

tools: Add python types hints

simon.chupin преди 3 години
родител
ревизия
44f3c19fa9

+ 0 - 9
tools/ci/mypy_ignore_list.txt

@@ -211,15 +211,7 @@ tools/find_apps.py
 tools/find_build_apps/common.py
 tools/find_build_apps/common.py
 tools/gen_esp_err_to_name.py
 tools/gen_esp_err_to_name.py
 tools/gen_soc_caps_kconfig/test/test_gen_soc_caps_kconfig.py
 tools/gen_soc_caps_kconfig/test/test_gen_soc_caps_kconfig.py
-tools/idf.py
-tools/idf_py_actions/core_ext.py
-tools/idf_py_actions/create_ext.py
-tools/idf_py_actions/debug_ext.py
-tools/idf_py_actions/dfu_ext.py
-tools/idf_py_actions/errors.py
-tools/idf_py_actions/serial_ext.py
 tools/idf_py_actions/tools.py
 tools/idf_py_actions/tools.py
-tools/idf_py_actions/uf2_ext.py
 tools/kconfig_new/confgen.py
 tools/kconfig_new/confgen.py
 tools/kconfig_new/confserver.py
 tools/kconfig_new/confserver.py
 tools/kconfig_new/gen_kconfig_doc.py
 tools/kconfig_new/gen_kconfig_doc.py
@@ -240,7 +232,6 @@ tools/ldgen/test/test_fragments.py
 tools/ldgen/test/test_generation.py
 tools/ldgen/test/test_generation.py
 tools/ldgen/test/test_output_commands.py
 tools/ldgen/test/test_output_commands.py
 tools/mass_mfg/mfg_gen.py
 tools/mass_mfg/mfg_gen.py
-tools/mkuf2.py
 tools/test_apps/build_system/ldgen_test/check_placements.py
 tools/test_apps/build_system/ldgen_test/check_placements.py
 tools/test_apps/protocols/mqtt/publish_connect_test/app_test.py
 tools/test_apps/protocols/mqtt/publish_connect_test/app_test.py
 tools/test_apps/protocols/openssl/app_test.py
 tools/test_apps/protocols/openssl/app_test.py

+ 13 - 12
tools/gen_esp_err_to_name.py

@@ -6,6 +6,7 @@
 from __future__ import print_function, unicode_literals
 from __future__ import print_function, unicode_literals
 
 
 import sys
 import sys
+from typing import Any, List, Optional, TextIO
 
 
 try:
 try:
     from builtins import object, range, str
     from builtins import object, range, str
@@ -57,7 +58,7 @@ class ErrItem(object):
     - rel_str - (optional) error string which is a base for the error
     - rel_str - (optional) error string which is a base for the error
     - rel_off - (optional) offset in relation to the base error
     - rel_off - (optional) offset in relation to the base error
     """
     """
-    def __init__(self, name, file, include_as=None, comment='', rel_str='', rel_off=0):
+    def __init__(self, name: str, file: str, include_as: Optional[Any]=None, comment: str='', rel_str: str='', rel_off: int=0) -> None:
         self.name = name
         self.name = name
         self.file = file
         self.file = file
         self.include_as = include_as
         self.include_as = include_as
@@ -65,7 +66,7 @@ class ErrItem(object):
         self.rel_str = rel_str
         self.rel_str = rel_str
         self.rel_off = rel_off
         self.rel_off = rel_off
 
 
-    def __str__(self):
+    def __str__(self) -> str:
         ret = self.name + ' from ' + self.file
         ret = self.name + ' from ' + self.file
         if (self.rel_str != ''):
         if (self.rel_str != ''):
             ret += ' is (' + self.rel_str + ' + ' + str(self.rel_off) + ')'
             ret += ' is (' + self.rel_str + ' + ' + str(self.rel_off) + ')'
@@ -73,7 +74,7 @@ class ErrItem(object):
             ret += ' // ' + self.comment
             ret += ' // ' + self.comment
         return ret
         return ret
 
 
-    def __cmp__(self, other):
+    def __cmp__(self, other) -> int:
         if self.file in priority_headers and other.file not in priority_headers:
         if self.file in priority_headers and other.file not in priority_headers:
             return -1
             return -1
         elif self.file not in priority_headers and other.file in priority_headers:
         elif self.file not in priority_headers and other.file in priority_headers:
@@ -101,11 +102,11 @@ class InputError(RuntimeError):
     """
     """
     Represents and error on the input
     Represents and error on the input
     """
     """
-    def __init__(self, p, e):
+    def __init__(self, p: str, e: str) -> None:
         super(InputError, self).__init__(p + ': ' + e)
         super(InputError, self).__init__(p + ': ' + e)
 
 
 
 
-def process(line, idf_path, include_as):
+def process(line: str, idf_path: str, include_as: Any) -> None:
     """
     """
     Process a line of text from file idf_path (relative to IDF project).
     Process a line of text from file idf_path (relative to IDF project).
     Fills the global list unproc_list and dictionaries err_dict, rev_err_dict
     Fills the global list unproc_list and dictionaries err_dict, rev_err_dict
@@ -168,7 +169,7 @@ def process(line, idf_path, include_as):
         unproc_list.append(ErrItem(words[1], idf_path, include_as, comment, related, num))
         unproc_list.append(ErrItem(words[1], idf_path, include_as, comment, related, num))
 
 
 
 
-def process_remaining_errors():
+def process_remaining_errors() -> None:
     """
     """
     Create errors which could not be processed before because the error code
     Create errors which could not be processed before because the error code
     for the BASE error code wasn't known.
     for the BASE error code wasn't known.
@@ -189,7 +190,7 @@ def process_remaining_errors():
     del unproc_list[:]
     del unproc_list[:]
 
 
 
 
-def path_to_include(path):
+def path_to_include(path: str) -> str:
     """
     """
     Process the path (relative to the IDF project) in a form which can be used
     Process the path (relative to the IDF project) in a form which can be used
     to include in a C file. Using just the filename does not work all the
     to include in a C file. Using just the filename does not work all the
@@ -210,7 +211,7 @@ def path_to_include(path):
         return os.sep.join(spl_path[i + 1:])  # subdirectories and filename in "include"
         return os.sep.join(spl_path[i + 1:])  # subdirectories and filename in "include"
 
 
 
 
-def print_warning(error_list, error_code):
+def print_warning(error_list: List, error_code: int) -> None:
     """
     """
     Print warning about errors with the same error code
     Print warning about errors with the same error code
     """
     """
@@ -219,7 +220,7 @@ def print_warning(error_list, error_code):
         print('    ' + str(e))
         print('    ' + str(e))
 
 
 
 
-def max_string_width():
+def max_string_width() -> int:
     max = 0
     max = 0
     for k in err_dict:
     for k in err_dict:
         for e in err_dict[k]:
         for e in err_dict[k]:
@@ -229,7 +230,7 @@ def max_string_width():
     return max
     return max
 
 
 
 
-def generate_c_output(fin, fout):
+def generate_c_output(fin: TextIO, fout: TextIO) -> None:
     """
     """
     Writes the output to fout based on th error dictionary err_dict and
     Writes the output to fout based on th error dictionary err_dict and
     template file fin.
     template file fin.
@@ -294,7 +295,7 @@ def generate_c_output(fin, fout):
             fout.write(line)
             fout.write(line)
 
 
 
 
-def generate_rst_output(fout):
+def generate_rst_output(fout: TextIO) -> None:
     for k in sorted(err_dict.keys()):
     for k in sorted(err_dict.keys()):
         v = err_dict[k][0]
         v = err_dict[k][0]
         fout.write(':c:macro:`{}` '.format(v.name))
         fout.write(':c:macro:`{}` '.format(v.name))
@@ -307,7 +308,7 @@ def generate_rst_output(fout):
         fout.write('\n\n')
         fout.write('\n\n')
 
 
 
 
-def main():
+def main() -> None:
     if 'IDF_PATH' in os.environ:
     if 'IDF_PATH' in os.environ:
         idf_path = os.environ['IDF_PATH']
         idf_path = os.environ['IDF_PATH']
     else:
     else:

+ 67 - 79
tools/idf.py

@@ -13,7 +13,7 @@
 # check_environment() function below. If possible, avoid importing
 # check_environment() function below. If possible, avoid importing
 # any external libraries here - put in external script, or import in
 # any external libraries here - put in external script, or import in
 # their specific function instead.
 # their specific function instead.
-from __future__ import print_function
+from __future__ import annotations
 
 
 import codecs
 import codecs
 import json
 import json
@@ -23,9 +23,11 @@ import os.path
 import signal
 import signal
 import subprocess
 import subprocess
 import sys
 import sys
-from collections import Counter, OrderedDict
+from collections import Counter, OrderedDict, _OrderedDictKeysView
 from importlib import import_module
 from importlib import import_module
 from pkgutil import iter_modules
 from pkgutil import iter_modules
+from types import FrameType
+from typing import Any, Callable, Dict, List, Optional, TextIO, Union
 
 
 # pyc files remain in the filesystem when switching between branches which might raise errors for incompatible
 # pyc files remain in the filesystem when switching between branches which might raise errors for incompatible
 # idf.py extensions. Therefore, pyc file generation is turned off:
 # idf.py extensions. Therefore, pyc file generation is turned off:
@@ -35,7 +37,8 @@ import python_version_checker  # noqa: E402
 
 
 try:
 try:
     from idf_py_actions.errors import FatalError  # noqa: E402
     from idf_py_actions.errors import FatalError  # noqa: E402
-    from idf_py_actions.tools import executable_exists, idf_version, merge_action_lists, realpath  # noqa: E402
+    from idf_py_actions.tools import (PropertyDict, executable_exists, idf_version, merge_action_lists,  # noqa: E402
+                                      realpath)
 except ImportError:
 except ImportError:
     # For example, importing click could cause this.
     # For example, importing click could cause this.
     print('Please use idf.py only in an ESP-IDF shell environment.', file=sys.stderr)
     print('Please use idf.py only in an ESP-IDF shell environment.', file=sys.stderr)
@@ -61,12 +64,12 @@ SHELL_COMPLETE_RUN = SHELL_COMPLETE_VAR in os.environ
 
 
 # function prints warning when autocompletion is not being performed
 # function prints warning when autocompletion is not being performed
 # set argument stream to sys.stderr for errors and exceptions
 # set argument stream to sys.stderr for errors and exceptions
-def print_warning(message, stream=None):
+def print_warning(message: str, stream: TextIO=None) -> None:
     if not SHELL_COMPLETE_RUN:
     if not SHELL_COMPLETE_RUN:
         print(message, file=stream or sys.stderr)
         print(message, file=stream or sys.stderr)
 
 
 
 
-def check_environment():
+def check_environment() -> List:
     """
     """
     Verify the environment contains the top-level tools we need to operate
     Verify the environment contains the top-level tools we need to operate
 
 
@@ -121,7 +124,7 @@ def check_environment():
     return checks_output
     return checks_output
 
 
 
 
-def _safe_relpath(path, start=None):
+def _safe_relpath(path: str, start: Optional[str]=None) -> str:
     """ Return a relative path, same as os.path.relpath, but only if this is possible.
     """ Return a relative path, same as os.path.relpath, but only if this is possible.
 
 
     It is not possible on Windows, if the start directory and the path are on different drives.
     It is not possible on Windows, if the start directory and the path are on different drives.
@@ -132,7 +135,7 @@ def _safe_relpath(path, start=None):
         return os.path.abspath(path)
         return os.path.abspath(path)
 
 
 
 
-def debug_print_idf_version():
+def debug_print_idf_version() -> None:
     version = idf_version()
     version = idf_version()
     if version:
     if version:
         print_warning('ESP-IDF %s' % version)
         print_warning('ESP-IDF %s' % version)
@@ -140,30 +143,13 @@ def debug_print_idf_version():
         print_warning('ESP-IDF version unknown')
         print_warning('ESP-IDF version unknown')
 
 
 
 
-class PropertyDict(dict):
-    def __getattr__(self, name):
-        if name in self:
-            return self[name]
-        else:
-            raise AttributeError("'PropertyDict' object has no attribute '%s'" % name)
-
-    def __setattr__(self, name, value):
-        self[name] = value
-
-    def __delattr__(self, name):
-        if name in self:
-            del self[name]
-        else:
-            raise AttributeError("'PropertyDict' object has no attribute '%s'" % name)
-
-
-def init_cli(verbose_output=None):
+def init_cli(verbose_output: List=None) -> Any:
     # Click is imported here to run it after check_environment()
     # Click is imported here to run it after check_environment()
     import click
     import click
 
 
     class Deprecation(object):
     class Deprecation(object):
         """Construct deprecation notice for help messages"""
         """Construct deprecation notice for help messages"""
-        def __init__(self, deprecated=False):
+        def __init__(self, deprecated: Union[Dict, str, bool]=False) -> None:
             self.deprecated = deprecated
             self.deprecated = deprecated
             self.since = None
             self.since = None
             self.removed = None
             self.removed = None
@@ -178,7 +164,7 @@ def init_cli(verbose_output=None):
             elif isinstance(deprecated, str):
             elif isinstance(deprecated, str):
                 self.custom_message = deprecated
                 self.custom_message = deprecated
 
 
-        def full_message(self, type='Option'):
+        def full_message(self, type: str='Option') -> str:
             if self.exit_with_error:
             if self.exit_with_error:
                 return '%s is deprecated %sand was removed%s.%s' % (
                 return '%s is deprecated %sand was removed%s.%s' % (
                     type,
                     type,
@@ -194,15 +180,15 @@ def init_cli(verbose_output=None):
                     ' %s' % self.custom_message if self.custom_message else '',
                     ' %s' % self.custom_message if self.custom_message else '',
                 )
                 )
 
 
-        def help(self, text, type='Option', separator=' '):
+        def help(self, text: str, type: str='Option', separator: str=' ') -> str:
             text = text or ''
             text = text or ''
             return self.full_message(type) + separator + text if self.deprecated else text
             return self.full_message(type) + separator + text if self.deprecated else text
 
 
-        def short_help(self, text):
+        def short_help(self, text: str) -> str:
             text = text or ''
             text = text or ''
             return ('Deprecated! ' + text) if self.deprecated else text
             return ('Deprecated! ' + text) if self.deprecated else text
 
 
-    def check_deprecation(ctx):
+    def check_deprecation(ctx: click.core.Context) -> None:
         """Prints deprecation warnings for arguments in given context"""
         """Prints deprecation warnings for arguments in given context"""
         for option in ctx.command.params:
         for option in ctx.command.params:
             default = () if option.multiple else option.default
             default = () if option.multiple else option.default
@@ -214,7 +200,8 @@ def init_cli(verbose_output=None):
                     print_warning('Warning: %s' % deprecation.full_message('Option "%s"' % option.name))
                     print_warning('Warning: %s' % deprecation.full_message('Option "%s"' % option.name))
 
 
     class Task(object):
     class Task(object):
-        def __init__(self, callback, name, aliases, dependencies, order_dependencies, action_args):
+        def __init__(self, callback: Callable, name: str, aliases: List, dependencies: Optional[List],
+                     order_dependencies: Optional[List], action_args: Dict) -> None:
             self.callback = callback
             self.callback = callback
             self.name = name
             self.name = name
             self.dependencies = dependencies
             self.dependencies = dependencies
@@ -222,7 +209,7 @@ def init_cli(verbose_output=None):
             self.action_args = action_args
             self.action_args = action_args
             self.aliases = aliases
             self.aliases = aliases
 
 
-        def __call__(self, context, global_args, action_args=None):
+        def __call__(self, context: click.core.Context, global_args: PropertyDict, action_args: Dict=None) -> None:
             if action_args is None:
             if action_args is None:
                 action_args = self.action_args
                 action_args = self.action_args
 
 
@@ -231,26 +218,24 @@ def init_cli(verbose_output=None):
     class Action(click.Command):
     class Action(click.Command):
         def __init__(
         def __init__(
                 self,
                 self,
-                name=None,
-                aliases=None,
-                deprecated=False,
-                dependencies=None,
-                order_dependencies=None,
-                hidden=False,
-                **kwargs):
+                name: Optional[str]=None,
+                aliases: Optional[List]=None,
+                deprecated: Union[Dict, str, bool]=False,
+                dependencies: Optional[List]=None,
+                order_dependencies: Optional[List]=None,
+                hidden: bool=False,
+                **kwargs: Any) -> None:
             super(Action, self).__init__(name, **kwargs)
             super(Action, self).__init__(name, **kwargs)
 
 
-            self.name = self.name or self.callback.__name__
-            self.deprecated = deprecated
-            self.hidden = hidden
+            self.name: str = self.name or self.callback.__name__
+            self.deprecated: Union[Dict, str, bool] = deprecated
+            self.hidden: bool = hidden
 
 
             if aliases is None:
             if aliases is None:
                 aliases = []
                 aliases = []
             self.aliases = aliases
             self.aliases = aliases
 
 
-            self.help = self.help or self.callback.__doc__
-            if self.help is None:
-                self.help = ''
+            self.help: str = self.help or self.callback.__doc__ or ''
 
 
             if dependencies is None:
             if dependencies is None:
                 dependencies = []
                 dependencies = []
@@ -259,7 +244,7 @@ def init_cli(verbose_output=None):
                 order_dependencies = []
                 order_dependencies = []
 
 
             # Show first line of help if short help is missing
             # Show first line of help if short help is missing
-            self.short_help = self.short_help or self.help.split('\n')[0]
+            self.short_help: str = self.short_help or self.help.split('\n')[0]
 
 
             if deprecated:
             if deprecated:
                 deprecation = Deprecation(deprecated)
                 deprecation = Deprecation(deprecated)
@@ -276,7 +261,7 @@ def init_cli(verbose_output=None):
             self.unwrapped_callback = self.callback
             self.unwrapped_callback = self.callback
             if self.callback is not None:
             if self.callback is not None:
 
 
-                def wrapped_callback(**action_args):
+                def wrapped_callback(**action_args: Any) -> Task:
                     return Task(
                     return Task(
                         callback=self.unwrapped_callback,
                         callback=self.unwrapped_callback,
                         name=self.name,
                         name=self.name,
@@ -288,7 +273,7 @@ def init_cli(verbose_output=None):
 
 
                 self.callback = wrapped_callback
                 self.callback = wrapped_callback
 
 
-        def invoke(self, ctx):
+        def invoke(self, ctx: click.core.Context) -> click.core.Context:
             if self.deprecated:
             if self.deprecated:
                 deprecation = Deprecation(self.deprecated)
                 deprecation = Deprecation(self.deprecated)
                 message = deprecation.full_message('Command "%s"' % self.name)
                 message = deprecation.full_message('Command "%s"' % self.name)
@@ -310,7 +295,7 @@ def init_cli(verbose_output=None):
 
 
         names - alias of 'param_decls'
         names - alias of 'param_decls'
         """
         """
-        def __init__(self, **kwargs):
+        def __init__(self, **kwargs: str):
             names = kwargs.pop('names')
             names = kwargs.pop('names')
             super(Argument, self).__init__(names, **kwargs)
             super(Argument, self).__init__(names, **kwargs)
 
 
@@ -325,7 +310,7 @@ def init_cli(verbose_output=None):
 
 
         SCOPES = ('default', 'global', 'shared')
         SCOPES = ('default', 'global', 'shared')
 
 
-        def __init__(self, scope=None):
+        def __init__(self, scope: Union['Scope', str]=None) -> None:
             if scope is None:
             if scope is None:
                 self._scope = 'default'
                 self._scope = 'default'
             elif isinstance(scope, str) and scope in self.SCOPES:
             elif isinstance(scope, str) and scope in self.SCOPES:
@@ -336,19 +321,19 @@ def init_cli(verbose_output=None):
                 raise FatalError('Unknown scope for option: %s' % scope)
                 raise FatalError('Unknown scope for option: %s' % scope)
 
 
         @property
         @property
-        def is_global(self):
+        def is_global(self) -> bool:
             return self._scope == 'global'
             return self._scope == 'global'
 
 
         @property
         @property
-        def is_shared(self):
+        def is_shared(self) -> bool:
             return self._scope == 'shared'
             return self._scope == 'shared'
 
 
-        def __str__(self):
+        def __str__(self) -> str:
             return self._scope
             return self._scope
 
 
     class Option(click.Option):
     class Option(click.Option):
         """Option that knows whether it should be global"""
         """Option that knows whether it should be global"""
-        def __init__(self, scope=None, deprecated=False, hidden=False, **kwargs):
+        def __init__(self, scope: Union[Scope, str]=None, deprecated: Union[Dict, str, bool]=False, hidden: bool=False, **kwargs: str) -> None:
             """
             """
             Keyword arguments additional to Click's Option class:
             Keyword arguments additional to Click's Option class:
 
 
@@ -369,7 +354,7 @@ def init_cli(verbose_output=None):
 
 
             if deprecated:
             if deprecated:
                 deprecation = Deprecation(deprecated)
                 deprecation = Deprecation(deprecated)
-                self.help = deprecation.help(self.help)
+                self.help: str = deprecation.help(self.help)
 
 
             if self.envvar:
             if self.envvar:
                 self.help += ' The default value can be set with the %s environment variable.' % self.envvar
                 self.help += ' The default value can be set with the %s environment variable.' % self.envvar
@@ -377,16 +362,16 @@ def init_cli(verbose_output=None):
             if self.scope.is_global:
             if self.scope.is_global:
                 self.help += ' This option can be used at most once either globally, or for one subcommand.'
                 self.help += ' This option can be used at most once either globally, or for one subcommand.'
 
 
-        def get_help_record(self, ctx):
+        def get_help_record(self, ctx: click.core.Context) -> Any:
             # Backport "hidden" parameter to click 5.0
             # Backport "hidden" parameter to click 5.0
             if self.hidden:
             if self.hidden:
-                return
+                return None
 
 
             return super(Option, self).get_help_record(ctx)
             return super(Option, self).get_help_record(ctx)
 
 
     class CLI(click.MultiCommand):
     class CLI(click.MultiCommand):
         """Action list contains all actions with options available for CLI"""
         """Action list contains all actions with options available for CLI"""
-        def __init__(self, all_actions=None, verbose_output=None, help=None):
+        def __init__(self, all_actions: Dict=None, verbose_output: List=None, help: str=None) -> None:
             super(CLI, self).__init__(
             super(CLI, self).__init__(
                 chain=True,
                 chain=True,
                 invoke_without_command=True,
                 invoke_without_command=True,
@@ -455,18 +440,21 @@ def init_cli(verbose_output=None):
 
 
                     self._actions[name].params.append(option)
                     self._actions[name].params.append(option)
 
 
-        def list_commands(self, ctx):
+        def list_commands(self, ctx: click.core.Context) -> List:
             return sorted(filter(lambda name: not self._actions[name].hidden, self._actions))
             return sorted(filter(lambda name: not self._actions[name].hidden, self._actions))
 
 
-        def get_command(self, ctx, name):
+        def get_command(self, ctx: click.core.Context, name: str) -> Optional[Action]:
             if name in self.commands_with_aliases:
             if name in self.commands_with_aliases:
                 return self._actions.get(self.commands_with_aliases.get(name))
                 return self._actions.get(self.commands_with_aliases.get(name))
 
 
             # Trying fallback to build target (from "all" action) if command is not known
             # Trying fallback to build target (from "all" action) if command is not known
             else:
             else:
-                return Action(name=name, callback=self._actions.get('fallback').unwrapped_callback)
+                callback = self._actions.get('fallback')
+                if callback:
+                    return Action(name=name, callback=callback.unwrapped_callback)
+                return None
 
 
-        def _print_closing_message(self, args, actions):
+        def _print_closing_message(self, args: PropertyDict, actions: _OrderedDictKeysView) -> None:
             # print a closing message of some kind
             # print a closing message of some kind
             #
             #
             if any(t in str(actions) for t in ('flash', 'dfu', 'uf2', 'uf2-app')):
             if any(t in str(actions) for t in ('flash', 'dfu', 'uf2', 'uf2-app')):
@@ -479,11 +467,13 @@ def init_cli(verbose_output=None):
 
 
             # Otherwise, if we built any binaries print a message about
             # Otherwise, if we built any binaries print a message about
             # how to flash them
             # how to flash them
-            def print_flashing_message(title, key):
-                with open(os.path.join(args.build_dir, 'flasher_args.json')) as f:
-                    flasher_args = json.load(f)
+            def print_flashing_message(title: str, key: str) -> None:
+                with open(os.path.join(args.build_dir, 'flasher_args.json')) as file:
+                    flasher_args: Dict[str, Any] = json.load(file)
 
 
-                def flasher_path(f):
+                def flasher_path(f: Union[str, os.PathLike[str]]) -> str:
+                    if type(args.build_dir) is bytes:
+                        args.build_dir = args.build_dir.decode()
                     return _safe_relpath(os.path.join(args.build_dir, f))
                     return _safe_relpath(os.path.join(args.build_dir, f))
 
 
                 if key != 'project':  # flashing a single item
                 if key != 'project':  # flashing a single item
@@ -536,11 +526,11 @@ def init_cli(verbose_output=None):
                 if 'bootloader' in actions:
                 if 'bootloader' in actions:
                     print_flashing_message('Bootloader', 'bootloader')
                     print_flashing_message('Bootloader', 'bootloader')
 
 
-        def execute_tasks(self, tasks, **kwargs):
+        def execute_tasks(self, tasks: List, **kwargs: str) -> OrderedDict:
             ctx = click.get_current_context()
             ctx = click.get_current_context()
             global_args = PropertyDict(kwargs)
             global_args = PropertyDict(kwargs)
 
 
-            def _help_and_exit():
+            def _help_and_exit() -> None:
                 print(ctx.get_help())
                 print(ctx.get_help())
                 ctx.exit()
                 ctx.exit()
 
 
@@ -592,7 +582,7 @@ def init_cli(verbose_output=None):
                 _help_and_exit()
                 _help_and_exit()
 
 
             # Build full list of tasks to and deal with dependencies and order dependencies
             # Build full list of tasks to and deal with dependencies and order dependencies
-            tasks_to_run = OrderedDict()
+            tasks_to_run: OrderedDict = OrderedDict()
             while tasks:
             while tasks:
                 task = tasks[0]
                 task = tasks[0]
                 tasks_dict = dict([(t.name, t) for t in tasks])
                 tasks_dict = dict([(t.name, t) for t in tasks])
@@ -661,13 +651,13 @@ def init_cli(verbose_output=None):
         },
         },
     )
     )
     @click.option('-C', '--project-dir', default=os.getcwd(), type=click.Path())
     @click.option('-C', '--project-dir', default=os.getcwd(), type=click.Path())
-    def parse_project_dir(project_dir):
+    def parse_project_dir(project_dir: str) -> Any:
         return realpath(project_dir)
         return realpath(project_dir)
 
 
     # Set `complete_var` to not existing environment variable name to prevent early cmd completion
     # Set `complete_var` to not existing environment variable name to prevent early cmd completion
     project_dir = parse_project_dir(standalone_mode=False, complete_var='_IDF.PY_COMPLETE_NOT_EXISTING')
     project_dir = parse_project_dir(standalone_mode=False, complete_var='_IDF.PY_COMPLETE_NOT_EXISTING')
 
 
-    all_actions = {}
+    all_actions: Dict = {}
     # Load extensions from components dir
     # Load extensions from components dir
     idf_py_extensions_path = os.path.join(os.environ['IDF_PATH'], 'tools', 'idf_py_actions')
     idf_py_extensions_path = os.path.join(os.environ['IDF_PATH'], 'tools', 'idf_py_actions')
     extension_dirs = [realpath(idf_py_extensions_path)]
     extension_dirs = [realpath(idf_py_extensions_path)]
@@ -730,12 +720,12 @@ def init_cli(verbose_output=None):
     return CLI(help=cli_help, verbose_output=verbose_output, all_actions=all_actions)
     return CLI(help=cli_help, verbose_output=verbose_output, all_actions=all_actions)
 
 
 
 
-def signal_handler(_signal, _frame):
+def signal_handler(_signal: int, _frame: Optional[FrameType]) -> None:
     # The Ctrl+C processed by other threads inside
     # The Ctrl+C processed by other threads inside
     pass
     pass
 
 
 
 
-def main():
+def main() -> None:
     # Processing of Ctrl+C event for all threads made by main()
     # Processing of Ctrl+C event for all threads made by main()
     signal.signal(signal.SIGINT, signal_handler)
     signal.signal(signal.SIGINT, signal_handler)
 
 
@@ -753,7 +743,7 @@ def main():
         cli(sys.argv[1:], prog_name=PROG, complete_var=SHELL_COMPLETE_VAR)
         cli(sys.argv[1:], prog_name=PROG, complete_var=SHELL_COMPLETE_VAR)
 
 
 
 
-def _valid_unicode_config():
+def _valid_unicode_config() -> Union[codecs.CodecInfo, bool]:
     # Python 2 is always good
     # Python 2 is always good
     if sys.version_info[0] == 2:
     if sys.version_info[0] == 2:
         return True
         return True
@@ -765,15 +755,13 @@ def _valid_unicode_config():
         return False
         return False
 
 
 
 
-def _find_usable_locale():
+def _find_usable_locale() -> str:
     try:
     try:
-        locales = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0]
+        locales = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()[0].decode('ascii', 'replace')
     except OSError:
     except OSError:
         locales = ''
         locales = ''
-    if isinstance(locales, bytes):
-        locales = locales.decode('ascii', 'replace')
 
 
-    usable_locales = []
+    usable_locales: List[str] = []
     for line in locales.splitlines():
     for line in locales.splitlines():
         locale = line.strip()
         locale = line.strip()
         locale_name = locale.lower().replace('-', '')
         locale_name = locale.lower().replace('-', '')

+ 27 - 49
tools/idf_py_actions/core_ext.py

@@ -7,20 +7,22 @@ import re
 import shutil
 import shutil
 import subprocess
 import subprocess
 import sys
 import sys
+from typing import Any, Dict, List, Optional
 from urllib.error import URLError
 from urllib.error import URLError
 from urllib.request import Request, urlopen
 from urllib.request import Request, urlopen
 from webbrowser import open_new_tab
 from webbrowser import open_new_tab
 
 
 import click
 import click
+from click.core import Context
 from idf_py_actions.constants import GENERATORS, PREVIEW_TARGETS, SUPPORTED_TARGETS, URL_TO_DOC
 from idf_py_actions.constants import GENERATORS, PREVIEW_TARGETS, SUPPORTED_TARGETS, URL_TO_DOC
 from idf_py_actions.errors import FatalError
 from idf_py_actions.errors import FatalError
 from idf_py_actions.global_options import global_options
 from idf_py_actions.global_options import global_options
-from idf_py_actions.tools import (TargetChoice, ensure_build_directory, get_target, idf_version, merge_action_lists,
-                                  realpath, run_target)
+from idf_py_actions.tools import (PropertyDict, TargetChoice, ensure_build_directory, get_target, idf_version,
+                                  merge_action_lists, realpath, run_target)
 
 
 
 
-def action_extensions(base_actions, project_path):
-    def build_target(target_name, ctx, args):
+def action_extensions(base_actions: Dict, project_path: str) -> Any:
+    def build_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
         """
         """
         Execute the target build system to build target 'target_name'
         Execute the target build system to build target 'target_name'
 
 
@@ -30,7 +32,7 @@ def action_extensions(base_actions, project_path):
         ensure_build_directory(args, ctx.info_name)
         ensure_build_directory(args, ctx.info_name)
         run_target(target_name, args)
         run_target(target_name, args)
 
 
-    def size_target(target_name, ctx, args):
+    def size_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
         """
         """
         Builds the app and then executes a size-related target passed in 'target_name'.
         Builds the app and then executes a size-related target passed in 'target_name'.
         `tool_error_handler` handler is used to suppress errors during the build,
         `tool_error_handler` handler is used to suppress errors during the build,
@@ -38,18 +40,18 @@ def action_extensions(base_actions, project_path):
 
 
         """
         """
 
 
-        def tool_error_handler(e):
+        def tool_error_handler(e: int) -> None:
             pass
             pass
 
 
         ensure_build_directory(args, ctx.info_name)
         ensure_build_directory(args, ctx.info_name)
         run_target('all', args, custom_error_handler=tool_error_handler)
         run_target('all', args, custom_error_handler=tool_error_handler)
         run_target(target_name, args)
         run_target(target_name, args)
 
 
-    def list_build_system_targets(target_name, ctx, args):
+    def list_build_system_targets(target_name: str, ctx: Context, args: PropertyDict) -> None:
         """Shows list of targets known to build sytem (make/ninja)"""
         """Shows list of targets known to build sytem (make/ninja)"""
         build_target('help', ctx, args)
         build_target('help', ctx, args)
 
 
-    def menuconfig(target_name, ctx, args, style):
+    def menuconfig(target_name: str, ctx: Context, args: PropertyDict, style: str) -> None:
         """
         """
         Menuconfig target is build_target extended with the style argument for setting the value for the environment
         Menuconfig target is build_target extended with the style argument for setting the value for the environment
         variable.
         variable.
@@ -61,7 +63,7 @@ def action_extensions(base_actions, project_path):
         os.environ['MENUCONFIG_STYLE'] = style
         os.environ['MENUCONFIG_STYLE'] = style
         build_target(target_name, ctx, args)
         build_target(target_name, ctx, args)
 
 
-    def fallback_target(target_name, ctx, args):
+    def fallback_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
         """
         """
         Execute targets that are not explicitly known to idf.py
         Execute targets that are not explicitly known to idf.py
         """
         """
@@ -80,42 +82,22 @@ def action_extensions(base_actions, project_path):
 
 
         run_target(target_name, args)
         run_target(target_name, args)
 
 
-    def verbose_callback(ctx, param, value):
+    def verbose_callback(ctx: Context, param: List, value: str) -> Optional[str]:
         if not value or ctx.resilient_parsing:
         if not value or ctx.resilient_parsing:
-            return
+            return None
 
 
         for line in ctx.command.verbose_output:
         for line in ctx.command.verbose_output:
             print(line)
             print(line)
 
 
         return value
         return value
 
 
-    def clean(action, ctx, args):
+    def clean(action: str, ctx: Context, args: PropertyDict) -> None:
         if not os.path.isdir(args.build_dir):
         if not os.path.isdir(args.build_dir):
             print("Build directory '%s' not found. Nothing to clean." % args.build_dir)
             print("Build directory '%s' not found. Nothing to clean." % args.build_dir)
             return
             return
         build_target('clean', ctx, args)
         build_target('clean', ctx, args)
 
 
-    def _delete_windows_symlinks(directory):
-        """
-        It deletes symlinks recursively on Windows. It is useful for Python 2 which doesn't detect symlinks on Windows.
-        """
-        deleted_paths = []
-        if os.name == 'nt':
-            import ctypes
-
-            for root, dirnames, _filenames in os.walk(directory):
-                for d in dirnames:
-                    full_path = os.path.join(root, d)
-                    try:
-                        full_path = full_path.decode('utf-8')
-                    except Exception:
-                        pass
-                    if ctypes.windll.kernel32.GetFileAttributesW(full_path) & 0x0400:
-                        os.rmdir(full_path)
-                        deleted_paths.append(full_path)
-        return deleted_paths
-
-    def fullclean(action, ctx, args):
+    def fullclean(action: str, ctx: Context, args: PropertyDict) -> None:
         build_dir = args.build_dir
         build_dir = args.build_dir
         if not os.path.isdir(build_dir):
         if not os.path.isdir(build_dir):
             print("Build directory '%s' not found. Nothing to clean." % build_dir)
             print("Build directory '%s' not found. Nothing to clean." % build_dir)
@@ -135,13 +117,8 @@ def action_extensions(base_actions, project_path):
                 raise FatalError(
                 raise FatalError(
                     "Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure."
                     "Refusing to automatically delete files in directory containing '%s'. Delete files manually if you're sure."
                     % red)
                     % red)
-        # OK, delete everything in the build directory...
-        # Note: Python 2.7 doesn't detect symlinks on Windows (it is supported form 3.2). Tools promising to not
-        # follow symlinks will actually follow them. Deleting the build directory with symlinks deletes also items
-        # outside of this directory.
-        deleted_symlinks = _delete_windows_symlinks(build_dir)
-        if args.verbose and len(deleted_symlinks) > 1:
-            print('The following symlinks were identified and removed:\n%s' % '\n'.join(deleted_symlinks))
+        if args.verbose and len(build_dir) > 1:
+            print('The following symlinks were identified and removed:\n%s' % '\n'.join(build_dir))
         for f in os.listdir(build_dir):  # TODO: once we are Python 3 only, this can be os.scandir()
         for f in os.listdir(build_dir):  # TODO: once we are Python 3 only, this can be os.scandir()
             f = os.path.join(build_dir, f)
             f = os.path.join(build_dir, f)
             if args.verbose:
             if args.verbose:
@@ -151,7 +128,7 @@ def action_extensions(base_actions, project_path):
             else:
             else:
                 os.remove(f)
                 os.remove(f)
 
 
-    def python_clean(action, ctx, args):
+    def python_clean(action: str, ctx: Context, args: PropertyDict) -> None:
         for root, dirnames, filenames in os.walk(os.environ['IDF_PATH']):
         for root, dirnames, filenames in os.walk(os.environ['IDF_PATH']):
             for d in dirnames:
             for d in dirnames:
                 if d == '__pycache__':
                 if d == '__pycache__':
@@ -165,7 +142,7 @@ def action_extensions(base_actions, project_path):
                     print('Removing: %s' % file_to_delete)
                     print('Removing: %s' % file_to_delete)
                 os.remove(file_to_delete)
                 os.remove(file_to_delete)
 
 
-    def set_target(action, ctx, args, idf_target):
+    def set_target(action: str, ctx: Context, args: PropertyDict, idf_target: str) -> None:
         if (not args['preview'] and idf_target in PREVIEW_TARGETS):
         if (not args['preview'] and idf_target in PREVIEW_TARGETS):
             raise FatalError(
             raise FatalError(
                 "%s is still in preview. You have to append '--preview' option after idf.py to use any preview feature."
                 "%s is still in preview. You have to append '--preview' option after idf.py to use any preview feature."
@@ -180,10 +157,10 @@ def action_extensions(base_actions, project_path):
         print('Set Target to: %s, new sdkconfig created. Existing sdkconfig renamed to sdkconfig.old.' % idf_target)
         print('Set Target to: %s, new sdkconfig created. Existing sdkconfig renamed to sdkconfig.old.' % idf_target)
         ensure_build_directory(args, ctx.info_name, True)
         ensure_build_directory(args, ctx.info_name, True)
 
 
-    def reconfigure(action, ctx, args):
+    def reconfigure(action: str, ctx: Context, args: PropertyDict) -> None:
         ensure_build_directory(args, ctx.info_name, True)
         ensure_build_directory(args, ctx.info_name, True)
 
 
-    def validate_root_options(ctx, args, tasks):
+    def validate_root_options(ctx: Context, args: PropertyDict, tasks: List) -> None:
         args.project_dir = realpath(args.project_dir)
         args.project_dir = realpath(args.project_dir)
         if args.build_dir is not None and args.project_dir == realpath(args.build_dir):
         if args.build_dir is not None and args.project_dir == realpath(args.build_dir):
             raise FatalError(
             raise FatalError(
@@ -193,7 +170,7 @@ def action_extensions(base_actions, project_path):
             args.build_dir = os.path.join(args.project_dir, 'build')
             args.build_dir = os.path.join(args.project_dir, 'build')
         args.build_dir = realpath(args.build_dir)
         args.build_dir = realpath(args.build_dir)
 
 
-    def idf_version_callback(ctx, param, value):
+    def idf_version_callback(ctx: Context, param: str, value: str) -> None:
         if not value or ctx.resilient_parsing:
         if not value or ctx.resilient_parsing:
             return
             return
 
 
@@ -205,7 +182,7 @@ def action_extensions(base_actions, project_path):
         print('ESP-IDF %s' % version)
         print('ESP-IDF %s' % version)
         sys.exit(0)
         sys.exit(0)
 
 
-    def list_targets_callback(ctx, param, value):
+    def list_targets_callback(ctx: Context, param: List, value: int) -> None:
         if not value or ctx.resilient_parsing:
         if not value or ctx.resilient_parsing:
             return
             return
 
 
@@ -218,12 +195,13 @@ def action_extensions(base_actions, project_path):
 
 
         sys.exit(0)
         sys.exit(0)
 
 
-    def show_docs(action, ctx, args, no_browser, language, starting_page, version, target):
+    def show_docs(action: str, ctx: Context, args: PropertyDict, no_browser: bool, language: str, starting_page: str, version: str, target: str) -> None:
         if language == 'cn':
         if language == 'cn':
             language = 'zh_CN'
             language = 'zh_CN'
         if not version:
         if not version:
             # '0.0-dev' here because if 'dev' in version it will transform in to 'latest'
             # '0.0-dev' here because if 'dev' in version it will transform in to 'latest'
-            version = re.search(r'v\d+\.\d+\.?\d*(-dev|-beta\d|-rc)?', idf_version() or '0.0-dev').group()
+            version_search = re.search(r'v\d+\.\d+\.?\d*(-dev|-beta\d|-rc)?', idf_version() or '0.0-dev')
+            version = version_search.group() if version_search else 'latest'
             if 'dev' in version:
             if 'dev' in version:
                 version = 'latest'
                 version = 'latest'
         elif version[0] != 'v':
         elif version[0] != 'v':
@@ -249,7 +227,7 @@ def action_extensions(base_actions, project_path):
             print(link)
             print(link)
         sys.exit(0)
         sys.exit(0)
 
 
-    def get_default_language():
+    def get_default_language() -> str:
         try:
         try:
             language = 'zh_CN' if locale.getdefaultlocale()[0] == 'zh_CN' else 'en'
             language = 'zh_CN' if locale.getdefaultlocale()[0] == 'zh_CN' else 'en'
         except ValueError:
         except ValueError:

+ 11 - 7
tools/idf_py_actions/create_ext.py

@@ -6,13 +6,17 @@ import os
 import re
 import re
 import sys
 import sys
 from distutils.dir_util import copy_tree
 from distutils.dir_util import copy_tree
+from typing import Dict
 
 
+import click
+from idf_py_actions.tools import PropertyDict
 
 
-def get_type(action):
+
+def get_type(action: str) -> str:
     return action.split('-')[1]
     return action.split('-')[1]
 
 
 
 
-def replace_in_file(filename, pattern, replacement):
+def replace_in_file(filename: str, pattern: str, replacement: str) -> None:
     with open(filename, 'r+') as f:
     with open(filename, 'r+') as f:
         content = f.read()
         content = f.read()
         overwritten_content = re.sub(pattern, replacement, content, flags=re.M)
         overwritten_content = re.sub(pattern, replacement, content, flags=re.M)
@@ -21,7 +25,7 @@ def replace_in_file(filename, pattern, replacement):
         f.truncate()
         f.truncate()
 
 
 
 
-def is_empty_and_create(path, action):
+def is_empty_and_create(path: str, action: str) -> None:
     abspath = os.path.abspath(path)
     abspath = os.path.abspath(path)
     if not os.path.exists(abspath):
     if not os.path.exists(abspath):
         os.makedirs(abspath)
         os.makedirs(abspath)
@@ -35,7 +39,7 @@ def is_empty_and_create(path, action):
         sys.exit(3)
         sys.exit(3)
 
 
 
 
-def create_project(target_path, name):
+def create_project(target_path: str, name: str) -> None:
     copy_tree(os.path.join(os.environ['IDF_PATH'], 'examples', 'get-started', 'sample_project'), target_path)
     copy_tree(os.path.join(os.environ['IDF_PATH'], 'examples', 'get-started', 'sample_project'), target_path)
     main_folder = os.path.join(target_path, 'main')
     main_folder = os.path.join(target_path, 'main')
     os.rename(os.path.join(main_folder, 'main.c'), os.path.join(main_folder, '.'.join((name, 'c'))))
     os.rename(os.path.join(main_folder, 'main.c'), os.path.join(main_folder, '.'.join((name, 'c'))))
@@ -44,7 +48,7 @@ def create_project(target_path, name):
     os.remove(os.path.join(target_path, 'README.md'))
     os.remove(os.path.join(target_path, 'README.md'))
 
 
 
 
-def create_component(target_path, name):
+def create_component(target_path: str, name: str) -> None:
     copy_tree(os.path.join(os.environ['IDF_PATH'], 'tools', 'templates', 'sample_component'), target_path)
     copy_tree(os.path.join(os.environ['IDF_PATH'], 'tools', 'templates', 'sample_component'), target_path)
     os.rename(os.path.join(target_path, 'main.c'), os.path.join(target_path, '.'.join((name, 'c'))))
     os.rename(os.path.join(target_path, 'main.c'), os.path.join(target_path, '.'.join((name, 'c'))))
     os.rename(os.path.join(target_path, 'include', 'main.h'),
     os.rename(os.path.join(target_path, 'include', 'main.h'),
@@ -54,8 +58,8 @@ def create_component(target_path, name):
     replace_in_file(os.path.join(target_path, 'CMakeLists.txt'), 'main', name)
     replace_in_file(os.path.join(target_path, 'CMakeLists.txt'), 'main', name)
 
 
 
 
-def action_extensions(base_actions, project_path):
-    def create_new(action, ctx, global_args, **action_args):
+def action_extensions(base_actions: Dict, project_path: str) -> Dict:
+    def create_new(action: str, ctx: click.core.Context, global_args: PropertyDict, **action_args: str) -> Dict:
         target_path = action_args.get('path') or os.path.join(project_path, action_args['name'])
         target_path = action_args.get('path') or os.path.join(project_path, action_args['name'])
 
 
         is_empty_and_create(target_path, action)
         is_empty_and_create(target_path, action)

+ 23 - 22
tools/idf_py_actions/debug_ext.py

@@ -9,21 +9,22 @@ import sys
 import threading
 import threading
 import time
 import time
 from threading import Thread
 from threading import Thread
-from typing import Any, Dict, List
+from typing import Any, Dict, List, Optional
 
 
+from click.core import Context
 from idf_py_actions.errors import FatalError
 from idf_py_actions.errors import FatalError
-from idf_py_actions.tools import ensure_build_directory
+from idf_py_actions.tools import PropertyDict, ensure_build_directory
 
 
 PYTHON = sys.executable
 PYTHON = sys.executable
 
 
 
 
-def action_extensions(base_actions, project_path):
+def action_extensions(base_actions: Dict, project_path: str) -> Dict:
     OPENOCD_OUT_FILE = 'openocd_out.txt'
     OPENOCD_OUT_FILE = 'openocd_out.txt'
     GDBGUI_OUT_FILE = 'gdbgui_out.txt'
     GDBGUI_OUT_FILE = 'gdbgui_out.txt'
     # Internal dictionary of currently active processes, threads and their output files
     # Internal dictionary of currently active processes, threads and their output files
-    processes = {'threads_to_join': [], 'openocd_issues': None}
+    processes: Dict = {'threads_to_join': [], 'openocd_issues': None}
 
 
-    def _check_for_common_openocd_issues(file_name, print_all=True):
+    def _check_for_common_openocd_issues(file_name: str, print_all: bool=True) -> Any:
         if processes['openocd_issues'] is not None:
         if processes['openocd_issues'] is not None:
             return processes['openocd_issues']
             return processes['openocd_issues']
         try:
         try:
@@ -39,7 +40,7 @@ def action_extensions(base_actions, project_path):
             processes['openocd_issues'] = message
             processes['openocd_issues'] = message
             return message
             return message
 
 
-    def _check_openocd_errors(fail_if_openocd_failed, target, ctx):
+    def _check_openocd_errors(fail_if_openocd_failed: Dict, target: str, ctx: Context) -> None:
         if fail_if_openocd_failed:
         if fail_if_openocd_failed:
             if 'openocd' in processes and processes['openocd'] is not None:
             if 'openocd' in processes and processes['openocd'] is not None:
                 p = processes['openocd']
                 p = processes['openocd']
@@ -62,7 +63,7 @@ def action_extensions(base_actions, project_path):
                 # OpenOCD exited or error message detected -> print possible output and terminate
                 # OpenOCD exited or error message detected -> print possible output and terminate
                 raise FatalError('Action "{}" failed due to errors in OpenOCD:\n{}'.format(target, _check_for_common_openocd_issues(name)), ctx)
                 raise FatalError('Action "{}" failed due to errors in OpenOCD:\n{}'.format(target, _check_for_common_openocd_issues(name)), ctx)
 
 
-    def _terminate_async_target(target):
+    def _terminate_async_target(target: str) -> None:
         if target in processes and processes[target] is not None:
         if target in processes and processes[target] is not None:
             try:
             try:
                 if target + '_outfile' in processes:
                 if target + '_outfile' in processes:
@@ -86,11 +87,11 @@ def action_extensions(base_actions, project_path):
                 print('Failed to close/kill {}'.format(target))
                 print('Failed to close/kill {}'.format(target))
             processes[target] = None  # to indicate this has ended
             processes[target] = None  # to indicate this has ended
 
 
-    def is_gdb_with_python(gdb):
+    def is_gdb_with_python(gdb: str) -> bool:
         # execute simple python command to check is it supported
         # execute simple python command to check is it supported
         return subprocess.run([gdb, '--batch-silent', '--ex', 'python import os'], stderr=subprocess.DEVNULL).returncode == 0
         return subprocess.run([gdb, '--batch-silent', '--ex', 'python import os'], stderr=subprocess.DEVNULL).returncode == 0
 
 
-    def create_local_gdbinit(gdb, gdbinit, elf_file):
+    def create_local_gdbinit(gdb: str, gdbinit: str, elf_file: str) -> None:
         with open(gdbinit, 'w') as f:
         with open(gdbinit, 'w') as f:
             if is_gdb_with_python(gdb):
             if is_gdb_with_python(gdb):
                 f.write('python\n')
                 f.write('python\n')
@@ -107,7 +108,7 @@ def action_extensions(base_actions, project_path):
             f.write('thb app_main\n')
             f.write('thb app_main\n')
             f.write('c\n')
             f.write('c\n')
 
 
-    def debug_cleanup():
+    def debug_cleanup() -> None:
         print('cleaning up debug targets')
         print('cleaning up debug targets')
         for t in processes['threads_to_join']:
         for t in processes['threads_to_join']:
             if threading.currentThread() != t:
             if threading.currentThread() != t:
@@ -116,7 +117,7 @@ def action_extensions(base_actions, project_path):
         _terminate_async_target('gdbgui')
         _terminate_async_target('gdbgui')
         _terminate_async_target('gdb')
         _terminate_async_target('gdb')
 
 
-    def post_debug(action, ctx, args, **kwargs):
+    def post_debug(action: str, ctx: Context, args: PropertyDict, **kwargs: str) -> None:
         """ Deal with asynchronous targets, such as openocd running in background """
         """ Deal with asynchronous targets, such as openocd running in background """
         if kwargs['block'] == 1:
         if kwargs['block'] == 1:
             for target in ['openocd', 'gdbgui']:
             for target in ['openocd', 'gdbgui']:
@@ -143,7 +144,7 @@ def action_extensions(base_actions, project_path):
         _terminate_async_target('openocd')
         _terminate_async_target('openocd')
         _terminate_async_target('gdbgui')
         _terminate_async_target('gdbgui')
 
 
-    def get_project_desc(args, ctx):
+    def get_project_desc(args: PropertyDict, ctx: Context) -> Any:
         desc_path = os.path.join(args.build_dir, 'project_description.json')
         desc_path = os.path.join(args.build_dir, 'project_description.json')
         if not os.path.exists(desc_path):
         if not os.path.exists(desc_path):
             ensure_build_directory(args, ctx.info_name)
             ensure_build_directory(args, ctx.info_name)
@@ -151,7 +152,7 @@ def action_extensions(base_actions, project_path):
             project_desc = json.load(f)
             project_desc = json.load(f)
             return project_desc
             return project_desc
 
 
-    def openocd(action, ctx, args, openocd_scripts, openocd_commands):
+    def openocd(action: str, ctx: Context, args: PropertyDict, openocd_scripts: Optional[str], openocd_commands: str) -> None:
         """
         """
         Execute openocd as external tool
         Execute openocd as external tool
         """
         """
@@ -188,14 +189,14 @@ def action_extensions(base_actions, project_path):
         processes['openocd_outfile_name'] = openocd_out_name
         processes['openocd_outfile_name'] = openocd_out_name
         print('OpenOCD started as a background task {}'.format(process.pid))
         print('OpenOCD started as a background task {}'.format(process.pid))
 
 
-    def get_gdb_args(gdbinit, project_desc: Dict[str, Any]) -> List[str]:
+    def get_gdb_args(gdbinit: str, project_desc: Dict[str, Any]) -> List:
         args = ['-x={}'.format(gdbinit)]
         args = ['-x={}'.format(gdbinit)]
         debug_prefix_gdbinit = project_desc.get('debug_prefix_map_gdbinit')
         debug_prefix_gdbinit = project_desc.get('debug_prefix_map_gdbinit')
         if debug_prefix_gdbinit:
         if debug_prefix_gdbinit:
             args.append('-ix={}'.format(debug_prefix_gdbinit))
             args.append('-ix={}'.format(debug_prefix_gdbinit))
         return args
         return args
 
 
-    def gdbui(action, ctx, args, gdbgui_port, gdbinit, require_openocd):
+    def gdbui(action: str, ctx: Context, args: PropertyDict, gdbgui_port: Optional[str], gdbinit: Optional[str], require_openocd: bool) -> None:
         """
         """
         Asynchronous GDB-UI target
         Asynchronous GDB-UI target
         """
         """
@@ -211,8 +212,8 @@ def action_extensions(base_actions, project_path):
         # - '"-x=foo -x=bar"', would return ['foo bar']
         # - '"-x=foo -x=bar"', would return ['foo bar']
         # - '-x=foo', would return ['-x', 'foo'] and mess up the former option '--gdb-args'
         # - '-x=foo', would return ['-x', 'foo'] and mess up the former option '--gdb-args'
         # so for one item, use extra double quotes. for more items, use no extra double quotes.
         # so for one item, use extra double quotes. for more items, use no extra double quotes.
-        gdb_args = get_gdb_args(gdbinit, project_desc)
-        gdb_args = '"{}"'.format(' '.join(gdb_args)) if len(gdb_args) == 1 else ' '.join(gdb_args)
+        gdb_args_list = get_gdb_args(gdbinit, project_desc)
+        gdb_args = '"{}"'.format(' '.join(gdb_args_list)) if len(gdb_args_list) == 1 else ' '.join(gdb_args_list)
         args = ['gdbgui', '-g', gdb, '--gdb-args', gdb_args]
         args = ['gdbgui', '-g', gdb, '--gdb-args', gdb_args]
         print(args)
         print(args)
 
 
@@ -238,8 +239,8 @@ def action_extensions(base_actions, project_path):
         print('gdbgui started as a background task {}'.format(process.pid))
         print('gdbgui started as a background task {}'.format(process.pid))
         _check_openocd_errors(fail_if_openocd_failed, action, ctx)
         _check_openocd_errors(fail_if_openocd_failed, action, ctx)
 
 
-    def global_callback(ctx, global_args, tasks):
-        def move_to_front(task_name):
+    def global_callback(ctx: Context, global_args: PropertyDict, tasks: List) -> None:
+        def move_to_front(task_name: str) -> None:
             for index, task in enumerate(tasks):
             for index, task in enumerate(tasks):
                 if task.name == task_name:
                 if task.name == task_name:
                     tasks.insert(0, tasks.pop(index))
                     tasks.insert(0, tasks.pop(index))
@@ -264,18 +265,18 @@ def action_extensions(base_actions, project_path):
                 if task.name in ('gdb', 'gdbgui', 'gdbtui'):
                 if task.name in ('gdb', 'gdbgui', 'gdbtui'):
                     task.action_args['require_openocd'] = True
                     task.action_args['require_openocd'] = True
 
 
-    def run_gdb(gdb_args):
+    def run_gdb(gdb_args: List) -> int:
         p = subprocess.Popen(gdb_args)
         p = subprocess.Popen(gdb_args)
         processes['gdb'] = p
         processes['gdb'] = p
         return p.wait()
         return p.wait()
 
 
-    def gdbtui(action, ctx, args, gdbinit, require_openocd):
+    def gdbtui(action: str, ctx: Context, args: PropertyDict, gdbinit: str, require_openocd: bool) -> None:
         """
         """
         Synchronous GDB target with text ui mode
         Synchronous GDB target with text ui mode
         """
         """
         gdb(action, ctx, args, 1, gdbinit, require_openocd)
         gdb(action, ctx, args, 1, gdbinit, require_openocd)
 
 
-    def gdb(action, ctx, args, gdb_tui, gdbinit, require_openocd):
+    def gdb(action: str, ctx: Context, args: PropertyDict, gdb_tui: Optional[int], gdbinit: Optional[str], require_openocd: bool) -> None:
         """
         """
         Synchronous GDB target
         Synchronous GDB target
         """
         """

+ 8 - 5
tools/idf_py_actions/dfu_ext.py

@@ -1,22 +1,25 @@
 # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 # SPDX-License-Identifier: Apache-2.0
 # SPDX-License-Identifier: Apache-2.0
+from typing import Dict
+
+from click.core import Context
 from idf_py_actions.errors import FatalError
 from idf_py_actions.errors import FatalError
-from idf_py_actions.tools import ensure_build_directory, is_target_supported, run_target
+from idf_py_actions.tools import PropertyDict, ensure_build_directory, is_target_supported, run_target
 
 
 
 
-def action_extensions(base_actions, project_path):
+def action_extensions(base_actions: Dict, project_path: str) -> Dict:
 
 
     SUPPORTED_TARGETS = ['esp32s2']
     SUPPORTED_TARGETS = ['esp32s2']
 
 
-    def dfu_target(target_name, ctx, args, part_size):
+    def dfu_target(target_name: str, ctx: Context, args: PropertyDict, part_size: str) -> None:
         ensure_build_directory(args, ctx.info_name)
         ensure_build_directory(args, ctx.info_name)
         run_target(target_name, args, {'ESP_DFU_PART_SIZE': part_size} if part_size else {})
         run_target(target_name, args, {'ESP_DFU_PART_SIZE': part_size} if part_size else {})
 
 
-    def dfu_list_target(target_name, ctx, args):
+    def dfu_list_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
         ensure_build_directory(args, ctx.info_name)
         ensure_build_directory(args, ctx.info_name)
         run_target(target_name, args)
         run_target(target_name, args)
 
 
-    def dfu_flash_target(target_name, ctx, args, path):
+    def dfu_flash_target(target_name: str, ctx: Context, args: PropertyDict, path: str) -> None:
         ensure_build_directory(args, ctx.info_name)
         ensure_build_directory(args, ctx.info_name)
 
 
         try:
         try:

+ 4 - 1
tools/idf_py_actions/errors.py

@@ -1,11 +1,14 @@
 # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 # SPDX-License-Identifier: Apache-2.0
 # SPDX-License-Identifier: Apache-2.0
+from click.core import Context
+
+
 class FatalError(RuntimeError):
 class FatalError(RuntimeError):
     """
     """
     Wrapper class for runtime errors that aren't caused by bugs in idf.py or the build process.
     Wrapper class for runtime errors that aren't caused by bugs in idf.py or the build process.
     """
     """
 
 
-    def __init__(self, message, ctx=None):
+    def __init__(self, message: str, ctx: Context=None):
         super(RuntimeError, self).__init__(message)
         super(RuntimeError, self).__init__(message)
         # if context is defined, check for the cleanup tasks
         # if context is defined, check for the cleanup tasks
         if ctx is not None and 'cleanup' in ctx.meta:
         if ctx is not None and 'cleanup' in ctx.meta:

+ 13 - 11
tools/idf_py_actions/serial_ext.py

@@ -4,18 +4,19 @@
 import json
 import json
 import os
 import os
 import sys
 import sys
+from typing import Any, Dict, List
 
 
 import click
 import click
 from idf_monitor_base.output_helpers import yellow_print
 from idf_monitor_base.output_helpers import yellow_print
 from idf_py_actions.errors import FatalError, NoSerialPortFoundError
 from idf_py_actions.errors import FatalError, NoSerialPortFoundError
 from idf_py_actions.global_options import global_options
 from idf_py_actions.global_options import global_options
-from idf_py_actions.tools import ensure_build_directory, get_sdkconfig_value, run_target, run_tool
+from idf_py_actions.tools import PropertyDict, ensure_build_directory, get_sdkconfig_value, run_target, run_tool
 
 
 PYTHON = sys.executable
 PYTHON = sys.executable
 
 
 
 
-def action_extensions(base_actions, project_path):
-    def _get_project_desc(ctx, args):
+def action_extensions(base_actions: Dict, project_path: str) -> Dict:
+    def _get_project_desc(ctx: click.core.Context, args: PropertyDict) -> Any:
         desc_path = os.path.join(args.build_dir, 'project_description.json')
         desc_path = os.path.join(args.build_dir, 'project_description.json')
         if not os.path.exists(desc_path):
         if not os.path.exists(desc_path):
             ensure_build_directory(args, ctx.info_name)
             ensure_build_directory(args, ctx.info_name)
@@ -23,7 +24,7 @@ def action_extensions(base_actions, project_path):
             project_desc = json.load(f)
             project_desc = json.load(f)
         return project_desc
         return project_desc
 
 
-    def _get_default_serial_port(args):
+    def _get_default_serial_port(args: PropertyDict) -> Any:
         # Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
         # Import is done here in order to move it after the check_environment() ensured that pyserial has been installed
         try:
         try:
             import esptool
             import esptool
@@ -45,7 +46,7 @@ def action_extensions(base_actions, project_path):
         except Exception as e:
         except Exception as e:
             raise FatalError('An exception occurred during detection of the serial port: {}'.format(e))
             raise FatalError('An exception occurred during detection of the serial port: {}'.format(e))
 
 
-    def _get_esptool_args(args):
+    def _get_esptool_args(args: PropertyDict) -> List:
         esptool_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/esptool.py')
         esptool_path = os.path.join(os.environ['IDF_PATH'], 'components/esptool_py/esptool/esptool.py')
         esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
         esptool_wrapper_path = os.environ.get('ESPTOOL_WRAPPER', '')
         if args.port is None:
         if args.port is None:
@@ -68,7 +69,7 @@ def action_extensions(base_actions, project_path):
             result += ['--no-stub']
             result += ['--no-stub']
         return result
         return result
 
 
-    def _get_commandline_options(ctx):
+    def _get_commandline_options(ctx: click.core.Context) -> List:
         """ Return all the command line options up to first action """
         """ Return all the command line options up to first action """
         # This approach ignores argument parsing done Click
         # This approach ignores argument parsing done Click
         result = []
         result = []
@@ -81,7 +82,8 @@ def action_extensions(base_actions, project_path):
 
 
         return result
         return result
 
 
-    def monitor(action, ctx, args, print_filter, monitor_baud, encrypted, no_reset, timestamps, timestamp_format):
+    def monitor(action: str, ctx: click.core.Context, args: PropertyDict, print_filter: str, monitor_baud: str, encrypted: bool,
+                no_reset: bool, timestamps: bool, timestamp_format: str) -> None:
         """
         """
         Run idf_monitor.py to watch build output
         Run idf_monitor.py to watch build output
         """
         """
@@ -152,7 +154,7 @@ def action_extensions(base_actions, project_path):
 
 
         run_tool('idf_monitor', monitor_args, args.project_dir)
         run_tool('idf_monitor', monitor_args, args.project_dir)
 
 
-    def flash(action, ctx, args):
+    def flash(action: str, ctx: click.core.Context, args: PropertyDict) -> None:
         """
         """
         Run esptool to flash the entire project, from an argfile generated by the build system
         Run esptool to flash the entire project, from an argfile generated by the build system
         """
         """
@@ -165,13 +167,13 @@ def action_extensions(base_actions, project_path):
         esp_port = args.port or _get_default_serial_port(args)
         esp_port = args.port or _get_default_serial_port(args)
         run_target(action, args, {'ESPBAUD': str(args.baud), 'ESPPORT': esp_port})
         run_target(action, args, {'ESPBAUD': str(args.baud), 'ESPPORT': esp_port})
 
 
-    def erase_flash(action, ctx, args):
+    def erase_flash(action: str, ctx: click.core.Context, args: PropertyDict) -> None:
         ensure_build_directory(args, ctx.info_name)
         ensure_build_directory(args, ctx.info_name)
         esptool_args = _get_esptool_args(args)
         esptool_args = _get_esptool_args(args)
         esptool_args += ['erase_flash']
         esptool_args += ['erase_flash']
         run_tool('esptool.py', esptool_args, args.build_dir)
         run_tool('esptool.py', esptool_args, args.build_dir)
 
 
-    def global_callback(ctx, global_args, tasks):
+    def global_callback(ctx: click.core.Context, global_args: Dict, tasks: PropertyDict) -> None:
         encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks])
         encryption = any([task.name in ('encrypted-flash', 'encrypted-app-flash') for task in tasks])
         if encryption:
         if encryption:
             for task in tasks:
             for task in tasks:
@@ -179,7 +181,7 @@ def action_extensions(base_actions, project_path):
                     task.action_args['encrypted'] = True
                     task.action_args['encrypted'] = True
                     break
                     break
 
 
-    def ota_targets(target_name, ctx, args):
+    def ota_targets(target_name: str, ctx: click.core.Context, args: PropertyDict) -> None:
         """
         """
         Execute the target build system to build target 'target_name'.
         Execute the target build system to build target 'target_name'.
         Additionally set global variables for baud and port.
         Additionally set global variables for baud and port.

+ 22 - 4
tools/idf_py_actions/tools.py

@@ -5,6 +5,7 @@ import re
 import subprocess
 import subprocess
 import sys
 import sys
 from io import open
 from io import open
+from typing import Any, List
 
 
 import click
 import click
 
 
@@ -340,12 +341,12 @@ class TargetChoice(click.Choice):
     - ignores hyphens
     - ignores hyphens
     - not case sensitive
     - not case sensitive
     """
     """
-    def __init__(self, choices):
+    def __init__(self, choices: List) -> None:
         super(TargetChoice, self).__init__(choices, case_sensitive=False)
         super(TargetChoice, self).__init__(choices, case_sensitive=False)
 
 
-    def convert(self, value, param, ctx):
-        def normalize(str):
-            return str.lower().replace('-', '')
+    def convert(self, value: Any, param: click.Parameter, ctx: click.Context) -> Any:
+        def normalize(string: str) -> str:
+            return string.lower().replace('-', '')
 
 
         saved_token_normalize_func = ctx.token_normalize_func
         saved_token_normalize_func = ctx.token_normalize_func
         ctx.token_normalize_func = normalize
         ctx.token_normalize_func = normalize
@@ -354,3 +355,20 @@ class TargetChoice(click.Choice):
             return super(TargetChoice, self).convert(value, param, ctx)
             return super(TargetChoice, self).convert(value, param, ctx)
         finally:
         finally:
             ctx.token_normalize_func = saved_token_normalize_func
             ctx.token_normalize_func = saved_token_normalize_func
+
+
+class PropertyDict(dict):
+    def __getattr__(self, name: str) -> Any:
+        if name in self:
+            return self[name]
+        else:
+            raise AttributeError("'PropertyDict' object has no attribute '%s'" % name)
+
+    def __setattr__(self, name: str, value: Any) -> None:
+        self[name] = value
+
+    def __delattr__(self, name: str) -> None:
+        if name in self:
+            del self[name]
+        else:
+            raise AttributeError("'PropertyDict' object has no attribute '%s'" % name)

+ 6 - 3
tools/idf_py_actions/uf2_ext.py

@@ -1,10 +1,13 @@
 # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 # SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
 # SPDX-License-Identifier: Apache-2.0
 # SPDX-License-Identifier: Apache-2.0
-from idf_py_actions.tools import ensure_build_directory, run_target
+from typing import Dict, List
 
 
+from click.core import Context
+from idf_py_actions.tools import PropertyDict, ensure_build_directory, run_target
 
 
-def action_extensions(base_actions, project_path):
-    def uf2_target(target_name, ctx, args):
+
+def action_extensions(base_actions: Dict, project_path: List) -> Dict:
+    def uf2_target(target_name: str, ctx: Context, args: PropertyDict) -> None:
         ensure_build_directory(args, ctx.info_name)
         ensure_build_directory(args, ctx.info_name)
         run_target(target_name, args)
         run_target(target_name, args)
 
 

+ 16 - 15
tools/mkuf2.py

@@ -11,11 +11,12 @@ import json
 import os
 import os
 import struct
 import struct
 from functools import partial
 from functools import partial
+from typing import Dict, List
 
 
 from future.utils import iteritems
 from future.utils import iteritems
 
 
 
 
-def round_up_int_div(n, d):
+def round_up_int_div(n: int, d: int) -> int:
     # equivalent to math.ceil(n / d)
     # equivalent to math.ceil(n / d)
     return (n + d - 1) // d
     return (n + d - 1) // d
 
 
@@ -32,23 +33,23 @@ class UF2Writer(object):
     UF2_FLAG_FAMILYID_PRESENT = 0x00002000
     UF2_FLAG_FAMILYID_PRESENT = 0x00002000
     UF2_FLAG_MD5_PRESENT = 0x00004000
     UF2_FLAG_MD5_PRESENT = 0x00004000
 
 
-    def __init__(self, chip_id, output_file, chunk_size):
+    def __init__(self, chip_id: int, output_file: os.PathLike, chunk_size: int) -> None:
         self.chip_id = chip_id
         self.chip_id = chip_id
         self.CHUNK_SIZE = self.UF2_DATA_SIZE - self.UF2_MD5_PART_SIZE if chunk_size is None else chunk_size
         self.CHUNK_SIZE = self.UF2_DATA_SIZE - self.UF2_MD5_PART_SIZE if chunk_size is None else chunk_size
         self.f = open(output_file, 'wb')
         self.f = open(output_file, 'wb')
 
 
-    def __enter__(self):
+    def __enter__(self) -> 'UF2Writer':
         return self
         return self
 
 
-    def __exit__(self, exc_type, exc_val, exc_tb):
+    def __exit__(self, exc_type: str, exc_val: int, exc_tb: List) -> None:
         if self.f:
         if self.f:
             self.f.close()
             self.f.close()
 
 
     @staticmethod
     @staticmethod
-    def _to_uint32(num):
+    def _to_uint32(num: int) -> bytes:
         return struct.pack('<I', num)
         return struct.pack('<I', num)
 
 
-    def _write_block(self, addr, chunk, len_chunk, block_no, blocks):
+    def _write_block(self, addr: int, chunk: bytes, len_chunk: int, block_no: int, blocks: int) -> None:
         assert len_chunk > 0
         assert len_chunk > 0
         assert len_chunk <= self.CHUNK_SIZE
         assert len_chunk <= self.CHUNK_SIZE
         assert block_no < blocks
         assert block_no < blocks
@@ -73,7 +74,7 @@ class UF2Writer(object):
         assert len(block) == self.UF2_BLOCK_SIZE
         assert len(block) == self.UF2_BLOCK_SIZE
         self.f.write(block)
         self.f.write(block)
 
 
-    def add_file(self, addr, f_path):
+    def add_file(self, addr: int, f_path: os.PathLike) -> None:
         blocks = round_up_int_div(os.path.getsize(f_path), self.CHUNK_SIZE)
         blocks = round_up_int_div(os.path.getsize(f_path), self.CHUNK_SIZE)
         with open(f_path, 'rb') as fin:
         with open(f_path, 'rb') as fin:
             a = addr
             a = addr
@@ -83,7 +84,7 @@ class UF2Writer(object):
                 a += len_chunk
                 a += len_chunk
 
 
 
 
-def action_write(args):
+def action_write(args: Dict) -> None:
     with UF2Writer(args['chip_id'], args['output_file'], args['chunk_size']) as writer:
     with UF2Writer(args['chip_id'], args['output_file'], args['chunk_size']) as writer:
         for addr, f in args['files']:
         for addr, f in args['files']:
             print('Adding {} at {:#x}'.format(f, addr))
             print('Adding {} at {:#x}'.format(f, addr))
@@ -91,19 +92,19 @@ def action_write(args):
     print('"{}" has been written.'.format(args['output_file']))
     print('"{}" has been written.'.format(args['output_file']))
 
 
 
 
-def main():
+def main() -> None:
     parser = argparse.ArgumentParser()
     parser = argparse.ArgumentParser()
 
 
-    def four_byte_aligned(integer):
+    def four_byte_aligned(integer: int) -> bool:
         return integer & 3 == 0
         return integer & 3 == 0
 
 
-    def parse_chunk_size(string):
+    def parse_chunk_size(string: str) -> int:
         num = int(string, 0)
         num = int(string, 0)
         if not four_byte_aligned(num):
         if not four_byte_aligned(num):
             raise argparse.ArgumentTypeError('Chunk size should be a 4-byte aligned number')
             raise argparse.ArgumentTypeError('Chunk size should be a 4-byte aligned number')
         return num
         return num
 
 
-    def parse_chip_id(string):
+    def parse_chip_id(string: str) -> int:
         num = int(string, 16)
         num = int(string, 16)
         if num < 0 or num > 0xFFFFFFFF:
         if num < 0 or num > 0xFFFFFFFF:
             raise argparse.ArgumentTypeError('Chip ID should be a 4-byte unsigned integer')
             raise argparse.ArgumentTypeError('Chip ID should be a 4-byte unsigned integer')
@@ -137,12 +138,12 @@ def main():
 
 
     args = parser.parse_args()
     args = parser.parse_args()
 
 
-    def check_file(file_name):
+    def check_file(file_name: str) -> str:
         if not os.path.isfile(file_name):
         if not os.path.isfile(file_name):
             raise RuntimeError('{} is not a regular file!'.format(file_name))
             raise RuntimeError('{} is not a regular file!'.format(file_name))
         return file_name
         return file_name
 
 
-    def parse_addr(string):
+    def parse_addr(string: str) -> int:
         num = int(string, 0)
         num = int(string, 0)
         if not four_byte_aligned(num):
         if not four_byte_aligned(num):
             raise RuntimeError('{} is not a 4-byte aligned valid address'.format(string))
             raise RuntimeError('{} is not a 4-byte aligned valid address'.format(string))
@@ -155,7 +156,7 @@ def main():
     if args.json:
     if args.json:
         json_dir = os.path.dirname(os.path.abspath(args.json))
         json_dir = os.path.dirname(os.path.abspath(args.json))
 
 
-        def process_json_file(path):
+        def process_json_file(path: str) -> str:
             '''
             '''
             The input path is relative to json_dir. This function makes it relative to the current working
             The input path is relative to json_dir. This function makes it relative to the current working
             directory.
             directory.