async def single_build_file_address( build_file_addresses: BuildFileAddresses, ) -> BuildFileAddress: if len(build_file_addresses.dependencies) == 0: raise ResolveError("No targets were matched") if len(build_file_addresses.dependencies) > 1: targets = [bfa.to_address() for bfa in build_file_addresses] output = '\n '.join(str(target) for target in targets) raise ResolveError( "Expected a single target, but was given multiple targets:\n" f"Did you mean one of:\n {output}") return build_file_addresses.dependencies[0]
def single_build_file_address(specs: Specs) -> BuildFileAddress: build_file_addresses = yield Get(BuildFileAddresses, Specs, specs) if len(build_file_addresses.dependencies) == 0: raise ResolveError("No targets were matched") if len(build_file_addresses.dependencies) > 1: potential_addresses = yield Get(BuildFileAddresses, Specs, specs) targets = [bfa.to_address() for bfa in potential_addresses] output = '\n '.join(str(target) for target in targets) raise ResolveError( "Expected a single target, but was given multiple targets:\n" f"Did you mean one of:\n {output}") yield build_file_addresses.dependencies[0]
def _raise_did_you_mean(address_family, name): names = [a.target_name for a in address_family.addressables] possibilities = '\n '.join(':{}'.format(target_name) for target_name in sorted(names)) raise ResolveError('"{}" was not found in namespace "{}". ' 'Did you mean one of:\n {}'.format( name, address_family.namespace, possibilities))
def addresses_from_address_families(address_mapper, address_families, spec): """Given a list of AddressFamilies and a Spec, return matching Addresses. Raises a ResolveError if: - there were no matching AddressFamilies, or - the Spec matches no addresses for SingleAddresses. """ if not address_families: raise ResolveError('Path "{}" contains no BUILD files.'.format(spec.directory)) def exclude_address(address): if address_mapper.exclude_patterns: address_str = address.spec return any(p.search(address_str) is not None for p in address_mapper.exclude_patterns) return False if type(spec) in (DescendantAddresses, SiblingAddresses, AscendantAddresses): addresses = tuple(a for af in address_families for a in af.addressables.keys() if not exclude_address(a)) elif type(spec) is SingleAddress: # TODO Could assert len(address_families) == 1, as it should always be true in this case. addresses = tuple(a for af in address_families for a in af.addressables.keys() if a.target_name == spec.name and not exclude_address(a)) if not addresses: if len(address_families) == 1: _raise_did_you_mean(address_families[0], spec.name) else: raise ValueError('Unrecognized Spec type: {}'.format(spec)) return BuildFileAddresses(addresses)
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. """ pathglobs_per_include = (filesystem_specs.path_globs_for_spec(spec) 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) result: List[AddressWithOrigin] = [] for spec, owners in zip(filesystem_specs.includes, owners_per_include): if (global_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://www.pantsbuild.org/build_files.html." ) if global_options.owners_not_found_behavior == OwnersNotFoundBehavior.warn: logger.warning(msg) else: raise ResolveError(msg) result.extend( AddressWithOrigin(address=bfa, origin=spec) for bfa in owners.addresses) return AddressesWithOrigins(result)
async def parse_address_family(address_mapper: AddressMapper, 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.directory_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 ) ) return AddressFamily.create(directory.path, address_maps)
def parse_address_family(address_mapper, directory): """Given an AddressMapper and a directory, return an AddressFamily. The AddressFamily may be empty, but it will not be None. """ patterns = tuple( join(directory.path, p) for p in address_mapper.build_patterns) path_globs = PathGlobs.create('', include=patterns, exclude=address_mapper.build_ignore_patterns) snapshot = yield Get(Snapshot, PathGlobs, path_globs) files_content = yield Get(FilesContent, DirectoryDigest, snapshot.directory_digest) if not files_content: raise ResolveError( 'Directory "{}" does not contain build files.'.format( directory.path)) address_maps = [] for filecontent_product in files_content.dependencies: address_maps.append( AddressMap.parse(filecontent_product.path, filecontent_product.content, address_mapper.parser)) yield AddressFamily.create(directory.path, address_maps)
async def provenanced_addresses_from_address_families( address_mapper: AddressMapper, address_specs: AddressSpecs, ) -> ProvenancedBuildFileAddresses: """Given an AddressMapper and list of AddressSpecs, return matching ProvenancedBuildFileAddresses. :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 = {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() addr_to_provenance: Dict[BuildFileAddress, 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_addr_tgt_pairs = address_spec.address_target_pairs_from_address_families( addr_families_for_spec) for addr, _ in all_addr_tgt_pairs: # A target might be covered by multiple specs, so we take the most specific one. addr_to_provenance[addr] = more_specific( addr_to_provenance.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( addr for (addr, tgt) in all_addr_tgt_pairs if address_specs.matcher.matches_target_address_pair(addr, tgt)) # NB: This may be empty, as the result of filtering by tag and exclude patterns! return ProvenancedBuildFileAddresses( tuple( ProvenancedBuildFileAddress(build_file_address=addr, provenance=addr_to_provenance[addr]) for addr in matched_addresses))
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
def parse_address_family(address_mapper, path, build_files): """Given the contents of the build files in one directory, return an AddressFamily. The AddressFamily may be empty, but it will not be None. """ files_content = build_files.files_content.dependencies if not files_content: raise ResolveError('Directory "{}" does not contain build files.'.format(path)) address_maps = [] for filecontent_product in files_content: address_maps.append(AddressMap.parse(filecontent_product.path, filecontent_product.content, address_mapper.parser)) return AddressFamily.create(path.path, address_maps)
def parse_address_family(address_mapper, path, build_files_content): """Given the contents of the build files in one directory, return an AddressFamily. The AddressFamily may be empty, but it will not be None. """ if not build_files_content.dependencies: raise ResolveError( 'Directory "{}" does not contain build files.'.format(path)) address_maps = [] for filepath, filecontent in build_files_content.dependencies: address_maps.append( AddressMap.parse(filepath, filecontent, address_mapper.symbol_table_cls, address_mapper.parser_cls)) return AddressFamily.create(path.path, address_maps)
def filter_buildfile_paths(address_mapper, directory_listing): if not directory_listing.exists: raise ResolveError('Directory "{}" does not exist.'.format( directory_listing.directory.path)) build_pattern = address_mapper.build_pattern def match(stat): return type(stat) is File and fnmatch(basename(stat.path), build_pattern) build_files = tuple( Path(stat.path, stat) for stat in directory_listing.dependencies if match(stat)) return BuildFiles(build_files)
def filter_buildfile_paths(address_mapper, directory_listing): if not directory_listing.exists: raise ResolveError('Directory "{}" does not exist.'.format( directory_listing.directory.path)) def match(stat): # Short circuit for ignored paths. if address_mapper.build_ignore_patterns.match_file(stat.path): return False return (type(stat) is File and any( fnmatch(basename(stat.path), pattern) for pattern in address_mapper.build_patterns)) build_files = tuple( Path(stat.path, stat) for stat in directory_listing.dependencies if match(stat)) return BuildFiles(build_files)
def filter_buildfile_paths(address_mapper, directory_listing): if not directory_listing.exists: raise ResolveError('Directory "{}" does not exist.'.format( directory_listing.directory.path)) build_pattern = address_mapper.build_pattern def match(stat): # TODO: Use match_file instead when pathspec 0.4.1 (TBD) is released. ignored = any( True for _ in address_mapper.build_ignore_patterns.match_files( [stat.path])) return (not ignored) and type(stat) is File and fnmatch( basename(stat.path), build_pattern) build_files = tuple( Path(stat.path, stat) for stat in directory_listing.dependencies if match(stat)) return BuildFiles(build_files)
def parse_address_family(address_mapper, path, build_files): """Given the contents of the build files in one directory, return an AddressFamily. The AddressFamily may be empty, but it will not be None. """ files_content = build_files.files_content.dependencies if not files_content: raise ResolveError('Directory "{}" does not contain build files.'.format(path)) address_maps = [] paths = (f.path for f in files_content) ignored_paths = set(address_mapper.build_ignore_patterns.match_files(paths)) for filecontent_product in files_content: if filecontent_product.path in ignored_paths: continue address_maps.append(AddressMap.parse(filecontent_product.path, filecontent_product.content, address_mapper.symbol_table_cls, address_mapper.parser_cls, address_mapper.exclude_patterns)) return AddressFamily.create(path.path, address_maps)
def addresses_from_address_families(address_mapper: AddressMapper, specs: Specs) -> BuildFileAddresses: """Given an AddressMapper and list of Specs, return matching BuildFileAddresses. :raises: :class:`ResolveError` if: - there were no matching AddressFamilies, or - the Spec 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 Specs, then group by directory. snapshot = yield Get(Snapshot, PathGlobs, _spec_to_globs(address_mapper, specs)) dirnames = {dirname(f) for f in snapshot.files} address_families = yield [Get(AddressFamily, Dir(d)) for d in dirnames] address_family_by_directory = {af.namespace: af for af in address_families} matched_addresses = OrderedSet() for spec in specs: # NB: if a 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 spec would have matched without them. try: addr_families_for_spec = spec.matching_address_families( address_family_by_directory) except Spec.AddressFamilyResolutionError as e: raise ResolveError(e) from e try: all_addr_tgt_pairs = spec.address_target_pairs_from_address_families( addr_families_for_spec) except Spec.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( addr for (addr, tgt) in all_addr_tgt_pairs if specs.matcher.matches_target_address_pair(addr, tgt)) # NB: This may be empty, as the result of filtering by tag and exclude patterns! yield BuildFileAddresses(tuple(matched_addresses))
def _raise_did_you_mean(address_family, name): possibilities = '\n '.join(str(a) for a in address_family.addressables) raise ResolveError('A Struct was not found in namespace {} for name "{}". ' 'Did you mean one of?:\n {}'.format( address_family.namespace, name, possibilities))
def raise_if_empty_address_families(): if not address_families: raise ResolveError('Path "{}" contains no BUILD files.'.format(spec.directory))
def raise_empty_address_family(spec): raise ResolveError('Path "{}" contains no BUILD files.'.format( spec.directory))