Add stub implementations of the android_sdk_repository and android_ndk_repository rules that will be used to reference Android SDK/NDK to be used for Android builds.

--
MOS_MIGRATED_REVID=95507994
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index bd56910..c43801a 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -31,6 +31,10 @@
 import com.google.devtools.build.lib.bazel.repository.NewLocalRepositoryFunction;
 import com.google.devtools.build.lib.bazel.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.bazel.repository.RepositoryFunction;
+import com.google.devtools.build.lib.bazel.rules.android.AndroidNdkRepositoryFunction;
+import com.google.devtools.build.lib.bazel.rules.android.AndroidNdkRepositoryRule;
+import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryFunction;
+import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.HttpArchiveRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.HttpJarRule;
 import com.google.devtools.build.lib.bazel.rules.workspace.LocalRepositoryRule;
@@ -70,6 +74,8 @@
         .put(MavenJarRule.NAME, new MavenJarFunction())
         .put(NewHttpArchiveRule.NAME, new NewHttpArchiveFunction())
         .put(NewLocalRepositoryRule.NAME, new NewLocalRepositoryFunction())
+        .put(AndroidSdkRepositoryRule.NAME, new AndroidSdkRepositoryFunction())
+        .put(AndroidNdkRepositoryRule.NAME, new AndroidNdkRepositoryFunction())
         .build();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/NewHttpArchiveFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/NewHttpArchiveFunction.java
index f6e2037..3000a95 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/NewHttpArchiveFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/NewHttpArchiveFunction.java
@@ -18,7 +18,6 @@
 import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
 import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.skyframe.FileValue;
-import com.google.devtools.build.lib.skyframe.RepositoryValue;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.skyframe.SkyFunction;
@@ -47,7 +46,7 @@
   public SkyValue compute(SkyKey skyKey, SkyFunction.Environment env)
       throws RepositoryFunctionException {
     RepositoryName repositoryName = (RepositoryName) skyKey.argument();
-    Rule rule = RepositoryFunction.getRule(repositoryName, NewHttpArchiveRule.NAME, env);
+    Rule rule = getRule(repositoryName, NewHttpArchiveRule.NAME, env);
     if (rule == null) {
       return null;
     }
@@ -87,13 +86,7 @@
     }
 
     // Add WORKSPACE and BUILD files.
-    NewLocalRepositoryFunction.createWorkspaceFile(decompressedDirectory, rule);
-    FileValue buildFile = NewLocalRepositoryFunction.createBuildFile(
-        rule, getWorkspace(), outputDirectory, env);
-    if (buildFile == null) {
-      return null;
-    }
-
-    return RepositoryValue.createNew(repositoryDirectory, buildFile);
+    createWorkspaceFile(decompressedDirectory, rule);
+    return symlinkBuildFile(rule, getWorkspace(), repositoryDirectory, env);
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/NewLocalRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/NewLocalRepositoryFunction.java
index 7cc8b25..eb01beb 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/NewLocalRepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/NewLocalRepositoryFunction.java
@@ -16,29 +16,15 @@
 
 import com.google.devtools.build.lib.analysis.RuleDefinition;
 import com.google.devtools.build.lib.bazel.rules.workspace.NewLocalRepositoryRule;
-import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
 import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
 import com.google.devtools.build.lib.packages.Rule;
-import com.google.devtools.build.lib.packages.Type;
-import com.google.devtools.build.lib.skyframe.FileSymlinkCycleException;
 import com.google.devtools.build.lib.skyframe.FileValue;
-import com.google.devtools.build.lib.skyframe.InconsistentFilesystemException;
-import com.google.devtools.build.lib.skyframe.RepositoryValue;
-import com.google.devtools.build.lib.syntax.EvalException;
-import com.google.devtools.build.lib.vfs.FileSystem;
-import com.google.devtools.build.lib.vfs.FileSystemUtils;
-import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
-import com.google.devtools.build.lib.vfs.RootedPath;
 import com.google.devtools.build.skyframe.SkyFunctionException;
-import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 
-import java.io.IOException;
-import java.nio.charset.Charset;
-
 /**
  * Create a repository from a directory on the local filesystem.
  */
