Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
pantsbuild.pants / core_tasks / explain_options_task.py
Size: Mime:
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import json

from colors import black, blue, cyan, green, magenta, red, white
from packaging.version import Version

from pants.option.options_fingerprinter import CoercingOptionEncoder
from pants.option.ranked_value import RankedValue
from pants.option.scope import GLOBAL_SCOPE
from pants.task.console_task import ConsoleTask
from pants.version import PANTS_SEMVER


class ExplainOptionsTask(ConsoleTask):
    """Display meta-information about options.

    This "meta-information" includes what values options have, and what values they *used* to have
    before they were overridden by a higher-rank value (eg, a HARDCODED value overridden by a CONFIG
    value and then a cli FLAG value).
    """

    _register_console_transitivity_option = False

    @classmethod
    def register_options(cls, register):
        super().register_options(register)
        register("--scope", help="Only show options in this scope. Use GLOBAL for global scope.")
        register("--name", help="Only show options with this name.")
        register(
            "--rank",
            choices=RankedValue.get_names(),
            help="Only show options with at least this importance.",
        )
        register(
            "--show-history",
            type=bool,
            help="Show the previous values options had before being overridden.",
        )
        register("--only-overridden", type=bool, help="Only show values that overrode defaults.")
        register(
            "--skip-inherited",
            type=bool,
            default=True,
            help="Do not show inherited options, unless their values differ from their parents.",
        )
        register(
            "--output-format",
            choices=["text", "json"],
            default="text",
            help="Specify the format options will be printed.",
        )
        register(
            "--transitive",
            type=bool,
            default=True,
            fingerprint=True,
            help="If True, use all targets in the build graph, else use only target roots.",
            removal_version="1.27.0.dev0",
            removal_hint="This option has no impact on the goal `options`.",
        )

    def _scope_filter(self, scope):
        pattern = self.get_options().scope
        return (
            not pattern
            or scope.startswith(pattern)
            or (pattern == "GLOBAL" and scope == GLOBAL_SCOPE)
        )

    def _option_filter(self, option):
        pattern = self.get_options().name
        if not pattern:
            return True
        pattern = pattern.replace("-", "_")
        return option == pattern

    def _rank_filter(self, rank):
        pattern = self.get_options().rank
        if not pattern:
            return True
        return rank >= RankedValue.get_rank_value(pattern)

    def _rank_color(self, rank):
        if not self.get_options().colors:
            return lambda x: x
        if rank == RankedValue.NONE:
            return white
        if rank == RankedValue.HARDCODED:
            return white
        if rank == RankedValue.ENVIRONMENT:
            return red
        if rank == RankedValue.CONFIG:
            return blue
        if rank == RankedValue.FLAG:
            return magenta
        return black

    def _format_scope(self, scope, option, no_color=False):
        if no_color:
            return "{scope}{option}".format(
                scope="{}.".format(scope) if scope else "", option=option,
            )
        scope_color = cyan if self.get_options().colors else lambda x: x
        option_color = blue if self.get_options().colors else lambda x: x
        return "{scope}{option}".format(
            scope=scope_color("{}.".format(scope) if scope else ""), option=option_color(option),
        )

    def _format_record(self, record):
        simple_rank = RankedValue.get_rank_name(record.rank)
        if self.is_json():
            return record.value, simple_rank
        elif self.is_text():
            simple_value = str(record.value)
            value_color = green if self.get_options().colors else lambda x: x
            formatted_value = value_color(simple_value)
            rank_color = self._rank_color(record.rank)
            formatted_rank = "(from {rank}{details})".format(
                rank=simple_rank,
                details=rank_color(" {}".format(record.details)) if record.details else "",
            )
            return "{value} {rank}".format(value=formatted_value, rank=formatted_rank,)

    def _show_history(self, history):
        for record in reversed(list(history)[:-1]):
            if record.rank > RankedValue.NONE:
                yield "  overrode {}".format(self._format_record(record))

    def _force_option_parsing(self):
        scopes = filter(self._scope_filter, list(self.context.options.known_scope_to_info.keys()))
        for scope in scopes:
            self.context.options.for_scope(scope)

    def _get_parent_scope_option(self, scope, name):
        if not scope:
            return None, None
        parent_scope = ""
        if "." in scope:
            parent_scope, _ = scope.rsplit(".", 1)
        options = self.context.options.for_scope(parent_scope)
        try:
            return parent_scope, options[name]
        except AttributeError:
            return None, None

    def is_json(self):
        return self.get_options().output_format == "json"

    def is_text(self):
        return self.get_options().output_format == "text"

    def console_output(self, targets):
        self._force_option_parsing()
        if self.is_json():
            output_map = {}
        for scope, options in sorted(self.context.options.tracker.option_history_by_scope.items()):
            if not self._scope_filter(scope):
                continue
            for option, history in sorted(options.items()):
                if not self._option_filter(option):
                    continue
                if not self._rank_filter(history.latest.rank):
                    continue
                if self.get_options().only_overridden and not history.was_overridden:
                    continue
                # Skip the option if it has already passed the deprecation period.
                if history.latest.deprecation_version and PANTS_SEMVER >= Version(
                    history.latest.deprecation_version
                ):
                    continue
                if self.get_options().skip_inherited:
                    parent_scope, parent_value = self._get_parent_scope_option(scope, option)
                    if parent_scope is not None and parent_value == history.latest.value:
                        continue
                if self.is_json():
                    value, rank_name = self._format_record(history.latest)
                    scope_key = self._format_scope(scope, option, True)
                    # We rely on the fact that option values are restricted to a set of types compatible with
                    # json. In particular, we expect dict, list, str, bool, int and float, and so do no
                    # processing here.
                    # TODO(John Sirois): The option parsing system currently lets options of unexpected types
                    # slide by, which can lead to un-overridable values and which would also blow up below in
                    # json encoding, fix options to restrict the allowed `type`s:
                    #   https://github.com/pantsbuild/pants/issues/4695
                    inner_map = dict(value=value, source=rank_name)
                    output_map[scope_key] = inner_map
                elif self.is_text():
                    yield "{} = {}".format(
                        self._format_scope(scope, option), self._format_record(history.latest)
                    )
                if self.get_options().show_history:
                    history_list = []
                    for line in self._show_history(history):
                        if self.is_text():
                            yield line
                        elif self.is_json():
                            history_list.append(line.strip())
                    if self.is_json():
                        inner_map["history"] = history_list
        if self.is_json():
            yield json.dumps(output_map, indent=2, sort_keys=True, cls=CoercingOptionEncoder)