Преглед изворни кода

idf.py: Add support for deprecation of command/options

Sergei Silnov пре 6 година
родитељ
комит
58418c790a
3 измењених фајлова са 164 додато и 2 уклоњено
  1. 12 0
      tools/ci/test_build_system_cmake.sh
  2. 82 2
      tools/idf.py
  3. 70 0
      tools/test_idf_py/idf_ext.py

+ 12 - 0
tools/ci/test_build_system_cmake.sh

@@ -527,6 +527,18 @@ endmenu\n" >> ${IDF_PATH}/Kconfig;
     rm -rf CMakeLists.txt
     mv CMakeLists.txt.bak CMakeLists.txt
     rm -rf CMakeLists.txt.bak
+    
+    print_status "Print all required argument deprecation warnings"
+    idf.py -C${IDF_PATH}/tools/test_idf_py --test-0=a --test-1=b --test-2=c --test-3=d test-0 --test-sub-0=sa --test-sub-1=sb ta test-1 > out.txt
+    ! grep -e '"test-0" is deprecated' -e '"test_0" is deprecated' out.txt || failure "Deprecation warnings are displayed for non-deprecated option/command"       
+    grep -e 'Warning: Option "test_sub_1" is deprecated and will be removed in future versions.' \
+        -e 'Warning: Command "test-1" is deprecated and will be removed in future versions. Please use alternative command.' \
+        -e 'Warning: Option "test_1" is deprecated and will be removed in future versions.' \
+        -e 'Warning: Option "test_2" is deprecated and will be removed in future versions. Please update your parameters.' \
+        -e 'Warning: Option "test_3" is deprecated and will be removed in future versions.' \
+        out.txt \
+        || failure "Deprecation warnings are not displayed"
+    rm out.txt
 
     print_status "All tests completed"
     if [ -n "${FAILURES}" ]; then

+ 82 - 2
tools/idf.py

@@ -545,6 +545,46 @@ def init_cli():
     # Click is imported here to run it after check_environment()
     import click
 