@@ -47,163 +33,26 @@
   @Override
   public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException {
     RepositoryName repositoryName = (RepositoryName) skyKey.argument();
-    Rule rule = RepositoryFunction.getRule(repositoryName, NewLocalRepositoryRule.NAME, env);
+    Rule rule = getRule(repositoryName, NewLocalRepositoryRule.NAME, env);
     if (rule == null) {
       return null;
     }
 
-    // Given a rule that looks like this:
-    // new_local_repository(
-    //     name = 'x',
-    //     path = '/some/path/to/y',
-    //     build_file = 'x.BUILD'
-    // )
-    //
-    // Assume /some/path/to/y contains files z, w, and v. This creates the following directory
-    // structure:
-    // .external-repository/
-    //   x/
-    //     WORKSPACE
-    //     BUILD -> <build_root>/x.BUILD
-    //     z -> /some/path/to/y/z
-    //     w -> /some/path/to/y/w
-    //     v -> /some/path/to/y/v
-    //
-    // Create x/
-    Path repositoryDirectory = getExternalRepositoryDirectory().getRelative(rule.getName());
-    try {
-      FileSystemUtils.deleteTree(repositoryDirectory);
-      FileSystemUtils.createDirectoryAndParents(repositoryDirectory);
-    } catch (IOException e) {
-      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
-    }
-    FileValue directoryValue = getRepositoryDirectory(repositoryDirectory, env);
+    FileValue directoryValue = prepareLocalRepositorySymlinkTree(rule, env);
     if (directoryValue == null) {
       return null;
     }
 
-    // Add x/WORKSPACE.
-    createWorkspaceFile(repositoryDirectory, rule);
-
-    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);
-    }
-
+    PathFragment pathFragment = getTargetPath(rule);
+    
     // Link x/y/z to /some/path/to/y/z.
