Пример #1
0
    def get_all_help_info(
        cls,
        options: Options,
        union_membership: UnionMembership,
        consumed_scopes_mapper: ConsumedScopesMapper,
        registered_target_types: RegisteredTargetTypes,
    ) -> AllHelpInfo:
        scope_to_help_info = {}
        name_to_goal_info = {}
        for scope_info in sorted(options.known_scope_to_info.values(),
                                 key=lambda x: x.scope):
            options.for_scope(scope_info.scope)  # Force parsing.
            subsystem_cls = scope_info.subsystem_cls
            if not scope_info.description:
                cls_name = (
                    f"{subsystem_cls.__module__}.{subsystem_cls.__qualname__}"
                    if subsystem_cls else "")
                raise ValueError(
                    f"Subsystem {cls_name} with scope `{scope_info.scope}` has no description. "
                    f"Add a class property `help`.")
            is_goal = subsystem_cls is not None and issubclass(
                subsystem_cls, GoalSubsystem)
            oshi = HelpInfoExtracter(
                scope_info.scope).get_option_scope_help_info(
                    scope_info.description,
                    options.get_parser(scope_info.scope), is_goal)
            scope_to_help_info[oshi.scope] = oshi

            if is_goal:
                goal_subsystem_cls = cast(Type[GoalSubsystem], subsystem_cls)
                is_implemented = union_membership.has_members_for_all(
                    goal_subsystem_cls.required_union_implementations)
                name_to_goal_info[scope_info.scope] = GoalHelpInfo(
                    goal_subsystem_cls.name,
                    scope_info.description,
                    is_implemented,
                    consumed_scopes_mapper(scope_info.scope),
                )

        name_to_target_type_info = {
            alias: TargetTypeHelpInfo.create(target_type,
                                             union_membership=union_membership)
            for alias, target_type in
            registered_target_types.aliases_to_types.items()
            if (not alias.startswith("_") and target_type.removal_version is
                None and alias != target_type.deprecated_alias)
        }

        return AllHelpInfo(
            scope_to_help_info=scope_to_help_info,
            name_to_goal_info=name_to_goal_info,
            name_to_target_type_info=name_to_target_type_info,
        )
Пример #2
0
    def verify_configs_against_options(self, options: Options) -> None:
        """Verify all loaded configs have correct scopes and options.

        :param options: Fully bootstrapped valid options.
        """
        error_log = []
        for config in self.config.configs():
            for section in config.sections():
                scope = GLOBAL_SCOPE if section == GLOBAL_SCOPE_CONFIG_SECTION else section
                try:
                    valid_options_under_scope = set(
                        options.for_scope(scope, include_passive_options=True))
                # Only catch ConfigValidationError. Other exceptions will be raised directly.
                except Config.ConfigValidationError:
                    error_log.append(
                        f"Invalid scope [{section}] in {config.config_path}")
                else:
                    # All the options specified under [`section`] in `config` excluding bootstrap defaults.
                    all_options_under_scope = set(
                        config.values.options(section)) - set(
                            config.values.defaults)
                    for option in sorted(all_options_under_scope):
                        if option not in valid_options_under_scope:
                            error_log.append(
                                f"Invalid option '{option}' under [{section}] in {config.config_path}"
                            )

        if error_log:
            for error in error_log:
                logger.error(error)
            raise Config.ConfigValidationError(
                "Invalid config entries detected. See log for details on which entries to update or "
                "remove.\n(Specify --no-verify-config to disable this check.)")
Пример #3
0
    def _init_graph_session(
        options_bootstrapper: OptionsBootstrapper,
        build_config: BuildConfiguration,
        options: Options,
    ) -> LegacyGraphSession:
        native = Native()
        native.set_panic_handler()
        graph_scheduler_helper = EngineInitializer.setup_legacy_graph(
            native, options_bootstrapper, build_config)

        v2_ui = options.for_global_scope().get("v2_ui", False)
        zipkin_trace_v2 = options.for_scope("reporting").zipkin_trace_v2
        # TODO(#8658) This should_report_workunits flag must be set to True for
        # StreamingWorkunitHandler to receive WorkUnits. It should eventually
        # be merged with the zipkin_trace_v2 flag, since they both involve most
        # of the same engine functionality, but for now is separate to avoid
        # breaking functionality associated with zipkin tracing while iterating on streaming workunit reporting.
        stream_workunits = len(
            options.for_global_scope().streaming_workunits_handlers) != 0
        return graph_scheduler_helper.new_session(
            zipkin_trace_v2,
            RunTracker.global_instance().run_id,
            v2_ui,
            should_report_workunits=stream_workunits,
        )
