def main(self, *, context):  # noqa: D102
        args = context.args

        descriptors = get_package_descriptors(args)

        # always perform topological order for the select package extensions
        decorators = topological_order_packages(descriptors,
                                                recursive_categories=('run', ))

        select_package_decorators(args, decorators)

        if not args.topological_order:
            decorators = sorted(decorators, key=lambda d: d.descriptor.name)
        lines = []
        for decorator in decorators:
            if not decorator.selected:
                continue
            pkg = decorator.descriptor
            if args.names_only:
                lines.append(pkg.name)
            elif args.paths_only:
                lines.append(str(pkg.path))
            else:
                lines.append(pkg.name + '\t' + str(pkg.path) +
                             '\t(%s)' % pkg.type)
        if not args.topological_order:
            # output names and / or paths in alphabetical order
            lines.sort()

        for line in lines:
            print(line)
    def main(self, *, context):  # noqa: D102
        descriptors = get_package_descriptors(context.args,
                                              additional_argument_names=['*'])
        decorators = get_decorators(descriptors)
        add_recursive_dependencies(decorators, recursive_categories=('run', ))
        select_package_decorators(context.args, decorators)

        if context.args.package_names:
            all_package_names = {d.descriptor.name for d in decorators}
            # warn about passed package names which are unknown
            for pkg_name in context.args.package_names:
                if pkg_name not in all_package_names:
                    print("Package '{pkg_name}' not found".format_map(
                        locals()),
                          file=sys.stderr)
            # filter decorators using passed package names
            decorators = [
                d for d in decorators
                if d.descriptor.name in context.args.package_names
            ]
            if not decorators:
                return 1
        if not decorators:
            return 'No packages found'

        for decorator in sorted(decorators, key=lambda d: d.descriptor.name):
            if not decorator.selected:
                continue
            pkg = decorator.descriptor
            print('path:', pkg.path)
            print('  type:', pkg.type)
            print('  name:', pkg.name)
            if pkg.dependencies:
                print('  dependencies:')
                for category in sorted(pkg.dependencies.keys()):
                    print('    {category}:'.format_map(locals()),
                          ' '.join(sorted(pkg.dependencies[category])))
            if pkg.hooks:
                print('  hooks:', ' '.join(pkg.hooks))
            if pkg.metadata:
                print('  metadata:')
                for key in sorted(pkg.metadata.keys()):
                    value = pkg.metadata[key]
                    print('    {key}: {value}'.format_map(locals()))
Exemple #3
0
    def _get_gcc_packages(context, additional_argument_names=None):
        descriptors = get_package_descriptors(
            context.args, additional_argument_names=additional_argument_names)

        # always perform topological order for the select package extensions
        decorators = topological_order_packages(descriptors,
                                                recursive_categories=('run', ))

        select_package_decorators(context.args, decorators)

        gcc_pkgs = []
        for decorator in decorators:
            if not decorator.selected:
                continue
            pkg = decorator.descriptor
            if pkg.type in ['ros.ament_cmake', 'ros.cmake', 'cmake']:
                gcc_pkgs.append(pkg)
            else:
                logger.info("Specified package {} is not a gcc package. Not "
                            "collecting coverage".format(pkg.name))
        return gcc_pkgs
Exemple #4
0
 def _get_coveragepy_packages(context, additional_argument_names=None):
     """Get packages that could have coverage.py results."""
     descriptors = get_package_descriptors(
         context.args,
         additional_argument_names=additional_argument_names,
     )
     decorators = topological_order_packages(descriptors,
                                             recursive_categories=('run', ))
     select_package_decorators(context.args, decorators)
     coveragepy_pkgs = []
     for decorator in decorators:
         if not decorator.selected:
             continue
         pkg = decorator.descriptor
         if pkg.type in ['ros.ament_cmake', 'ros.ament_python']:
             coveragepy_pkgs.append(pkg)
         else:
             logger.info(
                 "Specified package '{pkg.name}' is not a coverage.py-compatible "
                 'package. Not collecting coverage information.'.format_map(
                     locals()))
     return coveragepy_pkgs
