private static FileValue createSymbolicLink(Path from, Path to, Environment env)
      throws RepositoryFunctionException {
    try {
      if (!from.exists()) {
        from.createSymbolicLink(to);
      }
    } catch (IOException e) {
      throw new RepositoryFunctionException(
          new IOException(
              String.format(
                  "Error creating symbolic link from %s to %s: %s", from, to, e.getMessage())),
          Transience.TRANSIENT);
    }

    SkyKey outputDirectoryKey =
        FileValue.key(RootedPath.toRootedPath(from, PathFragment.EMPTY_FRAGMENT));
    try {
      return (FileValue)
          env.getValueOrThrow(
              outputDirectoryKey,
              IOException.class,
              FileSymlinkException.class,
              InconsistentFilesystemException.class);
    } catch (IOException | FileSymlinkException | InconsistentFilesystemException e) {
      throw new RepositoryFunctionException(
          new IOException(String.format("Could not access %s: %s", from, e.getMessage())),
          Transience.PERSISTENT);
    }
  }
  @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);
  }
 @Override
 public void setUp() throws Exception {
   super.setUp();
   input = scratch.file("input.txt", "Hello, world.");
   inputArtifact = getSourceArtifact("input.txt");
   Path linkedInput = directories.getExecRoot().getRelative("input.txt");
   FileSystemUtils.createDirectoryAndParents(linkedInput.getParentDirectory());
   linkedInput.createSymbolicLink(input);
   outputArtifact = getBinArtifactWithNoOwner("destination.txt");
   output = outputArtifact.getPath();
   FileSystemUtils.createDirectoryAndParents(output.getParentDirectory());
   action = new SymlinkAction(NULL_ACTION_OWNER, inputArtifact, outputArtifact, "Symlinking test");
 }