async def resolve_target( request: WrappedTargetRequest, target_types_to_generate_requests: TargetTypesToGenerateTargetsRequests, ) -> WrappedTarget: address = request.address base_address = address.maybe_convert_to_target_generator() parametrizations = await Get( _TargetParametrizations, _TargetParametrizationsRequest( base_address, description_of_origin=request.description_of_origin), ) if address.is_generated_target: # TODO: This is an accommodation to allow using file/generator Addresses for # non-generator atom targets. See https://github.com/pantsbuild/pants/issues/14419. original_target = parametrizations.get(base_address) if original_target and not target_types_to_generate_requests.is_generator( original_target): return WrappedTarget(original_target) target = parametrizations.get(address) if target is None: raise ValueError( f"The address `{address}` was not generated by the target `{base_address}`, which " "only generated these addresses:\n\n" f"{bullet_list(str(t.address) for t in parametrizations.all)}\n\n" "Did you mean to use one of those addresses?") return WrappedTarget(target)
async def resolve_target( hydrated_struct: HydratedStruct, registered_target_types: RegisteredTargetTypes, union_membership: UnionMembership, ) -> WrappedTarget: kwargs = hydrated_struct.value.kwargs().copy() type_alias = kwargs.pop("type_alias") # We special case `address` and the field `name`. The `Target` constructor requires an # `Address`, so we use the value pre-calculated via `build_files.py`'s `hydrate_struct` rule. # We throw away the field `name` because it can be accessed via `tgt.address.target_name`, so # there is no (known) reason to preserve the field. address = cast(Address, kwargs.pop("address")) kwargs.pop("name", None) target_type = registered_target_types.aliases_to_types.get(type_alias, None) if target_type is None: raise UnrecognizedTargetTypeException(type_alias, registered_target_types, address=address) # Not every target type has the Dependencies field registered, but the StructWithDependencies # code means that `kwargs` will always have an entry. We must remove `dependencies` from # `kwargs` for target types without the value, otherwise we'll get an "unrecognized field" # error. But, we also need to be careful to error if the user did explicitly specify # `dependencies` in the BUILD file. if kwargs["dependencies"] is None and not target_type.class_has_field( Dependencies, union_membership=union_membership ): kwargs.pop("dependencies") return WrappedTarget(target_type(kwargs, address=address))
def single_target_run( rule_runner: RuleRunner, address: Address, *, program_text: bytes, ) -> Run: workspace = Workspace(rule_runner.scheduler, _enforce_effects=False) class TestRunFieldSet(RunFieldSet): required_fields = () class TestBinaryTarget(Target): alias = "binary" core_fields = () target = TestBinaryTarget({}, address) field_set = TestRunFieldSet.create(target) with mock_console(rule_runner.options_bootstrapper) as (console, _): res = run_rule_with_mocks( run, rule_args=[ create_goal_subsystem(RunSubsystem, args=[], cleanup=True), create_subsystem(GlobalOptions, pants_workdir=rule_runner.pants_workdir, process_cleanup=True), workspace, BuildRoot(), rule_runner.environment, ], mock_gets=[ MockGet( output_type=TargetRootsToFieldSets, input_type=TargetRootsToFieldSetsRequest, mock=lambda _: TargetRootsToFieldSets( {target: [field_set]}), ), MockGet( output_type=WrappedTarget, input_type=WrappedTargetRequest, mock=lambda _: WrappedTarget(target), ), MockGet( output_type=RunRequest, input_type=TestRunFieldSet, mock=lambda _: create_mock_run_request( rule_runner, program_text), ), MockEffect( output_type=InteractiveProcessResult, input_type=InteractiveProcess, mock=rule_runner.run_interactive_process, ), ], ) return cast(Run, res)
async def resolve_target( address: Address, registered_target_types: RegisteredTargetTypes, union_membership: UnionMembership, ) -> WrappedTarget: if address.is_file_target: build_target = await Get(WrappedTarget, Address, address.maybe_convert_to_build_target()) subtarget = generate_subtarget( build_target.target, full_file_name=address.filename, union_membership=union_membership ) return WrappedTarget(subtarget) target_adaptor = await Get(TargetAdaptor, Address, address) target_type = registered_target_types.aliases_to_types.get(target_adaptor.type_alias, None) if target_type is None: raise UnrecognizedTargetTypeException( target_adaptor.type_alias, registered_target_types, address=address ) target = target_type(target_adaptor.kwargs, address=address, union_membership=union_membership) return WrappedTarget(target)
async def resolve_target( address: Address, registered_target_types: RegisteredTargetTypes, union_membership: UnionMembership, target_types_to_generate_requests: TargetTypesToGenerateTargetsRequests, ) -> WrappedTarget: if not address.is_generated_target: target_adaptor = await Get(TargetAdaptor, Address, address) target_type = registered_target_types.aliases_to_types.get( target_adaptor.type_alias, None) if target_type is None: raise UnrecognizedTargetTypeException(target_adaptor.type_alias, registered_target_types, address) if (target_type.deprecated_alias is not None and target_type.deprecated_alias == target_adaptor.type_alias and not address.is_generated_target): await Get(_WarnDeprecatedTarget, _WarnDeprecatedTargetRequest(target_type)) target = target_type(target_adaptor.kwargs, address, union_membership) return WrappedTarget(target) wrapped_generator_tgt = await Get( WrappedTarget, Address, address.maybe_convert_to_target_generator()) generator_tgt = wrapped_generator_tgt.target if not target_types_to_generate_requests.is_generator(generator_tgt): # TODO: Error in this case. You should not use a generator address (or file address) if # the generator does not actually generate. return wrapped_generator_tgt generate_request = target_types_to_generate_requests[type(generator_tgt)] generated = await Get(GeneratedTargets, GenerateTargetsRequest, generate_request(generator_tgt)) if address not in generated: raise ValueError( f"The address `{address}` is not generated by the `{generator_tgt.alias}` target " f"`{generator_tgt.address}`, which only generates these addresses:\n\n" f"{bullet_list(addr.spec for addr in generated)}\n\n" "Did you mean to use one of those addresses?") return WrappedTarget(generated[address])
async def resolve_target( hydrated_struct: HydratedStruct, registered_target_types: RegisteredTargetTypes, union_membership: UnionMembership, ) -> WrappedTarget: kwargs = hydrated_struct.value.kwargs().copy() type_alias = kwargs.pop("type_alias") # We special case `address` and the field `name`. The `Target` constructor requires an # `Address`, so we use the value pre-calculated via `build_files.py`'s `hydrate_struct` rule. # We throw away the field `name` because it can be accessed via `tgt.address.target_name`, so # there is no (known) reason to preserve the field. address = cast(Address, kwargs.pop("address")) kwargs.pop("name", None) # We convert `source` into `sources` because the Target API has no `Source` field, only # `Sources`. if "source" in kwargs and "sources" in kwargs: raise TargetDefinitionException( address, "Cannot specify both `source` and `sources` fields." ) if "source" in kwargs: source = kwargs.pop("source") if not isinstance(source, str): raise TargetDefinitionException( address, f"The `source` field must be a string containing a path relative to the target, " f"but got {source} of type {type(source)}.", ) kwargs["sources"] = [source] target_type = registered_target_types.aliases_to_types.get(type_alias, None) if target_type is None: raise TargetDefinitionException( address, f"Target type {repr(type_alias)} is not recognized. All valid target types: " f"{sorted(registered_target_types.aliases)}.", ) # Not every target type has the Dependencies field registered, but the StructWithDependencies # code means that `kwargs` will always have an entry. We must remove `dependencies` from # `kwargs` for target types without the value, otherwise we'll get an "unrecognized field" # error. But, we also need to be careful to error if the user did explicitly specify # `dependencies` in the BUILD file. if kwargs["dependencies"] is None and not target_type.class_has_field( Dependencies, union_membership=union_membership ): kwargs.pop("dependencies") return WrappedTarget(target_type(kwargs, address=address))
def test_transitive_targets(self) -> None: t1 = MockTarget({}, address=Address.parse(":t1")) t2 = MockTarget({Dependencies.alias: [t1.address]}, address=Address.parse(":t2")) d1 = MockTarget({Dependencies.alias: [t1.address]}, address=Address.parse(":d1")) d2 = MockTarget({Dependencies.alias: [t2.address]}, address=Address.parse(":d2")) d3 = MockTarget({}, address=Address.parse(":d3")) root = MockTarget( {Dependencies.alias: [d1.address, d2.address, d3.address]}, address=Address.parse(":root"), ) self.add_to_build_file( "", dedent("""\ target(name='t1') target(name='t2', dependencies=[':t1']) target(name='d1', dependencies=[':t1']) target(name='d2', dependencies=[':t2']) target(name='d3') target(name='root', dependencies=[':d1', ':d2', ':d3']) """), ) direct_deps = self.request_single_product( Targets, Params(DependenciesRequest(root[Dependencies]), create_options_bootstrapper())) assert direct_deps == Targets([d1, d2, d3]) transitive_target = self.request_single_product( TransitiveTarget, Params(WrappedTarget(root), create_options_bootstrapper())) assert transitive_target.root == root assert { dep_transitive_target.root for dep_transitive_target in transitive_target.dependencies } == {d1, d2, d3} transitive_targets = self.request_single_product( TransitiveTargets, Params(Addresses([root.address, d2.address]), create_options_bootstrapper()), ) assert transitive_targets.roots == (root, d2) assert transitive_targets.closure == FrozenOrderedSet( [root, d2, d1, d3, t2, t1])
def test_transitive_targets(self) -> None: t1 = MockTarget({}, address=Address.parse(":t1")) t2 = MockTarget({Dependencies.alias: [t1.address]}, address=Address.parse(":t2")) d1 = MockTarget({Dependencies.alias: [t1.address]}, address=Address.parse(":d1")) d2 = MockTarget({Dependencies.alias: [t2.address]}, address=Address.parse(":d2")) d3 = MockTarget({}, address=Address.parse(":d3")) root = MockTarget( {Dependencies.alias: [d1.address, d2.address, d3.address]}, address=Address.parse(":root"), ) # TODO: possibly figure out how to deduplicate this when developing utilities for testing # with the Target API. self.add_to_build_file( "", dedent("""\ target(name='t1') target(name='t2', dependencies=[':t1']) target(name='d1', dependencies=[':t1']) target(name='d2', dependencies=[':t2']) target(name='d3') target(name='root', dependencies=[':d1', ':d2', ':d3']) """), ) direct_deps = self.request_single_product( Targets, Addresses(root[Dependencies].value) # type: ignore[arg-type] ) assert direct_deps == Targets([d1, d2, d3]) transitive_target = self.request_single_product( TransitiveTarget, WrappedTarget(root)) assert transitive_target.root == root assert { dep_transitive_target.root for dep_transitive_target in transitive_target.dependencies } == {d1, d2, d3} transitive_targets = self.request_single_product( TransitiveTargets, Addresses([root.address, d2.address])) assert transitive_targets.roots == (root, d2) assert transitive_targets.closure == FrozenOrderedSet( [root, d2, d1, d3, t2, t1])
def assert_build( rule_runner: RuleRunner, address: Address, *extra_log_lines: str, options: dict | None = None, process_assertions: Callable[[Process], None] | None = None, exit_code: int = 0, copy_sources: tuple[str, ...] = (), build_context_snapshot: Snapshot = EMPTY_SNAPSHOT, version_tags: tuple[str, ...] = (), ) -> None: tgt = rule_runner.get_target(address) def build_context_mock( request: DockerBuildContextRequest) -> DockerBuildContext: return DockerBuildContext.create( snapshot=build_context_snapshot, dockerfile_info=DockerfileInfo( request.address, digest=EMPTY_DIGEST, source=os.path.join(address.spec_path, "Dockerfile"), copy_source_paths=copy_sources, version_tags=version_tags, ), build_args=rule_runner.request(DockerBuildArgs, [DockerBuildArgsRequest(tgt)]), build_env=rule_runner.request( DockerBuildEnvironment, [DockerBuildEnvironmentRequest(tgt)]), ) def run_process_mock(process: Process) -> FallibleProcessResult: if process_assertions: process_assertions(process) return FallibleProcessResult( exit_code=exit_code, stdout=b"stdout", stdout_digest=EMPTY_FILE_DIGEST, stderr=b"stderr", stderr_digest=EMPTY_FILE_DIGEST, output_digest=EMPTY_DIGEST, platform=Platform.current, metadata=ProcessResultMetadata(0, "ran_locally", 0), ) if options: opts = options or {} opts.setdefault("registries", {}) opts.setdefault("default_repository", "{name}") opts.setdefault("default_context_root", "") opts.setdefault("build_args", []) opts.setdefault("build_target_stage", None) opts.setdefault("build_verbose", False) opts.setdefault("env_vars", []) docker_options = create_subsystem( DockerOptions, **opts, ) else: docker_options = rule_runner.request(DockerOptions, []) global_options = rule_runner.request(GlobalOptions, []) result = run_rule_with_mocks( build_docker_image, rule_args=[ DockerFieldSet.create(tgt), docker_options, global_options, DockerBinary("/dummy/docker"), ProcessCleanupOption(True), ], mock_gets=[ MockGet( output_type=DockerBuildContext, input_type=DockerBuildContextRequest, mock=build_context_mock, ), MockGet( output_type=WrappedTarget, input_type=WrappedTargetRequest, mock=lambda _: WrappedTarget(tgt), ), MockGet( output_type=FallibleProcessResult, input_type=Process, mock=run_process_mock, ), ], ) assert result.digest == EMPTY_DIGEST assert len(result.artifacts) == 1 assert result.artifacts[0].relpath is None for log_line in extra_log_lines: assert log_line in result.artifacts[0].extra_log_lines