def test_get_field() -> None: extensions = ("FortranExt1",) tgt = FortranTarget({FortranExtensions.alias: extensions}, Address("", target_name="lib")) assert tgt[FortranExtensions].value == extensions assert tgt.get(FortranExtensions).value == extensions assert tgt.get(FortranExtensions, default_raw_value=["FortranExt2"]).value == extensions # Default field value. This happens when the field is registered on the target type, but the # user does not explicitly set the field in the BUILD file. default_field_tgt = FortranTarget({}, Address("", target_name="default")) assert default_field_tgt[FortranExtensions].value == () assert default_field_tgt.get(FortranExtensions).value == () assert default_field_tgt.get(FortranExtensions, default_raw_value=["FortranExt2"]).value == () # Example of a call site applying its own default value instead of the field's default value. assert default_field_tgt[FortranExtensions].value or 123 == 123 assert ( FortranTarget.class_get_field(FortranExtensions, union_membership=UnionMembership({})) is FortranExtensions ) # Field is not registered on the target. with pytest.raises(KeyError) as exc: default_field_tgt[UnrelatedField] assert UnrelatedField.__name__ in str(exc) with pytest.raises(KeyError) as exc: FortranTarget.class_get_field(UnrelatedField, union_membership=UnionMembership({})) assert UnrelatedField.__name__ in str(exc) assert default_field_tgt.get(UnrelatedField).value == UnrelatedField.default assert default_field_tgt.get( UnrelatedField, default_raw_value=not UnrelatedField.default ).value == (not UnrelatedField.default)
def test_union_membership_from_rules() -> None: @union class Base: pass class A: pass class B: pass assert UnionMembership.from_rules([UnionRule(Base, A), UnionRule(Base, B)]) == UnionMembership( {Base: FrozenOrderedSet([A, B])} )
def __init__( self, unhydrated_values: Dict[str, Any], *, address: Address, # NB: `union_membership` is only optional to facilitate tests. In production, we should # always provide this parameter. This should be safe to do because production code should # rarely directly instantiate Targets and should instead use the engine to request them. union_membership: Optional[UnionMembership] = None, ) -> None: self.address = address self.plugin_fields = self._find_plugin_fields(union_membership or UnionMembership({})) field_values = {} aliases_to_field_types = { field_type.alias: field_type for field_type in self.field_types } for alias, value in unhydrated_values.items(): if alias not in aliases_to_field_types: raise InvalidFieldException( f"Unrecognized field `{alias}={value}` in target {address}. Valid fields for " f"the target type `{self.alias}`: {sorted(aliases_to_field_types.keys())}.", ) field_type = aliases_to_field_types[alias] field_values[field_type] = field_type(value, address=address) # For undefined fields, mark the raw value as None. for field_type in set(self.field_types) - set(field_values.keys()): field_values[field_type] = field_type(raw_value=None, address=address) self.field_values = FrozenDict(field_values)
def test_determine_renamed_fields() -> None: class DeprecatedField(StringField): alias = "new_name" deprecated_alias = "old_name" deprecated_alias_removal_version = "99.9.0.dev0" class OkayField(StringField): alias = "okay" class Tgt(Target): alias = "tgt" core_fields = (DeprecatedField, OkayField) deprecated_alias = "deprecated_tgt" deprecated_alias_removal_version = "99.9.0.dev0" class TgtGenerator(TargetGenerator): alias = "generator" core_fields = () moved_fields = (DeprecatedField, OkayField) registered_targets = RegisteredTargetTypes.create([Tgt, TgtGenerator]) result = determine_renamed_field_types(registered_targets, UnionMembership({})) deprecated_fields = FrozenDict( {DeprecatedField.deprecated_alias: DeprecatedField.alias}) assert result.target_field_renames == FrozenDict({ k: deprecated_fields for k in (TgtGenerator.alias, Tgt.alias, Tgt.deprecated_alias) })
def test_list_single() -> None: class CustomField(BoolField): """My custom field! Use this field to... """ alias = "custom_field" required = True tests_target_stdout = run_goal( union_membership=UnionMembership.from_rules( [FortranTests.register_plugin_field(CustomField)] ), details_target=FortranTests.alias, ) assert tests_target_stdout == dedent( """\ fortran_tests ------------- Tests for Fortran code. This assumes that you use the FRUIT test framework. Valid fields: custom_field type: bool, required My custom field! Use this field to... fortran_version type: str | None, default: None timeout type: int | None, default: None The number of seconds to run before timing out. """ ) binary_target_stdout = run_goal(details_target=FortranBinary.alias) assert binary_target_stdout == dedent( """\ fortran_binary -------------- Valid fields: archive_format type: '.tar' | '.tgz' | None, default: '.tgz' error_behavior type: 'error' | 'ignore' | 'warn', required fortran_version type: str | None, default: None """ )
async def resources_bsp_target( request: ResourcesForOneBSPTargetRequest, union_membership: UnionMembership, ) -> BSPResourcesResult: targets = await Get(Targets, BSPBuildTargetInternal, request.bsp_target) resources_request_types: FrozenOrderedSet[Type[BSPResourcesRequest]] = union_membership.get( BSPResourcesRequest ) field_sets_by_request_type: dict[Type[BSPResourcesRequest], set[FieldSet]] = defaultdict(set) for target in targets: for resources_request_type in resources_request_types: field_set_type = resources_request_type.field_set_type if field_set_type.is_applicable(target): field_set = field_set_type.create(target) field_sets_by_request_type[resources_request_type].add(field_set) resources_results = await MultiGet( Get( BSPResourcesResult, BSPResourcesRequest, resources_request_type(bsp_target=request.bsp_target, field_sets=tuple(field_sets)), ) for resources_request_type, field_sets in field_sets_by_request_type.items() ) resources = tuple(sorted({resource for rr in resources_results for resource in rr.resources})) output_digest = await Get(Digest, MergeDigests([rr.output_digest for rr in resources_results])) return BSPResourcesResult( resources=resources, output_digest=output_digest, )
def for_targets( union_membership: UnionMembership, component: CoarsenedTarget, resolve: CoursierResolveKey ) -> ClasspathEntryRequest: """Constructs a subclass compatible with the members of the CoarsenedTarget.""" compatible = [] impls = union_membership.get(ClasspathEntryRequest) for impl in impls: if all(any(fs.is_applicable(t) for fs in impl.field_sets) for t in component.members): compatible.append(impl) if len(compatible) == 1: return compatible[0](component, resolve) impls_str = ", ".join(sorted(impl.__name__ for impl in impls)) targets_str = "\n ".join( sorted(f"{t.address.spec}\t({type(t).alias})" for t in component.members) ) if compatible: raise ClasspathSourceAmbiguity( f"More than one JVM compiler instance ({impls_str}) was compatible with " f"the inputs:\n {targets_str}" ) else: raise ClasspathSourceMissing( f"No single JVM compiler instance (from: {impls_str}) was compatible with all of the " f"the inputs:\n {targets_str}" )
async def resolve_bsp_build_target_addresses( bsp_target: BSPBuildTargetInternal, union_membership: UnionMembership, ) -> Targets: # NB: Using `RawSpecs` directly rather than `RawSpecsWithoutFileOwners` results in a rule graph cycle. targets = await Get( Targets, RawSpecsWithoutFileOwners, RawSpecsWithoutFileOwners.from_raw_specs(bsp_target.specs), ) if bsp_target.definition.resolve_filter is None: return targets resolve_filter = bsp_target.definition.resolve_filter resolve_prefix, matched, resolve_value = resolve_filter.partition(":") if not resolve_prefix or not matched: raise ValueError( f"The `resolve` filter for `{bsp_target}` must have a platform or language specific " f"prefix like `$lang:$filter`, but the configured value: `{resolve_filter}` did not." ) # TODO: See `BSPResolveFieldFactoryRequest` re: this awkwardness. factories = await MultiGet( Get(BSPResolveFieldFactoryResult, BSPResolveFieldFactoryRequest, request()) for request in union_membership.get(BSPResolveFieldFactoryRequest) if request.resolve_prefix == resolve_prefix) return Targets(t for t in targets if any((factory.resolve_field_value)(t) == resolve_value for factory in factories))
def run_typecheck_rule( *, request_types: List[Type[TypecheckRequest]], targets: List[TargetWithOrigin], include_sources: bool = True, ) -> Tuple[int, str]: console = MockConsole(use_colors=False) union_membership = UnionMembership({TypecheckRequest: request_types}) result: Typecheck = run_rule( typecheck, rule_args=[console, TargetsWithOrigins(targets), union_membership], mock_gets=[ MockGet( product_type=TypecheckResults, subject_type=TypecheckRequest, mock=lambda field_set_collection: field_set_collection.typecheck_results, ), MockGet( product_type=FieldSetsWithSources, subject_type=FieldSetsWithSourcesRequest, mock=lambda field_sets: FieldSetsWithSources( field_sets if include_sources else () ), ), ], union_membership=union_membership, ) assert not console.stdout.getvalue() return result.exit_code, console.stderr.getvalue()
def test_has_fields() -> None: empty_union_membership = UnionMembership({}) tgt = FortranTarget({}, Address("", target_name="lib")) assert tgt.field_types == (FortranExtensions, FortranVersion) assert FortranTarget.class_field_types( union_membership=empty_union_membership) == ( FortranExtensions, FortranVersion, ) assert tgt.has_fields([]) is True assert FortranTarget.class_has_fields( [], union_membership=empty_union_membership) is True assert tgt.has_fields([FortranExtensions]) is True assert tgt.has_field(FortranExtensions) is True assert (FortranTarget.class_has_fields( [FortranExtensions], union_membership=empty_union_membership) is True) assert (FortranTarget.class_has_field( FortranExtensions, union_membership=empty_union_membership) is True) assert tgt.has_fields([UnrelatedField]) is False assert tgt.has_field(UnrelatedField) is False assert (FortranTarget.class_has_fields( [UnrelatedField], union_membership=empty_union_membership) is False) assert (FortranTarget.class_has_field( UnrelatedField, union_membership=empty_union_membership) is False) assert tgt.has_fields([FortranExtensions, UnrelatedField]) is False assert (FortranTarget.class_has_fields( [FortranExtensions, UnrelatedField], union_membership=empty_union_membership) is False)
async def merge_first_party_module_mappings( union_membership: UnionMembership, ) -> FirstPartyPythonModuleMapping: all_mappings = await MultiGet( Get( FirstPartyPythonMappingImpl, FirstPartyPythonMappingImplMarker, marker_cls(), ) for marker_cls in union_membership.get(FirstPartyPythonMappingImplMarker) ) resolves_to_modules_to_providers: DefaultDict[ ResolveName, DefaultDict[str, list[ModuleProvider]] ] = defaultdict(lambda: defaultdict(list)) for mapping_impl in all_mappings: for resolve, modules_to_providers in mapping_impl.items(): for module, providers in modules_to_providers.items(): resolves_to_modules_to_providers[resolve][module].extend(providers) return FirstPartyPythonModuleMapping( ( resolve, FrozenDict( (mod, tuple(sorted(providers))) for mod, providers in sorted(mapping.items()) ), ) for resolve, mapping in sorted(resolves_to_modules_to_providers.items()) )
def _register_task(self, tasks, rule: TaskRule, union_membership: UnionMembership) -> None: """Register the given TaskRule with the native scheduler.""" self._native.lib.tasks_task_begin( tasks, rule.func, rule.output_type, issubclass(rule.output_type, EngineAwareReturnType), rule.cacheable, rule.canonical_name, rule.desc or "", rule.level.level, ) for selector in rule.input_selectors: self._native.lib.tasks_add_select(tasks, selector) def add_get_edge(product, subject): self._native.lib.tasks_add_get(tasks, product, subject) for the_get in rule.input_gets: if union.is_instance(the_get.input_type): # If the registered subject type is a union, add Get edges to all registered # union members. for union_member in union_membership.get(the_get.input_type): add_get_edge(the_get.output_type, union_member) else: # Otherwise, the Get subject is a "concrete" type, so add a single Get edge. add_get_edge(the_get.output_type, the_get.input_type) self._native.lib.tasks_task_end(tasks)
def mk_scheduler( self, rules, include_trace_on_error: bool = True, ) -> SchedulerSession: """Creates a SchedulerSession for a Scheduler with the given Rules installed.""" work_dir = self._create_work_dir() build_root = os.path.join(work_dir, "build_root") os.makedirs(build_root) local_store_dir = os.path.realpath(safe_mkdtemp()) local_execution_root_dir = os.path.realpath(safe_mkdtemp()) named_caches_dir = os.path.realpath(safe_mkdtemp()) scheduler = Scheduler( native=self._native, ignore_patterns=[], use_gitignore=False, build_root=build_root, local_store_dir=local_store_dir, local_execution_root_dir=local_execution_root_dir, named_caches_dir=named_caches_dir, ca_certs_path=None, rules=rules, union_membership=UnionMembership({}), executor=self._executor, execution_options=DEFAULT_EXECUTION_OPTIONS, include_trace_on_error=include_trace_on_error, ) return scheduler.new_session(build_id="buildid_for_test", )
def target_types_to_generate_targets_requests( union_membership: UnionMembership, ) -> TargetTypesToGenerateTargetsRequests: return TargetTypesToGenerateTargetsRequests({ request_cls.generate_from: request_cls # type: ignore[misc] for request_cls in union_membership.get(GenerateTargetsRequest) })
def test_targets_with_sources_types() -> None: class Sources1(Sources): pass class Sources2(Sources): pass class CodegenSources(Sources): pass class Tgt1(Target): alias = "tgt1" core_fields = (Sources1,) class Tgt2(Target): alias = "tgt2" core_fields = (Sources2,) class CodegenTgt(Target): alias = "codegen_tgt" core_fields = (CodegenSources,) class GenSources(GenerateSourcesRequest): input = CodegenSources output = Sources1 tgt1 = Tgt1({}, address=Address("tgt1")) tgt2 = Tgt2({}, address=Address("tgt2")) codegen_tgt = CodegenTgt({}, address=Address("codegen_tgt")) result = targets_with_sources_types( [Sources1], [tgt1, tgt2, codegen_tgt], union_membership=UnionMembership({GenerateSourcesRequest: [GenSources]}), ) assert set(result) == {tgt1, codegen_tgt}
def run_typecheck_rule( *, request_types: Sequence[Type[CheckRequest]], targets: list[Target], only: list[str] | None = None, ) -> Tuple[int, str]: union_membership = UnionMembership({CheckRequest: request_types}) check_subsystem = create_subsystem(CheckSubsystem, only=only or []) with mock_console(create_options_bootstrapper()) as (console, stdio_reader): rule_runner = RuleRunner() result: Check = run_rule_with_mocks( check, rule_args=[ console, Workspace(rule_runner.scheduler, _enforce_effects=False), Targets(targets), DistDir(relpath=Path("dist")), union_membership, check_subsystem, ], mock_gets=[ MockGet( output_type=CheckResults, input_type=CheckRequest, mock=lambda field_set_collection: field_set_collection.check_results, ), ], union_membership=union_membership, ) assert not stdio_reader.get_stdout() return result.exit_code, stdio_reader.get_stderr()
async def export( console: Console, targets: Targets, export_subsystem: ExportSubsystem, workspace: Workspace, union_membership: UnionMembership, build_root: BuildRoot, dist_dir: DistDir, ) -> Export: request_types = cast("Iterable[type[ExportableDataRequest]]", union_membership.get(ExportableDataRequest)) requests = tuple(request_type(targets) for request_type in request_types) exportables = await MultiGet( Get(ExportableData, ExportableDataRequest, request) for request in requests) prefixed_digests = await MultiGet( Get(Digest, AddPrefix(exp.digest, exp.reldir)) for exp in exportables) output_dir = os.path.join(str(dist_dir.relpath), "export") merged_digest = await Get(Digest, MergeDigests(prefixed_digests)) dist_digest = await Get(Digest, AddPrefix(merged_digest, output_dir)) workspace.write_digest(dist_digest) for exp in exportables: for symlink in exp.symlinks: # Note that if symlink.source_path is an abspath, join returns it unchanged. source_abspath = os.path.join(build_root.path, symlink.source_path) link_abspath = os.path.abspath( os.path.join(output_dir, exp.reldir, symlink.link_rel_path)) absolute_symlink(source_abspath, link_abspath) console.print_stdout( f"Wrote {exp.description} to {os.path.join(output_dir, exp.reldir)}" ) return Export(exit_code=0)
def run_lint_rule( *, config_collection_types: List[Type[LinterConfigurations]], targets: List[TargetWithOrigin], per_target_caching: bool, include_sources: bool = True, ) -> Tuple[int, str]: console = MockConsole(use_colors=False) union_membership = UnionMembership({LinterConfigurations: config_collection_types}) result: Lint = run_rule( lint, rule_args=[ console, TargetsWithOrigins(targets), create_goal_subsystem(LintOptions, per_target_caching=per_target_caching), union_membership, ], mock_gets=[ MockGet( product_type=LintResult, subject_type=LinterConfigurations, mock=lambda config_collection: config_collection.lint_result, ), MockGet( product_type=ConfigurationsWithSources, subject_type=ConfigurationsWithSourcesRequest, mock=lambda configs: ConfigurationsWithSources( configs if include_sources else () ), ), ], union_membership=union_membership, ) return result.exit_code, console.stdout.getvalue()
async def determine_setup_kwargs( exported_target: ExportedTarget, union_membership: UnionMembership) -> SetupKwargs: target = exported_target.target setup_kwargs_requests = union_membership.get( SetupKwargsRequest) # type: ignore[misc] applicable_setup_kwargs_requests = tuple( request for request in setup_kwargs_requests if request.is_applicable(target)) # If no provided implementations, fall back to our default implementation that simply returns # what the user explicitly specified in the BUILD file. if not applicable_setup_kwargs_requests: return SetupKwargs(exported_target.provides.kwargs, address=target.address) if len(applicable_setup_kwargs_requests) > 1: possible_requests = sorted( plugin.__name__ for plugin in applicable_setup_kwargs_requests) raise ValueError( f"Multiple of the registered `SetupKwargsRequest`s can work on the target " f"{target.address}, and it's ambiguous which to use: {possible_requests}\n\nPlease " "activate fewer implementations, or make the classmethod `is_applicable()` more " "precise so that only one implementation is applicable for this target." ) setup_kwargs_request = tuple(applicable_setup_kwargs_requests)[0] return await Get(SetupKwargs, SetupKwargsRequest, setup_kwargs_request(target))
def run_typecheck_rule( *, request_types: List[Type[TypecheckRequest]], targets: List[Target], include_sources: bool = True, ) -> Tuple[int, str]: union_membership = UnionMembership({TypecheckRequest: request_types}) with mock_console(create_options_bootstrapper()) as (console, stdio_reader): result: Typecheck = run_rule_with_mocks( typecheck, rule_args=[console, Targets(targets), union_membership], mock_gets=[ MockGet( output_type=EnrichedTypecheckResults, input_type=TypecheckRequest, mock=lambda field_set_collection: field_set_collection. typecheck_results, ), MockGet( output_type=FieldSetsWithSources, input_type=FieldSetsWithSourcesRequest, mock=lambda field_sets: FieldSetsWithSources( field_sets if include_sources else ()), ), ], union_membership=union_membership, ) assert not stdio_reader.get_stdout() return result.exit_code, stdio_reader.get_stderr()
def create( cls, env: Mapping[str, str], options_bootstrapper: OptionsBootstrapper, scheduler: Optional[LegacyGraphScheduler] = None, ) -> "LocalPantsRunner": """Creates a new LocalPantsRunner instance by parsing options. By the time this method runs, logging will already have been initialized in either PantsRunner or DaemonPantsRunner. :param env: The environment (e.g. os.environ) for this run. :param options_bootstrapper: The OptionsBootstrapper instance to reuse. :param scheduler: If being called from the daemon, a warmed scheduler to use. """ build_root = get_buildroot() global_bootstrap_options = options_bootstrapper.bootstrap_options.for_global_scope( ) options, build_config = LocalPantsRunner.parse_options( options_bootstrapper) # Option values are usually computed lazily on demand, # but command line options are eagerly computed for validation. for scope in options.scope_to_flags.keys(): options.for_scope(scope) # Verify configs. if global_bootstrap_options.verify_config: options.verify_configs(options_bootstrapper.config) union_membership = UnionMembership(build_config.union_rules()) # If we're running with the daemon, we'll be handed a warmed Scheduler, which we use # to initialize a session here. graph_session = cls._init_graph_session(options_bootstrapper, build_config, options, scheduler) global_options = options.for_global_scope() specs = SpecsCalculator.create( options=options, build_root=build_root, session=graph_session.scheduler_session, exclude_patterns=tuple(global_options.exclude_target_regexp), tags=tuple(global_options.tag), ) profile_path = env.get("PANTS_PROFILE") return cls( build_root=build_root, options=options, options_bootstrapper=options_bootstrapper, build_config=build_config, specs=specs, graph_session=graph_session, union_membership=union_membership, profile_path=profile_path, _run_tracker=RunTracker.global_instance(), )
def mk_scheduler( self, tmp_path: Path, rules, include_trace_on_error: bool = True, max_workunit_verbosity: LogLevel = LogLevel.DEBUG, ) -> SchedulerSession: """Creates a SchedulerSession for a Scheduler with the given Rules installed.""" build_root = tmp_path / "build_root" build_root.mkdir(parents=True, exist_ok=True) local_execution_root_dir = os.path.realpath(safe_mkdtemp()) named_caches_dir = os.path.realpath(safe_mkdtemp()) scheduler = Scheduler( ignore_patterns=[], use_gitignore=False, build_root=build_root.as_posix(), local_execution_root_dir=local_execution_root_dir, named_caches_dir=named_caches_dir, ca_certs_path=None, rules=rules, union_membership=UnionMembership({}), executor=self._executor, execution_options=DEFAULT_EXECUTION_OPTIONS, local_store_options=DEFAULT_LOCAL_STORE_OPTIONS, include_trace_on_error=include_trace_on_error, ) return scheduler.new_session( build_id="buildid_for_test", max_workunit_level=max_workunit_verbosity, )
def run_lint_rule( rule_runner: RuleRunner, *, lint_request_types: List[Type[LintRequest]], targets: List[Target], per_file_caching: bool, ) -> Tuple[int, str]: with mock_console(rule_runner.options_bootstrapper) as (console, stdio_reader): union_membership = UnionMembership({LintRequest: lint_request_types}) result: Lint = run_rule_with_mocks( lint, rule_args=[ console, Workspace(rule_runner.scheduler, _enforce_effects=False), Targets(targets), create_goal_subsystem(LintSubsystem, per_file_caching=per_file_caching, per_target_caching=False), union_membership, DistDir(relpath=Path("dist")), ], mock_gets=[ MockGet( output_type=LintResults, input_type=LintRequest, mock=lambda field_set_collection: field_set_collection. lint_results, ) ], union_membership=union_membership, ) assert not stdio_reader.get_stdout() return result.exit_code, stdio_reader.get_stderr()
async def construct_workunits_callback_factories( union_membership: UnionMembership, ) -> WorkunitsCallbackFactories: request_types = union_membership.get(WorkunitsCallbackFactoryRequest) workunit_callback_factories = await MultiGet( Get(WorkunitsCallbackFactory, WorkunitsCallbackFactoryRequest, request_type()) for request_type in request_types) return WorkunitsCallbackFactories(workunit_callback_factories)
def test_has_fields() -> None: empty_union_membership = UnionMembership({}) tgt = FortranTarget({}, address=Address.parse(":lib")) assert tgt.field_types == (FortranExtensions, FortranSources) assert FortranTarget.class_field_types( union_membership=empty_union_membership) == ( FortranExtensions, FortranSources, ) assert tgt.has_fields([]) is True assert FortranTarget.class_has_fields( [], union_membership=empty_union_membership) is True assert tgt.has_fields([FortranExtensions]) is True assert tgt.has_field(FortranExtensions) is True assert (FortranTarget.class_has_fields( [FortranExtensions], union_membership=empty_union_membership) is True) assert (FortranTarget.class_has_field( FortranExtensions, union_membership=empty_union_membership) is True) assert tgt.has_fields([UnrelatedField]) is False assert tgt.has_field(UnrelatedField) is False assert (FortranTarget.class_has_fields( [UnrelatedField], union_membership=empty_union_membership) is False) assert (FortranTarget.class_has_field( UnrelatedField, union_membership=empty_union_membership) is False) assert tgt.has_fields([FortranExtensions, UnrelatedField]) is False assert (FortranTarget.class_has_fields( [FortranExtensions, UnrelatedField], union_membership=empty_union_membership) is False)
def test_add_custom_fields() -> None: class CustomField(BoolField): alias: ClassVar = "custom_field" default: ClassVar = False union_membership = UnionMembership({FortranTarget.PluginField: [CustomField]}) tgt_values = {CustomField.alias: True} tgt = FortranTarget( tgt_values, address=Address.parse(":lib"), union_membership=union_membership ) assert tgt.field_types == (FortranExtensions, FortranSources, CustomField) assert tgt.core_fields == (FortranExtensions, FortranSources) assert tgt.plugin_fields == (CustomField,) assert tgt.has_field(CustomField) is True assert FortranTarget.class_field_types(union_membership=union_membership) == ( FortranExtensions, FortranSources, CustomField, ) assert FortranTarget.class_has_field(CustomField, union_membership=union_membership) is True assert tgt[CustomField].value is True default_tgt = FortranTarget( {}, address=Address.parse(":default"), union_membership=union_membership ) assert default_tgt[CustomField].value is False
def find_valid_field_sets( request: FieldSetsPerTargetRequest, union_membership: UnionMembership) -> FieldSetsPerTarget: field_set_types = union_membership.get(request.field_set_superclass) return FieldSetsPerTarget((field_set_type.create(target) for field_set_type in field_set_types if field_set_type.is_applicable(target)) for target in request.targets)
async def export_codegen( targets: Targets, union_membership: UnionMembership, workspace: Workspace, dist_dir: DistDir, registered_target_types: RegisteredTargetTypes, ) -> ExportCodegen: # We run all possible code generators. Running codegen requires specifying the expected # output_type, so we must inspect what is possible to generate. all_generate_request_types = union_membership.get(GenerateSourcesRequest) inputs_to_outputs = { req.input: req.output for req in all_generate_request_types } codegen_sources_fields_with_output = [] for tgt in targets: if not tgt.has_field(SourcesField): continue sources = tgt[SourcesField] for input_type in inputs_to_outputs: if isinstance(sources, input_type): output_type = inputs_to_outputs[input_type] codegen_sources_fields_with_output.append( (sources, output_type)) if not codegen_sources_fields_with_output: codegen_targets = sorted({ tgt_type.alias for tgt_type in registered_target_types.types for input_sources in inputs_to_outputs.keys() if tgt_type.class_has_field(input_sources, union_membership=union_membership) }) logger.warning( "No codegen files/targets matched. All codegen target types: " f"{', '.join(codegen_targets)}") return ExportCodegen(exit_code=0) all_hydrated_sources = await MultiGet( Get( HydratedSources, HydrateSourcesRequest( sources_and_output_type[0], for_sources_types=(sources_and_output_type[1], ), enable_codegen=True, ), ) for sources_and_output_type in codegen_sources_fields_with_output) merged_digest = await Get( Digest, MergeDigests(hydrated_sources.snapshot.digest for hydrated_sources in all_hydrated_sources), ) dest = str(dist_dir.relpath / "codegen") logger.info(f"Writing generated files to {dest}") workspace.write_digest(merged_digest, path_prefix=dest) return ExportCodegen(exit_code=0)
def test_get_field() -> None: extensions = ("FortranExt1", ) tgt = FortranTarget({FortranExtensions.alias: extensions}, address=Address.parse(":lib")) assert tgt[FortranExtensions].value == extensions assert tgt.get(FortranExtensions).value == extensions assert tgt.get(FortranExtensions, default_raw_value=["FortranExt2"]).value == extensions # Default field value. This happens when the field is registered on the target type, but the # user does not explicitly set the field in the BUILD file. # # NB: `default_raw_value` is not used in this case - that parameter is solely used when # the field is not registered on the target type. To override the default field value, either # subclass the Field and create a new target, or, in your call site, interpret the result and # and apply your default. default_field_tgt = FortranTarget({}, address=Address.parse(":default")) assert default_field_tgt[FortranExtensions].value == () assert default_field_tgt.get(FortranExtensions).value == () assert default_field_tgt.get(FortranExtensions, default_raw_value=["FortranExt2"]).value == () # Example of a call site applying its own default value instead of the field's default value. assert default_field_tgt[FortranExtensions].value or 123 == 123 assert (FortranTarget.class_get_field(FortranExtensions, union_membership=UnionMembership({})) is FortranExtensions) # Field is not registered on the target. with pytest.raises(KeyError) as exc: default_field_tgt[UnrelatedField] assert UnrelatedField.__name__ in str(exc) with pytest.raises(KeyError) as exc: FortranTarget.class_get_field(UnrelatedField, union_membership=UnionMembership({})) assert UnrelatedField.__name__ in str(exc) assert default_field_tgt.get( UnrelatedField).value == UnrelatedField.default assert default_field_tgt.get( UnrelatedField, default_raw_value=not UnrelatedField.default).value == ( not UnrelatedField.default)
def run_goal_rules( self, *, options_bootstrapper: OptionsBootstrapper, union_membership: UnionMembership, options: Options, goals: Iterable[str], specs: Specs, ) -> int: """Runs @goal_rules sequentially and interactively by requesting their implicit Goal products. For retryable failures, raises scheduler.ExecutionError. :returns: An exit code. """ global_options = options.for_global_scope() console = Console( use_colors=global_options.colors, session=self.scheduler_session if global_options.get("v2_ui") else None, ) workspace = Workspace(self.scheduler_session) interactive_runner = InteractiveRunner(self.scheduler_session) for goal in goals: goal_product = self.goal_map[goal] # NB: We no-op for goals that have no V2 implementation because no relevant backends are # registered. This allows us to safely set `--v1 --v2`, even if no V2 backends are registered. # Once V1 is removed, we might want to reconsider the behavior to instead warn or error when # trying to run something like `./pants run` without any backends registered. is_implemented = union_membership.has_members_for_all( goal_product.subsystem_cls.required_union_implementations) if not is_implemented: continue params = Params( specs.provided_specs, options_bootstrapper, console, workspace, interactive_runner, ) logger.debug( f"requesting {goal_product} to satisfy execution of `{goal}` goal" ) try: exit_code = self.scheduler_session.run_goal_rule( goal_product, params) finally: console.flush() if exit_code != PANTS_SUCCEEDED_EXIT_CODE: return exit_code return PANTS_SUCCEEDED_EXIT_CODE