-    FileSystem fs = getOutputBase().getFileSystem();
-    Path targetDirectory = fs.getPath(pathFragment);
-    try {
-      for (Path target : targetDirectory.getDirectoryEntries()) {
-        Path symlinkPath = repositoryDirectory.getRelative(target.getBaseName());
-        if (createSymbolicLink(symlinkPath, target, env) == null) {
-          return null;
-        }
-      }
-    } catch (IOException e) {
-      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
+    if (!symlinkLocalRepositoryContents(
+        directoryValue, getOutputBase().getFileSystem().getPath(pathFragment), env)) {
+      return null;
     }
 
     // Link x/BUILD to <build_root>/x.BUILD.
-    FileValue buildFile = createBuildFile(rule, getWorkspace(), repositoryDirectory, env);
-    if (buildFile == null) {
-      return null;
-    }
-
-    return RepositoryValue.createNew(directoryValue, buildFile);
-  }
-
-  public static void createWorkspaceFile(Path repositoryDirectory, Rule rule)
-      throws RepositoryFunctionException {
-    try {
-      Path workspaceFile = repositoryDirectory.getRelative("WORKSPACE");
-      FileSystemUtils.writeContent(workspaceFile, Charset.forName("UTF-8"),
-          String.format("# DO NOT EDIT: automatically generated WORKSPACE file for %s\n", rule));
-    } catch (IOException e) {
-      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
-    }
-  }
-
-  /**
-   * 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 repositoryDirectory the external repository's root directory.
-   * @param env the Skyframe environment.
-   * @return the file value of the symlink created.
-   * @throws RepositoryFunctionException if the BUILD file specified does not exist or cannot be
-   *         linked.
-   */
-  public static FileValue createBuildFile(
-      Rule rule, Path workspaceDirectory, Path repositoryDirectory, 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,
-          FileSymlinkCycleException.class, InconsistentFilesystemException.class);
-      if (buildFileValue == null) {
-        return null;
-      }
-    } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException e) {
-      throw new RepositoryFunctionException(
-          new IOException("Cannot lookup " + buildFile + ": " + e.getMessage()),
-          Transience.TRANSIENT);
-    }
-
-    Path buildFilePath = repositoryDirectory.getRelative("BUILD");
-    if (createSymbolicLink(buildFilePath, buildFileTarget, env) == null) {
-      return null;
-    }
-    return buildFileValue;
-  }
-
-  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,
-          FileSymlinkCycleException.class, InconsistentFilesystemException.class);
-    } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException e) {
-      throw new RepositoryFunctionException(
-          new IOException(String.format("Could not access %s: %s", from, e.getMessage())),
-          Transience.PERSISTENT);
-    }
+    return symlinkBuildFile(rule, getWorkspace(), directoryValue, env);
   }
 
   /**
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryFunction.java
index e581252..efc2beb 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryFunction.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Preconditions;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.packages.AggregatingAttributeMapper;
 import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException;
 import com.google.devtools.build.lib.packages.BuildFileNotFoundException;
 import com.google.devtools.build.lib.packages.ExternalPackage;
@@ -24,11 +25,14 @@
 import com.google.devtools.build.lib.packages.PackageIdentifier;
 import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
 import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.packages.Type;
 import com.google.devtools.build.lib.skyframe.FileSymlinkCycleException;
 import com.google.devtools.build.lib.skyframe.FileValue;
 import com.google.devtools.build.lib.skyframe.InconsistentFilesystemException;
 import com.google.devtools.build.lib.skyframe.PackageValue;
+import com.google.devtools.build.lib.skyframe.RepositoryValue;
 import com.google.devtools.build.lib.syntax.EvalException;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.RootedPath;
@@ -39,6 +43,7 @@
 import com.google.devtools.build.skyframe.SkyKey;
 
 import java.io.IOException;
+import java.nio.charset.Charset;
 
 import javax.annotation.Nullable;
 
@@ -46,54 +51,206 @@
  * Parent class for repository-related Skyframe functions.
  */
 public abstract class RepositoryFunction implements SkyFunction {
+  /**
+   * Exception thrown when something goes wrong accessing a remote repository.
+   *
+   * <p>This exception should be used by child classes to limit the types of exceptions
+   * {@link RepositoryDelegatorFunction} has to know how to catch.</p>
+   */
+  public static final class RepositoryFunctionException extends SkyFunctionException {
+    public RepositoryFunctionException(NoSuchPackageException cause, Transience transience) {
+      super(cause, transience);
+    }
+
+    /**
+     * Error reading or writing to the filesystem.
+     */
+    public RepositoryFunctionException(IOException cause, Transience transience) {
+      super(cause, transience);
+    }
+
+    /**
+     * For errors in WORKSPACE file rules (e.g., malformed paths or URLs).
+     */
+    public RepositoryFunctionException(EvalException cause, Transience transience) {
+      super(cause, transience);
+    }
+  }
+
   private BlazeDirectories directories;
 
-  @Override
-  public String extractTag(SkyKey skyKey) {
-    return null;
+  protected FileValue prepareLocalRepositorySymlinkTree(Rule rule, Environment env)
+      throws RepositoryFunctionException {
+    Path repositoryDirectory = getExternalRepositoryDirectory().getRelative(rule.getName());
+    try {
+      FileSystemUtils.deleteTree(repositoryDirectory);
+      FileSystemUtils.createDirectoryAndParents(repositoryDirectory);
+    } catch (IOException e) {
+      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
+    }
+    FileValue directoryValue = getRepositoryDirectory(repositoryDirectory, env);
+
+    if (directoryValue == null) {
+      return null;
+    }
+
+    // Add x/WORKSPACE.
+    createWorkspaceFile(repositoryDirectory, rule);
+    return directoryValue;
+  }
+
+  protected void createWorkspaceFile(Path repositoryDirectory, Rule rule)
+      throws RepositoryFunctionException {
+    try {
+      Path workspaceFile = repositoryDirectory.getRelative("WORKSPACE");
+      FileSystemUtils.writeContent(workspaceFile, Charset.forName("UTF-8"),
+          String.format("# DO NOT EDIT: automatically generated WORKSPACE file for %s\n", rule));
+    } catch (IOException e) {
+      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
+    }
+  }
+
+  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);
   }
 
   /**
-   * Gets Skyframe's name for this.
+   * 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.
    */
-  public abstract SkyFunctionName getSkyFunctionName();
+  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);
+    }
 
-  /**
-   * Sets up output path information.
-   */
-  public void setDirectories(BlazeDirectories directories) {
-    this.directories = directories;
+    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,
+          FileSymlinkCycleException.class, InconsistentFilesystemException.class);
+      if (buildFileValue == null) {
+        return null;
+      }
+    } catch (IOException | FileSymlinkCycleException | 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;
+    }
+
+    if (buildFileValue == null) {
+      return null;
+    }
+
+    return RepositoryValue.createNew(directoryValue, buildFileValue);
   }
 
