Exemplo n.º 1
0
 def test_nodes_symlink_globbed_file(self):
     self.assert_fsnodes(
         ['d.ln/b/*.txt'],
         [
             # NB: Needs to scandir every Dir on the way down to track whether
             # it is traversing a symlink.
             (Dir(''), DirectoryListing),
             # Traverse one symlink.
             (Link('d.ln'), ReadLink),
             (Dir('a'), DirectoryListing),
             (Dir('a/b'), DirectoryListing),
         ])
Exemplo n.º 2
0
 def test_nodes_symlink_globbed_dir(self):
     self.assert_fsnodes(
         ['*/2'],
         [
             # Scandir for the root.
             (Dir(''), DirectoryListing),
             # Read links to determine whether they're actually directories.
             (Link('c.ln'), ReadLink),
             (Link('d.ln'), ReadLink),
             # Scan second level destinations: `a/b` is matched via `c.ln`.
             (Dir('a'), DirectoryListing),
             (Dir('a/b'), DirectoryListing),
         ])
Exemplo n.º 3
0
 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=(),
         build_file_imports_behavior=BuildFileImportsBehavior.error,
     )
     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)
Exemplo n.º 4
0
 def test_empty(self):
   """Test that parsing an empty BUILD file results in an empty AddressFamily."""
   address_mapper = AddressMapper(JsonParser(TestTable()))
   af = run_rule(parse_address_family, address_mapper, Dir('/dev/null'), {
       (FilesContent, PathGlobs): lambda _: FilesContent([FileContent('/dev/null/BUILD', '')])
     })
   self.assertEquals(len(af.objects_by_name), 0)
Exemplo n.º 5
0
class StorageTest(unittest.TestCase):
  TEST_KEY = b'hello'
  TEST_VALUE = b'world'

  TEST_PATH = File('/foo')
  TEST_PATH2 = Dir('/bar')

  class SomeException(Exception): pass

  def setUp(self):
    self.storage = Storage.create()
    self.result = 'something'
    self.request = Runnable(func=_runnable, args=('this is an arg',), cacheable=True)

  def test_storage(self):
    key = self.storage.put(self.TEST_PATH)
    self.assertEquals(self.TEST_PATH, self.storage.get(key))

    with self.assertRaises(InvalidKeyError):
      self.assertFalse(self.storage.get(self.TEST_KEY))

  def test_storage_key_mappings(self):
    key1 = self.storage.put(self.TEST_PATH)
    key2 = self.storage.put(self.TEST_PATH2)
    self.storage.add_mapping(key1, key2)
    self.assertEquals(key2, self.storage.get_mapping(key1))

    # key2 isn't mapped to any other key.
    self.assertIsNone(self.storage.get_mapping(key2))
Exemplo n.º 6
0
 def generate_subjects(self, filenames):
     """Given filenames, generate a set of subjects for invalidation predicate matching."""
     for f in filenames:
         # ReadLink, or FileContent for the literal path.
         yield File(f)
         yield Link(f)
         # DirectoryListing for parent dirs.
         yield Dir(dirname(f))
Exemplo n.º 7
0
def filter_build_dirs(address_mapper, snapshot):
    """Given a Snapshot matching a build pattern, return parent directories as BuildDirs."""
    dirnames = set(dirname(f.stat.path) for f in snapshot.files)
    ignored_dirnames = address_mapper.build_ignore_patterns.match_files(
        '{}/'.format(dirname) for dirname in dirnames)
    ignored_dirnames = set(d.rstrip('/') for d in ignored_dirnames)
    return BuildDirs(
        tuple(Dir(d) for d in dirnames if d not in ignored_dirnames))
Exemplo n.º 8
0
def filter_build_dirs(address_mapper, build_files):
    """Given Files matching a build pattern, return their parent directories as BuildDirs."""
    dirnames = set(dirname(f.stat.path) for f in build_files.dependencies)
    ignored_dirnames = address_mapper.build_ignore_patterns.match_files(
        '{}/'.format(dirname) for dirname in dirnames)
    ignored_dirnames = set(d.rstrip('/') for d in ignored_dirnames)
    return BuildDirs(
        tuple(Dir(d) for d in dirnames if d not in ignored_dirnames))