Exemple #5
0
def test_select_package_decorators():
    args = Mock()
    deco1 = Mock()
    deco1.selected = True
    deco2 = Mock()
    deco2.selected = True
    decos = [deco1, deco2]

    with EntryPointContext(extension1=Extension1, extension2=Extension2):
        extensions = get_package_selection_extensions()

        # raise exception
        extensions['extension2'].select_packages = Mock(return_value=None)
        with patch('colcon_core.package_selection.logger.error') as error:
            select_package_decorators(args, decos)
        # the raised exception is catched and results in an error message
        assert error.call_count == 1
        assert len(error.call_args[0]) == 1
        assert error.call_args[0][0].startswith(
            "Exception in package selection extension 'extension1': \n")

        # invalid return value
        extensions['extension1'].select_packages = Mock(return_value=True)
        with patch('colcon_core.package_selection.logger.error') as error:
            select_package_decorators(args, decos)
        # the raised assertion is catched and results in an error message
        assert error.call_count == 1
        assert len(error.call_args[0]) == 1
        assert error.call_args[0][0].startswith(
            "Exception in package selection extension 'extension1': "
            'select_packages() should return None\n')

        # select some packages
        extensions['extension1'].select_packages = Mock(
            side_effect=select_some_packages)
        select_package_decorators(args, decos)
        assert not deco1.selected
        assert deco2.selected
    def main(self, *, context):  # noqa: D102
        args = context.args
        if args.topological_graph or args.topological_graph_dot:
            args.topological_order = True

        descriptors = get_package_descriptors(args)

        # always perform topological order for the select package extensions
        decorators = topological_order_packages(descriptors,
                                                recursive_categories=('run', ))

        select_package_decorators(args, decorators)

        if args.topological_graph:
            if args.topological_graph_legend:
                print('+ marks when the package in this row can be processed')
                print('* marks a direct dependency '
                      'from the package indicated by the + in the same column '
                      'to the package in this row')
                print('. marks a transitive dependency')
                print()

            # draw dependency graph in ASCII
            shown_decorators = list(filter(lambda d: d.selected, decorators))
            max_length = max(
                [len(m.descriptor.name) for m in shown_decorators] + [0])
            lines = [
                m.descriptor.name.ljust(max_length + 2)
                for m in shown_decorators
            ]
            depends = [
                m.descriptor.get_dependencies() for m in shown_decorators
            ]
            rec_depends = [
                m.descriptor.get_recursive_dependencies(
                    [d.descriptor for d in decorators],
                    recursive_categories=('run', )) for m in shown_decorators
            ]

            empty_cells = 0
            for i, decorator in enumerate(shown_decorators):
                for j in range(len(lines)):
                    if j == i:
                        # package i is being processed
                        lines[j] += '+'
                    elif shown_decorators[j].descriptor.name in depends[i]:
                        # package i directly depends on package j
                        lines[j] += '*'
                    elif shown_decorators[j].descriptor.name in rec_depends[i]:
                        # package i recursively depends on package j
                        lines[j] += '.'
                    else:
                        # package i doesn't depend on package j
                        lines[j] += ' '
                        empty_cells += 1
            if args.topological_graph_density:
                empty_fraction = \
                    empty_cells / (len(lines) * (len(lines) - 1)) \
                    if len(lines) > 1 else 1.0
                # normalize to 200% since half of the matrix should be empty
                density_percentage = 200.0 * (1.0 - empty_fraction)
                print('dependency density %.2f %%' % density_percentage)
                print()

        elif args.topological_graph_dot:
            lines = ['digraph graphname {']

            decorators_by_name = defaultdict(set)
            for deco in decorators:
                decorators_by_name[deco.descriptor.name].add(deco)

            selected_pkg_names = [
                m.descriptor.name for m in decorators if m.selected
            ]
            has_duplicate_names = \
                len(selected_pkg_names) != len(set(selected_pkg_names))
            selected_pkg_names = set(selected_pkg_names)

            # collect selected package descriptors and their parent path
            nodes = OrderedDict()
            for deco in reversed(decorators):
                if not deco.selected:
                    continue
                nodes[deco.descriptor] = Path(deco.descriptor.path).parent

            # collect direct dependencies
            direct_edges = defaultdict(set)
            for deco in reversed(decorators):
                if not deco.selected:
                    continue
                # iterate over dependency categories
                for category, deps in deco.descriptor.dependencies.items():
                    # iterate over dependencies
                    for dep in deps:
                        if dep not in selected_pkg_names:
                            continue
                        # store the category of each dependency
                        # use the decorator descriptor
                        # since there might be packages with the same name
                        direct_edges[(deco.descriptor, dep)].add(category)

            # collect indirect dependencies
            indirect_edges = defaultdict(set)
            for deco in reversed(decorators):
                if not deco.selected:
                    continue
                # iterate over dependency categories
                for category, deps in deco.descriptor.dependencies.items():
                    # iterate over dependencies
                    for dep in deps:
                        # ignore direct dependencies
                        if dep in selected_pkg_names:
                            continue
                        # ignore unknown dependencies
                        if dep not in decorators_by_name.keys():
                            continue
                        # iterate over recursive dependencies
                        for rdep in itertools.chain.from_iterable(
                                d.recursive_dependencies
                                for d in decorators_by_name[dep]):
                            if rdep not in selected_pkg_names:
                                continue
                            # skip edges which are redundant to direct edges
                            if (deco.descriptor, rdep) in direct_edges:
                                continue
                            indirect_edges[(deco.descriptor,
                                            rdep)].add(category)

            try:
                # HACK Python 3.5 can't handle Path objects
                common_path = os.path.commonpath(
                    [str(p) for p in nodes.values()])
            except ValueError:
                common_path = None

            def get_node_data(descriptor):
                nonlocal has_duplicate_names
                if not has_duplicate_names:
                    # use name where possible so the dot code is easy to read
                    return descriptor.name, ''
                # otherwise append the descriptor id to make each node unique
                descriptor_id = id(descriptor)
                return (
                    '{descriptor.name}_{descriptor_id}'.format_map(locals()),
                    ' [label = "{descriptor.name}"]'.format_map(locals()),
                )

            if not args.topological_graph_dot_cluster or common_path is None:
                # output nodes
                for desc in nodes.keys():
                    node_name, attributes = get_node_data(desc)
                    lines.append('  "{node_name}"{attributes};'.format_map(
                        locals()))
            else:
                # output clusters
                clusters = defaultdict(set)
                for desc, path in nodes.items():
                    clusters[path.relative_to(common_path)].add(desc)
                for i, cluster in zip(range(len(clusters)), clusters.items()):
                    path, descs = cluster
                    if path.name:
                        # wrap cluster in subgraph
                        lines.append('  subgraph cluster_{i} {{'.format_map(
                            locals()))
                        lines.append('    label = "{path}";'.format_map(
                            locals()))
                        indent = '    '
                    else:
                        indent = '  '
                    for desc in descs:
                        node_name, attributes = get_node_data(desc)
                        lines.append(
                            '{indent}"{node_name}"{attributes};'.format_map(
                                locals()))
                    if path.name:
                        lines.append('  }')

            # output edges
            color_mapping = OrderedDict((
                ('build', 'blue'),
                ('run', 'red'),
                ('test', 'tan'),
            ))
            for style, edges in zip(
                ('', ', style="dashed"'),
                (direct_edges, indirect_edges),
            ):
                for (desc_start, node_end), categories in edges.items():
                    colors = ':'.join([
                        color for category, color in color_mapping.items()
                        if category in categories
                    ])
                    start_name, _ = get_node_data(desc_start)
                    for deco in decorators_by_name[node_end]:
                        end_name, _ = get_node_data(deco.descriptor)
                        lines.append('  "{start_name}" -> "{end_name}" '
                                     '[color="{colors}"{style}];'.format_map(
                                         locals()))

            lines.append('}')

        else:
            if not args.topological_order:
                decorators = sorted(decorators,
                                    key=lambda d: d.descriptor.name)
            lines = []
            for decorator in decorators:
                if not decorator.selected:
                    continue
                pkg = decorator.descriptor
                if args.names_only:
                    lines.append(pkg.name)
                elif args.paths_only:
                    lines.append(str(pkg.path))
                else:
                    lines.append(pkg.name + '\t' + str(pkg.path) +
                                 '\t(%s)' % pkg.type)
            if not args.topological_order:
                # output names and / or paths in alphabetical order
                lines.sort()

        for line in lines:
            print(line)
    def main(self, *, context):  # noqa: D102
        args = context.args

        descriptors = get_package_descriptors(args)

        decorators = topological_order_packages(descriptors,
                                                recursive_categories=('run', ))

        select_package_decorators(args, decorators)

        if not args.dot:
            if args.legend:
                print('+ marks when the package in this row can be processed')
                print('* marks a direct dependency '
                      'from the package indicated by the + in the same column '
                      'to the package in this row')
                print('. marks a transitive dependency')
                print()

            # draw dependency graph in ASCII
            shown_decorators = list(filter(lambda d: d.selected, decorators))
            max_length = max(
                [len(m.descriptor.name) for m in shown_decorators] + [0])
            lines = [
                m.descriptor.name.ljust(max_length + 2)
                for m in shown_decorators
            ]
            depends = [
                m.descriptor.get_dependencies() for m in shown_decorators
            ]
            rec_depends = [
                m.descriptor.get_recursive_dependencies(
                    [d.descriptor for d in decorators],
                    recursive_categories=('run', )) for m in shown_decorators
            ]

            empty_cells = 0
            for i, decorator in enumerate(shown_decorators):
                for j in range(len(lines)):
                    if j == i:
                        # package i is being processed
                        lines[j] += '+'
                    elif shown_decorators[j].descriptor.name in depends[i]:
                        # package i directly depends on package j
                        lines[j] += '*'
                    elif shown_decorators[j].descriptor.name in rec_depends[i]:
                        # package i recursively depends on package j
                        lines[j] += '.'
                    else:
                        # package i doesn't depend on package j
                        lines[j] += ' '
                        empty_cells += 1
            if args.density:
                empty_fraction = \
                    empty_cells / (len(lines) * (len(lines) - 1)) \
                    if len(lines) > 1 else 1.0
                # normalize to 200% since half of the matrix should be empty
                density_percentage = 200.0 * (1.0 - empty_fraction)
                print('dependency density %.2f %%' % density_percentage)
                print()

        else:  # --dot
            lines = ['digraph graphname {']

            decorators_by_name = defaultdict(set)
            for deco in decorators:
                decorators_by_name[deco.descriptor.name].add(deco)

            selected_pkg_names = [
                m.descriptor.name for m in decorators
                if m.selected or args.dot_include_skipped
            ]
            has_duplicate_names = \
                len(selected_pkg_names) != len(set(selected_pkg_names))
            selected_pkg_names = set(selected_pkg_names)

            # collect selected package decorators and their parent path
            nodes = OrderedDict()
            for deco in reversed(decorators):
                if deco.selected or args.dot_include_skipped:
                    nodes[deco] = Path(deco.descriptor.path).parent

            # collect direct dependencies
            direct_edges = defaultdict(set)
            for deco in reversed(decorators):
                if (not deco.selected and not args.dot_include_skipped):
                    continue
                # iterate over dependency categories
                for category, deps in deco.descriptor.dependencies.items():
                    # iterate over dependencies
                    for dep in deps:
                        if dep not in selected_pkg_names:
                            continue
                        # store the category of each dependency
                        # use the decorator
                        # since there might be packages with the same name
                        direct_edges[(deco, dep)].add(category)

            # collect indirect dependencies
            indirect_edges = defaultdict(set)
            for deco in reversed(decorators):
                if not deco.selected:
                    continue
                # iterate over dependency categories
                for category, deps in deco.descriptor.dependencies.items():
                    # iterate over dependencies
                    for dep in deps:
                        # ignore direct dependencies
                        if dep in selected_pkg_names:
                            continue
                        # ignore unknown dependencies
                        if dep not in decorators_by_name.keys():
                            continue
                        # iterate over recursive dependencies
                        for rdep in itertools.chain.from_iterable(
                                d.recursive_dependencies
                                for d in decorators_by_name[dep]):
                            if rdep not in selected_pkg_names:
                                continue
                            # skip edges which are redundant to direct edges
                            if (deco, rdep) in direct_edges:
                                continue
                            indirect_edges[(deco, rdep)].add(category)

            try:
                # HACK Python 3.5 can't handle Path objects
                common_path = os.path.commonpath(
                    [str(p) for p in nodes.values()])
            except ValueError:
                common_path = None

            def get_node_data(decorator):
                nonlocal args
                nonlocal has_duplicate_names
                if not has_duplicate_names:
                    # use name where possible so the dot code is easy to read
                    return decorator.descriptor.name, \
                        '' if (
                            decorator.selected or
                            not args.dot_include_skipped
                        ) else '[color = "gray" fontcolor = "gray"]'
                # otherwise append the descriptor id to make each node unique
                descriptor_id = id(decorator.descriptor)
                return (
                    '{decorator.descriptor.name}_{descriptor_id}'.format_map(
                        locals()),
                    ' [label = "{decorator.descriptor.name}"]'.format_map(
                        locals()),
                )

            if not args.dot_cluster or common_path is None:
                # output nodes
                for deco in nodes.keys():
                    if (not deco.selected and not args.dot_include_skipped):
                        continue
                    node_name, attributes = get_node_data(deco)
                    lines.append('  "{node_name}"{attributes};'.format_map(
                        locals()))
            else:
                # output clusters
                clusters = defaultdict(set)
                for deco, path in nodes.items():
                    clusters[path.relative_to(common_path)].add(deco)
                for i, cluster in zip(range(len(clusters)), clusters.items()):
                    path, decos = cluster
                    if path.name:
                        # wrap cluster in subgraph
                        lines.append('  subgraph cluster_{i} {{'.format_map(
                            locals()))
                        lines.append('    label = "{path}";'.format_map(
                            locals()))
                        indent = '    '
                    else:
                        indent = '  '
                    for deco in decos:
                        node_name, attributes = get_node_data(deco)
                        lines.append(
                            '{indent}"{node_name}"{attributes};'.format_map(
                                locals()))
                    if path.name:
                        lines.append('  }')

            # output edges
            color_mapping = OrderedDict((
                ('build', '#0000ff'),  # blue
                ('run', '#ff0000'),  # red
                ('test', '#d2b48c'),  # tan
            ))
            for style, edges in zip(
                ('', ', style="dashed"'),
                (direct_edges, indirect_edges),
            ):
                for (deco_start, node_end), categories in edges.items():
                    start_name, _ = get_node_data(deco_start)
                    for deco in decorators_by_name[node_end]:
                        end_name, _ = get_node_data(deco)
                        edge_alpha = '' \
                            if deco_start.selected and deco.selected else '77'
                        colors = ':'.join([
                            color + edge_alpha
                            for category, color in color_mapping.items()
                            if category in categories
                        ])
                        lines.append('  "{start_name}" -> "{end_name}" '
                                     '[color="{colors}"{style}];'.format_map(
                                         locals()))

            if args.legend:
                lines.append('  subgraph cluster_legend {')
                lines.append('    color=gray')
                lines.append('    label="Legend";')
                lines.append('    margin=0;')
                # invisible nodes between the dependency edges
                lines.append('    node [label="", shape=none];')

                previous_node = '_legend_first'
                # an edge for each dependency type
                for dependency_type, color in color_mapping.items():
                    next_node = '_legend_' + dependency_type
                    lines.append(
                        '    {previous_node} -> {next_node} '
                        '[label="{dependency_type} dep.", color="{color}"];'.
                        format_map(locals()))
                    previous_node = next_node
                lines.append(
                    '    {previous_node} -> _legend_last '
                    '[label="indirect dep.", style="dashed"];'.format_map(
                        locals()))

                # layout all legend nodes on the same rank
                lines.append('    {')
                lines.append('      rank=same;')
                lines.append('      _legend_first;')
                for dependency_type in color_mapping.keys():
                    lines.append('      _legend_{dependency_type};'.format_map(
                        locals()))
                lines.append('      _legend_last;')
                lines.append('    }')

                lines.append('  }')

            lines.append('}')

        for line in lines:
            print(line)