Пример #4
0
    def prepare_v1_graph_run_v2(
        self,
        options: Options,
        options_bootstrapper: OptionsBootstrapper,
    ) -> Tuple[LegacyGraphSession, Specs, int]:
        """For v1 (and v2): computing Specs for a later v1 run.

        For v2: running an entire v2 run The exit_code in the return indicates whether any issue was
        encountered
        """
        # If any nodes exist in the product graph, wait for the initial watchman event to avoid
        # racing watchman startup vs invalidation events.
        graph_len = self._scheduler.graph_len()
        if graph_len > 0:
            self._logger.debug(
                "graph len was {}, waiting for initial watchman event".format(
                    graph_len))
            self._watchman_is_running.wait()
        build_id = RunTracker.global_instance().run_id
        v2_ui = options.for_global_scope().get("v2_ui", False)
        zipkin_trace_v2 = options.for_scope("reporting").zipkin_trace_v2
        session = self._graph_helper.new_session(zipkin_trace_v2, build_id,
                                                 v2_ui)

        if options.for_global_scope().get("loop", False):
            fn = self._loop
        else:
            fn = self._body

        specs, exit_code = fn(session, options, options_bootstrapper)
        return session, specs, exit_code
Пример #5
0
    def create(
        cls,
        options_bootstrapper: OptionsBootstrapper,
        options: Options,
        session: SchedulerSession,
        build_root: Optional[str] = None,
    ) -> Specs:
        specs = cls.parse_specs(raw_specs=options.specs, build_root=build_root)
        changed_options = ChangedOptions.from_options(
            options.for_scope("changed"))

        logger.debug("specs are: %s", specs)
        logger.debug("changed_options are: %s", changed_options)

        if specs.provided and changed_options.provided:
            changed_name = "--changed-since" if changed_options.since else "--changed-diffspec"
            if specs.filesystem_specs and specs.address_specs:
                specs_description = "target and file arguments"
            elif specs.filesystem_specs:
                specs_description = "file arguments"
            else:
                specs_description = "target arguments"
            raise InvalidSpecConstraint(
                f"You used `{changed_name}` at the same time as using {specs_description}. Please "
                "use only one.")

        if not changed_options.provided:
            return specs

        scm = get_scm()
        if not scm:
            raise InvalidSpecConstraint(
                "The `--changed-*` options are not available without a recognized SCM (usually "
                "Git).")
        changed_request = ChangedRequest(
            sources=tuple(changed_options.changed_files(scm=scm)),
            dependees=changed_options.dependees,
        )
        (changed_addresses, ) = session.product_request(
            ChangedAddresses, [Params(changed_request, options_bootstrapper)])
        logger.debug("changed addresses: %s", changed_addresses)

        address_specs = []
        filesystem_specs = []
        for address in cast(ChangedAddresses, changed_addresses):
            if not address.is_base_target:
                # TODO: Should adjust Specs parsing to support parsing the disambiguated file
                # Address, which would bypass-rediscovering owners.
                filesystem_specs.append(FilesystemLiteralSpec(
                    address.filename))
            else:
                address_specs.append(
                    SingleAddress(address.spec_path, address.target_name))

        return Specs(
            AddressSpecs(address_specs, filter_by_global_options=True),
            FilesystemSpecs(filesystem_specs),
        )
