def __init__( self, path: str, name: str, type_alias: str, triggering_sources: Iterable[str], owned_sources: Iterable[str], *, addressable: bool = True, kwargs: Mapping[str, str | int | bool | tuple[str, ...]] | None = None, comments: Iterable[str] = tuple(), ) -> None: self.path = path self.name = name self.type_alias = type_alias self.triggering_sources = tuple(triggering_sources) self.owned_sources = tuple(owned_sources) self.addressable = addressable self.kwargs = FrozenDict(kwargs or {}) self.comments = tuple(comments)
async def map_import_paths_to_packages(go_tgts: AllGoTargets) -> ImportPathToPackages: mapping: dict[str, list[Address]] = defaultdict(list) first_party_addresses = [] first_party_gets = [] for tgt in go_tgts: if tgt.has_field(GoImportPathField): import_path = tgt[GoImportPathField].value mapping[import_path].append(tgt.address) else: first_party_addresses.append(tgt.address) first_party_gets.append( Get(FirstPartyPkgImportPath, FirstPartyPkgImportPathRequest(tgt.address)) ) first_party_import_paths = await MultiGet(first_party_gets) for import_path_info, addr in zip(first_party_import_paths, first_party_addresses): mapping[import_path_info.import_path].append(addr) frozen_mapping = FrozenDict({ip: tuple(tgts) for ip, tgts in mapping.items()}) return ImportPathToPackages(frozen_mapping)
async def map_addresses_to_dependees() -> AddressToDependees: # Get every target in the project so that we can iterate over them to find their dependencies. all_expanded_targets, all_explicit_targets = await MultiGet( Get(Targets, AddressSpecs([DescendantAddresses("")])), Get(UnexpandedTargets, AddressSpecs([DescendantAddresses("")])), ) all_targets = {*all_expanded_targets, *all_explicit_targets} dependencies_per_target = await MultiGet( Get(Addresses, DependenciesRequest(tgt.get(Dependencies))) for tgt in all_targets ) address_to_dependees = defaultdict(set) for tgt, dependencies in zip(all_targets, dependencies_per_target): for dependency in dependencies: address_to_dependees[dependency].add(tgt.address) return AddressToDependees( FrozenDict( {addr: FrozenOrderedSet(dependees) for addr, dependees in address_to_dependees.items()} ) )
def assert_parsed( spec: str, *, path_component: str, target_component: str | None = None, parameters: dict[str, str] | None = None, generated_component: str | None = None, relative_to: str | None = None, ) -> None: ai = AddressInput.parse(spec, relative_to=relative_to) assert ai.path_component == path_component if target_component is None: assert ai.target_component is None else: assert ai.target_component == target_component assert ai.parameters == FrozenDict(parameters or {}) if generated_component is None: assert ai.generated_component is None else: assert ai.generated_component == generated_component
def test_first_party_modules_mapping() -> None: util_addr = Address("src/python/util", relative_file_path="strutil.py") test_addr = Address("tests/python/project_test", relative_file_path="test.py") mapping = FirstPartyModuleToAddressMapping( FrozenDict({ "util.strutil": (util_addr, ), "project_test.test": (test_addr, ) })) assert mapping.addresses_for_module("util.strutil") == (util_addr, ) assert mapping.addresses_for_module("util.strutil.ensure_text") == ( util_addr, ) assert not mapping.addresses_for_module("util") assert mapping.addresses_for_module("project_test.test") == (test_addr, ) assert mapping.addresses_for_module("project_test.test.TestDemo") == ( test_addr, ) assert not mapping.addresses_for_module( "project_test.test.TestDemo.method") assert not mapping.addresses_for_module("project_test") assert not mapping.addresses_for_module("project.test")
def __init__( self, argv: Iterable[str], *, env: Optional[Mapping[str, str]] = None, input_digest: Digest = EMPTY_DIGEST, run_in_workspace: bool = False, ) -> None: """Request to run a subprocess in the foreground, similar to subprocess.run(). Unlike `Process`, the result will not be cached. To run the process, request `InteractiveRunner` in a `@goal_rule`, then use `interactive_runner.run()`. """ self.argv = tuple(argv) self.env = FrozenDict(env or {}) self.input_digest = input_digest self.run_in_workspace = run_in_workspace self.__post_init__()
def perform_test(extra_targets: list[str], dym: str) -> None: parser = Parser( target_type_aliases=["tgt", *extra_targets], object_aliases=BuildFileAliases( objects={"obj": 0}, context_aware_object_factories={"caof": lambda parse_context: lambda _: None}, ), ) prelude_symbols = BuildFilePreludeSymbols(FrozenDict({"prelude": 0})) fmt_extra_sym = str(extra_targets)[1:-1] + (", ") if len(extra_targets) != 0 else "" with pytest.raises(ParseError) as exc: parser.parse("dir/BUILD", "fake", prelude_symbols) assert str(exc.value) == ( f"Name 'fake' is not defined.\n\n{dym}" "If you expect to see more symbols activated in the below list," f" refer to {docs_url('enabling-backends')} for all available" " backends to activate.\n\n" f"All registered symbols: ['caof', {fmt_extra_sym}'obj', 'prelude', 'tgt']" )
class ModuleMappingField(DictStringToStringSequenceField): alias = "module_mapping" help = softwrap(f""" A mapping of requirement names to a list of the modules they provide. For example, `{{"ansicolors": ["colors"]}}`. Any unspecified requirements will use a default. See the `{PythonRequirementModulesField.alias}` field from the `{PythonRequirementTarget.alias}` target for more information. """) value: FrozenDict[str, tuple[str, ...]] default: ClassVar[FrozenDict[str, tuple[str, ...]]] = FrozenDict() @classmethod def compute_value( # type: ignore[override] cls, raw_value: Dict[str, Iterable[str]], address: Address) -> FrozenDict[str, Tuple[str, ...]]: value_or_default = super().compute_value(raw_value, address) return normalize_module_mapping(value_or_default)
def test_group_field_sets_by_constraints() -> None: py2_fs = MockFieldSet.create_for_test(Address("", target_name="py2"), ">=2.7,<3") py3_fs = [ MockFieldSet.create_for_test(Address("", target_name="py3"), "==3.6.*"), MockFieldSet.create_for_test(Address("", target_name="py3_second"), "==3.6.*"), ] no_constraints_fs = MockFieldSet.create_for_test( Address("", target_name="no_constraints"), None) assert PexInterpreterConstraints.group_field_sets_by_constraints( [py2_fs, *py3_fs, no_constraints_fs], python_setup=create_subsystem(PythonSetup, interpreter_constraints=[]), ) == FrozenDict({ PexInterpreterConstraints(): (no_constraints_fs, ), PexInterpreterConstraints(["CPython>=2.7,<3"]): (py2_fs, ), PexInterpreterConstraints(["CPython==3.6.*"]): tuple(py3_fs), })
class TypeStubsModuleMappingField(DictStringToStringSequenceField): alias = "type_stubs_module_mapping" help = ( "A mapping of type-stub requirement names to a list of the modules they provide.\n\n" 'For example, `{"types-requests": ["requests"]}`.\n\n' "If the requirement is not specified _and_ it starts with `types-` or `stubs-`, or ends " "with `-types` or `-stubs`, the requirement will be treated as a type stub for the " 'corresponding module, e.g. "types-request" has the module "requests". Otherwise, ' "the requirement is treated like a normal dependency (see the field " f"{ModuleMappingField.alias}).\n\n" "This is used to infer dependencies for type stubs.") value: FrozenDict[str, tuple[str, ...]] default: ClassVar[FrozenDict[str, tuple[str, ...]]] = FrozenDict() @classmethod def compute_value( # type: ignore[override] cls, raw_value: Dict[str, Iterable[str]], address: Address) -> FrozenDict[str, Tuple[str, ...]]: value_or_default = super().compute_value(raw_value, address) return normalize_module_mapping(value_or_default)
def test_empty(self) -> None: """Test that parsing an empty BUILD file results in an empty AddressFamily.""" address_mapper = AddressMapper(parser=JsonParser(TEST_TABLE), prelude_glob_patterns=()) af = run_rule( parse_address_family, rule_args=[address_mapper, BuildFilePreludeSymbols(FrozenDict()), Dir("/dev/null")], mock_gets=[ MockGet( product_type=Snapshot, subject_type=PathGlobs, mock=lambda _: Snapshot(Digest("abc", 10), ("/dev/null/BUILD",), ()), ), MockGet( product_type=FilesContent, subject_type=Digest, mock=lambda _: FilesContent([FileContent(path="/dev/null/BUILD", content=b"")]), ), ], ) self.assertEqual(len(af.objects_by_name), 0)
class TypeStubsModuleMappingField(DictStringToStringSequenceField): alias = "type_stubs_module_mapping" help = softwrap(f""" A mapping of type-stub requirement names to a list of the modules they provide. For example, `{{"types-requests": ["requests"]}}`. If the requirement is not specified _and_ its name looks like a type stub, Pants will use a default. See the `{PythonRequirementTypeStubModulesField.alias}` field from the `{PythonRequirementTarget.alias}` target for more information. """) value: FrozenDict[str, tuple[str, ...]] default: ClassVar[FrozenDict[str, tuple[str, ...]]] = FrozenDict() @classmethod def compute_value( # type: ignore[override] cls, raw_value: Dict[str, Iterable[str]], address: Address) -> FrozenDict[str, Tuple[str, ...]]: value_or_default = super().compute_value(raw_value, address) return normalize_module_mapping(value_or_default)
def target_types_by_alias( self) -> FrozenDict[str, FrozenSet[Type[Target]]]: """Returns a mapping from target alias to the target types produced for that alias. Normally there is 1 target type per alias, but macros can expand a single alias to several target types. :API: public :rtype: dict """ target_types_by_alias = defaultdict(list) for alias, target_type in self.target_types.items(): target_types_by_alias[alias].append(target_type) for alias, target_macro_factory in self.target_macro_factories.items(): target_types_by_alias[alias].extend( target_macro_factory.target_types) return FrozenDict( (alias, frozenset(target_types)) for alias, target_types in target_types_by_alias.items())
def test_map_third_party_modules_to_addresses(rule_runner: RuleRunner) -> None: rule_runner.add_to_build_file( "3rdparty/python", dedent("""\ python_requirement_library( name='ansicolors', requirements=['ansicolors==1.21'], module_mapping={'ansicolors': ['colors']}, ) python_requirement_library( name='req1', requirements=['req1', 'two_owners'], ) python_requirement_library( name='un_normalized', requirements=['Un-Normalized-Project>3', 'two_owners'], ) python_requirement_library( name='direct_references', requirements=[ 'pip@ git+https://github.com/pypa/pip.git', 'local_dist@ file:///path/to/dist.whl', ], ) """), ) result = rule_runner.request(ThirdPartyModuleToAddressMapping, []) assert result.mapping == FrozenDict({ "colors": Address("3rdparty/python", target_name="ansicolors"), "local_dist": Address("3rdparty/python", target_name="direct_references"), "pip": Address("3rdparty/python", target_name="direct_references"), "req1": Address("3rdparty/python", target_name="req1"), "un_normalized_project": Address("3rdparty/python", target_name="un_normalized"), })
def assert_pants_requirement( self, build_file_entry: str, *, expected_target_name: str, expected_dist: str = "pantsbuild.pants", expected_module: str = "pants", ) -> None: self.add_to_build_file("3rdparty/python", f"{build_file_entry}\n") target = self.request_single_product( WrappedTarget, Params( Address("3rdparty/python", target_name=expected_target_name), create_options_bootstrapper(), ), ).target assert isinstance(target, PythonRequirementLibrary) assert target[PythonRequirementsField].value == ( Requirement.parse(f"{expected_dist}=={pants_version()}"), ) assert target[ModuleMappingField].value == FrozenDict( {expected_dist: (expected_module, )})
def test_docker_push_env(rule_runner: RuleRunner) -> None: rule_runner.set_options( ["--docker-env-vars=DOCKER_CONFIG"], env_inherit={"PATH", "PYENV_ROOT", "HOME"}, env={"DOCKER_CONFIG": "/etc/docker/custom-config"}, ) result, docker = run_publish(rule_runner, Address("src/default")) assert len(result) == 1 assert_publish( result[0], ("default/default:latest",), None, process_assertion( argv=( docker.path, "push", "default/default:latest", ), env=FrozenDict({"DOCKER_CONFIG": "/etc/docker/custom-config"}), ), )
async def analyze_import_path_to_package_mapping( ) -> GoImportPathToPackageMapping: mapping: dict[str, list[Address]] = defaultdict(list) all_targets = await Get(Targets, AddressSpecs([DescendantAddresses("")])) for tgt in all_targets: if not tgt.has_field(GoImportPath): continue # Note: This will usually skip go_package targets since they need analysis to infer the import path # since there is no way in the engine to attach inferred values as fields. import_path = tgt[GoImportPath].value if not import_path: continue mapping[import_path].append(tgt.address) frozen_mapping = FrozenDict( {ip: tuple(tgts) for ip, tgts in mapping.items()}) return GoImportPathToPackageMapping(mapping=frozen_mapping)
def test_parse_address_family_empty() -> None: """Test that parsing an empty BUILD file results in an empty AddressFamily.""" address_mapper = AddressMapper(parser=Parser( target_type_aliases=[], object_aliases=BuildFileAliases())) af = run_rule( parse_address_family, rule_args=[ address_mapper, BuildFilePreludeSymbols(FrozenDict()), Dir("/dev/null") ], mock_gets=[ MockGet( product_type=DigestContents, subject_type=PathGlobs, mock=lambda _: DigestContents( [FileContent(path="/dev/null/BUILD", content=b"")]), ), ], ) assert len(af.name_to_target_adaptors) == 0
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 check_docker_proc(process: Process): assert process.argv == ( "/dummy/docker", "build", "-t", "args1:1.2.3", "--build-arg", "INHERIT", "--build-arg", "VAR=value", "-f", "docker/test/Dockerfile", ".", ) # Check that we pull in name only args via env. assert process.env == FrozenDict( { "INHERIT": "from Pants env", } )
async def map_third_party_modules_to_addresses() -> ThirdPartyModuleToAddressMapping: all_targets = await Get(Targets, AddressSpecs([DescendantAddresses("")])) modules_to_addresses: Dict[str, Address] = {} modules_with_multiple_owners: Set[str] = set() for tgt in all_targets: if not tgt.has_field(PythonRequirementsField): continue module_map = tgt.get(ModuleMappingField).value or {} # type: ignore[var-annotated] for python_req in tgt[PythonRequirementsField].value: modules = module_map.get( python_req.project_name, [python_req.project_name.lower().replace("-", "_")], ) for module in modules: if module in modules_to_addresses: modules_with_multiple_owners.add(module) else: modules_to_addresses[module] = tgt.address # Remove modules with ambiguous owners. for module in modules_with_multiple_owners: modules_to_addresses.pop(module) return ThirdPartyModuleToAddressMapping(FrozenDict(sorted(modules_to_addresses.items())))
async def map_first_party_modules_to_addresses( ) -> FirstPartyModuleToAddressMapping: all_expanded_targets = await Get(Targets, AddressSpecs([DescendantAddresses("")])) candidate_targets = tuple(tgt for tgt in all_expanded_targets if tgt.has_field(PythonSources)) stripped_sources_per_explicit_target = await MultiGet( Get(StrippedSourceFiles, SourceFilesRequest([tgt[PythonSources]])) for tgt in candidate_targets) modules_to_addresses: DefaultDict[str, List[Address]] = defaultdict(list) modules_with_multiple_implementations: Set[str] = set() for tgt, stripped_sources in zip(candidate_targets, stripped_sources_per_explicit_target): for stripped_f in stripped_sources.snapshot.files: module = PythonModule.create_from_stripped_path( PurePath(stripped_f)).module if module in modules_to_addresses: # We check if one of the targets is an implementation (.py file) and the other is a type stub (.pyi # file), which we allow. Otherwise, we have ambiguity. either_targets_are_type_stubs = len( modules_to_addresses[module]) == 1 and ( tgt.address.filename.endswith(".pyi") or modules_to_addresses[module][0].filename.endswith( ".pyi")) if either_targets_are_type_stubs: modules_to_addresses[module].append(tgt.address) else: modules_with_multiple_implementations.add(module) else: modules_to_addresses[module].append(tgt.address) # Remove modules with ambiguous owners. for module in modules_with_multiple_implementations: modules_to_addresses.pop(module) return FirstPartyModuleToAddressMapping( FrozenDict({ module: tuple(sorted(addresses)) for module, addresses in sorted(modules_to_addresses.items()) }))
def test_map_first_party_modules_to_addresses(self) -> None: options_bootstrapper = create_options_bootstrapper(args=[ "--source-root-patterns=['src/python', 'tests/python', 'build-support']" ]) # Two modules belonging to the same target. We should generate subtargets for each file. self.create_files("src/python/project/util", ["dirutil.py", "tarutil.py"]) self.add_to_build_file("src/python/project/util", "python_library()") # A module with two owners, meaning that neither should be resolved. self.create_file("src/python/two_owners.py") self.add_to_build_file("src/python", "python_library()") self.create_file("build-support/two_owners.py") self.add_to_build_file("build-support", "python_library()") # A package module. Because there's only one source file belonging to the target, we should # not generate subtargets. self.create_file("tests/python/project_test/demo_test/__init__.py") self.add_to_build_file("tests/python/project_test/demo_test", "python_library()") result = self.request_single_product(FirstPartyModuleToAddressMapping, options_bootstrapper) assert result.mapping == FrozenDict({ "project.util.dirutil": Address( "src/python/project/util", relative_file_path="dirutil.py", target_name="util", ), "project.util.tarutil": Address( "src/python/project/util", relative_file_path="tarutil.py", target_name="util", ), "project_test.demo_test": Address( "tests/python/project_test/demo_test", relative_file_path="__init__.py", target_name="demo_test", ), })
def get_subset( self, requested: Sequence[str], *, allowed: Optional[Sequence[str]] = None ) -> FrozenDict[str, str]: """Extract a subset of named env vars. Given a list of extra environment variable specifiers as strings, filter the contents of the pants environment to only those variables. Each variable can be specified either as a name or as a name=value pair. In the former case, the value for that name is taken from this env. In the latter case the specified value overrides the value in this env. If `allowed` is specified, the requested variable names must be in that list, or an error will be raised. """ allowed_set = None if allowed is None else set(allowed) env_var_subset: Dict[str, str] = {} def check_and_set(name: str, value: Optional[str]): if allowed_set is not None and name not in allowed_set: raise ValueError( f"{name} is not in the list of variable names that are allowed to be set. " f"Must be one of {','.join(sorted(allowed_set))}." ) if value is not None: env_var_subset[name] = value for env_var in requested: name_value_match = name_value_re.match(env_var) if name_value_match: check_and_set(name_value_match[1], name_value_match[2]) elif shorthand_re.match(env_var): check_and_set(env_var, self.get(env_var)) else: raise ValueError( f"An invalid variable was requested via the --test-extra-env-var " f"mechanism: {env_var}" ) return FrozenDict(env_var_subset)
async def map_first_party_modules_to_addresses() -> FirstPartyModuleToAddressMapping: all_expanded_targets = await Get(Targets, AddressSpecs([DescendantAddresses("")])) candidate_targets = tuple(tgt for tgt in all_expanded_targets if tgt.has_field(PythonSources)) stripped_sources_per_explicit_target = await MultiGet( Get(StrippedSourceFiles, SourceFilesRequest([tgt[PythonSources]])) for tgt in candidate_targets ) modules_to_addresses: Dict[str, Address] = {} modules_with_multiple_owners: Set[str] = set() for tgt, stripped_sources in zip(candidate_targets, stripped_sources_per_explicit_target): for stripped_f in stripped_sources.snapshot.files: module = PythonModule.create_from_stripped_path(PurePath(stripped_f)).module if module in modules_to_addresses: modules_with_multiple_owners.add(module) else: modules_to_addresses[module] = tgt.address # Remove modules with ambiguous owners. for module in modules_with_multiple_owners: modules_to_addresses.pop(module) return FirstPartyModuleToAddressMapping(FrozenDict(sorted(modules_to_addresses.items())))
def __init__( self, *, argv: Iterable[str], description: str, additional_input_digest: Optional[Digest] = None, extra_env: Optional[Mapping[str, str]] = None, output_files: Optional[Iterable[str]] = None, output_directories: Optional[Iterable[str]] = None, python: Optional[PythonExecutable] = None, level: LogLevel = LogLevel.INFO, ) -> None: self.argv = tuple(argv) self.description = description self.additional_input_digest = additional_input_digest self.extra_env = FrozenDict(extra_env) if extra_env else None self.output_files = tuple(output_files) if output_files else None self.output_directories = tuple( output_directories) if output_directories else None self.python = python self.level = level self.__post_init__()
async def evalute_preludes( address_mapper: AddressMapper) -> BuildFilePreludeSymbols: snapshot = await Get[Snapshot](PathGlobs( address_mapper.prelude_glob_patterns, glob_match_error_behavior=GlobMatchErrorBehavior.ignore, )) prelude_files_content = await Get[FilesContent](Digest, snapshot.digest) values: Dict[str, Any] = {} for file_content in prelude_files_content: try: file_content_str = file_content.content.decode() content = compile(file_content_str, file_content.path, "exec") exec(content, values) except Exception as e: raise Exception( f"Error parsing prelude file {file_content.path}: {e}") error_on_imports(file_content_str, file_content.path) # __builtins__ is a dict, so isn't hashable, and can't be put in a FrozenDict. # Fortunately, we don't care about it - preludes should not be able to override builtins, so we just pop it out. # TODO: Give a nice error message if a prelude tries to set a expose a non-hashable value. values.pop("__builtins__", None) return BuildFilePreludeSymbols(FrozenDict(values))
async def evaluate_preludes(global_options: GlobalOptions) -> BuildFilePreludeSymbols: prelude_digest_contents = await Get( DigestContents, PathGlobs( global_options.options.build_file_prelude_globs, glob_match_error_behavior=GlobMatchErrorBehavior.ignore, ), ) values: Dict[str, Any] = {} for file_content in prelude_digest_contents: try: file_content_str = file_content.content.decode() content = compile(file_content_str, file_content.path, "exec") exec(content, values) except Exception as e: raise Exception(f"Error parsing prelude file {file_content.path}: {e}") error_on_imports(file_content_str, file_content.path) # __builtins__ is a dict, so isn't hashable, and can't be put in a FrozenDict. # Fortunately, we don't care about it - preludes should not be able to override builtins, so we just pop it out. # TODO: Give a nice error message if a prelude tries to set a expose a non-hashable value. values.pop("__builtins__", None) return BuildFilePreludeSymbols(FrozenDict(values))
def test_issue_15111(rule_runner: RuleRunner) -> None: """Ensure we can handle when a single address implement multiple modules. This is currently only possible with third-party targets. """ rule_runner.write_files({ "BUILD": "python_requirement(name='req', requirements=['docopt', 'types-docopt'])" }) rule_runner.set_options(["--python-enable-resolves"]) result = rule_runner.request(ThirdPartyPythonModuleMapping, []) assert result == ThirdPartyPythonModuleMapping({ "python-default": FrozenDict({ "docopt": ( ModuleProvider(Address("", target_name="req"), ModuleProviderType.IMPL), ModuleProvider(Address("", target_name="req"), ModuleProviderType.TYPE_STUB), ), }) })
def test_parse_address_family_empty() -> None: """Test that parsing an empty BUILD file results in an empty AddressFamily.""" af = run_rule_with_mocks( parse_address_family, rule_args=[ Parser(target_type_aliases=[], object_aliases=BuildFileAliases()), create_subsystem(GlobalOptions, build_patterns=["BUILD"], build_ignore=[]), BuildFilePreludeSymbols(FrozenDict()), AddressFamilyDir("/dev/null"), ], mock_gets=[ MockGet( output_type=DigestContents, input_type=PathGlobs, mock=lambda _: DigestContents( [FileContent(path="/dev/null/BUILD", content=b"")]), ), ], ) assert len(af.name_to_target_adaptors) == 0