-  protected Path getExternalRepositoryDirectory() {
-    return RepositoryFunction.getExternalRepositoryDirectory(directories);
-  }
+  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);
+    }
 
-  public static Path getExternalRepositoryDirectory(BlazeDirectories directories) {
-    return directories.getOutputBase().getRelative(ExternalPackage.NAME);
+    return pathFragment;
   }
 
   /**
-   * Gets the base directory repositories should be stored in locally.
+   * Given a targetDirectory /some/path/to/y that contains files z, w, and v, create the following
+   * directory structure:
+   * <pre>
+   * .external-repository/
+   *   x/
+   *     WORKSPACE
+   *     BUILD -> <build_root>/x.BUILD
+   *     z -> /some/path/to/y/z
+   *     w -> /some/path/to/y/w
+   *     v -> /some/path/to/y/v
+   * </pre>
    */
-  protected Path getOutputBase() {
-    return directories.getOutputBase();
+  public static boolean symlinkLocalRepositoryContents(
+      FileValue repositoryDirectory, Path targetDirectory, Environment env)
+      throws RepositoryFunctionException {
+    try {
+      for (Path target : targetDirectory.getDirectoryEntries()) {
+        Path symlinkPath =
+            repositoryDirectory.realRootedPath().asPath().getRelative(target.getBaseName());
+        if (createSymbolicLink(symlinkPath, target, env) == null) {
+          return false;
+        }
+      }
+    } catch (IOException e) {
+      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
+    }
+
+    return true;
   }
 
-  /**
-   * Gets the directory the WORKSPACE file for the build is in.
-   */
-  protected Path getWorkspace() {
-    return directories.getWorkspace();
+  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,
+          FileSymlinkCycleException.class, InconsistentFilesystemException.class);
+    } catch (IOException | FileSymlinkCycleException | InconsistentFilesystemException e) {
+      throw new RepositoryFunctionException(
+          new IOException(String.format("Could not access %s: %s", from, e.getMessage())),
+          Transience.PERSISTENT);
+    }
   }
 
 
   /**
-   * Returns the RuleDefinition class for this type of repository.
-   */
-  public abstract Class<? extends RuleDefinition> getRuleDefinition();
-
-  /**
    * Uses a remote repository name to fetch the corresponding Rule describing how to get it.
    * This should be called from {@link SkyFunction#compute} functions, which should return null if
    * this returns null. If {@code ruleClassName} is set, the rule found must have a matching rule
@@ -137,7 +294,7 @@
    * actual directory.
    */
   @Nullable
-  protected static FileValue getRepositoryDirectory(Path repositoryDirectory, Environment env)
+  public static FileValue getRepositoryDirectory(Path repositoryDirectory, Environment env)
       throws RepositoryFunctionException {
     SkyKey outputDirectoryKey = FileValue.key(RootedPath.toRootedPath(
         repositoryDirectory, PathFragment.EMPTY_FRAGMENT));
@@ -153,29 +310,48 @@
     return value;
   }
 
