private ResolvedTargets<Void> getTargetsInPackage(
     String originalPattern, PathFragment packageNameFragment, FilteringPolicy policy)
     throws TargetParsingException, InterruptedException {
   TargetPatternResolverUtil.validatePatternPackage(originalPattern, packageNameFragment, this);
   try {
     PackageIdentifier packageId = PackageIdentifier.createInDefaultRepo(packageNameFragment);
     Package pkg = packageProvider.getPackage(env.getListener(), packageId);
     ResolvedTargets<Target> packageTargets =
         TargetPatternResolverUtil.resolvePackageTargets(pkg, policy);
     ImmutableList.Builder<SkyKey> builder = ImmutableList.builder();
     for (Target target : packageTargets.getTargets()) {
       builder.add(TransitiveTraversalValue.key(target.getLabel()));
     }
     ImmutableList<SkyKey> skyKeys = builder.build();
     env.getValuesOrThrow(skyKeys, NoSuchPackageException.class, NoSuchTargetException.class);
     if (env.valuesMissing()) {
       throw new MissingDepException();
     }
     return ResolvedTargets.empty();
   } catch (NoSuchThingException e) {
     String message =
         TargetPatternResolverUtil.getParsingErrorMessage(
             "package contains errors", originalPattern);
     throw new TargetParsingException(message, e);
   }
 }
 @Override
 public boolean isPackage(String packageName) {
   // TODO(bazel-team): this should get the whole PackageIdentifier. Using only the package name
   // makes it impossible to use wildcards to refer to targets in remote repositories.
   return packageProvider.isPackage(
       env.getListener(), PackageIdentifier.createInDefaultRepo(packageName));
 }
 private static SkyKey key(RepositoryName repo, PathFragment fileToImport)
     throws ASTLookupInputException {
   // Skylark import lookup keys need to be valid AST file lookup keys.
   checkInputArgument(fileToImport);
   return new SkyKey(
       SkyFunctions.SKYLARK_IMPORTS_LOOKUP, PackageIdentifier.create(repo, fileToImport));
 }
  private void assertSkipsFoo(ImmutableList<String> patternSequence) throws Exception {

    // When PrepareDepsOfPatternsFunction completes evaluation (successfully),
    WalkableGraph walkableGraph =
        getGraphFromPatternsEvaluation(
            patternSequence, /*successExpected=*/ true, /*keepGoing=*/ true);

    // Then the graph contains a package value for "@//foo",
    assertTrue(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo"))));

    // But no package value for "@//foo/foo",
    assertFalse(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo/foo"))));

    // And the graph does not contain a value for the target "@//foo/foo:foofoo".
    Label label = Label.create("@//foo/foo", "foofoo");
    assertFalse(walkableGraph.exists(getKeyForLabel(label)));
  }
  @Test
  public void testRecursiveEvaluationFailsOnBadBuildFile() throws Exception {
    // Given a well-formed package "@//foo" and a malformed package "@//foo/foo",
    createFooAndFooFoo();

    // Given a target pattern sequence consisting of a recursive pattern for "//foo/...",
    ImmutableList<String> patternSequence = ImmutableList.of("//foo/...");

    // When PrepareDepsOfPatternsFunction completes evaluation (with no error because it was
    // recovered from),
    WalkableGraph walkableGraph =
        getGraphFromPatternsEvaluation(
            patternSequence, /*successExpected=*/ true, /*keepGoing=*/ true);

    // Then the graph contains package values for "@//foo" and "@//foo/foo",
    assertTrue(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo"))));
    assertTrue(walkableGraph.exists(PackageValue.key(PackageIdentifier.parse("@//foo/foo"))));

    // But the graph does not contain a value for the target "@//foo/foo:foofoo".
    assertFalse(walkableGraph.exists(getKeyForLabel(Label.create("@//foo/foo", "foofoo"))));
  }
Beispiel #6
0
  @Before
  public void setUp() throws Exception {
    buildFile = scratch.file("isolated/BUILD", "# contents don't matter in this test");
    scratch.file("isolated/sub/BUILD", "# contents don't matter in this test");

    packageDirectory = buildFile.getParentDirectory();

    scratch.file("isolated/first.txt", "# this is first.txt");

    scratch.file("isolated/second.txt", "# this is second.txt");

    scratch.file("isolated/first.js", "# this is first.js");

    scratch.file("isolated/second.js", "# this is second.js");

    // Files in subdirectories

    scratch.file("isolated/foo/first.js", "# this is foo/first.js");

    scratch.file("isolated/foo/second.js", "# this is foo/second.js");

    scratch.file("isolated/bar/first.js", "# this is bar/first.js");

    scratch.file("isolated/bar/second.js", "# this is bar/second.js");

    scratch.file("isolated/sub/sub.js", "# this is sub/sub.js");

    cache =
        new GlobCache(
            packageDirectory,
            PackageIdentifier.createInDefaultRepo("isolated"),
            new CachingPackageLocator() {
              @Override
              public Path getBuildFileForPackage(PackageIdentifier packageId) {
                String packageName = packageId.getPackageFragment().getPathString();
                if (packageName.equals("isolated")) {
                  return scratch.resolve("isolated/BUILD");
                } else if (packageName.equals("isolated/sub")) {
                  return scratch.resolve("isolated/sub/BUILD");
                } else {
                  return null;
                }
              }
            },
            null,
            TestUtils.getPool());
  }
Beispiel #7
0
 private Package deserializeInternal(InputStream in)
     throws PackageDeserializationException, IOException, InterruptedException {
   // Read the initial Package message so we have the data to initialize the builder. We will read
   // the Targets in individually later.
   Build.Package packagePb = Build.Package.parseDelimitedFrom(in);
   Package.Builder builder;
   try {
     builder =
         new Package.Builder(
             PackageIdentifier.create(
                 packagePb.getRepository(), new PathFragment(packagePb.getName())),
             null);
   } catch (LabelSyntaxException e) {
     throw new PackageDeserializationException(e);
   }
   StoredEventHandler eventHandler = new StoredEventHandler();
   deserializeInternal(packagePb, eventHandler, builder, in);
   builder.addEvents(eventHandler.getEvents());
   return builder.build();
 }
  /**
   * Converts the PathFragment of the Skylark file to a Label using the BUILD file closest to the
   * Skylark file in its directory hierarchy - finds the package to which the Skylark file belongs.
   * Throws an exception if no such BUILD file exists.
   */
  private Label pathFragmentToLabel(RepositoryName repo, PathFragment file, Environment env)
      throws SkylarkImportLookupFunctionException {
    ContainingPackageLookupValue containingPackageLookupValue = null;
    try {
      PackageIdentifier newPkgId = PackageIdentifier.create(repo, file.getParentDirectory());
      containingPackageLookupValue =
          (ContainingPackageLookupValue)
              env.getValueOrThrow(
                  ContainingPackageLookupValue.key(newPkgId),
                  BuildFileNotFoundException.class,
                  InconsistentFilesystemException.class);
    } catch (BuildFileNotFoundException e) {
      // Thrown when there are IO errors looking for BUILD files.
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    } catch (InconsistentFilesystemException e) {
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    }

    if (containingPackageLookupValue == null) {
      return null;
    }

    if (!containingPackageLookupValue.hasContainingPackage()) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.noBuildFile(file));
    }

    PathFragment pkgName =
        containingPackageLookupValue.getContainingPackageName().getPackageFragment();
    PathFragment fileInPkg = file.relativeTo(pkgName);

    try {
      // This code relies on PackageIdentifier.RepositoryName.toString()
      return Label.parseAbsolute(repo + "//" + pkgName.getPathString() + ":" + fileInPkg);
    } catch (LabelSyntaxException e) {
      throw new IllegalStateException(e);
    }
  }
 /**
  * Get SkyKeys for the FileValues for the given {@code pathFragments}. To do this, we look for a
  * package lookup node for each path fragment, since package lookup nodes contain the "root" of a
  * package. The returned SkyKeys correspond to FileValues that may not exist in the graph.
  */
 private Collection<SkyKey> getSkyKeysForFileFragments(Iterable<PathFragment> pathFragments) {
   Set<SkyKey> result = new HashSet<>();
   Multimap<PathFragment, PathFragment> currentToOriginal = ArrayListMultimap.create();
   for (PathFragment pathFragment : pathFragments) {
     currentToOriginal.put(pathFragment, pathFragment);
   }
   while (!currentToOriginal.isEmpty()) {
     Map<SkyKey, PathFragment> keys = new HashMap<>();
     for (PathFragment pathFragment : currentToOriginal.keySet()) {
       keys.put(
           PackageLookupValue.key(PackageIdentifier.createInDefaultRepo(pathFragment)),
           pathFragment);
     }
     Map<SkyKey, SkyValue> lookupValues = graph.getSuccessfulValues(keys.keySet());
     for (Map.Entry<SkyKey, SkyValue> entry : lookupValues.entrySet()) {
       PackageLookupValue packageLookupValue = (PackageLookupValue) entry.getValue();
       if (packageLookupValue.packageExists()) {
         PathFragment dir = keys.get(entry.getKey());
         Collection<PathFragment> originalFiles = currentToOriginal.get(dir);
         Preconditions.checkState(!originalFiles.isEmpty(), entry);
         for (PathFragment fileName : originalFiles) {
           result.add(
               FileValue.key(RootedPath.toRootedPath(packageLookupValue.getRoot(), fileName)));
         }
         currentToOriginal.removeAll(dir);
       }
     }
     Multimap<PathFragment, PathFragment> newCurrentToOriginal = ArrayListMultimap.create();
     for (PathFragment pathFragment : currentToOriginal.keySet()) {
       PathFragment parent = pathFragment.getParentDirectory();
       if (parent != null) {
         newCurrentToOriginal.putAll(parent, currentToOriginal.get(pathFragment));
       }
     }
     currentToOriginal = newCurrentToOriginal;
   }
   return result;
 }
  SkyValue computeInternal(
      SkyKey skyKey, Environment env, @Nullable Set<SkyKey> visitedKeysForCycle)
      throws SkyFunctionException, InterruptedException {
    PackageIdentifier arg = (PackageIdentifier) skyKey.argument();
    PathFragment file = arg.getPackageFragment();
    ASTFileLookupValue astLookupValue = null;
    try {
      SkyKey astLookupKey = ASTFileLookupValue.key(arg);
      astLookupValue =
          (ASTFileLookupValue)
              env.getValueOrThrow(
                  astLookupKey,
                  ErrorReadingSkylarkExtensionException.class,
                  InconsistentFilesystemException.class);
    } catch (ErrorReadingSkylarkExtensionException e) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.errorReadingFile(file, e.getMessage()));
    } catch (InconsistentFilesystemException e) {
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    }
    if (astLookupValue == null) {
      return null;
    }
    if (astLookupValue.getAST() == null) {
      // Skylark import files have to exist.
      throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.noFile(file));
    }

    BuildFileAST ast = astLookupValue.getAST();
    if (ast.containsErrors()) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.skylarkErrors(file));
    }

    Label label = pathFragmentToLabel(arg.getRepository(), file, env);
    if (label == null) {
      Preconditions.checkState(env.valuesMissing(), "null label with no missing %s", file);
      return null;
    }

    Map<Location, PathFragment> astImports = ast.getImports();
    Map<PathFragment, Extension> importMap = Maps.newHashMapWithExpectedSize(astImports.size());
    ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
    Map<SkyKey, PathFragment> skylarkImports = Maps.newHashMapWithExpectedSize(astImports.size());
    for (Map.Entry<Location, PathFragment> entry : ast.getImports().entrySet()) {
      try {
        skylarkImports.put(
            PackageFunction.getImportKey(entry, ruleClassProvider.getPreludePath(), file, arg),
            entry.getValue());
      } catch (ASTLookupInputException e) {
        throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
      }
    }

    Map<SkyKey, SkyValue> skylarkImportMap;
    boolean valuesMissing = false;
    if (visitedKeysForCycle == null) {
      // Not inlining.
      skylarkImportMap = env.getValues(skylarkImports.keySet());
      valuesMissing = env.valuesMissing();
    } else {
      // inlining calls to SkylarkImportLookupFunction.
      if (!visitedKeysForCycle.add(skyKey)) {
        ImmutableList<SkyKey> cycle =
            CycleUtils.splitIntoPathAndChain(Predicates.equalTo(skyKey), visitedKeysForCycle)
                .second;
        if (env.getValue(SkylarkImportUniqueCycleValue.key(cycle)) == null) {
          return null;
        }
        throw new SkylarkImportLookupFunctionException(
            new SkylarkImportFailedException("Skylark import cycle"));
      }
      skylarkImportMap = Maps.newHashMapWithExpectedSize(astImports.size());
      for (SkyKey skylarkImport : skylarkImports.keySet()) {
        SkyValue skyValue = this.computeWithInlineCalls(skylarkImport, env, visitedKeysForCycle);
        if (skyValue == null) {
          Preconditions.checkState(
              env.valuesMissing(), "no skylark import value for %s", skylarkImport);
          // Don't give up on computing. This is against the Skyframe contract, but we don't want to
          // pay the price of serializing all these calls, since they are fundamentally independent.
          valuesMissing = true;
        } else {
          skylarkImportMap.put(skylarkImport, skyValue);
        }
      }
      // All imports traversed, this key can no longer be part of a cycle.
      visitedKeysForCycle.remove(skyKey);
    }

    if (valuesMissing) {
      // This means some imports are unavailable.
      return null;
    }

    for (Map.Entry<SkyKey, SkyValue> entry : skylarkImportMap.entrySet()) {
      SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue) entry.getValue();
      importMap.put(
          skylarkImports.get(entry.getKey()), importLookupValue.getEnvironmentExtension());
      fileDependencies.add(importLookupValue.getDependency());
    }

    // Skylark UserDefinedFunction-s in that file will share this function definition Environment,
    // which will be frozen by the time it is returned by createExtension.
    Extension extension = createExtension(ast, file, importMap, env);

    return new SkylarkImportLookupValue(
        extension, new SkylarkFileDependency(label, fileDependencies.build()));
  }
 @VisibleForTesting
 static SkyKey key(PackageIdentifier pkgIdentifier) throws ASTLookupInputException {
   return key(pkgIdentifier.getRepository(), pkgIdentifier.getPackageFragment());
 }
  @Override
  public SkyValue compute(SkyKey skyKey, Environment env)
      throws SkyFunctionException, InterruptedException {
    PackageIdentifier arg = (PackageIdentifier) skyKey.argument();
    PathFragment file = arg.getPackageFragment();
    ASTFileLookupValue astLookupValue = null;
    try {
      SkyKey astLookupKey = ASTFileLookupValue.key(arg);
      astLookupValue =
          (ASTFileLookupValue)
              env.getValueOrThrow(
                  astLookupKey,
                  ErrorReadingSkylarkExtensionException.class,
                  InconsistentFilesystemException.class);
    } catch (ErrorReadingSkylarkExtensionException e) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.errorReadingFile(file, e.getMessage()));
    } catch (InconsistentFilesystemException e) {
      throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
    }
    if (astLookupValue == null) {
      return null;
    }
    if (astLookupValue.getAST() == null) {
      // Skylark import files have to exist.
      throw new SkylarkImportLookupFunctionException(SkylarkImportFailedException.noFile(file));
    }

    BuildFileAST ast = astLookupValue.getAST();
    if (ast.containsErrors()) {
      throw new SkylarkImportLookupFunctionException(
          SkylarkImportFailedException.skylarkErrors(file));
    }

    Map<Location, PathFragment> astImports = ast.getImports();
    Map<PathFragment, Extension> importMap = Maps.newHashMapWithExpectedSize(astImports.size());
    ImmutableList.Builder<SkylarkFileDependency> fileDependencies = ImmutableList.builder();
    Map<SkyKey, PathFragment> skylarkImports = Maps.newHashMapWithExpectedSize(astImports.size());
    for (Map.Entry<Location, PathFragment> entry : ast.getImports().entrySet()) {
      try {
        skylarkImports.put(
            PackageFunction.getImportKey(entry, ruleClassProvider.getPreludePath(), file, arg),
            entry.getValue());
      } catch (ASTLookupInputException e) {
        throw new SkylarkImportLookupFunctionException(e, Transience.PERSISTENT);
      }
    }
    Map<SkyKey, SkyValue> skylarkImportMap = env.getValues(skylarkImports.keySet());

    if (env.valuesMissing()) {
      // This means some imports are unavailable.
      return null;
    }
    for (Map.Entry<SkyKey, SkyValue> entry : skylarkImportMap.entrySet()) {
      SkylarkImportLookupValue importLookupValue = (SkylarkImportLookupValue) entry.getValue();
      importMap.put(
          skylarkImports.get(entry.getKey()), importLookupValue.getEnvironmentExtension());
      fileDependencies.add(importLookupValue.getDependency());
    }

    Label label = pathFragmentToLabel(arg.getRepository(), file, env);

    if (label == null) {
      Preconditions.checkState(env.valuesMissing(), "label null but no missing for %s", file);
      return null;
    }

    // Skylark UserDefinedFunction-s in that file will share this function definition Environment,
    // which will be frozen by the time it is returned by createExtension.
    Extension extension = createExtension(ast, file, importMap, env);

    return new SkylarkImportLookupValue(
        extension, new SkylarkFileDependency(label, fileDependencies.build()));
  }
 public BuildFileContainsErrorsException(PackageIdentifier packageIdentifier) {
   super(
       packageIdentifier,
       "Package '" + packageIdentifier.getPackageFragment().getPathString() + "' contains errors");
 }
  /**
   * Looks in the directory specified by {@code recursivePkgKey} for a package, does some work as
   * specified by {@link Visitor} if such a package exists, then recursively does work in each
   * non-excluded subdirectory as specified by {@link #getSkyKeyForSubdirectory}, and finally
   * aggregates the {@link Visitor} value along with values from each subdirectory as specified by
   * {@link #aggregateWithSubdirectorySkyValues}, and returns that aggregation.
   *
   * <p>Returns null if {@code env.valuesMissing()} is true, checked after each call to one of
   * {@link RecursiveDirectoryTraversalFunction}'s abstract methods that were given {@code env}.
   * (And after each of {@code visitDirectory}'s own uses of {@code env}, of course.)
   */
  TReturn visitDirectory(RecursivePkgKey recursivePkgKey, Environment env) {
    RootedPath rootedPath = recursivePkgKey.getRootedPath();
    ImmutableSet<PathFragment> excludedPaths = recursivePkgKey.getExcludedPaths();
    Path root = rootedPath.getRoot();
    PathFragment rootRelativePath = rootedPath.getRelativePath();

    SkyKey fileKey = FileValue.key(rootedPath);
    FileValue fileValue;
    try {
      fileValue =
          (FileValue)
              env.getValueOrThrow(
                  fileKey,
                  InconsistentFilesystemException.class,
                  FileSymlinkException.class,
                  IOException.class);
    } catch (InconsistentFilesystemException | FileSymlinkException | IOException e) {
      return reportErrorAndReturn(
          "Failed to get information about path", e, rootRelativePath, env.getListener());
    }
    if (fileValue == null) {
      return null;
    }

    if (!fileValue.isDirectory()) {
      return getEmptyReturn();
    }

    PackageIdentifier packageId =
        PackageIdentifier.create(recursivePkgKey.getRepository(), rootRelativePath);

    if ((packageId.getRepository().isDefault() || packageId.getRepository().isMain())
        && fileValue.isSymlink()
        && fileValue
            .getUnresolvedLinkTarget()
            .startsWith(directories.getOutputBase().asFragment())) {
      // Symlinks back to the output base are not traversed so that we avoid convenience symlinks.
      // Note that it's not enough to just check for the convenience symlinks themselves, because
      // if the value of --symlink_prefix changes, the old symlinks are left in place. This
      // algorithm also covers more creative use cases where people create convenience symlinks
      // somewhere in the directory tree manually.
      return getEmptyReturn();
    }

    SkyKey pkgLookupKey = PackageLookupValue.key(packageId);
    SkyKey dirListingKey = DirectoryListingValue.key(rootedPath);
    Map<
            SkyKey,
            ValueOrException4<
                NoSuchPackageException,
                InconsistentFilesystemException,
                FileSymlinkException,
                IOException>>
        pkgLookupAndDirectoryListingDeps =
            env.getValuesOrThrow(
                ImmutableList.of(pkgLookupKey, dirListingKey),
                NoSuchPackageException.class,
                InconsistentFilesystemException.class,
                FileSymlinkException.class,
                IOException.class);
    if (env.valuesMissing()) {
      return null;
    }
    PackageLookupValue pkgLookupValue;
    try {
      pkgLookupValue =
          (PackageLookupValue)
              Preconditions.checkNotNull(
                  pkgLookupAndDirectoryListingDeps.get(pkgLookupKey).get(),
                  "%s %s",
                  recursivePkgKey,
                  pkgLookupKey);
    } catch (NoSuchPackageException | InconsistentFilesystemException e) {
      return reportErrorAndReturn("Failed to load package", e, rootRelativePath, env.getListener());
    } catch (IOException | FileSymlinkException e) {
      throw new IllegalStateException(e);
    }

    TVisitor visitor = getInitialVisitor();

    DirectoryListingValue dirListingValue;
    try {
      dirListingValue =
          (DirectoryListingValue)
              Preconditions.checkNotNull(
                  pkgLookupAndDirectoryListingDeps.get(dirListingKey).get(),
                  "%s %s",
                  recursivePkgKey,
                  dirListingKey);
    } catch (InconsistentFilesystemException | IOException e) {
      return reportErrorAndReturn(
          "Failed to list directory contents", e, rootRelativePath, env.getListener());
    } catch (FileSymlinkException e) {
      // DirectoryListingFunction only throws FileSymlinkCycleException when FileFunction throws it,
      // but FileFunction was evaluated for rootedPath above, and didn't throw there. It shouldn't
      // be able to avoid throwing there but throw here.
      throw new IllegalStateException(
          "Symlink cycle found after not being found for \"" + rootedPath + "\"");
    } catch (NoSuchPackageException e) {
      throw new IllegalStateException(e);
    }

    boolean followSymlinks = shouldFollowSymlinksWhenTraversing(dirListingValue.getDirents());
    List<SkyKey> childDeps = new ArrayList<>();
    for (Dirent dirent : dirListingValue.getDirents()) {
      Type type = dirent.getType();
      if (type != Type.DIRECTORY
          && (type != Type.SYMLINK || (type == Type.SYMLINK && !followSymlinks))) {
        // Non-directories can never host packages. Symlinks to non-directories are weeded out at
        // the next level of recursion when we check if its FileValue is a directory. This is slower
        // if there are a lot of symlinks in the tree, but faster if there are only a few, which is
        // the case most of the time.
        //
        // We are not afraid of weird symlink structure here: both cyclical ones and ones that give
        // rise to infinite directory trees are diagnosed by FileValue.
        continue;
      }
      String basename = dirent.getName();
      if (rootRelativePath.equals(PathFragment.EMPTY_FRAGMENT)
          && PathPackageLocator.DEFAULT_TOP_LEVEL_EXCLUDES.contains(basename)) {
        continue;
      }
      PathFragment subdirectory = rootRelativePath.getRelative(basename);

      // If this subdirectory is one of the excluded paths, don't recurse into it.
      if (excludedPaths.contains(subdirectory)) {
        continue;
      }

      // If we have an excluded path that isn't below this subdirectory, we shouldn't pass that
      // excluded path to our evaluation of the subdirectory, because the exclusion can't
      // possibly match anything beneath the subdirectory.
      //
      // For example, if we're currently evaluating directory "a", are looking at its subdirectory
      // "a/b", and we have an excluded path "a/c/d", there's no need to pass the excluded path
      // "a/c/d" to our evaluation of "a/b".
      //
      // This strategy should help to get more skyframe sharing. Consider the example above. A
      // subsequent request of "a/b/...", without any excluded paths, will be a cache hit.
      //
      // TODO(bazel-team): Replace the excludedPaths set with a trie or a SortedSet for better
      // efficiency.
      ImmutableSet<PathFragment> excludedSubdirectoriesBeneathThisSubdirectory =
          PathFragment.filterPathsStartingWith(excludedPaths, subdirectory);
      RootedPath subdirectoryRootedPath = RootedPath.toRootedPath(root, subdirectory);
      childDeps.add(
          getSkyKeyForSubdirectory(
              recursivePkgKey.getRepository(),
              subdirectoryRootedPath,
              excludedSubdirectoriesBeneathThisSubdirectory));
    }

    Map<SkyKey, SkyValue> subdirectorySkyValues;
    if (pkgLookupValue.packageExists() && pkgLookupValue.getRoot().equals(root)) {
      SkyKey packageKey = PackageValue.key(packageId);
      Map<SkyKey, ValueOrException<NoSuchPackageException>> dependentSkyValues =
          env.getValuesOrThrow(
              Iterables.concat(childDeps, ImmutableList.of(packageKey)),
              NoSuchPackageException.class);
      if (env.valuesMissing()) {
        return null;
      }
      Package pkg = null;
      try {
        PackageValue pkgValue = (PackageValue) dependentSkyValues.get(packageKey).get();
        if (pkgValue == null) {
          return null;
        }
        pkg = pkgValue.getPackage();
        if (pkg.containsErrors()) {
          env.getListener()
              .handle(Event.error("package contains errors: " + rootRelativePath.getPathString()));
        }
      } catch (NoSuchPackageException e) {
        // The package had errors, but don't fail-fast as there might be subpackages below the
        // current directory.
        env.getListener()
            .handle(Event.error("package contains errors: " + rootRelativePath.getPathString()));
      }
      if (pkg != null) {
        visitor.visitPackageValue(pkg, env);
        if (env.valuesMissing()) {
          return null;
        }
      }
      ImmutableMap.Builder<SkyKey, SkyValue> subdirectoryBuilder = ImmutableMap.builder();
      for (Map.Entry<SkyKey, ValueOrException<NoSuchPackageException>> entry :
          Maps.filterKeys(dependentSkyValues, Predicates.not(Predicates.equalTo(packageKey)))
              .entrySet()) {
        try {
          subdirectoryBuilder.put(entry.getKey(), entry.getValue().get());
        } catch (NoSuchPackageException e) {
          // ignored.
        }
      }
      subdirectorySkyValues = subdirectoryBuilder.build();
    } else {
      subdirectorySkyValues = env.getValues(childDeps);
    }
    if (env.valuesMissing()) {
      return null;
    }
    return aggregateWithSubdirectorySkyValues(visitor, subdirectorySkyValues);
  }