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    
Size: Mime:
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).


from pants.base.exceptions import TaskError
from pants.java.jar.jar_dependency import JarDependency
from pants.task.console_task import ConsoleTask


class Depmap(ConsoleTask):
    """Depict the target's dependencies.

    Generates either a textual dependency tree or a graphviz digraph dot file for the dependency set
    of a target.
    """

    class SourceRootTypes:
        """Defines SourceRoot Types Constants."""

        SOURCE = "SOURCE"  # Source Target
        TEST = "TEST"  # Test Target
        SOURCE_GENERATED = "SOURCE_GENERATED"  # Code Gen Source Targets
        EXCLUDED = "EXCLUDED"  # Excluded Target
        RESOURCE = "RESOURCE"  # Resource belonging to Source Target
        TEST_RESOURCE = "TEST_RESOURCE"  # Resource belonging to Test Target

    _register_console_transitivity_option = False

    @classmethod
    def register_options(cls, register):
        super().register_options(register)
        register(
            "--internal-only",
            type=bool,
            help="Specifies that only internal dependencies should be included in the graph "
            "output (no external jars).",
        )
        register(
            "--external-only",
            type=bool,
            help="Specifies that only external dependencies should be included in the graph "
            "output (only external jars).",
        )
        register(
            "--minimal",
            type=bool,
            help="For a textual dependency tree, only prints a dependency the 1st "
            "time it is encountered. This is a no-op for --graph.",
        )
        register(
            "--graph",
            type=bool,
            help="Specifies the internal dependency graph should be output in the dot digraph "
            "format.",
        )
        register(
            "--tree",
            type=bool,
            help="For text output, show an ascii tree to help visually line up indentions.",
        )
        register("--show-types", type=bool, help="Show types of objects in depmap --graph.")
        register(
            "--separator",
            default="-",
            help="Specifies the separator to use between the org/name/rev components of a "
            "dependency's fully qualified name.",
        )
        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 `depmap`.",
        )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.is_internal_only = self.get_options().internal_only
        self.is_external_only = self.get_options().external_only

        if self.is_internal_only and self.is_external_only:
            raise TaskError("At most one of --internal-only or --external-only can be selected.")

        self.is_minimal = self.get_options().minimal
        self.is_graph = self.get_options().graph
        self.should_tree = self.get_options().tree
        self.show_types = self.get_options().show_types
        self.separator = self.get_options().separator
        self.target_aliases_map = None

    def console_output(self, targets):
        if len(self.context.target_roots) == 0:
            raise TaskError("One or more target addresses are required.")

        for target in self.context.target_roots:
            out = (
                self._output_digraph(target)
                if self.is_graph
                else self._output_dependency_tree(target)
            )
            for line in out:
                yield line

    def _dep_id(self, dependency):
        """Returns a tuple of dependency_id, is_internal_dep."""
        params = dict(sep=self.separator)

        if isinstance(dependency, JarDependency):
            # TODO(kwilson): handle 'classifier' and 'type'.
            params.update(org=dependency.org, name=dependency.name, rev=dependency.rev)
            is_internal_dep = False
        else:
            params.update(org="internal", name=dependency.id)
            is_internal_dep = True

        return (
            ("{org}{sep}{name}{sep}{rev}" if params.get("rev") else "{org}{sep}{name}").format(
                **params
            ),
            is_internal_dep,
        )

    def _enumerate_visible_deps(self, dep, predicate):
        # We present the dependencies out of classpath order and instead in alphabetized internal deps,
        # then alphabetized external deps order for ease in scanning output.
        dependencies = sorted(x for x in getattr(dep, "dependencies", []))
        if not self.is_internal_only:
            dependencies.extend(
                sorted(
                    (x for x in getattr(dep, "jar_dependencies", [])),
                    key=lambda x: (x.org, x.name, x.rev, x.classifier),
                )
            )
        for inner_dep in dependencies:
            dep_id, internal = self._dep_id(inner_dep)
            if predicate(internal):
                yield inner_dep

    def output_candidate(self, internal):
        return (
            (not self.is_internal_only and not self.is_external_only)
            or (self.is_internal_only and internal)
            or (self.is_external_only and not internal)
        )

    def _output_dependency_tree(self, target):
        """Plain-text depmap output handler."""

        def make_line(dep, indent, is_dupe=False):
            indent_join, indent_chars = ("--", "  |") if self.should_tree else ("", "  ")
            dupe_char = "*" if is_dupe else ""
            return "".join((indent * indent_chars, indent_join, dupe_char, dep))

        def output_deps(dep, indent, outputted, stack):
            dep_id, internal = self._dep_id(dep)

            if self.is_minimal and dep_id in outputted:
                return

            if self.output_candidate(internal):
                yield make_line(
                    dep_id, 0 if self.is_external_only else indent, is_dupe=dep_id in outputted
                )
                outputted.add(dep_id)

            for sub_dep in self._enumerate_visible_deps(dep, self.output_candidate):
                for item in output_deps(sub_dep, indent + 1, outputted, stack + [(dep_id, indent)]):
                    yield item

        for item in output_deps(target, 0, set(), []):
            yield item

    def _output_digraph(self, target):
        """Graphviz format depmap output handler."""
        color_by_type = {}

        def maybe_add_type(dep, dep_id):
            """Add a class type to a dependency id if --show-types is passed."""
            return dep_id if not self.show_types else "\\n".join((dep_id, dep.__class__.__name__))

        def make_node(dep, dep_id, internal):
            line_fmt = '  "{id}" [style=filled, fillcolor={color}{internal}];'
            int_shape = ", shape=ellipse" if not internal else ""

            dep_class = dep.__class__.__name__
            if dep_class not in color_by_type:
                color_by_type[dep_class] = len(color_by_type.keys()) + 1

            return line_fmt.format(id=dep_id, internal=int_shape, color=color_by_type[dep_class])

        def make_edge(from_dep_id, to_dep_id, internal):
            style = " [style=dashed]" if not internal else ""
            return '  "{}" -> "{}"{};'.format(from_dep_id, to_dep_id, style)

        def output_deps(dep, parent, parent_id, outputted):
            dep_id, internal = self._dep_id(dep)

            if dep_id not in outputted:
                yield make_node(dep, maybe_add_type(dep, dep_id), internal)
                outputted.add(dep_id)

                for sub_dep in self._enumerate_visible_deps(dep, self.output_candidate):
                    for item in output_deps(sub_dep, dep, dep_id, outputted):
                        yield item

            if parent:
                edge_id = (parent_id, dep_id)
                if edge_id not in outputted:
                    yield make_edge(
                        maybe_add_type(parent, parent_id), maybe_add_type(dep, dep_id), internal
                    )
                    outputted.add(edge_id)

        yield 'digraph "{}" {{'.format(target.id)
        yield "  node [shape=rectangle, colorscheme=set312;];"
        yield "  rankdir=LR;"
        for line in output_deps(target, parent=None, parent_id=None, outputted=set()):
            yield line
        yield "}"