Implements repository_ctx.template

repository_ctx.template enable writing a file in the remote repository tree
using a template and a list of substitution in a similar way as the
ctx.template_action for regular skylark rule.

Issue #893: Step 4 of http://goo.gl/OZV3o0. See http://goo.gl/fD4ZsY.

--
MOS_MIGRATED_REVID=115439344
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
index 45138da..e2500da 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryContext.java
@@ -33,6 +33,8 @@
 import com.google.devtools.build.lib.syntax.Runtime;
 import com.google.devtools.build.lib.syntax.SkylarkType;
 import com.google.devtools.build.lib.syntax.Type;
+import com.google.devtools.build.lib.util.StringUtilities;
+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;
@@ -201,6 +203,34 @@
     }
   }
 
+  @SkylarkCallable(
+    name = "template",
+    doc =
+        "Generate a new file using a <code>template</code>. Every occurrence in "
+            + "<code>template</code> of a key of <code>substitutions</code> will be replaced by "
+            + "the corresponding value. The result is written in <code>path</code>."
+  )
+  public void createFileFromTemplate(
+      Object path, Object template, Map<String, String> substitutions)
+      throws RepositoryFunctionException, EvalException {
+    SkylarkPath p = getPath("template()", path);
+    SkylarkPath t = getPath("template()", template);
+    try {
+      checkInOutputDirectory(p);
+      makeDirectories(p.path);
+      String tpl = FileSystemUtils.readContent(t.path, StandardCharsets.UTF_8);
+      for (Map.Entry<String, String> substitution : substitutions.entrySet()) {
+        tpl =
+            StringUtilities.replaceAllLiteral(tpl, substitution.getKey(), substitution.getValue());
+      }
+      try (OutputStream stream = p.path.getOutputStream()) {
+        stream.write(tpl.getBytes(StandardCharsets.UTF_8));
+      }
+    } catch (IOException e) {
+      throw new RepositoryFunctionException(e, Transience.TRANSIENT);
+    }
+  }
+
   // Create parent directories for the given path
   private void makeDirectories(Path path) throws IOException {
     if (!path.isRootDirectory()) {
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java
index aaca4d0..dcdc6e4 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java
@@ -195,4 +195,30 @@
     Object path = target.getTarget().getAssociatedRule().getAttributeContainer().getAttr("path");
     assertThat(path).isEqualTo("foo");
   }
+
+  @Test
+  public void testSkylarkRepositoryTemplate() throws Exception {
+    scratch.file("/repo2/bar.txt", "filegroup(name='{target}', srcs=['foo.txt'], path='{path}')");
+    scratch.file("/repo2/BUILD");
+    scratch.file("/repo2/WORKSPACE");
+    scratch.file(
+        "def.bzl",
+        "def _impl(ctx):",
+        "  ctx.template('BUILD', Label('@repo2//:bar.txt'), {'{target}': 'bar', '{path}': 'foo'})",
+        "  ctx.file('foo.txt', 'foo')",
+        "",
+        "repo = repository_rule(",
+        "    implementation=_impl,",
+        "    local=True)");
+    scratch.file(rootDirectory.getRelative("BUILD").getPathString());
+    scratch.overwriteFile(
+        rootDirectory.getRelative("WORKSPACE").getPathString(),
+        "local_repository(name='repo2', path='/repo2')",
+        "load('//:def.bzl', 'repo')",
+        "repo(name='foo')");
+    invalidatePackages();
+    ConfiguredTarget target = getConfiguredTarget("@foo//:bar");
+    Object path = target.getTarget().getAssociatedRule().getAttributeContainer().getAttr("path");
+    assertThat(path).isEqualTo("foo");
+  }
 }