Ejemplo n.º 1
0
def test_address_specs_deduplication(
        address_specs_rule_runner: RuleRunner) -> None:
    """When multiple specs cover the same address, we should deduplicate to one single
    AddressWithOrigin.

    We should use the most specific origin spec possible, such as AddressLiteralSpec >
    SiblingAddresses.
    """
    address_specs_rule_runner.create_file("demo/f.txt")
    address_specs_rule_runner.add_to_build_file("demo",
                                                "mock_tgt(sources=['f.txt'])")
    # We also include a file address to ensure that that is included in the result.
    specs = [
        AddressLiteralSpec("demo", "demo"),
        AddressLiteralSpec("demo/f.txt", "demo"),
        SiblingAddresses("demo"),
        DescendantAddresses("demo"),
        AscendantAddresses("demo"),
    ]
    assert resolve_address_specs(address_specs_rule_runner, specs) == {
        AddressWithOrigin(Address("demo"), AddressLiteralSpec("demo", "demo")),
        AddressWithOrigin(
            Address("demo", relative_file_path="f.txt"),
            AddressLiteralSpec("demo/f.txt", "demo"),
        ),
    }
Ejemplo n.º 2
0
async def addresses_with_origins_from_filesystem_specs(
    filesystem_specs: FilesystemSpecs, global_options: GlobalOptions,
) -> AddressesWithOrigins:
    """Find the owner(s) for each FilesystemSpec while preserving the original FilesystemSpec those
    owners come from.

    This will merge FilesystemSpecs that come from the same owning target into a single
    FilesystemMergedSpec.
    """
    pathglobs_per_include = (
        filesystem_specs.path_globs_for_spec(
            spec, global_options.options.owners_not_found_behavior.to_glob_match_error_behavior(),
        )
        for spec in filesystem_specs.includes
    )
    snapshot_per_include = await MultiGet(
        Get[Snapshot](PathGlobs, pg) for pg in pathglobs_per_include
    )
    owners_per_include = await MultiGet(
        Get[Owners](OwnersRequest(sources=snapshot.files)) for snapshot in snapshot_per_include
    )
    addresses_to_specs: DefaultDict[
        Address, List[Union[FilesystemLiteralSpec, FilesystemResolvedGlobSpec]]
    ] = defaultdict(list)
    for spec, snapshot, owners in zip(
        filesystem_specs.includes, snapshot_per_include, owners_per_include
    ):
        if (
            global_options.options.owners_not_found_behavior != OwnersNotFoundBehavior.ignore
            and isinstance(spec, FilesystemLiteralSpec)
            and not owners.addresses
        ):
            file_path = PurePath(spec.to_spec_string())
            msg = (
                f"No owning targets could be found for the file `{file_path}`.\n\nPlease check "
                f"that there is a BUILD file in `{file_path.parent}` with a target whose `sources` field "
                f"includes `{file_path}`. See https://pants.readme.io/docs/targets for more "
                "information on target definitions.\n"
                "If you would like to ignore un-owned files, please pass `--owners-not-found-behavior=ignore`."
            )
            if global_options.options.owners_not_found_behavior == OwnersNotFoundBehavior.warn:
                logger.warning(msg)
            else:
                raise ResolveError(msg)
        # We preserve what literal files any globs resolved to. This allows downstream goals to be
        # more precise in which files they operate on.
        origin: Union[FilesystemLiteralSpec, FilesystemResolvedGlobSpec] = (
            spec
            if isinstance(spec, FilesystemLiteralSpec)
            else FilesystemResolvedGlobSpec(glob=spec.glob, files=snapshot.files)
        )
        for address in owners.addresses:
            addresses_to_specs[address].append(origin)
    return AddressesWithOrigins(
        AddressWithOrigin(
            address, specs[0] if len(specs) == 1 else FilesystemMergedSpec.create(specs)
        )
        for address, specs in addresses_to_specs.items()
    )
Ejemplo n.º 3
0
def test_strip_address_origin() -> None:
    addr = Address.parse("//:demo")
    result = run_rule(
        strip_address_origins,
        rule_args=[
            AddressesWithOrigins(
                [AddressWithOrigin(addr, SingleAddress("", "demo"))])
        ],
    )
    assert list(result) == [addr]
