def test_owners_multiple_owners(self) -> None: """Even if there are multiple owners of the same file, we still use generated subtargets.""" self.create_files("demo", ["f1.txt", "f2.txt"]) self.add_to_build_file( "demo", dedent( """\ target(name='all', sources=['*.txt']) target(name='f2', sources=['f2.txt']) """ ), ) one_owner_result = self.request_single_product(Owners, OwnersRequest(("demo/f1.txt",))) assert one_owner_result == Owners( [Address("demo", relative_file_path="f1.txt", target_name="all")] ) two_owners_result = self.request_single_product(Owners, OwnersRequest(("demo/f2.txt",))) assert two_owners_result == Owners( [ Address("demo", relative_file_path="f2.txt", target_name="all"), Address("demo", relative_file_path="f2.txt", target_name="f2"), ] )
async def find_owners( build_configuration: BuildConfiguration, address_mapper: AddressMapper, changed_request: ChangedRequest, ) -> ChangedAddresses: owners = await Get[Owners](OwnersRequest(sources=changed_request.sources)) # If the ChangedRequest does not require dependees, then we're done. if changed_request.include_dependees == IncludeDependeesOption.NONE: return ChangedAddresses(owners.addresses) # Otherwise: find dependees. all_addresses = await Get[Addresses](AddressSpecs( (DescendantAddresses(""), ))) all_structs = [ s.value for s in await MultiGet(Get[HydratedStruct](Address, a) for a in all_addresses) ] bfa = build_configuration.registered_aliases() graph = _DependentGraph.from_iterable( target_types_from_build_file_aliases(bfa), address_mapper, all_structs) if changed_request.include_dependees == IncludeDependeesOption.DIRECT: return ChangedAddresses( Addresses(graph.dependents_of_addresses(owners.addresses))) return ChangedAddresses( Addresses(graph.transitive_dependents_of_addresses(owners.addresses)))
async def addresses_from_raw_specs_with_only_file_owners( specs: RawSpecsWithOnlyFileOwners, owners_not_found_behavior: OwnersNotFoundBehavior) -> Addresses: """Find the owner(s) for each spec.""" paths_per_include = await MultiGet( Get(Paths, PathGlobs, specs.path_globs_for_spec(spec)) for spec in specs.all_specs()) owners_per_include = await MultiGet( Get( Owners, OwnersRequest( paths.files, filter_by_global_options=specs.filter_by_global_options), ) for paths in paths_per_include) addresses: set[Address] = set() for spec, owners in zip(specs.all_specs(), owners_per_include): if (not specs.from_change_detection and owners_not_found_behavior != OwnersNotFoundBehavior.ignore and isinstance(spec, FileLiteralSpec) and not owners): _log_or_raise_unmatched_owners( [PurePath(str(spec))], owners_not_found_behavior, ignore_option="--owners-not-found-behavior=ignore", ) addresses.update(owners) return Addresses(sorted(addresses))
async def find_changed_owners(request: ChangedRequest) -> ChangedAddresses: owners = await Get(Owners, OwnersRequest(request.sources)) if request.dependees == DependeesOption.NONE: return ChangedAddresses(owners) dependees_with_roots = await Get( Dependees, DependeesRequest( owners, transitive=request.dependees == DependeesOption.TRANSITIVE, include_roots=True, ), ) return ChangedAddresses(dependees_with_roots)
def test_owners_source_file_does_not_exist(self) -> None: """Test when a source file belongs to a target, even though the file does not actually exist. This happens, for example, when the file is deleted and we're computing `--changed-since`. In this case, we should not attempt to generate a subtarget and should use the original target. """ self.create_file("demo/f.txt") self.add_to_build_file("demo", "target(sources=['*.txt'])") result = self.request_single_product(Owners, OwnersRequest(("demo/deleted.txt",))) assert result == Owners([Address("demo", target_name="demo")]) # For files that do exist, we should still use a generated subtarget, though. result = self.request_single_product(Owners, OwnersRequest(("demo/f.txt",))) assert result == Owners([Address("demo", relative_file_path="f.txt", target_name="demo")]) # However, if a sibling file uses the original target, then both should be used. result = self.request_single_product( Owners, OwnersRequest(("demo/f.txt", "demo/deleted.txt")) ) assert result == Owners([Address("demo", relative_file_path="f.txt"), Address("demo")])
async def infer_python_init_dependencies( request: InferInitDependencies, python_infer_subsystem: PythonInferSubsystem) -> InferredDependencies: if not python_infer_subsystem.inits: return InferredDependencies([]) fp = request.sources_field.file_path assert fp is not None init_files = await Get( AncestorFiles, AncestorFilesRequest(input_files=(fp, ), requested=("__init__.py", "__init__.pyi")), ) owners = await MultiGet( Get(Owners, OwnersRequest((f, ))) for f in init_files.snapshot.files) return InferredDependencies(itertools.chain.from_iterable(owners))
async def find_changed_owners(request: ChangedRequest, specs_filter: SpecsFilter) -> ChangedAddresses: no_dependees = request.dependees == DependeesOption.NONE owners = await Get( Owners, OwnersRequest( request.sources, # If `--changed-dependees` is used, we cannot eagerly filter out root targets. We # need to first find their dependees, and only then should we filter. See # https://github.com/pantsbuild/pants/issues/15544 filter_by_global_options=no_dependees, ), ) if no_dependees: return ChangedAddresses(owners) # See https://github.com/pantsbuild/pants/issues/15313. We filter out target generators because # they are not useful as aliases for their generated targets in the context of # `--changed-since`. Including them makes it look like all sibling targets from the same # target generator have also changed. # # However, we also must be careful to preserve if target generators are direct owners, which # happens when a generated file is deleted. owner_target_generators = FrozenOrderedSet( addr.maybe_convert_to_target_generator() for addr in owners if addr.is_generated_target) dependees = await Get( Dependees, DependeesRequest( owners, transitive=request.dependees == DependeesOption.TRANSITIVE, include_roots=False, ), ) result = FrozenOrderedSet(owners) | (dependees - owner_target_generators) if specs_filter.is_specified: # Finally, we must now filter out the result to only include what matches our tags, as the # last step of https://github.com/pantsbuild/pants/issues/15544. # # Note that we use `UnexpandedTargets` rather than `Targets` or `FilteredTargets` so that # we preserve target generators. result_as_tgts = await Get(UnexpandedTargets, Addresses(result)) result = FrozenOrderedSet(tgt.address for tgt in result_as_tgts if specs_filter.matches(tgt)) return ChangedAddresses(result)
async def infer_python_init_dependencies( request: InferInitDependencies, python_infer_subsystem: PythonInferSubsystem, python_setup: PythonSetup, ) -> InferredDependencies: if ( not python_infer_subsystem.options.is_default("inits") and not python_infer_subsystem.inits ) or python_infer_subsystem.init_files is InitFilesInference.never: return InferredDependencies([]) ignore_empty_files = ( python_infer_subsystem.options.is_default("inits") and python_infer_subsystem.init_files is InitFilesInference.content_only ) fp = request.sources_field.file_path assert fp is not None init_files = await Get( AncestorFiles, AncestorFilesRequest( input_files=(fp,), requested=("__init__.py", "__init__.pyi"), ignore_empty_files=ignore_empty_files, ), ) owners = await MultiGet(Get(Owners, OwnersRequest((f,))) for f in init_files.snapshot.files) original_tgt, owner_tgts = await MultiGet( Get( WrappedTarget, WrappedTargetRequest( request.sources_field.address, description_of_origin="<infallible>" ), ), Get(Targets, Addresses(itertools.chain.from_iterable(owners))), ) resolve = original_tgt.target[PythonResolveField].normalized_value(python_setup) python_owners = [ tgt.address for tgt in owner_tgts if ( tgt.has_field(PythonSourceField) and tgt[PythonResolveField].normalized_value(python_setup) == resolve ) ] return InferredDependencies(python_owners)
def test_owners_build_file(self) -> None: """A BUILD file owns every target defined in it.""" self.create_files("demo", ["f1.txt", "f2.txt"]) self.add_to_build_file( "demo", dedent( """\ target(name='f1', sources=['f1.txt']) target(name='f2_first', sources=['f2.txt']) target(name='f2_second', sources=['f2.txt']) """ ), ) result = self.request_single_product(Owners, OwnersRequest(("demo/BUILD",))) assert set(result) == { Address("demo", relative_file_path="f1.txt", target_name="f1"), Address("demo", relative_file_path="f2.txt", target_name="f2_first"), Address("demo", relative_file_path="f2.txt", target_name="f2_second"), }
async def infer_python_conftest_dependencies( request: InferConftestDependencies, python_infer_subsystem: PythonInferSubsystem, ) -> InferredDependencies: if not python_infer_subsystem.conftests: return InferredDependencies([]) fp = request.sources_field.file_path assert fp is not None conftest_files = await Get( AncestorFiles, AncestorFilesRequest(input_files=(fp, ), requested=("conftest.py", )), ) owners = await MultiGet( # NB: Because conftest.py files effectively always have content, we require an # owning target. Get(Owners, OwnersRequest((f, ), OwnersNotFoundBehavior.error)) for f in conftest_files.snapshot.files) return InferredDependencies(itertools.chain.from_iterable(owners))
async def infer_python_conftest_dependencies( request: InferConftestDependencies, python_inference: PythonInference, ) -> InferredDependencies: if not python_inference.conftests: return InferredDependencies() # Locate conftest.py files not already in the Snapshot. hydrated_sources = await Get(HydratedSources, HydrateSourcesRequest(request.sources_field)) extra_conftest_files = await Get( AncestorFiles, AncestorFilesRequest("conftest.py", hydrated_sources.snapshot), ) # And add dependencies on their owners. # NB: Because conftest.py files effectively always have content, we require an owning target. owners = await MultiGet( Get(Owners, OwnersRequest((f,), OwnersNotFoundBehavior.error)) for f in extra_conftest_files.snapshot.files ) return InferredDependencies(itertools.chain.from_iterable(owners))
async def infer_python_init_dependencies( request: InferInitDependencies, python_inference: PythonInference ) -> InferredDependencies: if not python_inference.inits: return InferredDependencies() # Locate __init__.py files not already in the Snapshot. hydrated_sources = await Get(HydratedSources, HydrateSourcesRequest(request.sources_field)) extra_init_files = await Get( AncestorFiles, AncestorFilesRequest("__init__.py", hydrated_sources.snapshot), ) # And add dependencies on their owners. # NB: Because the python_sources rules always locate __init__.py files, and will trigger an # error for files that have content but have not already been included via a dependency, we # don't need to error for unowned files here. owners = await MultiGet( Get(Owners, OwnersRequest((f,))) for f in extra_init_files.snapshot.files ) return InferredDependencies(itertools.chain.from_iterable(owners))
async def infer_python_conftest_dependencies( request: InferConftestDependencies, python_infer_subsystem: PythonInferSubsystem, python_setup: PythonSetup, ) -> InferredDependencies: if not python_infer_subsystem.conftests: return InferredDependencies([]) fp = request.sources_field.file_path assert fp is not None conftest_files = await Get( AncestorFiles, AncestorFilesRequest(input_files=(fp,), requested=("conftest.py",)), ) owners = await MultiGet( # NB: Because conftest.py files effectively always have content, we require an # owning target. Get(Owners, OwnersRequest((f,), OwnersNotFoundBehavior.error)) for f in conftest_files.snapshot.files ) original_tgt, owner_tgts = await MultiGet( Get( WrappedTarget, WrappedTargetRequest( request.sources_field.address, description_of_origin="<infallible>" ), ), Get(Targets, Addresses(itertools.chain.from_iterable(owners))), ) resolve = original_tgt.target[PythonResolveField].normalized_value(python_setup) python_owners = [ tgt.address for tgt in owner_tgts if ( tgt.has_field(PythonSourceField) and tgt[PythonResolveField].normalized_value(python_setup) == resolve ) ] return InferredDependencies(python_owners)
def owner(self, owner, f): request = OwnersRequest(sources=(f, )) owners = self.request_single_product(Owners, request) self.assertEqual(set(owner), {i.spec for i in owners.addresses})