Exemplo n.º 9
0
 def test_empty(self):
   """Test that parsing an empty BUILD file results in an empty AddressFamily."""
   address_mapper = AddressMapper(JsonParser(TestTable()))
   af = run_rule(parse_address_family, address_mapper, Dir('/dev/null'), {
       (Snapshot, PathGlobs): lambda _: Snapshot(DirectoryDigest('abc', 10), (File('/dev/null/BUILD'),)),
       (FilesContent, DirectoryDigest): lambda _: FilesContent([FileContent('/dev/null/BUILD', b'')]),
     })
   self.assertEqual(len(af.objects_by_name), 0)
Exemplo n.º 10
0
async def find_build_file(address: Address) -> BuildFileAddress:
    address_family = await Get[AddressFamily](Dir(address.spec_path))
    if address not in address_family.addressables:
        _raise_did_you_mean(address_family=address_family,
                            name=address.target_name)
    return next(build_file_address
                for build_file_address in address_family.addressables.keys()
                if build_file_address == address)
Exemplo n.º 11
0
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))
Exemplo n.º 12
0
class PathRoot(datatype('PathRoot', []), PathGlob):
    """A PathGlob matching the root of the ProjectTree.

  The root is special because it's the only symbolic path that we can implicit trust is
  not a symlink.
  """
    canonical_stat = Dir('')
    symbolic_path = ''

    paths = Paths((Path(symbolic_path, canonical_stat), ))
Exemplo n.º 13
0
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 _did_you_mean_exception(address_family, address.target_name)
    return target_adaptor
Exemplo n.º 14
0
async def find_build_file(address: Address) -> BuildFileAddress:
    address_family = await Get(AddressFamily, Dir(address.spec_path))
    owning_address = address.maybe_convert_to_base_target()
    if address_family.get_target_adaptor(owning_address) is None:
        raise _did_you_mean_exception(address_family=address_family,
                                      name=owning_address.target_name)
    bfa = next(build_file_address
               for build_file_address in address_family.build_file_addresses
               if build_file_address.address == owning_address)
    return (bfa if address.is_base_target else BuildFileAddress(
        rel_path=bfa.rel_path, address=address))
Exemplo n.º 15
0
 def _lstat(self, relpath):
   mode = type(self._reader.lstat(self._scm_relpath(relpath)))
   if mode == NoneType:
     return None
   elif mode == self._reader.Symlink:
     return Link(relpath)
   elif mode == self._reader.Dir:
     return Dir(relpath)
   elif mode == self._reader.File:
     return File(relpath)
   else:
     raise IOError('Unsupported file type in {}: {}'.format(self, relpath))
Exemplo n.º 16
0
    def create_from_spec(cls, canonical_stat, symbolic_path, filespec):
        """Given a filespec, return a tuple of PathGlob objects.

    :param canonical_stat: A canonical Dir relative to the ProjectTree, to which the filespec
      is relative.
    :param symbolic_path: A symbolic name for the canonical_stat (or the same name, if no symlinks
      were traversed while expanding it).
    :param filespec: A filespec, relative to the canonical_stat.
    """
        if not isinstance(canonical_stat, Dir):
            raise ValueError(
                'Expected a Dir as the canonical_stat. Got: {}'.format(
                    canonical_stat))

        parts = normpath(filespec).split(os_sep)
        if canonical_stat == Dir('') and len(parts) == 1 and parts[0] == '.':
            # A request for the root path.
            return (PathRoot(), )
        elif cls._DOUBLE == parts[0]:
            parts = cls._prune_doublestar(parts)

            if len(parts) == 1:
                # Per https://git-scm.com/docs/gitignore:
                #
                #  "A trailing '/**' matches everything inside. For example, 'abc/**' matches all files inside
                #   directory "abc", relative to the location of the .gitignore file, with infinite depth."
                #
                return (PathDirWildcard(canonical_stat, symbolic_path, '*',
                                        '**'),
                        PathWildcard(canonical_stat, symbolic_path, '*'))

            # There is a double-wildcard in a dirname of the path: double wildcards are recursive,
            # so there are two remainder possibilities: one with the double wildcard included, and the
            # other without.
            pathglob_with_doublestar = PathDirWildcard(canonical_stat,
                                                       symbolic_path, '*',
                                                       join(*parts[0:]))
            if len(parts) == 2:
                pathglob_no_doublestar = PathWildcard(canonical_stat,
                                                      symbolic_path, parts[1])
            else:
                pathglob_no_doublestar = PathDirWildcard(
                    canonical_stat, symbolic_path, parts[1], join(*parts[2:]))
            return (pathglob_with_doublestar, pathglob_no_doublestar)
        elif len(parts) == 1:
            # This is the path basename.
            return (PathWildcard(canonical_stat, symbolic_path, parts[0]), )
        else:
            # This is a path dirname.
            return (PathDirWildcard(canonical_stat, symbolic_path, parts[0],
                                    join(*parts[1:])), )