Ejemplo n.º 4
0
def test_address_specs_filter_by_tag(
        address_specs_rule_runner: RuleRunner) -> None:
    address_specs_rule_runner.create_file("demo/f.txt")
    address_specs_rule_runner.add_to_build_file(
        "demo",
        dedent("""\
            mock_tgt(name="a", sources=["f.txt"])
            mock_tgt(name="b", sources=["f.txt"], tags=["integration"])
            mock_tgt(name="c", sources=["f.txt"], tags=["ignore"])
            """),
    )
    bootstrapper = create_options_bootstrapper(args=["--tag=+integration"])

    assert resolve_address_specs(address_specs_rule_runner,
                                 [SiblingAddresses("demo")],
                                 bootstrapper=bootstrapper) == {
                                     AddressWithOrigin(
                                         Address("demo", target_name="b"),
                                         SiblingAddresses("demo"))
                                 }

    # The same filtering should work when given literal addresses, including file addresses.
    # For file addresses, we look up the `tags` field of the original base target.
    literals_result = resolve_address_specs(
        address_specs_rule_runner,
        [
            AddressLiteralSpec("demo", "a"),
            AddressLiteralSpec("demo", "b"),
            AddressLiteralSpec("demo", "c"),
            AddressLiteralSpec("demo/f.txt", "a"),
            AddressLiteralSpec("demo/f.txt", "b"),
            AddressLiteralSpec("demo/f.txt", "c"),
        ],
        bootstrapper=bootstrapper,
    )
    assert literals_result == {
        AddressWithOrigin(
            Address("demo", relative_file_path="f.txt", target_name="b"),
            AddressLiteralSpec("demo/f.txt", "b"),
        ),
        AddressWithOrigin(Address("demo", target_name="b"),
                          AddressLiteralSpec("demo", "b")),
    }
Ejemplo n.º 5
0
def test_strip_address_origin() -> None:
    addr = Address.parse("//:demo")
    result = run_rule_with_mocks(
        strip_address_origins,
        rule_args=[
            AddressesWithOrigins(
                [AddressWithOrigin(addr, AddressLiteralSpec("", "demo"))])
        ],
    )
    assert list(result) == [addr]
Ejemplo n.º 6
0
async def addresses_with_origins_from_address_families(
    address_mapper: AddressMapper,
    address_specs: AddressSpecs,
) -> AddressesWithOrigins:
    """Given an AddressMapper and list of AddressSpecs, return matching AddressesWithOrigins.

    :raises: :class:`ResolveError` if:
       - there were no matching AddressFamilies, or
       - the AddressSpec matches no addresses for SingleAddresses.
    :raises: :class:`AddressLookupError` if no targets are matched for non-SingleAddress specs.
    """
    # Capture a Snapshot covering all paths for these AddressSpecs, then group by directory.
    snapshot = await Get[Snapshot](PathGlobs,
                                   _address_spec_to_globs(
                                       address_mapper, address_specs))
    dirnames = {os.path.dirname(f) for f in snapshot.files}
    address_families = await MultiGet(Get[AddressFamily](Dir(d))
                                      for d in dirnames)
    address_family_by_directory = {af.namespace: af for af in address_families}

    matched_addresses: OrderedSet[Address] = OrderedSet()
    addr_to_origin: Dict[Address, AddressSpec] = {}

    for address_spec in address_specs:
        # NB: if an address spec is provided which expands to some number of targets, but those targets
        # match --exclude-target-regexp, we do NOT fail! This is why we wait to apply the tag and
        # exclude patterns until we gather all the targets the address spec would have matched
        # without them.
        try:
            addr_families_for_spec = address_spec.matching_address_families(
                address_family_by_directory)
        except AddressSpec.AddressFamilyResolutionError as e:
            raise ResolveError(e) from e

        try:
            all_bfaddr_tgt_pairs = address_spec.address_target_pairs_from_address_families(
                addr_families_for_spec)
            for bfaddr, _ in all_bfaddr_tgt_pairs:
                addr = bfaddr.to_address()
                # A target might be covered by multiple specs, so we take the most specific one.
                addr_to_origin[addr] = more_specific(addr_to_origin.get(addr),
                                                     address_spec)
        except AddressSpec.AddressResolutionError as e:
            raise AddressLookupError(e) from e
        except SingleAddress._SingleAddressResolutionError as e:
            _raise_did_you_mean(e.single_address_family, e.name, source=e)

        matched_addresses.update(
            bfaddr.to_address() for (bfaddr, tgt) in all_bfaddr_tgt_pairs
            if address_specs.matcher.matches_target_address_pair(bfaddr, tgt))

    # NB: This may be empty, as the result of filtering by tag and exclude patterns!
    return AddressesWithOrigins(
        AddressWithOrigin(address=addr, origin=addr_to_origin[addr])
        for addr in matched_addresses)