+    class DeprecationMessage(object):
+        """Construct deprecation notice for help messages"""
+
+        def __init__(self, deprecated=False):
+            self.deprecated = deprecated
+            self.since = None
+            self.removed = None
+            self.custom_message = ""
+
+            if isinstance(deprecated, dict):
+                self.custom_message = deprecated.get("message", "")
+                self.since = deprecated.get("since", None)
+                self.removed = deprecated.get("removed", None)
+            elif isinstance(deprecated, str):
+                self.custom_message = deprecated
+
+        def full_message(self, type="Option"):
+            return "%s is deprecated %sand will be removed in%s.%s" % (
+                type,
+                "since %s " % self.since if self.since else "",
+                " %s" % self.removed if self.removed else " future versions",
+                " %s" % self.custom_message if self.custom_message else ""
+            )
+
+        def help(self, text, type="Option", separator=" "):
+            text = text or ""
+            return self.full_message(type) + separator + text if self.deprecated else text
+
+        def short_help(self, text):
+            text = text or ""
+            return ("Deprecated! " + text) if self.deprecated else text
+
+    def print_deprecation_warning(ctx):
+        """Prints deprectation warnings for arguments in given context"""
+        for option in ctx.command.params:
+            default = () if option.multiple else option.default
+            if isinstance(option, Option) and option.deprecated and ctx.params[option.name] != default:
+                print("Warning: %s" % DeprecationMessage(option.deprecated).
+                      full_message('Option "%s"' % option.name))
+
     class Task(object):
         def __init__(
             self, callback, name, aliases, dependencies, order_dependencies, action_args
@@ -567,6 +607,7 @@ def init_cli():
             self,
             name=None,
             aliases=None,
+            deprecated=False,
             dependencies=None,
             order_dependencies=None,
             **kwargs
@@ -574,6 +615,7 @@ def init_cli():
             super(Action, self).__init__(name, **kwargs)
 
             self.name = self.name or self.callback.__name__
+            self.deprecated = deprecated
 
             if aliases is None:
                 aliases = []
@@ -592,6 +634,11 @@ def init_cli():
             # Show first line of help if short help is missing
             self.short_help = self.short_help or self.help.split("\n")[0]
 
+            if deprecated:
+                deprecation = DeprecationMessage(deprecated)
+                self.short_help = deprecation.short_help(self.short_help)
+                self.help = deprecation.help(self.help, type="Command", separator="\n")
+
             # Add aliases to help string
             if aliases:
                 aliases_help = "Aliases: %s." % ", ".join(aliases)
@@ -614,8 +661,21 @@ def init_cli():
 
                 self.callback = wrapped_callback
 
+        def invoke(self, ctx):
+            if self.deprecated:
+                print("Warning: %s" % DeprecationMessage(self.deprecated).full_message('Command "%s"' % self.name))
+                self.deprecated = False  # disable Click's built-in deprecation handling
+
+            # Print warnings for options
+            print_deprecation_warning(ctx)
+            return super(Action, self).invoke(ctx)
+
     class Argument(click.Argument):
-        """Positional argument"""
+        """
+        Positional argument
+
+        names - alias of 'param_decls'
+        """
 
         def __init__(self, **kwargs):
             names = kwargs.pop("names")
@@ -656,12 +716,28 @@ def init_cli():
     class Option(click.Option):
         """Option that knows whether it should be global"""
 
-        def __init__(self, scope=None, **kwargs):
+        def __init__(self, scope=None, deprecated=False, **kwargs):
+            """
+            Keyword arguments additional to Click's Option class:
+
+            names - alias of 'param_decls'
+            deprecated - marks option as deprecated. May be boolean, string (with custom deprecation message)
+            or dict with optional keys:
+                since: version of deprecation
+                removed: version when option will be removed
+                custom_message:  Additional text to deprecation warning
+            """
+
             kwargs["param_decls"] = kwargs.pop("names")
             super(Option, self).__init__(**kwargs)
 
+            self.deprecated = deprecated
             self.scope = Scope(scope)
 
+            if deprecated:
+                deprecation = DeprecationMessage(deprecated)
+                self.help = deprecation.help(self.help)
+
             if self.scope.is_global:
                 self.help += " This option can be used at most once either globally, or for one subcommand."
 
@@ -823,6 +899,7 @@ def init_cli():
             for task in tasks:
                 for key in list(task.action_args):
                     option = next((o for o in ctx.command.params if o.name == key), None)
+
                     if option and (option.scope.is_global or option.scope.is_shared):
                         local_value = task.action_args.pop(key)
                         global_value = global_args[key]
@@ -836,6 +913,9 @@ def init_cli():
                         if local_value != default:
                             global_args[key] = local_value
 
+            # Show warnings about global arguments
+            print_deprecation_warning(ctx)
+
             # Validate global arguments
             for action_callback in ctx.command.global_action_callbacks:
                 action_callback(ctx, global_args, tasks)

+ 70 - 0
tools/test_idf_py/idf_ext.py

@@ -0,0 +1,70 @@
+def action_extensions(base_actions, project_path=None):
+    def echo(name, *args, **kwargs):
+        print(name, args, kwargs)
+
+    # Add global options
+    extensions = {
+        "global_options": [
+            {
+                "names": ["--test-0"],
+                "help": "Non-deprecated option.",
+                "deprecated": False
+            },
+            {
+                "names": ["--test-1"],
+                "help": "Deprecated option 1.",
+                "deprecated": True
+            },
+            {
+                "names": ["--test-2"],
+                "help": "Deprecated option 2.",
+                "deprecated": "Please update your parameters."
+            },
+            {
+                "names": ["--test-3"],
+                "help": "Deprecated option 3.",
+                "deprecated": {
+                    "custom_message": "Please update your parameters."
+                }
+            },
+            {
+                "names": ["--test-4"],
+                "help": "Deprecated option 3.",
+                "deprecated": {
+                    "since": "v4.0",
+                    "removed": "v5.0"
+                }
+            },
+        ],
+        "actions": {
+            "test-0": {
+                "callback":
+                echo,
+                "help":
+                "Non-deprecated command 0",
+                "options": [
+                    {
+                        "names": ["--test-sub-0"],
+                        "help": "Non-deprecated subcommand option 0",
+                        "default": None,
+                    },
+                    {
+                        "names": ["--test-sub-1"],
+                        "help": "Deprecated subcommand option 1",
+                        "default": None,
+                        "deprecated": True
+                    },
+                ],
+                "arguments": [{
+                    "names": ["test-arg-0"],
+                }],
+            },
+            "test-1": {
+                "callback": echo,
+                "help": "Deprecated command 1",
+                "deprecated": "Please use alternative command."
+            },
+        },
+    }
+
+    return extensions