def _get_imports_info( address: Address, owners_per_import: Iterable[PythonModuleOwners], parsed_imports: ParsedPythonImports, explicitly_provided_deps: ExplicitlyProvidedDependencies, ) -> tuple[set[Address], set[str]]: inferred_deps: set[Address] = set() unowned_imports: set[str] = set() for owners, imp in zip(owners_per_import, parsed_imports): inferred_deps.update(owners.unambiguous) explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( owners.ambiguous, address, import_reference="module", context=f"The target {address} imports `{imp}`", ) maybe_disambiguated = explicitly_provided_deps.disambiguated(owners.ambiguous) if maybe_disambiguated: inferred_deps.add(maybe_disambiguated) if ( not owners.unambiguous and imp.split(".")[0] not in DEFAULT_UNOWNED_DEPENDENCIES and not parsed_imports[imp].weak ): unowned_imports.add(imp) return inferred_deps, unowned_imports
def get_disambiguated( *, ambiguous: List[Address], ignores: List[Address], includes: Optional[List[Address]] = None, ) -> Optional[Address]: epd = ExplicitlyProvidedDependencies( includes=FrozenOrderedSet(includes or []), ignores=FrozenOrderedSet(ignores) ) return epd.disambiguated_via_ignores(tuple(ambiguous))
def maybe_warn( *, ambiguous: List[Address], ignores: Optional[List[Address]] = None, includes: Optional[List[Address]] = None, ) -> None: caplog.clear() epd = ExplicitlyProvidedDependencies( includes=FrozenOrderedSet(includes or []), ignores=FrozenOrderedSet(ignores or []) ) epd.maybe_warn_of_ambiguous_dependency_inference( tuple(ambiguous), Address("some_dir"), import_reference="file", context="foo" )
def test_explicitly_provided_dependencies_remaining_after_disambiguation( ) -> None: # First check disambiguation via ignores (`!` and `!!`). addr = Address("", target_name="a") generated_addr = Address("", target_name="b", generated_name="gen") epd = ExplicitlyProvidedDependencies( Address("", target_name="input_tgt"), includes=FrozenOrderedSet(), ignores=FrozenOrderedSet([addr, generated_addr]), ) def assert_disambiguated_via_ignores(ambiguous: List[Address], expected: Set[Address]) -> None: assert (epd.remaining_after_disambiguation( tuple(ambiguous), owners_must_be_ancestors=False) == expected) assert_disambiguated_via_ignores([], set()) assert_disambiguated_via_ignores([addr], set()) assert_disambiguated_via_ignores([generated_addr], set()) assert_disambiguated_via_ignores([addr, generated_addr], set()) # Generated targets are covered if their original target generator is in the ignores. assert_disambiguated_via_ignores( [Address("", target_name="a", generated_name="gen")], set()) bad_tgt = Address("", target_name="x") bad_generated_tgt = Address("", target_name="x", generated_name="gen") assert_disambiguated_via_ignores([bad_tgt], {bad_tgt}) assert_disambiguated_via_ignores([bad_generated_tgt], {bad_generated_tgt}) assert_disambiguated_via_ignores([bad_generated_tgt, addr, generated_addr], {bad_generated_tgt}) # Check disambiguation via `owners_must_be_ancestors`. epd = ExplicitlyProvidedDependencies(Address("src/lang/project"), FrozenOrderedSet(), FrozenOrderedSet()) valid_candidates = { Address("src/lang/project", target_name="another_tgt"), Address("src/lang"), Address("src"), Address("", target_name="root_owner"), } invalid_candidates = { Address("tests/lang"), Address("src/another_lang"), Address("src/lang/another_project"), Address("src/lang/project/subdir"), } assert (epd.remaining_after_disambiguation( (*valid_candidates, *invalid_candidates), owners_must_be_ancestors=True) == valid_candidates)
def get_disambiguated( ambiguous: List[Address], *, ignores: Optional[List[Address]] = None, includes: Optional[List[Address]] = None, owners_must_be_ancestors: bool = False, ) -> Optional[Address]: epd = ExplicitlyProvidedDependencies( address=Address("dir", target_name="input_tgt"), includes=FrozenOrderedSet(includes or []), ignores=FrozenOrderedSet(ignores or []), ) return epd.disambiguated( tuple(ambiguous), owners_must_be_ancestors=owners_must_be_ancestors)
def maybe_warn( ambiguous: List[Address], *, ignores: Optional[List[Address]] = None, includes: Optional[List[Address]] = None, owners_must_be_ancestors: bool = False, ) -> None: caplog.clear() epd = ExplicitlyProvidedDependencies( Address("dir", target_name="input_tgt"), includes=FrozenOrderedSet(includes or []), ignores=FrozenOrderedSet(ignores or []), ) epd.maybe_warn_of_ambiguous_dependency_inference( tuple(ambiguous), Address("some_dir"), import_reference="file", context="foo", owners_must_be_ancestors=owners_must_be_ancestors, )
def _get_inferred_asset_deps( address: Address, request_file_path: str, assets_by_path: AllAssetTargetsByPath, assets: ParsedPythonAssetPaths, explicitly_provided_deps: ExplicitlyProvidedDependencies, ) -> Iterator[Address]: for filepath in assets: # NB: Resources in Python's ecosystem are loaded relative to a package, so we only try and # query for a resource relative to requesting module's path # (I.e. we assume the user is doing something like `pkgutil.get_data(__file__, "foo/bar")`) # See https://docs.python.org/3/library/pkgutil.html#pkgutil.get_data # and Pants' own docs on resources. # # Files in Pants are always loaded relative to the build root without any source root # stripping, so we use the full filepath to query for files. # (I.e. we assume the user is doing something like `open("src/python/configs/prod.json")`) # # In either case we could also try and query based on the others' key, however this will # almost always lead to a false positive. resource_path = PurePath(request_file_path).parent / filepath file_path = PurePath(filepath) inferred_resource_tgts = assets_by_path.resources.get( resource_path, frozenset()) inferred_file_tgts = assets_by_path.files.get(file_path, frozenset()) inferred_tgts = inferred_resource_tgts | inferred_file_tgts if inferred_tgts: possible_addresses = tuple(tgt.address for tgt in inferred_tgts) explicitly_provided_deps.maybe_warn_of_ambiguous_dependency_inference( possible_addresses, address, import_reference="asset", context=f"The target {address} uses `{filepath}`", ) maybe_disambiguated = explicitly_provided_deps.disambiguated( possible_addresses) if maybe_disambiguated: yield maybe_disambiguated
def test_explicitly_provided_dependencies_remaining_after_ignores() -> None: build_tgt = Address("", target_name="a") file_tgt = Address("", target_name="b", relative_file_path="f.ext") epd = ExplicitlyProvidedDependencies( includes=FrozenOrderedSet(), ignores=FrozenOrderedSet([build_tgt, file_tgt]), ) assert epd.remaining_after_ignores(()) == set() assert epd.remaining_after_ignores((build_tgt,)) == set() assert epd.remaining_after_ignores((file_tgt,)) == set() assert epd.remaining_after_ignores((build_tgt, file_tgt)) == set() # File addresses are covered if their original BUILD address is in the includes. assert ( epd.remaining_after_ignores((Address("", target_name="a", relative_file_path="f.ext"),)) == set() ) bad_build_tgt = Address("", target_name="x") bad_file_tgt = Address("", target_name="x", relative_file_path="f.ext") assert epd.remaining_after_ignores((bad_build_tgt,)) == {bad_build_tgt} assert epd.remaining_after_ignores((bad_file_tgt,)) == {bad_file_tgt} assert epd.remaining_after_ignores((bad_file_tgt, build_tgt, file_tgt)) == {bad_file_tgt}
def test_explicitly_provided_dependencies_any_are_covered_by_includes() -> None: build_tgt = Address("", target_name="a") file_tgt = Address("", target_name="b", relative_file_path="f.ext") epd = ExplicitlyProvidedDependencies( includes=FrozenOrderedSet([build_tgt, file_tgt]), ignores=FrozenOrderedSet(), ) assert epd.any_are_covered_by_includes(()) is False assert epd.any_are_covered_by_includes((build_tgt,)) is True assert epd.any_are_covered_by_includes((file_tgt,)) is True assert epd.any_are_covered_by_includes((build_tgt, file_tgt)) is True # File addresses are covered if their original BUILD address is in the includes. assert ( epd.any_are_covered_by_includes((Address("", target_name="a", relative_file_path="f.ext"),)) is True ) assert epd.any_are_covered_by_includes((Address("", target_name="x"),)) is False assert ( epd.any_are_covered_by_includes((Address("", target_name="x", relative_file_path="f.ext"),)) is False ) # Ensure we check for _any_, not _all_. assert epd.any_are_covered_by_includes((Address("", target_name="x"), build_tgt)) is True
def test_explicitly_provided_dependencies_any_are_covered_by_includes( ) -> None: addr = Address("", target_name="a") generated_addr = Address("", target_name="b", generated_name="gen") epd = ExplicitlyProvidedDependencies( Address("", target_name="input_tgt"), includes=FrozenOrderedSet([addr, generated_addr]), ignores=FrozenOrderedSet(), ) assert epd.any_are_covered_by_includes(()) is False assert epd.any_are_covered_by_includes((addr, )) is True assert epd.any_are_covered_by_includes((generated_addr, )) is True assert epd.any_are_covered_by_includes((addr, generated_addr)) is True # Generated targets are covered if their original target generator is in the includes. assert (epd.any_are_covered_by_includes( (Address("", target_name="a", generated_name="gen"), )) is True) assert epd.any_are_covered_by_includes( (Address("", target_name="x"), )) is False assert (epd.any_are_covered_by_includes( (Address("", target_name="x", generated_name="gen"), )) is False) # Ensure we check for _any_, not _all_. assert epd.any_are_covered_by_includes( (Address("", target_name="x"), addr)) is True
async def determine_explicitly_provided_dependencies( request: DependenciesRequest, union_membership: UnionMembership, registered_target_types: RegisteredTargetTypes, subproject_roots: SubprojectRoots, ) -> ExplicitlyProvidedDependencies: parse = functools.partial( AddressInput.parse, relative_to=request.field.address.spec_path, subproject_roots=subproject_roots, description_of_origin= (f"the `{request.field.alias}` field from the target {request.field.address}" ), ) addresses: list[AddressInput] = [] ignored_addresses: list[AddressInput] = [] for v in request.field.value or (): is_ignore = v.startswith("!") if is_ignore: # Check if it's a transitive exclude, rather than a direct exclude. if v.startswith("!!"): if not request.field.supports_transitive_excludes: raise TransitiveExcludesNotSupportedError( bad_value=v, address=request.field.address, registered_target_types=registered_target_types.types, union_membership=union_membership, ) v = v[2:] else: v = v[1:] result = parse(v) if is_ignore: ignored_addresses.append(result) else: addresses.append(result) parsed_includes = await MultiGet( Get(Address, AddressInput, ai) for ai in addresses) parsed_ignores = await MultiGet( Get(Address, AddressInput, ai) for ai in ignored_addresses) return ExplicitlyProvidedDependencies( request.field.address, FrozenOrderedSet(sorted(parsed_includes)), FrozenOrderedSet(sorted(parsed_ignores)), )