Ejemplo n.º 7
0
def test_address_specs_filter_by_exclude_pattern(
        address_specs_rule_runner: RuleRunner) -> None:
    address_specs_rule_runner.create_file("demo/f.txt")
    address_specs_rule_runner.add_to_build_file(
        "demo",
        dedent("""\
            mock_tgt(name="exclude_me", sources=["f.txt"])
            mock_tgt(name="not_me", sources=["f.txt"])
            """),
    )
    bootstrapper = create_options_bootstrapper(
        args=["--exclude-target-regexp=exclude_me.*"])

    assert resolve_address_specs(address_specs_rule_runner,
                                 [SiblingAddresses("demo")],
                                 bootstrapper=bootstrapper) == {
                                     AddressWithOrigin(
                                         Address("demo", target_name="not_me"),
                                         SiblingAddresses("demo"))
                                 }

    # The same filtering should work when given literal addresses, including file addresses.
    # The filtering will operate against the normalized Address.spec.
    literals_result = resolve_address_specs(
        address_specs_rule_runner,
        [
            AddressLiteralSpec("demo", "exclude_me"),
            AddressLiteralSpec("demo", "not_me"),
            AddressLiteralSpec("demo/f.txt", "exclude_me"),
            AddressLiteralSpec("demo/f.txt", "not_me"),
        ],
        bootstrapper=bootstrapper,
    )

    assert literals_result == {
        AddressWithOrigin(
            Address("demo", relative_file_path="f.txt", target_name="not_me"),
            AddressLiteralSpec("demo/f.txt", "not_me"),
        ),
        AddressWithOrigin(Address("demo", target_name="not_me"),
                          AddressLiteralSpec("demo", "not_me")),
    }
Ejemplo n.º 8
0
 def test_filesystem_specs_literal_file(self) -> None:
     self.create_files("demo", ["f1.txt", "f2.txt"])
     self.add_to_build_file("demo", "target(sources=['*.txt'])")
     spec = FilesystemLiteralSpec("demo/f1.txt")
     result = self.request_single_product(
         AddressesWithOrigins, Params(FilesystemSpecs([spec]), create_options_bootstrapper())
     )
     assert len(result) == 1
     assert result[0] == AddressWithOrigin(
         Address("demo", relative_file_path="f1.txt", target_name="demo"), origin=spec
     )
Ejemplo n.º 9
0
async def addresses_with_origins_from_address_specs(
        address_mapper: AddressMapper,
        address_specs: AddressSpecs) -> AddressesWithOrigins:
    """Given an AddressMapper and list of AddressSpecs, return matching AddressesWithOrigins.

    :raises: :class:`ResolveError` if there were no matching AddressFamilies or no targets
        were matched.
    """
    # Snapshot all BUILD files covered by the AddressSpecs, then group by directory.
    snapshot = await Get(
        Snapshot,
        PathGlobs,
        address_specs.to_path_globs(
            build_patterns=address_mapper.build_patterns,
            build_ignore_patterns=address_mapper.build_ignore_patterns,
        ),
    )
    dirnames = {os.path.dirname(f) for f in snapshot.files}
    address_families = await MultiGet(
        Get(AddressFamily, Dir(d)) for d in dirnames)
    address_family_by_directory = {af.namespace: af for af in address_families}

    matched_addresses: OrderedSet[Address] = OrderedSet()
    addr_to_origin: Dict[Address, AddressSpec] = {}

    for address_spec in address_specs:
        # These may raise ResolveError, depending on the type of spec.
        addr_families_for_spec = address_spec.matching_address_families(
            address_family_by_directory)
        addr_target_pairs_for_spec = address_spec.matching_addresses(
            addr_families_for_spec)

        if isinstance(address_spec,
                      SingleAddress) and not addr_target_pairs_for_spec:
            addr_family = assert_single_element(addr_families_for_spec)
            raise _did_you_mean_exception(addr_family, address_spec.name)

        for addr, _ in addr_target_pairs_for_spec:
            # A target might be covered by multiple specs, so we take the most specific one.
            addr_to_origin[addr] = AddressSpecs.more_specific(
                addr_to_origin.get(addr), address_spec)

        matched_addresses.update(
            addr for (addr, tgt) in addr_target_pairs_for_spec
            if (address_specs.filter_by_global_options is False
                or address_mapper.matches_filter_options(addr, tgt)))

    return AddressesWithOrigins(
        AddressWithOrigin(address=addr, origin=addr_to_origin[addr])
        for addr in matched_addresses)