Пример #6
0
    def get_all_help_info(
        cls,
        options: Options,
        union_membership: UnionMembership,
        consumed_scopes_mapper: ConsumedScopesMapper,
    ) -> AllHelpInfo:
        scope_to_help_info = {}
        name_to_goal_info = {}
        for scope_info in sorted(options.known_scope_to_info.values(), key=lambda x: x.scope):
            options.for_scope(scope_info.scope)  # Force parsing.
            optionable_cls = scope_info.optionable_cls
            if not scope_info.description:
                cls_name = (
                    f"{optionable_cls.__module__}.{optionable_cls.__qualname__}"
                    if optionable_cls
                    else ""
                )
                raise ValueError(
                    f"Subsystem {cls_name} with scope `{scope_info.scope}` has no description. "
                    f"Add a docstring or implement get_description()."
                )
            is_goal = optionable_cls is not None and issubclass(optionable_cls, GoalSubsystem)
            oshi: OptionScopeHelpInfo = HelpInfoExtracter(
                scope_info.scope
            ).get_option_scope_help_info(
                scope_info.description, options.get_parser(scope_info.scope), is_goal
            )
            scope_to_help_info[oshi.scope] = oshi

            if is_goal:
                goal_subsystem_cls = cast(Type[GoalSubsystem], optionable_cls)
                is_implemented = union_membership.has_members_for_all(
                    goal_subsystem_cls.required_union_implementations
                )
                name_to_goal_info[scope_info.scope] = GoalHelpInfo(
                    goal_subsystem_cls.name,
                    scope_info.description,
                    is_implemented,
                    consumed_scopes_mapper(scope_info.scope),
                )

        return AllHelpInfo(
            scope_to_help_info=scope_to_help_info, name_to_goal_info=name_to_goal_info
        )
Пример #7
0
def calculate_specs(
    options_bootstrapper: OptionsBootstrapper,
    options: Options,
    session: SchedulerSession,
    *,
    build_root: Optional[str] = None,
) -> Specs:
    """Determine the specs for a given Pants run."""
    build_root = build_root or get_buildroot()
    specs = SpecsParser(build_root).parse_specs(options.specs)
    changed_options = ChangedOptions.from_options(options.for_scope("changed"))

    logger.debug("specs are: %s", specs)
    logger.debug("changed_options are: %s", changed_options)

    if specs.provided and changed_options.provided:
        changed_name = "--changed-since" if changed_options.since else "--changed-diffspec"
        if specs.filesystem_specs and specs.address_specs:
            specs_description = "target and file arguments"
        elif specs.filesystem_specs:
            specs_description = "file arguments"
        else:
            specs_description = "target arguments"
        raise InvalidSpecConstraint(
            f"You used `{changed_name}` at the same time as using {specs_description}. Please "
            "use only one.")

    if not changed_options.provided:
        return specs

    git = get_git()
    if not git:
        raise InvalidSpecConstraint(
            "The `--changed-*` options are only available if Git is used for the repository."
        )
    changed_request = ChangedRequest(
        sources=tuple(changed_options.changed_files(git)),
        dependees=changed_options.dependees,
    )
    (changed_addresses, ) = session.product_request(
        ChangedAddresses, [Params(changed_request, options_bootstrapper)])
    logger.debug("changed addresses: %s", changed_addresses)

    address_specs = []
    for address in cast(ChangedAddresses, changed_addresses):
        address_input = AddressInput.parse(address.spec)
        address_specs.append(
            AddressLiteralSpec(
                path_component=address_input.path_component,
                # NB: AddressInput.target_component may be None, but AddressLiteralSpec expects a
                # string.
                target_component=address_input.target_component
                or address.target_name,
            ))
    return Specs(AddressSpecs(address_specs, filter_by_global_options=True),
                 FilesystemSpecs([]))
Пример #8
0
    def create(
        cls,
        options: Options,
        session: SchedulerSession,
        build_root: Optional[str] = None,
        exclude_patterns: Optional[Iterable[str]] = None,
        tags: Optional[Iterable[str]] = None,
    ) -> Specs:
        specs = cls.parse_specs(
            raw_specs=options.specs,
            build_root=build_root,
            exclude_patterns=exclude_patterns,
            tags=tags,
        )

        changed_options = ChangedOptions.from_options(
            options.for_scope("changed"))

        logger.debug("specs are: %s", specs)
        logger.debug("changed_options are: %s", changed_options)

        if changed_options.is_actionable(
        ) and specs.provided_specs.dependencies:
            # We've been provided both a change request and specs.
            raise InvalidSpecConstraint(
                "Multiple target selection methods provided. Please use only one of "
                "`--changed-*`, address specs, or filesystem specs.")

        if changed_options.is_actionable():
            scm = get_scm()
            if not scm:
                raise InvalidSpecConstraint(
                    "The `--changed-*` options are not available without a recognized SCM (usually Git)."
                )
            changed_request = ChangedRequest(
                sources=tuple(changed_options.changed_files(scm=scm)),
                include_dependees=changed_options.include_dependees,
            )
            (changed_addresses, ) = session.product_request(
                ChangedAddresses, [changed_request])
            logger.debug("changed addresses: %s", changed_addresses.addresses)
            dependencies = tuple(
                SingleAddress(a.spec_path, a.target_name)
                for a in changed_addresses.addresses)
            return Specs(
                address_specs=AddressSpecs(
                    dependencies=dependencies,
                    exclude_patterns=exclude_patterns,
                    tags=tags,
                ),
                filesystem_specs=FilesystemSpecs([]),
            )

        return specs
