def assert_single_address(addresses: Sequence[Address]) -> None: """Assert that exactly one address must be contained in the collection.""" if len(addresses) == 0: raise ResolveError("No targets were matched.") if len(addresses) > 1: raise ResolveError( "Expected a single target, but was given multiple targets.\n\n" f"Did you mean one of these?\n\n{bullet_list(address.spec for address in addresses)}" )
def _log_or_raise_unmatched_owners( file_paths: Sequence[PurePath], owners_not_found_behavior: OwnersNotFoundBehavior, ignore_option: Optional[str] = None, ) -> None: option_msg = ( f"\n\nIf you would like to ignore un-owned files, please pass `{ignore_option}`." if ignore_option else "") if len(file_paths) == 1: prefix = ( f"No owning targets could be found for the file `{file_paths[0]}`.\n\n" f"Please check that there is a BUILD file in the parent directory " f"{file_paths[0].parent} with a target whose `sources` field includes the file." ) else: prefix = ( f"No owning targets could be found for the files {sorted(map(str, file_paths))}`.\n\n" f"Please check that there are BUILD files in each file's parent directory with a " f"target whose `sources` field includes the file.") msg = ( f"{prefix} See {bracketed_docs_url('targets')} for more information on target definitions." f"\n\nYou may want to run `./pants tailor` to autogenerate your BUILD files. See " f"{bracketed_docs_url('create-initial-build-files')}.{option_msg}") if owners_not_found_behavior == OwnersNotFoundBehavior.warn: logger.warning(msg) else: raise ResolveError(msg)
async def parse_address_family(address_mapper: AddressMapper, prelude_symbols: BuildFilePreludeSymbols, directory: Dir) -> AddressFamily: """Given an AddressMapper and a directory, return an AddressFamily. The AddressFamily may be empty, but it will not be None. """ path_globs = PathGlobs(globs=( *(os.path.join(directory.path, p) for p in address_mapper.build_patterns), *(f"!{p}" for p in address_mapper.build_ignore_patterns), )) snapshot = await Get[Snapshot](PathGlobs, path_globs) files_content = await Get[FilesContent](Digest, snapshot.digest) if not files_content: raise ResolveError( 'Directory "{}" does not contain any BUILD files.'.format( directory.path)) address_maps = [] for filecontent_product in files_content: address_maps.append( AddressMap.parse( filecontent_product.path, filecontent_product.content, address_mapper.parser, prelude_symbols, )) return AddressFamily.create(directory.path, address_maps)
async def parse_address_family( parser: Parser, build_file_options: BuildFileOptions, prelude_symbols: BuildFilePreludeSymbols, directory: AddressFamilyDir, ) -> AddressFamily: """Given an AddressMapper and a directory, return an AddressFamily. The AddressFamily may be empty, but it will not be None. """ digest_contents = await Get( DigestContents, PathGlobs(globs=( *(os.path.join(directory.path, p) for p in build_file_options.patterns), *(f"!{p}" for p in build_file_options.ignores), )), ) if not digest_contents: raise ResolveError( f"Directory '{directory.path}' does not contain any BUILD files.") address_maps = [ AddressMap.parse(fc.path, fc.content.decode(), parser, prelude_symbols) for fc in digest_contents ] return AddressFamily.create(directory.path, address_maps)
async def determine_main_pkg_for_go_binary( request: GoBinaryMainPackageRequest, ) -> GoBinaryMainPackage: addr = request.field.address if request.field.value: wrapped_specified_tgt = await Get( WrappedTarget, AddressInput, AddressInput.parse(request.field.value, relative_to=addr.spec_path), ) if not wrapped_specified_tgt.target.has_field( GoFirstPartyPackageSourcesField): raise InvalidFieldException( f"The {repr(GoBinaryMainPackageField.alias)} field in target {addr} must point to " "a `go_first_party_package` target, but was the address for a " f"`{wrapped_specified_tgt.target.alias}` target.\n\n" "Hint: you should normally not specify this field so that Pants will find the " "`go_first_party_package` target for you. (Pants generates " "`go_first_party_package` targets based on the `go_mod` target)." ) return GoBinaryMainPackage(wrapped_specified_tgt.target.address) candidate_targets = await Get( Targets, AddressSpecs([SiblingAddresses(addr.spec_path)])) relevant_pkg_targets = [ tgt for tgt in candidate_targets if tgt.has_field(GoFirstPartyPackageSourcesField) and tgt.residence_dir == addr.spec_path ] if len(relevant_pkg_targets) == 1: return GoBinaryMainPackage(relevant_pkg_targets[0].address) wrapped_tgt = await Get(WrappedTarget, Address, addr) alias = wrapped_tgt.target.alias if not relevant_pkg_targets: raise ResolveError( f"The `{alias}` target {addr} requires that there is a `go_first_party_package` " f"target for its directory {addr.spec_path}, but none were found.\n\n" "Have you added a `go_mod` target (which will generate `go_first_party_package` " "targets)?") raise ResolveError( f"There are multiple `go_first_party_package` targets for the same directory of the " f"`{alias}` target {addr}: {addr.spec_path}. It is ambiguous what to use as the `main` " "package.\n\n" f"To fix, please either set the `main` field for `{addr} or remove these " "`go_first_party_package` targets so that only one remains: " f"{sorted(tgt.address.spec for tgt in relevant_pkg_targets)}")
def test_address_specs_do_not_exist( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.write_files({ "real/f.txt": "", "real/BUILD": "mock_tgt(sources=['f.txt'])", "empty/BUILD": "# empty" }) def assert_resolve_error(specs: Iterable[AddressSpec], *, expected: str) -> None: with pytest.raises(ExecutionError) as exc: resolve_address_specs(address_specs_rule_runner, specs) assert expected in str(exc.value) # Literal addresses require both a BUILD file to exist and for a target to be resolved. assert_resolve_error([AddressLiteralSpec("fake", "tgt")], expected="'fake' does not exist on disk") assert_resolve_error( [AddressLiteralSpec("fake/f.txt", "tgt")], expected="'fake/f.txt' does not exist on disk", ) did_you_mean = ResolveError.did_you_mean(bad_name="fake_tgt", known_names=["real"], namespace="real") assert_resolve_error([AddressLiteralSpec("real", "fake_tgt")], expected=str(did_you_mean)) assert_resolve_error([AddressLiteralSpec("real/f.txt", "fake_tgt")], expected=str(did_you_mean)) # SiblingAddresses requires the BUILD file to exist and at least one match. assert_resolve_error( [SiblingAddresses("fake")], expected= ("'fake' does not contain any BUILD files, but 'fake:' expected matching targets " "there."), ) assert_resolve_error( [SiblingAddresses("empty")], expected="Address spec 'empty:' does not match any targets", ) # MaybeEmptySiblingAddresses does not require a BUILD file to exist nor any matches. assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("fake")]) assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("empty")]) # DescendantAddresses requires at least one match, even if BUILD files exist. assert_resolve_error( [DescendantAddresses("fake"), DescendantAddresses("empty")], expected="Address spec 'fake::' does not match any targets", ) # AscendantAddresses does not require any matches or BUILD files. assert not resolve_address_specs( address_specs_rule_runner, [AscendantAddresses("fake"), AscendantAddresses("empty")])
async def addresses_with_origins_from_filesystem_specs( filesystem_specs: FilesystemSpecs, global_options: GlobalOptions, ) -> AddressesWithOrigins: """Find the owner(s) for each FilesystemSpec while preserving the original FilesystemSpec those owners come from. This will merge FilesystemSpecs that come from the same owning target into a single FilesystemMergedSpec. """ pathglobs_per_include = ( filesystem_specs.path_globs_for_spec( spec, global_options.options.owners_not_found_behavior.to_glob_match_error_behavior(), ) for spec in filesystem_specs.includes ) snapshot_per_include = await MultiGet( Get[Snapshot](PathGlobs, pg) for pg in pathglobs_per_include ) owners_per_include = await MultiGet( Get[Owners](OwnersRequest(sources=snapshot.files)) for snapshot in snapshot_per_include ) addresses_to_specs: DefaultDict[ Address, List[Union[FilesystemLiteralSpec, FilesystemResolvedGlobSpec]] ] = defaultdict(list) for spec, snapshot, owners in zip( filesystem_specs.includes, snapshot_per_include, owners_per_include ): if ( global_options.options.owners_not_found_behavior != OwnersNotFoundBehavior.ignore and isinstance(spec, FilesystemLiteralSpec) and not owners.addresses ): file_path = PurePath(spec.to_spec_string()) msg = ( f"No owning targets could be found for the file `{file_path}`.\n\nPlease check " f"that there is a BUILD file in `{file_path.parent}` with a target whose `sources` field " f"includes `{file_path}`. See https://pants.readme.io/docs/targets for more " "information on target definitions.\n" "If you would like to ignore un-owned files, please pass `--owners-not-found-behavior=ignore`." ) if global_options.options.owners_not_found_behavior == OwnersNotFoundBehavior.warn: logger.warning(msg) else: raise ResolveError(msg) # We preserve what literal files any globs resolved to. This allows downstream goals to be # more precise in which files they operate on. origin: Union[FilesystemLiteralSpec, FilesystemResolvedGlobSpec] = ( spec if isinstance(spec, FilesystemLiteralSpec) else FilesystemResolvedGlobSpec(glob=spec.glob, files=snapshot.files) ) for address in owners.addresses: addresses_to_specs[address].append(origin) return AddressesWithOrigins( AddressWithOrigin( address, specs[0] if len(specs) == 1 else FilesystemMergedSpec.create(specs) ) for address, specs in addresses_to_specs.items() )
def matching_addresses( self, address_families: Sequence["AddressFamily"] ) -> Sequence[Tuple[Address, TargetAdaptor]]: matching = super().matching_addresses(address_families) if len(matching) == 0: raise ResolveError( f"Address spec '{self}' does not match any targets.") return matching
def _address_family_for_dir( dir_path: str, address_families: Mapping[str, "AddressFamily"]) -> "AddressFamily": maybe_af = address_families.get(dir_path) if maybe_af is None: raise ResolveError( f"Path '{dir_path}' does not contain any BUILD files.") return maybe_af
def matching_address_families( self, address_families_dict: Mapping[str, "AddressFamily"] ) -> Tuple["AddressFamily", ...]: maybe_af = address_families_dict.get(self.directory) if maybe_af is None: raise ResolveError( f"Path '{self.directory}' does not contain any BUILD files, but '{self}' expected " "matching targets there.") return (maybe_af, )
def test_address_specs_do_not_exist( address_specs_rule_runner: RuleRunner) -> None: address_specs_rule_runner.write_files({ "real/f.txt": "", "real/BUILD": "mock_tgt(sources=['f.txt'])", "empty/BUILD": "# empty" }) def assert_resolve_error(specs: Iterable[AddressSpec], *, expected: str) -> None: with engine_error(contains=expected): resolve_address_specs(address_specs_rule_runner, specs) # Literal addresses require for the relevant BUILD file to exist and for the target to be # resolved. assert_resolve_error([AddressLiteralSpec("fake", "tgt")], expected="'fake' does not exist on disk") assert_resolve_error( [AddressLiteralSpec("fake/f.txt", "tgt")], expected="'fake/f.txt' does not exist on disk", ) did_you_mean = ResolveError.did_you_mean(bad_name="fake_tgt", known_names=["real"], namespace="real") assert_resolve_error([AddressLiteralSpec("real", "fake_tgt")], expected=str(did_you_mean)) assert_resolve_error([AddressLiteralSpec("real/f.txt", "fake_tgt")], expected=str(did_you_mean)) # SiblingAddresses requires at least one match. assert_resolve_error( [SiblingAddresses("fake")], expected="No targets found for the address glob `fake:`", ) assert_resolve_error( [SiblingAddresses("empty")], expected="No targets found for the address glob `empty:`") # MaybeEmptySiblingAddresses does not require any matches. assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("fake")]) assert not resolve_address_specs(address_specs_rule_runner, [MaybeEmptySiblingAddresses("empty")]) # DescendantAddresses requires at least one match. assert_resolve_error( [DescendantAddresses("fake"), DescendantAddresses("empty")], expected= "No targets found for these address globs: ['empty::', 'fake::']", ) # AscendantAddresses does not require any matches. assert not resolve_address_specs( address_specs_rule_runner, [AscendantAddresses("fake"), AscendantAddresses("empty")])
def matching_address_families( self, address_families_dict: Mapping[str, "AddressFamily"] ) -> Tuple["AddressFamily", ...]: address_families_tuple = super().matching_address_families( address_families_dict) if not address_families_tuple: raise ResolveError( f"Path '{self.directory}' does not contain any BUILD files, but '{self}' expected " "matching targets there.") return address_families_tuple
async def addresses_with_origins_from_address_families( address_mapper: AddressMapper, address_specs: AddressSpecs, ) -> AddressesWithOrigins: """Given an AddressMapper and list of AddressSpecs, return matching AddressesWithOrigins. :raises: :class:`ResolveError` if: - there were no matching AddressFamilies, or - the AddressSpec matches no addresses for SingleAddresses. :raises: :class:`AddressLookupError` if no targets are matched for non-SingleAddress specs. """ # Capture a Snapshot covering all paths for these AddressSpecs, then group by directory. snapshot = await Get[Snapshot](PathGlobs, _address_spec_to_globs( address_mapper, address_specs)) 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} matched_addresses: OrderedSet[Address] = OrderedSet() addr_to_origin: Dict[Address, AddressSpec] = {} for address_spec in address_specs: # NB: if an address spec is provided which expands to some number of targets, but those targets # match --exclude-target-regexp, we do NOT fail! This is why we wait to apply the tag and # exclude patterns until we gather all the targets the address spec would have matched # without them. try: addr_families_for_spec = address_spec.matching_address_families( address_family_by_directory) except AddressSpec.AddressFamilyResolutionError as e: raise ResolveError(e) from e try: all_bfaddr_tgt_pairs = address_spec.address_target_pairs_from_address_families( addr_families_for_spec) for bfaddr, _ in all_bfaddr_tgt_pairs: addr = bfaddr.to_address() # A target might be covered by multiple specs, so we take the most specific one. addr_to_origin[addr] = more_specific(addr_to_origin.get(addr), address_spec) except AddressSpec.AddressResolutionError as e: raise AddressLookupError(e) from e except SingleAddress._SingleAddressResolutionError as e: _raise_did_you_mean(e.single_address_family, e.name, source=e) matched_addresses.update( bfaddr.to_address() for (bfaddr, tgt) in all_bfaddr_tgt_pairs if address_specs.matcher.matches_target_address_pair(bfaddr, tgt)) # NB: This may be empty, as the result of filtering by tag and exclude patterns! return AddressesWithOrigins( AddressWithOrigin(address=addr, origin=addr_to_origin[addr]) for addr in matched_addresses)
def _did_you_mean_exception(address_family: AddressFamily, name: str) -> ResolveError: names = [ addr.target_name or os.path.basename(addr.spec_path) for addr in address_family.addresses_to_target_adaptors ] possibilities = "\n ".join(":{}".format(target_name) for target_name in sorted(names)) return ResolveError( f"'{name}' was not found in namespace '{address_family.namespace}'. Did you mean one " f"of:\n {possibilities}")
def _raise_did_you_mean(address_family: AddressFamily, name: str, source=None) -> None: names = [a.target_name for a in address_family.addressables] possibilities = "\n ".join(":{}".format(target_name) for target_name in sorted(names)) resolve_error = ResolveError( '"{}" was not found in namespace "{}". ' "Did you mean one of:\n {}".format(name, address_family.namespace, possibilities) ) if source: raise resolve_error from source raise resolve_error
async def find_build_file(address: Address) -> BuildFileAddress: address_family = await Get(AddressFamily, AddressFamilyDir(address.spec_path)) owning_address = address.maybe_convert_to_target_generator() if address_family.get_target_adaptor(owning_address) is None: raise ResolveError.did_you_mean( bad_name=owning_address.target_name, known_names=address_family.target_names, namespace=address_family.namespace, ) bfa = next(build_file_address for build_file_address in address_family.build_file_addresses if build_file_address.address == owning_address) return BuildFileAddress( address, bfa.rel_path) if address.is_generated_target else bfa
async def find_target_adaptor(address: Address) -> TargetAdaptor: """Hydrate a TargetAdaptor so that it may be converted into the Target API.""" if not address.is_base_target: raise ValueError( f"Subtargets are not resident in BUILD files, and so do not have TargetAdaptors: {address}" ) address_family = await Get(AddressFamily, Dir(address.spec_path)) target_adaptor = address_family.get_target_adaptor(address) if target_adaptor is None: raise ResolveError.did_you_mean( bad_name=address.target_name, known_names=address_family.target_names, namespace=address_family.namespace, ) return target_adaptor
async def find_target_adaptor(address: Address) -> TargetAdaptor: """Hydrate a TargetAdaptor so that it may be converted into the Target API.""" if address.is_generated_target: raise AssertionError( "Generated targets are not defined in BUILD files, and so do not have " f"TargetAdaptors: {address}") address_family = await Get(AddressFamily, AddressFamilyDir(address.spec_path)) target_adaptor = address_family.get_target_adaptor(address) if target_adaptor is None: raise ResolveError.did_you_mean( bad_name=address.target_name, known_names=address_family.target_names, namespace=address_family.namespace, ) return target_adaptor
def test_address_specs_do_not_exist(self) -> None: self.create_file("real/f.txt") self.add_to_build_file("real", "mock_tgt(sources=['f.txt'])") self.add_to_build_file("empty", "# empty") def assert_resolve_error(specs: Iterable[AddressSpec], *, expected: str) -> None: with pytest.raises(ExecutionError) as exc: self.resolve_address_specs(specs) assert expected in str(exc.value) # Literal addresses require both a BUILD file to exist and for a target to be resolved. assert_resolve_error([AddressLiteralSpec("fake", "tgt")], expected="'fake' does not exist on disk") assert_resolve_error( [AddressLiteralSpec("fake/f.txt", "tgt")], expected="'fake/f.txt' does not exist on disk", ) did_you_mean = ResolveError.did_you_mean(bad_name="fake_tgt", known_names=["real"], namespace="real") assert_resolve_error([AddressLiteralSpec("real", "fake_tgt")], expected=str(did_you_mean)) assert_resolve_error([AddressLiteralSpec("real/f.txt", "fake_tgt")], expected=str(did_you_mean)) # SiblingAddresses require the BUILD file to exist, but are okay if no targets are resolved. assert_resolve_error( [SiblingAddresses("fake")], expected= ("'fake' does not contain any BUILD files, but 'fake:' expected matching targets " "there."), ) assert not self.resolve_address_specs([SiblingAddresses("empty")]) # DescendantAddresses requires at least one match, even if BUILD files exist. assert_resolve_error( [DescendantAddresses("fake"), DescendantAddresses("empty")], expected="Address spec 'fake::' does not match any targets", ) # AscendantAddresses does not require any matches or BUILD files. assert not self.resolve_address_specs( [AscendantAddresses("fake"), AscendantAddresses("empty")])
async def resolve_address(address_input: AddressInput) -> Address: # Determine the type of the path_component of the input. if address_input.path_component: snapshot = await Get(Snapshot, PathGlobs(globs=(address_input.path_component, ))) is_file, is_dir = bool(snapshot.files), bool(snapshot.dirs) else: # It is an address in the root directory. is_file, is_dir = False, True if is_file: return address_input.file_to_address() elif is_dir: return address_input.dir_to_address() else: raise ResolveError( f"The file or directory '{address_input.path_component}' does not exist on disk in the " f"workspace: cannot resolve '{address_input.target_component}' relative to it." )
async def resolve_address(address_input: AddressInput) -> Address: # Determine the type of the path_component of the input. if address_input.path_component: paths = await Get(Paths, PathGlobs(globs=(address_input.path_component, ))) is_file, is_dir = bool(paths.files), bool(paths.dirs) else: # It is an address in the root directory. is_file, is_dir = False, True if is_file: return address_input.file_to_address() elif is_dir: return address_input.dir_to_address() else: spec = address_input.path_component if address_input.target_component: spec += f":{address_input.target_component}" raise ResolveError( f"The file or directory '{address_input.path_component}' does not exist on disk in the " f"workspace, so the address '{spec}' cannot be resolved.")
def _log_or_raise_unmatched_owners( file_paths: Sequence[PurePath], owners_not_found_behavior: OwnersNotFoundBehavior, ignore_option: Optional[str] = None, ) -> None: msgs = [] if ignore_option: option_msg = f"\nIf you would like to ignore un-owned files, please pass `{ignore_option}`." else: option_msg = "" for file_path in file_paths: msgs.append( f"No owning targets could be found for the file `{file_path}`.\n\nPlease check " f"that there is a BUILD file in `{file_path.parent}` with a target whose `sources` " f"field includes `{file_path}`. See {docs_url('targets')} for more " f"information on target definitions.{option_msg}") if owners_not_found_behavior == OwnersNotFoundBehavior.warn: for msg in msgs: logger.warning(msg) else: raise ResolveError("\n\n".join(msgs))
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))