Ejemplo n.º 10
0
    def test_resolve_addresses(self) -> None:
        """This tests that we correctly handle resolving from both address and filesystem specs."""
        self.create_file("fs_spec/f.txt")
        self.add_to_build_file("fs_spec", "target(sources=['f.txt'])")
        self.create_file("address_spec/f.txt")
        self.add_to_build_file("address_spec", "target(sources=['f.txt'])")
        no_interaction_specs = ["fs_spec/f.txt", "address_spec:address_spec"]

        # If a generated subtarget's original base target is included via an address spec,
        # we will still include the generated subtarget for consistency. When we expand Targets
        # into their base targets this redundancy is removed, but during Address expansion we
        # get literal matches.
        self.create_files("multiple_files", ["f1.txt", "f2.txt"])
        self.add_to_build_file("multiple_files", "target(sources=['*.txt'])")
        multiple_files_specs = ["multiple_files/f2.txt", "multiple_files:multiple_files"]

        specs = SpecsCalculator.parse_specs([*no_interaction_specs, *multiple_files_specs])
        result = self.request_single_product(
            AddressesWithOrigins, Params(specs, create_options_bootstrapper())
        )
        assert set(result) == {
            AddressWithOrigin(
                Address("fs_spec", relative_file_path="f.txt"),
                origin=FilesystemLiteralSpec("fs_spec/f.txt"),
            ),
            AddressWithOrigin(
                Address("address_spec"), origin=SingleAddress("address_spec", "address_spec"),
            ),
            AddressWithOrigin(
                Address("multiple_files"), origin=SingleAddress("multiple_files", "multiple_files"),
            ),
            AddressWithOrigin(
                Address("multiple_files", relative_file_path="f2.txt"),
                origin=FilesystemLiteralSpec(file="multiple_files/f2.txt"),
            ),
        }
Ejemplo n.º 11
0
    def test_filesystem_specs_glob(self) -> None:
        self.create_files("demo", ["f1.txt", "f2.txt"])
        self.add_to_build_file("demo", "target(sources=['*.txt'])")
        spec = FilesystemGlobSpec("demo/*.txt")
        result = self.request_single_product(
            AddressesWithOrigins, Params(FilesystemSpecs([spec]), create_options_bootstrapper()),
        )
        assert result == AddressesWithOrigins(
            [
                AddressWithOrigin(
                    Address("demo", relative_file_path="f1.txt", target_name="demo"), origin=spec
                ),
                AddressWithOrigin(
                    Address("demo", relative_file_path="f2.txt", target_name="demo"), origin=spec
                ),
            ]
        )

        # If a glob and a literal spec both resolve to the same file, the literal spec should be
        # used as it's more precise.
        literal_spec = FilesystemLiteralSpec("demo/f1.txt")
        result = self.request_single_product(
            AddressesWithOrigins,
            Params(FilesystemSpecs([spec, literal_spec]), create_options_bootstrapper()),
        )
        assert result == AddressesWithOrigins(
            [
                AddressWithOrigin(
                    Address("demo", relative_file_path="f1.txt", target_name="demo"),
                    origin=literal_spec,
                ),
                AddressWithOrigin(
                    Address("demo", relative_file_path="f2.txt", target_name="demo"), origin=spec
                ),
            ]
        )
