private void collectTransitiveClosure(
      PackageProviderForConfigurations packageProvider, Set<Label> reachableLabels, Label from)
      throws NoSuchThingException {
    if (!reachableLabels.add(from)) {
      return;
    }
    Target fromTarget = packageProvider.getTarget(from);
    if (fromTarget instanceof Rule) {
      Rule rule = (Rule) fromTarget;
      if (rule.getRuleClassObject().hasAttr("srcs", BuildType.LABEL_LIST)) {
        // TODO(bazel-team): refine this. This visits "srcs" reachable under *any* configuration,
        // not necessarily the configuration actually applied to the rule. We should correlate the
        // two. However, doing so requires faithfully reflecting the configuration transitions that
        // might happen as we traverse the dependency chain.
        // TODO(bazel-team): Why don't we use AbstractAttributeMapper#visitLabels() here?
        for (List<Label> labelsForConfiguration :
            AggregatingAttributeMapper.of(rule).visitAttribute("srcs", BuildType.LABEL_LIST)) {
          for (Label label : labelsForConfiguration) {
            collectTransitiveClosure(
                packageProvider, reachableLabels, from.resolveRepositoryRelative(label));
          }
        }
      }

      if (rule.getRuleClass().equals("bind")) {
        Label actual = AggregatingAttributeMapper.of(rule).get("actual", BuildType.LABEL);
        if (actual != null) {
          collectTransitiveClosure(packageProvider, reachableLabels, actual);
        }
      }
    }
  }
  @Override
  public List<Target> getLabelListAttr(
      QueryExpression caller, Target target, String attrName, String errorMsgPrefix)
      throws QueryException {
    Preconditions.checkArgument(target instanceof Rule);

    List<Target> result = new ArrayList<>();
    Rule rule = (Rule) target;

    AggregatingAttributeMapper attrMap = AggregatingAttributeMapper.of(rule);
    Type<?> attrType = attrMap.getAttributeType(attrName);
    if (attrType == null) {
      // Return an empty list if the attribute isn't defined for this rule.
      return ImmutableList.of();
    }

    for (Label label : attrMap.getReachableLabels(attrName, false)) {
      try {
        result.add(queryEnvironment.getTarget(label));
      } catch (TargetNotFoundException e) {
        queryEnvironment.reportBuildFileError(caller, errorMsgPrefix + e.getMessage());
      }
    }

    return result;
  }
  @Override
  public SkyValue fetch(Rule rule, Path outputDirectory, Environment env)
      throws SkyFunctionException {
    AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
    PathFragment pathFragment = new PathFragment(mapper.get("path", Type.STRING));
    try {
      FileSystem fs = outputDirectory.getFileSystem();
      if (fs.supportsSymbolicLinksNatively()) {
        outputDirectory.createSymbolicLink(pathFragment);
      } else {
        FileSystemUtils.createDirectoryAndParents(outputDirectory);
        FileSystemUtils.copyTreesBelow(
            fs.getPath(getTargetPath(rule, getWorkspace())), outputDirectory);
      }
    } catch (IOException e) {
      throw new RepositoryFunctionException(
          new IOException(
              "Could not create symlink to repository " + pathFragment + ": " + e.getMessage()),
          Transience.TRANSIENT);
    }
    FileValue repositoryValue = getRepositoryDirectory(outputDirectory, env);
    if (repositoryValue == null) {
      // TODO(bazel-team): If this returns null, we unnecessarily recreate the symlink above on the
      // second execution.
      return null;
    }

    if (!repositoryValue.isDirectory()) {
      throw new RepositoryFunctionException(
          new IOException(rule + " must specify an existing directory"), Transience.TRANSIENT);
    }

    return RepositoryValue.create(outputDirectory);
  }
  /**
   * Symlinks a BUILD file from the local filesystem into the external repository's root.
   *
   * @param rule the rule that declares the build_file path.
   * @param workspaceDirectory the workspace root for the build.
   * @param directoryValue the FileValue corresponding to the external repository's root directory.
   * @param env the Skyframe environment.
   * @return the file value of the symlink created.
   * @throws
   *     com.google.devtools.build.lib.bazel.repository.RepositoryFunction.RepositoryFunctionException
   *     if the BUILD file specified does not exist or cannot be linked.
   */
  protected RepositoryValue symlinkBuildFile(
      Rule rule, Path workspaceDirectory, FileValue directoryValue, Environment env)
      throws RepositoryFunctionException {
    AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
    PathFragment buildFile = new PathFragment(mapper.get("build_file", Type.STRING));
    Path buildFileTarget = workspaceDirectory.getRelative(buildFile);
    if (!buildFileTarget.exists()) {
      throw new RepositoryFunctionException(
          new EvalException(
              rule.getLocation(),
              String.format(
                  "In %s the 'build_file' attribute does not specify an existing file "
                      + "(%s does not exist)",
                  rule, buildFileTarget)),
          Transience.PERSISTENT);
    }

    RootedPath rootedBuild;
    if (buildFile.isAbsolute()) {
      rootedBuild =
          RootedPath.toRootedPath(
              buildFileTarget.getParentDirectory(),
              new PathFragment(buildFileTarget.getBaseName()));
    } else {
      rootedBuild = RootedPath.toRootedPath(workspaceDirectory, buildFile);
    }
    SkyKey buildFileKey = FileValue.key(rootedBuild);
    FileValue buildFileValue;
    try {
      buildFileValue =
          (FileValue)
              env.getValueOrThrow(
                  buildFileKey,
                  IOException.class,
                  FileSymlinkException.class,
                  InconsistentFilesystemException.class);
      if (buildFileValue == null) {
        return null;
      }
    } catch (IOException | FileSymlinkException | InconsistentFilesystemException e) {
      throw new RepositoryFunctionException(
          new IOException("Cannot lookup " + buildFile + ": " + e.getMessage()),
          Transience.TRANSIENT);
    }

    Path buildFilePath = directoryValue.realRootedPath().asPath().getRelative("BUILD");
    if (createSymbolicLink(buildFilePath, buildFileTarget, env) == null) {
      return null;
    }
    return RepositoryValue.createNew(directoryValue, buildFileValue);
  }
  protected static PathFragment getTargetPath(Rule rule) throws RepositoryFunctionException {
    AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
    String path = mapper.get("path", Type.STRING);
    PathFragment pathFragment = new PathFragment(path);
    if (!pathFragment.isAbsolute()) {
      throw new RepositoryFunctionException(
          new EvalException(
              rule.getLocation(),
              "In " + rule + " the 'path' attribute must specify an absolute path"),
          Transience.PERSISTENT);
    }

    return pathFragment;
  }
 protected SkyValue compute(Environment env, Rule rule) throws RepositoryFunctionException {
   // The output directory is always under .external-repository (to stay out of the way of
   // artifacts from this repository) and uses the rule's name to avoid conflicts with other
   // remote repository rules. For example, suppose you had the following WORKSPACE file:
   //
   // http_archive(name = "png", url = "http://example.com/downloads/png.tar.gz", sha256 = "...")
   //
   // This would download png.tar.gz to .external-repository/png/png.tar.gz.
   Path outputDirectory = getExternalRepositoryDirectory().getRelative(rule.getName());
   FileValue directoryValue = createDirectory(outputDirectory, env);
   if (directoryValue == null) {
     return null;
   }
   AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule);
   URL url = null;
   try {
     url = new URL(mapper.get("url", Type.STRING));
   } catch (MalformedURLException e) {
     throw new RepositoryFunctionException(
         new EvalException(rule.getLocation(), "Error parsing URL: " + e.getMessage()),
         Transience.PERSISTENT);
   }
   String sha256 = mapper.get("sha256", Type.STRING);
   HttpDownloader downloader = new HttpDownloader(url, sha256, outputDirectory);
   try {
     Path archiveFile = downloader.download();
     outputDirectory =
         DecompressorFactory.create(
                 rule.getTargetKind(), rule.getName(), archiveFile, outputDirectory)
             .decompress();
   } catch (IOException e) {
     // Assumes all IO errors transient.
     throw new RepositoryFunctionException(e, Transience.TRANSIENT);
   } catch (DecompressorException e) {
     throw new RepositoryFunctionException(new IOException(e.getMessage()), Transience.TRANSIENT);
   }
   return new RepositoryValue(outputDirectory, directoryValue);
 }
  @Override
  public Iterable<String> getAttrAsString(Target target, String attrName) {
    Preconditions.checkArgument(target instanceof Rule);
    List<String> values = new ArrayList<>(); // May hold null values.
    Attribute attribute = ((Rule) target).getAttributeDefinition(attrName);
    if (attribute != null) {
      Type<?> attributeType = attribute.getType();
      for (Object attrValue :
          AggregatingAttributeMapper.of((Rule) target)
              .visitAttribute(attribute.getName(), attributeType)) {

        // Ugly hack to maintain backward 'attr' query compatibility for BOOLEAN and TRISTATE
        // attributes. These are internally stored as actual Boolean or TriState objects but were
        // historically queried as integers. To maintain compatibility, we inspect their actual
        // value and return the integer equivalent represented as a String. This code is the
        // opposite of the code in BooleanType and TriStateType respectively.
        if (attributeType == BOOLEAN) {
          values.add(Type.BOOLEAN.cast(attrValue) ? "1" : "0");
        } else if (attributeType == TRISTATE) {
          switch (BuildType.TRISTATE.cast(attrValue)) {
            case AUTO:
              values.add("-1");
              break;
            case NO:
              values.add("0");
              break;
            case YES:
              values.add("1");
              break;
            default:
              throw new AssertionError("This can't happen!");
          }
        } else {
          values.add(attrValue == null ? null : attrValue.toString());
        }
      }
    }
    return values;
  }