@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);
  }
  protected RepositoryValue writeBuildFile(FileValue directoryValue, String contents)
      throws RepositoryFunctionException {
    Path buildFilePath = directoryValue.realRootedPath().asPath().getRelative("BUILD");
    try {
      FileSystemUtils.writeContentAsLatin1(buildFilePath, contents);
    } catch (IOException e) {
      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
    }

    return RepositoryValue.create(directoryValue);
  }
  /**
   * 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);
  }