def test_parse_dependencies_field() -> None: """Ensure that we correctly handle `!` ignores. We leave the rest of the parsing to AddressInput and Address. """ result = parse_dependencies_field( ["a/b/c", "!a/b/c", "f.txt", "!f.txt"], spec_path="demo/subdir", subproject_roots=[], ) expected_addresses = {AddressInput("a/b/c"), AddressInput("f.txt")} assert set(result.addresses) == expected_addresses assert set(result.ignored_addresses) == expected_addresses
def test_resolve_address() -> None: rule_runner = RuleRunner(rules=[QueryRule(Address, (AddressInput, ))]) def assert_is_expected(address_input: AddressInput, expected: Address) -> None: assert rule_runner.request(Address, [address_input]) == expected rule_runner.create_file("a/b/c.txt") assert_is_expected( AddressInput("a/b/c.txt"), Address("a/b", target_name=None, relative_file_path="c.txt")) assert_is_expected( AddressInput("a/b"), Address("a/b", target_name=None, relative_file_path=None)) assert_is_expected(AddressInput("a/b", target_component="c"), Address("a/b", target_name="c")) assert_is_expected( AddressInput("a/b/c.txt", target_component="c"), Address("a/b", relative_file_path="c.txt", target_name="c"), ) # Top-level addresses will not have a path_component, unless they are a file address. rule_runner.create_file("f.txt") assert_is_expected( AddressInput("f.txt", target_component="original"), Address("", relative_file_path="f.txt", target_name="original"), ) assert_is_expected(AddressInput("", target_component="t"), Address("", target_name="t")) with pytest.raises(ExecutionError) as exc: rule_runner.request(Address, [AddressInput("a/b/fake")]) assert "'a/b/fake' does not exist on disk" in str(exc.value)
def test_resolve_address(self) -> None: def assert_is_expected(address_input: AddressInput, expected: Address) -> None: assert self.request_single_product(Address, address_input) == expected self.create_file("a/b/c.txt") assert_is_expected( AddressInput("a/b/c.txt"), Address("a/b", target_name=None, relative_file_path="c.txt")) assert_is_expected( AddressInput("a/b"), Address("a/b", target_name=None, relative_file_path=None)) assert_is_expected(AddressInput("a/b", target_component="c"), Address("a/b", target_name="c")) assert_is_expected( AddressInput("a/b/c.txt", target_component="c"), Address("a/b", relative_file_path="c.txt", target_name="c"), ) # Top-level addresses will not have a path_component, unless they are a file address. self.create_file("f.txt") assert_is_expected( AddressInput("f.txt", target_component="original"), Address("", relative_file_path="f.txt", target_name="original"), ) assert_is_expected(AddressInput("", target_component="t"), Address("", target_name="t")) with pytest.raises(ExecutionError) as exc: self.request_single_product(Address, AddressInput("a/b/fake")) assert "'a/b/fake' does not exist on disk" in str(exc.value)
async def addresses_from_address_specs( address_specs: AddressSpecs, global_options: GlobalOptions, specs_filter: AddressSpecsFilter) -> Addresses: matched_addresses: OrderedSet[Address] = OrderedSet() filtering_disabled = address_specs.filter_by_global_options is False # First convert all `AddressLiteralSpec`s. Some of the resulting addresses may be generated # addresses. This will raise an exception if any of the addresses are not valid. literal_addresses = await MultiGet( Get( Address, AddressInput(spec.path_component, spec.target_component, spec.generated_component), ) for spec in address_specs.literals) literal_target_adaptors = await MultiGet( Get(TargetAdaptor, Address, addr.maybe_convert_to_target_generator()) for addr in literal_addresses) # We convert to targets for the side effect of validating that any generated targets actually # belong to their target generator. await Get( UnexpandedTargets, Addresses(addr for addr in literal_addresses if addr.is_generated_target)) for literal_spec, addr, target_adaptor in zip(address_specs.literals, literal_addresses, literal_target_adaptors): if filtering_disabled or specs_filter.matches(addr, target_adaptor): matched_addresses.add(addr) # Then, convert all `AddressGlobSpecs`. Resolve all BUILD files covered by the specs, then # group by directory. paths = await Get( Paths, PathGlobs, address_specs.to_path_globs( build_patterns=global_options.options.build_patterns, build_ignore_patterns=global_options.options.build_ignore, ), ) dirnames = {os.path.dirname(f) for f in paths.files} address_families = await MultiGet( Get(AddressFamily, AddressFamilyDir(d)) for d in dirnames) address_family_by_directory = {af.namespace: af for af in address_families} for glob_spec in address_specs.globs: # These may raise ResolveError, depending on the type of spec. addr_families_for_spec = glob_spec.matching_address_families( address_family_by_directory) addr_target_pairs_for_spec = glob_spec.matching_addresses( addr_families_for_spec) matched_addresses.update( addr for (addr, tgt) in addr_target_pairs_for_spec # TODO(#11123): handle the edge case if a generated target's `tags` != its generator's. if filtering_disabled or specs_filter.matches(addr, tgt)) return Addresses(sorted(matched_addresses))
async def _determine_literal_addresses_from_raw_specs( literal_specs: tuple[AddressLiteralSpec, ...], *, description_of_origin: str) -> tuple[WrappedTarget, ...]: literal_addresses = await MultiGet( Get( Address, AddressInput( spec.path_component, spec.target_component, generated_component=spec.generated_component, parameters=spec.parameters, description_of_origin=description_of_origin, ), ) for spec in literal_specs) # We replace references to parametrized target templates with all their created targets. For # example: # - dir:tgt -> (dir:tgt@k=v1, dir:tgt@k=v2) # - dir:tgt@k=v -> (dir:tgt@k=v,another=a, dir:tgt@k=v,another=b), but not anything # where @k=v is not true. literal_parametrizations = await MultiGet( Get( _TargetParametrizations, _TargetParametrizationsRequest( address.maybe_convert_to_target_generator(), description_of_origin=description_of_origin, ), ) for address in literal_addresses) # Note that if the address is not in the _TargetParametrizations, we must fall back to that # address's value. This will allow us to error that the address is invalid. all_candidate_addresses = itertools.chain.from_iterable( list(params.get_all_superset_targets(address)) or [address] for address, params in zip(literal_addresses, literal_parametrizations)) # We eagerly call the `WrappedTarget` rule because it will validate that every final address # actually exists, such as with generated target addresses. return await MultiGet( Get( WrappedTarget, WrappedTargetRequest(addr, description_of_origin=description_of_origin)) for addr in all_candidate_addresses)
async def addresses_from_address_specs( address_specs: AddressSpecs, build_file_options: BuildFileOptions, specs_filter: AddressSpecsFilter, ) -> Addresses: matched_addresses: OrderedSet[Address] = OrderedSet() filtering_disabled = address_specs.filter_by_global_options is False # Resolve all `AddressLiteralSpec`s. Will error on invalid addresses. literal_wrapped_targets = await MultiGet( Get( WrappedTarget, AddressInput(spec.path_component, spec.target_component, spec.generated_component), ) for spec in address_specs.literals) matched_addresses.update( wrapped_tgt.target.address for wrapped_tgt in literal_wrapped_targets if filtering_disabled or specs_filter.matches(wrapped_tgt.target)) if not address_specs.globs: return Addresses(matched_addresses) # Resolve all `AddressGlobSpecs`. build_file_paths = await Get( Paths, PathGlobs, address_specs.to_build_file_path_globs( build_patterns=build_file_options.patterns, build_ignore_patterns=build_file_options.ignores, ), ) dirnames = {os.path.dirname(f) for f in build_file_paths.files} address_families = await MultiGet( Get(AddressFamily, AddressFamilyDir(d)) for d in dirnames) base_addresses = Addresses( itertools.chain.from_iterable( address_family.addresses_to_target_adaptors for address_family in address_families)) target_parametrizations_list = await MultiGet( Get(_TargetParametrizations, Address, base_address) for base_address in base_addresses) residence_dir_to_targets = defaultdict(list) for target_parametrizations in target_parametrizations_list: for tgt in target_parametrizations.all: residence_dir_to_targets[tgt.residence_dir].append(tgt) matched_globs = set() for glob_spec in address_specs.globs: for residence_dir in residence_dir_to_targets: if not glob_spec.matches(residence_dir): continue matched_globs.add(glob_spec) matched_addresses.update( tgt.address for tgt in residence_dir_to_targets[residence_dir] if filtering_disabled or specs_filter.matches(tgt)) unmatched_globs = [ glob for glob in address_specs.globs if glob not in matched_globs and glob.error_if_no_matches ] if unmatched_globs: glob_description = ( f"the address glob `{unmatched_globs[0]}`" if len(unmatched_globs) == 1 else f"these address globs: {sorted(str(glob) for glob in unmatched_globs)}" ) raise ResolveError( f"No targets found for {glob_description}\n\n" f"Do targets exist in those directories? Maybe run `{bin_name()} tailor` to generate " f"BUILD files? See {doc_url('targets')} about targets and BUILD files." ) return Addresses(sorted(matched_addresses))
async def addresses_with_origins_from_address_specs( address_specs: AddressSpecs, global_options: GlobalOptions, specs_filter: AddressSpecsFilter ) -> AddressesWithOrigins: """Given an AddressMapper and list of AddressSpecs, return matching AddressesWithOrigins. :raises: :class:`ResolveError` if the provided specs fail to match targets, and those spec types expect to have matched something. """ matched_addresses: OrderedSet[Address] = OrderedSet() addr_to_origin: Dict[Address, AddressSpec] = {} filtering_disabled = address_specs.filter_by_global_options is False # First convert all `AddressLiteralSpec`s. Some of the resulting addresses may be file # addresses. This will raise an exception if any of the addresses are not valid. literal_addresses = await MultiGet( Get(Address, AddressInput(spec.path_component, spec.target_component)) for spec in address_specs.literals ) literal_target_adaptors = await MultiGet( Get(TargetAdaptor, Address, addr.maybe_convert_to_base_target()) for addr in literal_addresses ) # We convert to targets for the side effect of validating that any file addresses actually # belong to the specified base targets. await Get( UnexpandedTargets, Addresses(addr for addr in literal_addresses if not addr.is_base_target) ) for literal_spec, addr, target_adaptor in zip( address_specs.literals, literal_addresses, literal_target_adaptors ): addr_to_origin[addr] = literal_spec if filtering_disabled or specs_filter.matches(addr, target_adaptor): matched_addresses.add(addr) # Then, convert all `AddressGlobSpecs`. Snapshot all BUILD files covered by the specs, then # group by directory. snapshot = await Get( Snapshot, PathGlobs, address_specs.to_path_globs( build_patterns=global_options.options.build_patterns, build_ignore_patterns=global_options.options.build_ignore, ), ) dirnames = {os.path.dirname(f) for f in snapshot.files} address_families = await MultiGet(Get(AddressFamily, Dir(d)) for d in dirnames) address_family_by_directory = {af.namespace: af for af in address_families} for glob_spec in address_specs.globs: # These may raise ResolveError, depending on the type of spec. addr_families_for_spec = glob_spec.matching_address_families(address_family_by_directory) addr_target_pairs_for_spec = glob_spec.matching_addresses(addr_families_for_spec) for addr, _ in addr_target_pairs_for_spec: # A target might be covered by multiple specs, so we take the most specific one. addr_to_origin[addr] = AddressSpecs.more_specific(addr_to_origin.get(addr), glob_spec) matched_addresses.update( addr for (addr, tgt) in addr_target_pairs_for_spec if filtering_disabled or specs_filter.matches(addr, tgt) ) return AddressesWithOrigins( AddressWithOrigin(address=addr, origin=addr_to_origin[addr]) for addr in matched_addresses )