def hydrate_struct(address_mapper, address): """Given an AddressMapper and an Address, resolve a Struct from a BUILD file. Recursively collects any embedded addressables within the Struct, but will not walk into a dependencies field, since those should be requested explicitly by rules. """ address_family = yield Get(AddressFamily, Dir(address.spec_path)) struct = address_family.addressables.get(address) addresses = address_family.addressables if not struct or address not in addresses: _raise_did_you_mean(address_family, address.target_name) # TODO: This is effectively: "get the BuildFileAddress for this Address". # see https://github.com/pantsbuild/pants/issues/6657 address = next(build_address for build_address in addresses if build_address == address) inline_dependencies = [] def maybe_append(outer_key, value): if isinstance(value, str): if outer_key != 'dependencies': inline_dependencies.append( Address.parse( value, relative_to=address.spec_path, subproject_roots=address_mapper.subproject_roots)) elif isinstance(value, Struct): collect_inline_dependencies(value) def collect_inline_dependencies(item): for key, value in sorted(item._asdict().items(), key=_key_func): if not AddressableDescriptor.is_addressable(item, key): continue if isinstance(value, MutableMapping): for _, v in sorted(value.items(), key=_key_func): maybe_append(key, v) elif isinstance(value, MutableSequence): for v in value: maybe_append(key, v) else: maybe_append(key, value) # Recursively collect inline dependencies from the fields of the struct into `inline_dependencies`. collect_inline_dependencies(struct) # And then hydrate the inline dependencies. hydrated_inline_dependencies = yield [ Get(HydratedStruct, Address, a) for a in inline_dependencies ] dependencies = [d.value for d in hydrated_inline_dependencies] def maybe_consume(outer_key, value): if isinstance(value, str): if outer_key == 'dependencies': # Don't recurse into the dependencies field of a Struct, since those will be explicitly # requested by tasks. But do ensure that their addresses are absolute, since we're # about to lose the context in which they were declared. value = Address.parse( value, relative_to=address.spec_path, subproject_roots=address_mapper.subproject_roots) else: value = dependencies[maybe_consume.idx] maybe_consume.idx += 1 elif isinstance(value, Struct): value = consume_dependencies(value) return value # NB: Some pythons throw an UnboundLocalError for `idx` if it is a simple local variable. maybe_consume.idx = 0 # 'zip' the previously-requested dependencies back together as struct fields. def consume_dependencies(item, args=None): hydrated_args = args or {} for key, value in sorted(item._asdict().items(), key=_key_func): if not AddressableDescriptor.is_addressable(item, key): hydrated_args[key] = value continue if isinstance(value, MutableMapping): container_type = type(value) hydrated_args[key] = container_type( (k, maybe_consume(key, v)) for k, v in sorted(value.items(), key=_key_func)) elif isinstance(value, MutableSequence): container_type = type(value) hydrated_args[key] = container_type( maybe_consume(key, v) for v in value) else: hydrated_args[key] = maybe_consume(key, value) return _hydrate(type(item), address.spec_path, **hydrated_args) yield HydratedStruct( consume_dependencies(struct, args={'address': address}))
async def hydrate_struct(address_mapper: AddressMapper, address: Address) -> HydratedStruct: """Given an AddressMapper and an Address, resolve a Struct from a BUILD file. Recursively collects any embedded addressables within the Struct, but will not walk into a dependencies field, since those should be requested explicitly by rules. """ address_family = await Get[AddressFamily](Dir(address.spec_path)) # NB: `address_family.addressables` is a dictionary of `BuildFileAddress`es and we look it up # with an `Address`. This works because `BuildFileAddress` is a subclass, but MyPy warns that it # could be a bug. struct = address_family.addressables.get( address) # type: ignore[call-overload] addresses = address_family.addressables if not struct or address not in addresses: _raise_did_you_mean(address_family, address.target_name) # TODO: This is effectively: "get the BuildFileAddress for this Address". # see https://github.com/pantsbuild/pants/issues/6657 address = next(build_address for build_address in addresses if build_address == address) inline_dependencies = [] def maybe_append(outer_key, value): if isinstance(value, str): if outer_key != "dependencies": inline_dependencies.append( Address.parse( value, relative_to=address.spec_path, subproject_roots=address_mapper.subproject_roots, )) elif isinstance(value, Struct): collect_inline_dependencies(value) def collect_inline_dependencies(item): for key, value in sorted(item._asdict().items(), key=_key_func): if not AddressableDescriptor.is_addressable(item, key): continue if isinstance(value, MutableMapping): for _, v in sorted(value.items(), key=_key_func): maybe_append(key, v) elif isinstance(value, MutableSequence): for v in value: maybe_append(key, v) else: maybe_append(key, value) # Recursively collect inline dependencies from the fields of the struct into `inline_dependencies`. collect_inline_dependencies(struct) # And then hydrate the inline dependencies. hydrated_inline_dependencies = await MultiGet( Get[HydratedStruct](Address, a) for a in inline_dependencies) dependencies = [d.value for d in hydrated_inline_dependencies] def maybe_consume(outer_key, value): if isinstance(value, str): if outer_key == "dependencies": # Don't recurse into the dependencies field of a Struct, since those will be explicitly # requested by tasks. But do ensure that their addresses are absolute, since we're # about to lose the context in which they were declared. value = Address.parse( value, relative_to=address.spec_path, subproject_roots=address_mapper.subproject_roots, ) else: value = dependencies[maybe_consume.idx] maybe_consume.idx += 1 elif isinstance(value, Struct): value = consume_dependencies(value) return value # NB: Some pythons throw an UnboundLocalError for `idx` if it is a simple local variable. # TODO(#8496): create a decorator for functions which declare a sentinel variable like this! maybe_consume.idx = 0 # type: ignore[attr-defined] # 'zip' the previously-requested dependencies back together as struct fields. def consume_dependencies(item, args=None): hydrated_args = args or {} for key, value in sorted(item._asdict().items(), key=_key_func): if not AddressableDescriptor.is_addressable(item, key): hydrated_args[key] = value continue if isinstance(value, MutableMapping): container_type = type(value) hydrated_args[key] = container_type( (k, maybe_consume(key, v)) for k, v in sorted(value.items(), key=_key_func)) elif isinstance(value, MutableSequence): container_type = type(value) hydrated_args[key] = container_type( maybe_consume(key, v) for v in value) else: hydrated_args[key] = maybe_consume(key, value) return _hydrate(type(item), address.spec_path, **hydrated_args) return HydratedStruct( consume_dependencies(struct, args={"address": address}))
async def hydrate_struct(address_mapper: AddressMapper, address: Address) -> HydratedStruct: """Given an AddressMapper and an Address, resolve a Struct from a BUILD file. Recursively collects any embedded addressables within the Struct, but will not walk into a dependencies field, since those should be requested explicitly by rules. """ build_file_address = await Get[BuildFileAddress](Address, address) address_family = await Get[AddressFamily](Dir(address.spec_path)) struct = address_family.addressables.get(build_file_address) inline_dependencies = [] def maybe_append(outer_key, value): if isinstance(value, str): if outer_key != "dependencies": inline_dependencies.append( Address.parse( value, relative_to=address.spec_path, subproject_roots=address_mapper.subproject_roots, )) elif isinstance(value, Struct): collect_inline_dependencies(value) def collect_inline_dependencies(item): for key, value in sorted(item._asdict().items(), key=_key_func): if not AddressableDescriptor.is_addressable(item, key): continue if isinstance(value, Mapping): for _, v in sorted(value.items(), key=_key_func): maybe_append(key, v) elif isinstance(value, (list, tuple)): for v in value: maybe_append(key, v) else: maybe_append(key, value) # Recursively collect inline dependencies from the fields of the struct into `inline_dependencies`. collect_inline_dependencies(struct) # And then hydrate the inline dependencies. hydrated_inline_dependencies = await MultiGet( Get[HydratedStruct](Address, a) for a in inline_dependencies) dependencies = tuple(d.value for d in hydrated_inline_dependencies) def maybe_consume(outer_key, value): if isinstance(value, str): if outer_key == "dependencies": # Don't recurse into the dependencies field of a Struct, since those will be explicitly # requested by tasks. But do ensure that their addresses are absolute, since we're # about to lose the context in which they were declared. value = Address.parse( value, relative_to=address.spec_path, subproject_roots=address_mapper.subproject_roots, ) else: value = dependencies[maybe_consume.idx] maybe_consume.idx += 1 elif isinstance(value, Struct): value = consume_dependencies(value) return value # NB: Some pythons throw an UnboundLocalError for `idx` if it is a simple local variable. # TODO(#8496): create a decorator for functions which declare a sentinel variable like this! maybe_consume.idx = 0 # type: ignore[attr-defined] # 'zip' the previously-requested dependencies back together as struct fields. def consume_dependencies(item, args=None): hydrated_args = args or {} for key, value in sorted(item._asdict().items(), key=_key_func): if not AddressableDescriptor.is_addressable(item, key): hydrated_args[key] = value continue if isinstance(value, Mapping): hydrated_args[key] = { k: maybe_consume(key, v) for k, v in sorted(value.items(), key=_key_func) } elif isinstance(value, (list, tuple)): hydrated_args[key] = tuple( maybe_consume(key, v) for v in value) else: hydrated_args[key] = maybe_consume(key, value) return _hydrate(type(item), address.spec_path, **hydrated_args) return HydratedStruct( consume_dependencies(struct, args={"address": address}))