-  /**
-   * Exception thrown when something goes wrong accessing a remote repository.
-   *
-   * <p>This exception should be used by child classes to limit the types of exceptions
-   * {@link RepositoryDelegatorFunction} has to know how to catch.</p>
-   */
-  static final class RepositoryFunctionException extends SkyFunctionException {
-    public RepositoryFunctionException(NoSuchPackageException cause, Transience transience) {
-      super(cause, transience);
-    }
-
-    /**
-     * Error reading or writing to the filesystem.
-     */
-    public RepositoryFunctionException(IOException cause, Transience transience) {
-      super(cause, transience);
-    }
-
-    /**
-     * For errors in WORKSPACE file rules (e.g., malformed paths or URLs).
-     */
-    public RepositoryFunctionException(EvalException cause, Transience transience) {
-      super(cause, transience);
-    }
+  @Override
+  public String extractTag(SkyKey skyKey) {
+    return null;
   }
+
+  /**
+   * Gets Skyframe's name for this.
+   */
+  public abstract SkyFunctionName getSkyFunctionName();
+
+  /**
+   * Sets up output path information.
+   */
+  public void setDirectories(BlazeDirectories directories) {
+    this.directories = directories;
+  }
+
+  protected Path getExternalRepositoryDirectory() {
+    return RepositoryFunction.getExternalRepositoryDirectory(directories);
+  }
+
+  public static Path getExternalRepositoryDirectory(BlazeDirectories directories) {
+    return directories.getOutputBase().getRelative(ExternalPackage.NAME);
+  }
+
+  /**
+   * Gets the base directory repositories should be stored in locally.
+   */
+  protected Path getOutputBase() {
+    return directories.getOutputBase();
+  }
+
+  /**
+   * Gets the directory the WORKSPACE file for the build is in.
+   */
+  protected Path getWorkspace() {
+    return directories.getWorkspace();
+  }
+
+
+  /**
+   * Returns the RuleDefinition class for this type of repository.
+   */
+  public abstract Class<? extends RuleDefinition> getRuleDefinition();
 }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index b7c95dc..a95c2df 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -30,6 +30,8 @@
 import com.google.devtools.build.lib.analysis.config.FragmentOptions;
 import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException;
 import com.google.devtools.build.lib.analysis.constraints.EnvironmentRule;
+import com.google.devtools.build.lib.bazel.rules.android.AndroidNdkRepositoryRule;
+import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryRule;
 import com.google.devtools.build.lib.bazel.rules.common.BazelActionListenerRule;
 import com.google.devtools.build.lib.bazel.rules.common.BazelExtraActionRule;
 import com.google.devtools.build.lib.bazel.rules.common.BazelFilegroupRule;
@@ -301,6 +303,8 @@
     builder.addRuleDefinition(new MavenJarRule());
     builder.addRuleDefinition(new NewHttpArchiveRule());
     builder.addRuleDefinition(new NewLocalRepositoryRule());