Exemplo n.º 17
0
async def addresses_with_origins_from_address_specs(
        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 no targets
        were matched.
    """
    # Snapshot all BUILD files covered by the AddressSpecs, then group by directory.
    snapshot = await Get(
        Snapshot,
        PathGlobs,
        address_specs.to_path_globs(
            build_patterns=address_mapper.build_patterns,
            build_ignore_patterns=address_mapper.build_ignore_patterns,
        ),
    )
    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:
        # These may raise ResolveError, depending on the type of spec.
        addr_families_for_spec = address_spec.matching_address_families(
            address_family_by_directory)
        addr_target_pairs_for_spec = address_spec.matching_addresses(
            addr_families_for_spec)

        if isinstance(address_spec,
                      SingleAddress) and not addr_target_pairs_for_spec:
            addr_family = assert_single_element(addr_families_for_spec)
            raise _did_you_mean_exception(addr_family, address_spec.name)

        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), address_spec)

        matched_addresses.update(
            addr for (addr, tgt) in addr_target_pairs_for_spec
            if (address_specs.filter_by_global_options is False
                or address_mapper.matches_filter_options(addr, tgt)))

    return AddressesWithOrigins(
        AddressWithOrigin(address=addr, origin=addr_to_origin[addr])
        for addr in matched_addresses)
Exemplo n.º 18
0
class StorageTest(unittest.TestCase):
    TEST_KEY = b'hello'
    TEST_VALUE = b'world'

    TEST_PATH = File('/foo')
    TEST_PATH2 = Dir('/bar')

    class SomeException(Exception):
        pass

    def setUp(self):
        self.storage = Storage.create()
        self.result = 'something'
        self.request = Runnable(func=_runnable,
                                args=('this is an arg', ),
                                cacheable=True)

    def test_lmdb_key_value_store(self):
        lmdb = Lmdb.create()[0]
        with closing(lmdb) as kvs:
            # Initially key does not exist.
            self.assertFalse(kvs.get(self.TEST_KEY))

            # Now write a key value pair and read back.
            written = kvs.put(self.TEST_KEY, self.TEST_VALUE)
            self.assertTrue(written)
            self.assertEquals(self.TEST_VALUE,
                              kvs.get(self.TEST_KEY).getvalue())

            # Write the same key again will not overwrite.
            self.assertFalse(kvs.put(self.TEST_KEY, self.TEST_VALUE))

    def test_storage(self):
        with closing(self.storage) as storage:
            key = storage.put(self.TEST_PATH)
            self.assertEquals(self.TEST_PATH, storage.get(key))

            with self.assertRaises(InvalidKeyError):
                self.assertFalse(storage.get(self.TEST_KEY))

    def test_storage_key_mappings(self):
        with closing(self.storage) as storage:
            key1 = storage.put(self.TEST_PATH)
            key2 = storage.put(self.TEST_PATH2)
            storage.add_mapping(key1, key2)
            self.assertEquals(key2, storage.get_mapping(key1))

            # key2 isn't mapped to any other key.
            self.assertIsNone(storage.get_mapping(key2))
Exemplo n.º 19
0
def resolve_unhydrated_struct(address_mapper, address):
    """Given an AddressMapper and an Address, resolve an UnhydratedStruct.

  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)

    dependencies = []

    def maybe_append(outer_key, value):
        if isinstance(value, six.string_types):
            if outer_key != 'dependencies':
                dependencies.append(
                    Address.parse(
                        value,
                        relative_to=address.spec_path,
                        subproject_roots=address_mapper.subproject_roots))
        elif isinstance(value, Struct):
            collect_dependencies(value)

    def collect_dependencies(item):
        for key, value in sorted(item._asdict().items(), key=_key_func):
            if not AddressableDescriptor.is_addressable(item, key):
                continue
            if isinstance(value, collections.MutableMapping):
                for _, v in sorted(value.items(), key=_key_func):
                    maybe_append(key, v)
            elif isinstance(value, collections.MutableSequence):
                for v in value:
                    maybe_append(key, v)
            else:
                maybe_append(key, value)

    collect_dependencies(struct)

    yield UnhydratedStruct(
        filter(lambda build_address: build_address == address, addresses)[0],
        struct, dependencies)