Пример #9
0
    def prepare_graph(self, options: Options) -> LegacyGraphSession:
        # If any nodes exist in the product graph, wait for the initial watchman event to avoid
        # racing watchman startup vs invalidation events.
        graph_len = self._scheduler.graph_len()
        if graph_len > 0:
            self._logger.debug(f"graph len was {graph_len}, waiting for initial watchman event")
            self._watchman_is_running.wait()

        global_options = options.for_global_scope()
        build_id = RunTracker.global_instance().run_id
        v2_ui = global_options.get("v2_ui", False)
        zipkin_trace_v2 = options.for_scope("reporting").zipkin_trace_v2
        return self._graph_helper.new_session(zipkin_trace_v2, build_id, v2_ui)
Пример #10
0
 def run(
     self,
     *,
     build_config: BuildConfiguration,
     graph_session: GraphSession,
     options: Options,
     specs: Specs,
     union_membership: UnionMembership,
 ) -> ExitCode:
     goal_options = options.for_scope(self.name)
     if goal_options.server:
         return self._run_server(
             graph_session=graph_session,
             union_membership=union_membership,
         )
     current_session_values = graph_session.scheduler_session.py_session.session_values
     env = current_session_values[CompleteEnvironment]
     return self._setup_bsp_connection(
         union_membership=union_membership, env=env, options=goal_options
     )
Пример #11
0
    def _init_graph_session(
        options_bootstrapper: OptionsBootstrapper,
        build_config: BuildConfiguration,
        options: Options,
        scheduler: Optional[LegacyGraphScheduler] = None,
    ) -> LegacyGraphSession:
        native = Native()
        native.set_panic_handler()
        graph_scheduler_helper = scheduler or EngineInitializer.setup_legacy_graph(
            options_bootstrapper, build_config)

        global_scope = options.for_global_scope()

        if global_scope.v2:
            dynamic_ui = resolve_conflicting_options(
                old_option="v2_ui",
                new_option="dynamic_ui",
                old_scope=GLOBAL_SCOPE,
                new_scope=GLOBAL_SCOPE,
                old_container=global_scope,
                new_container=global_scope,
            )
        else:
            dynamic_ui = False
        use_colors = global_scope.get("colors", True)

        zipkin_trace_v2 = options.for_scope("reporting").zipkin_trace_v2
        # TODO(#8658) This should_report_workunits flag must be set to True for
        # StreamingWorkunitHandler to receive WorkUnits. It should eventually
        # be merged with the zipkin_trace_v2 flag, since they both involve most
        # of the same engine functionality, but for now is separate to avoid
        # breaking functionality associated with zipkin tracing while iterating on streaming workunit reporting.
        stream_workunits = len(
            options.for_global_scope().streaming_workunits_handlers) != 0
        return graph_scheduler_helper.new_session(
            zipkin_trace_v2,
            RunTracker.global_instance().run_id,
            dynamic_ui=dynamic_ui,
            use_colors=use_colors,
            should_report_workunits=stream_workunits,
        )