Ejemplo n.º 12
0
async def addresses_with_origins_from_filesystem_specs(
    filesystem_specs: FilesystemSpecs,
    global_options: GlobalOptions,
) -> AddressesWithOrigins:
    """Find the owner(s) for each FilesystemSpec while preserving the original FilesystemSpec those
    owners come from.

    Every returned address will be a generated subtarget, meaning that each address will have
    exactly one file in its `sources` field.
    """
    owners_not_found_behavior = global_options.options.owners_not_found_behavior
    paths_per_include = await MultiGet(
        Get(
            Paths,
            PathGlobs,
            filesystem_specs.path_globs_for_spec(
                spec, owners_not_found_behavior.to_glob_match_error_behavior()
            ),
        )
        for spec in filesystem_specs.includes
    )
    owners_per_include = await MultiGet(
        Get(Owners, OwnersRequest(sources=paths.files)) for paths in paths_per_include
    )
    addresses_to_specs: Dict[Address, FilesystemSpec] = {}
    for spec, owners in zip(filesystem_specs.includes, owners_per_include):
        if (
            owners_not_found_behavior != OwnersNotFoundBehavior.ignore
            and isinstance(spec, FilesystemLiteralSpec)
            and not owners
        ):
            _log_or_raise_unmatched_owners(
                [PurePath(str(spec))],
                global_options.options.owners_not_found_behavior,
                ignore_option="--owners-not-found-behavior=ignore",
            )
        for address in owners:
            # A target might be covered by multiple specs, so we take the most specific one.
            addresses_to_specs[address] = FilesystemSpecs.more_specific(
                addresses_to_specs.get(address), spec
            )
    return AddressesWithOrigins(
        AddressWithOrigin(address, spec) for address, spec in addresses_to_specs.items()
    )
Ejemplo n.º 13
0
async def addresses_with_origins_from_address_specs(
    address_specs: AddressSpecs, global_options: GlobalOptions, specs_filter: AddressSpecsFilter
) -> AddressesWithOrigins:
    """Given an AddressMapper and list of AddressSpecs, return matching AddressesWithOrigins.

    :raises: :class:`ResolveError` if the provided specs fail to match targets, and those spec
        types expect to have matched something.
    """
    matched_addresses: OrderedSet[Address] = OrderedSet()
    addr_to_origin: Dict[Address, AddressSpec] = {}
    filtering_disabled = address_specs.filter_by_global_options is False

    # First convert all `AddressLiteralSpec`s. Some of the resulting addresses may be file
    # addresses. This will raise an exception if any of the addresses are not valid.
    literal_addresses = await MultiGet(
        Get(Address, AddressInput(spec.path_component, spec.target_component))
        for spec in address_specs.literals
    )
    literal_target_adaptors = await MultiGet(
        Get(TargetAdaptor, Address, addr.maybe_convert_to_base_target())
        for addr in literal_addresses
    )
    # We convert to targets for the side effect of validating that any file addresses actually
    # belong to the specified base targets.
    await Get(
        UnexpandedTargets, Addresses(addr for addr in literal_addresses if not addr.is_base_target)
    )
    for literal_spec, addr, target_adaptor in zip(
        address_specs.literals, literal_addresses, literal_target_adaptors
    ):
        addr_to_origin[addr] = literal_spec
        if filtering_disabled or specs_filter.matches(addr, target_adaptor):
            matched_addresses.add(addr)

    # Then, convert all `AddressGlobSpecs`. Snapshot all BUILD files covered by the specs, then
    # group by directory.
    snapshot = await Get(
        Snapshot,
        PathGlobs,
        address_specs.to_path_globs(
            build_patterns=global_options.options.build_patterns,
            build_ignore_patterns=global_options.options.build_ignore,
        ),
    )
    dirnames = {os.path.dirname(f) for f in snapshot.files}
    address_families = await MultiGet(Get(AddressFamily, Dir(d)) for d in dirnames)
    address_family_by_directory = {af.namespace: af for af in address_families}

    for glob_spec in address_specs.globs:
        # These may raise ResolveError, depending on the type of spec.
        addr_families_for_spec = glob_spec.matching_address_families(address_family_by_directory)
        addr_target_pairs_for_spec = glob_spec.matching_addresses(addr_families_for_spec)

        for addr, _ in addr_target_pairs_for_spec:
            # A target might be covered by multiple specs, so we take the most specific one.
            addr_to_origin[addr] = AddressSpecs.more_specific(addr_to_origin.get(addr), glob_spec)

        matched_addresses.update(
            addr
            for (addr, tgt) in addr_target_pairs_for_spec
            if filtering_disabled or specs_filter.matches(addr, tgt)
        )

    return AddressesWithOrigins(
        AddressWithOrigin(address=addr, origin=addr_to_origin[addr]) for addr in matched_addresses
    )