+    builder.addRuleDefinition(new AndroidSdkRepositoryRule());
+    builder.addRuleDefinition(new AndroidNdkRepositoryRule());
 
     builder.addConfigurationFragment(new BazelConfiguration.Loader());
     builder.addConfigurationFragment(new CppConfigurationLoader(
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryFunction.java
new file mode 100644
index 0000000..c5c05bb
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryFunction.java
@@ -0,0 +1,63 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.bazel.rules.android;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.bazel.repository.RepositoryFunction;
+import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.skyframe.FileValue;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+/**
+ * Implementation of the {@code android_ndk} repository rule.
+ */
+public class AndroidNdkRepositoryFunction extends RepositoryFunction {
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException {
+    RepositoryName repositoryName = (RepositoryName) skyKey.argument();
+    Rule rule = getRule(repositoryName, AndroidNdkRepositoryRule.NAME, env);
+    if (rule == null) {
+      return null;
+    }
+
+    FileValue directoryValue = prepareLocalRepositorySymlinkTree(rule, env);
+    if (directoryValue == null) {
+      return null;
+    }
+
+    PathFragment pathFragment = getTargetPath(rule);
+
+    if (!symlinkLocalRepositoryContents(
+        directoryValue, getOutputBase().getFileSystem().getPath(pathFragment), env)) {
+      return null;
+    }
+
+    return writeBuildFile(directoryValue, "filegroup(name='ndk')");
+  }
+
+  @Override
+  public SkyFunctionName getSkyFunctionName() {
+    return SkyFunctionName.computed(AndroidNdkRepositoryRule.NAME.toUpperCase());
+  }
+
+  @Override
+  public Class<? extends RuleDefinition> getRuleDefinition() {
+    return AndroidNdkRepositoryRule.class;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryRule.java
new file mode 100644
index 0000000..2de9bd7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidNdkRepositoryRule.java
@@ -0,0 +1,53 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.bazel.rules.android;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.INTEGER;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.bazel.rules.workspace.WorkspaceBaseRule;
+import com.google.devtools.build.lib.bazel.rules.workspace.WorkspaceConfiguredTargetFactory;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+
+/**
+ * Definition of the {@code android_ndk} rule.
+ */
+public class AndroidNdkRepositoryRule implements RuleDefinition {
+  public static final String NAME = "android_ndk_repository";
+
+  @Override
+  public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
+    return builder
+        .setUndocumented()
+        .setWorkspaceOnly()
+        .add(attr("path", STRING).mandatory())
+        .add(attr("api_level", INTEGER).mandatory())
+        .build();
+  }
+
+  @Override
+  public Metadata getMetadata() {
+    return RuleDefinition.Metadata.builder()
+        .name(AndroidNdkRepositoryRule.NAME)
+        .type(RuleClassType.WORKSPACE)
+        .ancestors(WorkspaceBaseRule.class)
+        .factoryClass(WorkspaceConfiguredTargetFactory.class)
+        .build();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryFunction.java
new file mode 100644
index 0000000..b03a94f
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryFunction.java
@@ -0,0 +1,66 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.bazel.rules.android;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.bazel.repository.RepositoryFunction;
+import com.google.devtools.build.lib.packages.PackageIdentifier.RepositoryName;
+import com.google.devtools.build.lib.packages.Rule;
+import com.google.devtools.build.lib.skyframe.FileValue;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.skyframe.SkyFunctionException;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+/**
+ * Implementation of the {@code android_sdk} repository rule.
+ */
+public class AndroidSdkRepositoryFunction extends RepositoryFunction {
+  @Override
+  public SkyValue compute(SkyKey skyKey, Environment env) throws SkyFunctionException {
+    RepositoryName repositoryName = (RepositoryName) skyKey.argument();
+    Rule rule = getRule(repositoryName, AndroidSdkRepositoryRule.NAME, env);
+    if (rule == null) {
+      return null;
+    }
+
+    FileValue directoryValue = prepareLocalRepositorySymlinkTree(rule, env);
+    if (directoryValue == null) {
+      return null;
+    }
+
+    PathFragment pathFragment = getTargetPath(rule);
+
+    if (!symlinkLocalRepositoryContents(
+        directoryValue, getOutputBase().getFileSystem().getPath(pathFragment), env)) {
+      return null;
+    }
+
+    return writeBuildFile(directoryValue, "filegroup(name='sdk')");
+  }
+
+  /**
+   * @see RepositoryFunction#getRule(RepositoryName, String, Environment)
+   */
+  @Override
+  public SkyFunctionName getSkyFunctionName() {
+    return SkyFunctionName.computed(AndroidSdkRepositoryRule.NAME.toUpperCase());
+  }
+
+  @Override
+  public Class<? extends RuleDefinition> getRuleDefinition() {
+    return AndroidSdkRepositoryRule.class;
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryRule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryRule.java
new file mode 100644
index 0000000..2fbc6d6
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/android/AndroidSdkRepositoryRule.java
@@ -0,0 +1,54 @@
+// Copyright 2015 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.devtools.build.lib.bazel.rules.android;
+
+import static com.google.devtools.build.lib.packages.Attribute.attr;
+import static com.google.devtools.build.lib.packages.Type.INTEGER;
+import static com.google.devtools.build.lib.packages.Type.STRING;
+
+import com.google.devtools.build.lib.analysis.RuleDefinition;
+import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.bazel.rules.workspace.WorkspaceBaseRule;
+import com.google.devtools.build.lib.bazel.rules.workspace.WorkspaceConfiguredTargetFactory;
+import com.google.devtools.build.lib.packages.RuleClass;
+import com.google.devtools.build.lib.packages.RuleClass.Builder;
+import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType;
+
+/**
+ * Definition of the {@code android_sdk} repository rule.
+ */
+public class AndroidSdkRepositoryRule implements RuleDefinition {
+  public static final String NAME = "android_sdk_repository";
+
+  @Override
+  public RuleClass build(Builder builder, RuleDefinitionEnvironment environment) {
+    return builder
+        .setUndocumented()
+        .setWorkspaceOnly()
+        .add(attr("path", STRING).mandatory())
+        .add(attr("build_tools", STRING).mandatory())
+        .add(attr("api_level", INTEGER).mandatory())
+        .build();
+  }
+
+  @Override
+  public Metadata getMetadata() {
+    return RuleDefinition.Metadata.builder()
+        .name(AndroidSdkRepositoryRule.NAME)
+        .type(RuleClassType.WORKSPACE)
+        .ancestors(WorkspaceBaseRule.class)
+        .factoryClass(WorkspaceConfiguredTargetFactory.class)
+        .build();
+  }
+}