Пример #12
0
    def create(
        cls,
        options: Options,
        session: SchedulerSession,
        build_root: Optional[str] = None,
        exclude_patterns: Optional[Iterable[str]] = None,
        tags: Optional[Iterable[str]] = None,
    ) -> TargetRoots:
        # Determine the literal target roots.
        address_spec_roots = cls.parse_address_specs(
            target_specs=options.specs,
            build_root=build_root,
            exclude_patterns=exclude_patterns,
            tags=tags)

        # Determine `Changed` arguments directly from options to support pre-`Subsystem`
        # initialization paths.
        changed_options = options.for_scope('changed')
        changed_request = ChangedRequest.from_options(changed_options)

        # Determine the `--owner-of=` arguments provided from the global options
        owned_files = options.for_global_scope().owner_of

        logger.debug('address_spec_roots are: %s', address_spec_roots)
        logger.debug('changed_request is: %s', changed_request)
        logger.debug('owned_files are: %s', owned_files)
        targets_specified = sum(1 for item in (changed_request.is_actionable(),
                                               owned_files,
                                               address_spec_roots.dependencies)
                                if item)

        if targets_specified > 1:
            # We've been provided more than one of: a change request, an owner request, or spec roots.
            raise InvalidSpecConstraint(
                'Multiple target selection methods provided. Please use only one of '
                '--changed-*, --owner-of, or target specs')

        if changed_request.is_actionable():
            scm = get_scm()
            if not scm:
                raise InvalidSpecConstraint(
                    'The --changed-* options are not available without a recognized SCM (usually git).'
                )
            changed_files = cls.changed_files(
                scm,
                changes_since=changed_request.changes_since,
                diffspec=changed_request.diffspec)
            # We've been provided no spec roots (e.g. `./pants list`) AND a changed request. Compute
            # alternate target roots.
            request = OwnersRequest(
                sources=tuple(changed_files),
                include_dependees=changed_request.include_dependees,
            )
            changed_addresses, = session.product_request(
                BuildFileAddresses, [request])
            logger.debug('changed addresses: %s', changed_addresses)
            dependencies = tuple(
                SingleAddress(a.spec_path, a.target_name)
                for a in changed_addresses)
            return TargetRoots(
                AddressSpecs(dependencies=dependencies,
                             exclude_patterns=exclude_patterns,
                             tags=tags), )

        if owned_files:
            # We've been provided no spec roots (e.g. `./pants list`) AND a owner request. Compute
            # alternate target roots.
            request = OwnersRequest(
                sources=tuple(owned_files),
                include_dependees=IncludeDependeesOption.NONE,
            )
            owner_addresses, = session.product_request(BuildFileAddresses,
                                                       [request])
            logger.debug('owner addresses: %s', owner_addresses)
            dependencies = tuple(
                SingleAddress(a.spec_path, a.target_name)
                for a in owner_addresses)
            return TargetRoots(
                AddressSpecs(dependencies=dependencies,
                             exclude_patterns=exclude_patterns,
                             tags=tags), )

        return TargetRoots(address_spec_roots)
Пример #13
0
def calculate_specs(
    options_bootstrapper: OptionsBootstrapper,
    options: Options,
    session: SchedulerSession,
) -> Specs:
    """Determine the specs for a given Pants run."""
    global_options = options.for_global_scope()
    unmatched_cli_globs = global_options.unmatched_cli_globs.to_glob_match_error_behavior(
    )
    convert_dir_literal_to_address_literal = (
        global_options.use_deprecated_directory_cli_args_semantics)
    if global_options.is_default(
            "use_deprecated_directory_cli_args_semantics"):
        warn_or_error(
            "2.14.0.dev0",
            "`use_deprecated_directory_cli_args_semantics` defaulting to True",
            softwrap(f"""
                Currently, a directory argument like `{bin_name()} test dir` is shorthand for the
                target `dir:dir`, i.e. the target that leaves off `name=`.

                In Pants 2.14, by default, a directory argument will instead match all
                targets/files in the directory.

                To opt into the new and more intuitive semantics early, set
                `use_deprecated_directory_cli_args_semantics = false` in the `[GLOBAL]` section in
                `pants.toml`. Otherwise, set to `true` to silence this warning.
                """),
        )
    specs = SpecsParser().parse_specs(
        options.specs,
        description_of_origin="CLI arguments",
        unmatched_glob_behavior=unmatched_cli_globs,
        convert_dir_literal_to_address_literal=
        convert_dir_literal_to_address_literal,
    )

    changed_options = ChangedOptions.from_options(options.for_scope("changed"))
    logger.debug("specs are: %s", specs)
    logger.debug("changed_options are: %s", changed_options)

    if specs and changed_options.provided:
        changed_name = "--changed-since" if changed_options.since else "--changed-diffspec"
        specs_description = specs.arguments_provided_description()
        assert specs_description is not None
        raise InvalidSpecConstraint(
            f"You used `{changed_name}` at the same time as using {specs_description}. You can "
            f"only use `{changed_name}` or use normal arguments.")

    if not changed_options.provided:
        return specs

    (git_binary, ) = session.product_request(GitBinary,
                                             [Params(GitBinaryRequest())])
    (maybe_git_worktree, ) = session.product_request(
        MaybeGitWorktree, [Params(GitWorktreeRequest(), git_binary)])
    if not maybe_git_worktree.git_worktree:
        raise InvalidSpecConstraint(
            "The `--changed-*` options are only available if Git is used for the repository."
        )

    changed_files = tuple(
        changed_options.changed_files(maybe_git_worktree.git_worktree))
    file_literal_specs = tuple(FileLiteralSpec(f) for f in changed_files)

    changed_request = ChangedRequest(changed_files, changed_options.dependees)
    (changed_addresses, ) = session.product_request(
        ChangedAddresses, [Params(changed_request, options_bootstrapper)])
    logger.debug("changed addresses: %s", changed_addresses)

    address_literal_specs = []
    for address in cast(ChangedAddresses, changed_addresses):
        address_input = AddressInput.parse(
            address.spec, description_of_origin="`--changed-since`")
        address_literal_specs.append(
            AddressLiteralSpec(
                path_component=address_input.path_component,
                target_component=address_input.target_component,
                generated_component=address_input.generated_component,
                parameters=address_input.parameters,
            ))

    return Specs(
        includes=RawSpecs(
            # We need both address_literals and file_literals to cover all our edge cases, including
            # target-aware vs. target-less goals, e.g. `list` vs `count-loc`.
            address_literals=tuple(address_literal_specs),
            file_literals=file_literal_specs,
            unmatched_glob_behavior=unmatched_cli_globs,
            filter_by_global_options=True,
            from_change_detection=True,
            description_of_origin="`--changed-since`",
        ),
        ignores=RawSpecs(description_of_origin="`--changed-since`"),
    )