Exemplo n.º 20
0
 def test_nodes_symlink_file(self):
     self.assert_fsnodes(['c.ln/2'], [
         (Dir(''), DirectoryListing),
         (Link('c.ln'), ReadLink),
         (Dir('a'), DirectoryListing),
         (Dir('a/b'), DirectoryListing),
     ])
     self.assert_fsnodes(['d.ln/b/1.txt'], [
         (Dir(''), DirectoryListing),
         (Link('d.ln'), ReadLink),
         (Dir('a'), DirectoryListing),
         (Dir('a/b'), DirectoryListing),
     ])
Exemplo n.º 21
0
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))
Exemplo n.º 22
0
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
Exemplo n.º 23
0
 def test_empty(self) -> None:
     """Test that parsing an empty BUILD file results in an empty AddressFamily."""
     address_mapper = AddressMapper(JsonParser(TEST_TABLE))
     af = run_rule(
         parse_address_family,
         rule_args=[address_mapper, 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)
Exemplo n.º 24
0
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()),
            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
Exemplo n.º 25
0
    def _scandir_raw(self, relpath):
        # Sanity check. TODO: this should probably be added to the ProjectTree interface as
        # an optional call, so that we can use it in fs.py rather than applying it by default.
        abspath = os.path.normpath(self._join(relpath))
        if os.path.realpath(abspath) != abspath:
            raise ValueError(
                'scandir for non-canonical path "{}" not supported in {}.'.
                format(relpath, self))

        for entry in os.scandir(abspath):
            # NB: We don't use `DirEntry.stat`, as the scandir docs indicate that that always requires
            # an additional syscall on Unixes.
            entry_path = os.path.normpath(os.path.join(relpath, entry.name))
            if entry.is_file(follow_symlinks=False):
                yield File(entry_path)
            elif entry.is_dir(follow_symlinks=False):
                yield Dir(entry_path)
            elif entry.is_symlink():
                yield Link(entry_path)
            else:
                raise IOError('Unsupported file type in {}: {}'.format(
                    self, entry_path))
Exemplo n.º 26
0
Arquivo: fs.py Projeto: ebubae/pants
    def create_from_spec(cls, canonical_stat, symbolic_path, filespec):
        """Given a filespec, return a PathGlob object.

    :param canonical_stat: A canonical Dir relative to the ProjectTree, to which the filespec
      is relative.
    :param symbolic_path: A symbolic name for the canonical_stat (or the same name, if no symlinks
      were traversed while expanding it).
    :param filespec: A filespec, relative to the canonical_stat.
    """
        if not isinstance(canonical_stat, Dir):
            raise ValueError(
                'Expected a Dir as the canonical_stat. Got: {}'.format(
                    canonical_stat))

        parts = normpath(filespec).split(os_sep)
        if canonical_stat == Dir('') and len(parts) == 1 and parts[0] == '.':
            # A request for the root path.
            return PathRoot()
        elif cls._DOUBLE in parts[0]:
            if parts[0] != cls._DOUBLE:
                raise ValueError(
                    'Illegal component "{}" in filespec under {}: {}'.format(
                        parts[0], symbolic_path, filespec))
            # There is a double-wildcard in a dirname of the path: double wildcards are recursive,
            # so there are two remainder possibilities: one with the double wildcard included, and the
            # other without.
            remainders = (join(*parts[1:]), join(*parts[0:]))
            return PathDirWildcard(canonical_stat, symbolic_path, parts[0],
                                   remainders)
        elif len(parts) == 1:
            # This is the path basename.
            return PathWildcard(canonical_stat, symbolic_path, parts[0])
        else:
            # This is a path dirname.
            remainders = (join(*parts[1:]), )
            return PathDirWildcard(canonical_stat, symbolic_path, parts[0],
                                   remainders)
Exemplo n.º 27
0
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}))
Exemplo n.º 28
0
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))
    struct = address_family.addressables_as_address_keyed.get(address)
    if struct is None:
        _raise_did_you_mean(address_family, address.target_name)

    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}))
Exemplo n.º 29
0
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
    )
Exemplo n.º 30
0
 def test_nodes_file(self):
     self.assert_fsnodes(['4.txt'], [
         (Dir(''), DirectoryListing),
     ])