Пример #14
0
    def create(
        cls,
        options: Options,
        session: SchedulerSession,
        build_root: Optional[str] = None,
        exclude_patterns: Optional[Iterable[str]] = None,
        tags: Optional[Iterable[str]] = None,
    ) -> Specs:
        specs = cls.parse_specs(
            raw_specs=options.specs,
            build_root=build_root,
            exclude_patterns=exclude_patterns,
            tags=tags,
        )

        changed_options = ChangedOptions.from_options(
            options.for_scope('changed'))
        owned_files = options.for_global_scope().owner_of

        logger.debug('specs are: %s', specs)
        logger.debug('changed_options are: %s', changed_options)
        logger.debug('owned_files are: %s', owned_files)
        targets_specified = sum(1
                                for item in (changed_options.is_actionable(),
                                             owned_files,
                                             specs.provided_specs.dependencies)
                                if item)

        if targets_specified > 1:
            # We've been provided more than one of: a change request, an owner request, or specs.
            raise InvalidSpecConstraint(
                'Multiple target selection methods provided. Please use only one of '
                '`--changed-*`, `--owner-of`, address specs, or filesystem specs.'
            )

        if changed_options.is_actionable():
            scm = get_scm()
            if not scm:
                raise InvalidSpecConstraint(
                    'The `--changed-*` options are not available without a recognized SCM (usually git).'
                )
            changed_request = ChangedRequest(
                sources=tuple(changed_options.changed_files(scm=scm)),
                include_dependees=changed_options.include_dependees,
            )
            changed_addresses, = session.product_request(
                ChangedAddresses, [changed_request])
            logger.debug('changed addresses: %s', changed_addresses.addresses)
            dependencies = tuple(
                SingleAddress(a.spec_path, a.target_name)
                for a in changed_addresses.addresses)
            return Specs(
                address_specs=AddressSpecs(
                    dependencies=dependencies,
                    exclude_patterns=exclude_patterns,
                    tags=tags,
                ),
                filesystem_specs=FilesystemSpecs([]),
            )

        if owned_files:
            owner_request = OwnersRequest(sources=tuple(owned_files))
            owner_request.validate(
                pants_bin_name=options.for_global_scope().pants_bin_name)
            owners, = session.product_request(Owners, [owner_request])
            logger.debug('owner addresses: %s', owners.addresses)
            dependencies = tuple(
                SingleAddress(a.spec_path, a.target_name)
                for a in owners.addresses)
            return Specs(address_specs=AddressSpecs(
                dependencies=dependencies,
                exclude_patterns=exclude_patterns,
                tags=tags,
            ),
                         filesystem_specs=FilesystemSpecs([]))

        return specs