Add Jsonnet rules to Bazel.

RELNOTES: Add Jsonnet rules to Bazel

--
MOS_MIGRATED_REVID=102895524
diff --git a/WORKSPACE b/WORKSPACE
index 3486acd..897060c 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -95,3 +95,10 @@
     sha256 = "aa76bb83c38b3f7495516eb08977fc9700c664d7a945ba3ac3c0004a6a8509f2",
     build_file = "tools/build_defs/d/dmd.BUILD",
 )
+
+new_git_repository(
+    name = "jsonnet",
+    remote = "https://github.com/google/jsonnet.git",
+    tag = "v0.8.0",
+    build_file = "tools/build_defs/jsonnet/jsonnet.BUILD",
+)
diff --git a/examples/jsonnet/BUILD b/examples/jsonnet/BUILD
new file mode 100644
index 0000000..3c829df
--- /dev/null
+++ b/examples/jsonnet/BUILD
@@ -0,0 +1,41 @@
+package(default_visibility = ["//visibility:public"])
+
+load("/tools/build_defs/jsonnet/jsonnet", "jsonnet_library", "jsonnet_to_json")
+
+jsonnet_library(
+    name = "workflow",
+    srcs = ["workflow.jsonnet"],
+)
+
+jsonnet_to_json(
+    name = "wordcount",
+    src = "wordcount.jsonnet",
+    outs = ["wordcount.json"],
+    deps = [":workflow"],
+)
+
+jsonnet_to_json(
+    name = "intersection",
+    src = "intersection.jsonnet",
+    outs = ["intersection.json"],
+    deps = [":workflow"],
+)
+
+jsonnet_library(
+    name = "shell-workflows-lib",
+    srcs = [
+        "intersection.jsonnet",
+        "wordcount.jsonnet",
+    ],
+    deps = [":workflow"],
+)
+
+jsonnet_to_json(
+    name = "shell-workflows",
+    src = "shell-workflows.jsonnet",
+    outs = [
+        "intersection-workflow.json",
+        "wordcount-workflow.json",
+    ],
+    deps = [":shell-workflows-lib"],
+)
diff --git a/examples/jsonnet/intersection.jsonnet b/examples/jsonnet/intersection.jsonnet
new file mode 100644
index 0000000..e0f2fd1
--- /dev/null
+++ b/examples/jsonnet/intersection.jsonnet
@@ -0,0 +1,60 @@
+// 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.
+
+local workflow = import "examples/jsonnet/workflow.jsonnet";
+
+// Workflow that performs an intersection of two files using shell commands.
+{
+  intersection: workflow.Workflow {
+    jobs: {
+      local input_file1 = "/tmp/list1",
+      local input_file2 = "/tmp/list2",
+      local sorted_file1 = "/tmp/list1_sorted",
+      local sorted_file2 = "/tmp/list2_sorted",
+      local intersection = "/tmp/intersection",
+
+      SortJob:: workflow.ShJob {
+        input_file:: "",
+        output_file:: "",
+        command: "sort %s > %s" % [self.input_file, self.output_file],
+        inputs: [self.input_file],
+        outputs: [self.output_file],
+      },
+
+      sort_file1: self.SortJob {
+        input_file:: input_file1,
+        output_file:: sorted_file1,
+      },
+
+      sort_file2: self.SortJob {
+        input_file:: input_file2,
+        output_file:: sorted_file2,
+      },
+
+      intersect: workflow.ShJob {
+        deps: [
+          ":sort_file1",
+          ":sort_file2",
+        ],
+        command: "comm -12 %s %s > %s" %
+            [sorted_file1, sorted_file2, intersection],
+        inputs: [
+          sorted_file1,
+          sorted_file2,
+        ],
+        outputs: [intersection],
+      },
+    }
+  }
+}
diff --git a/examples/jsonnet/shell-workflows.jsonnet b/examples/jsonnet/shell-workflows.jsonnet
new file mode 100644
index 0000000..d5e36a0
--- /dev/null
+++ b/examples/jsonnet/shell-workflows.jsonnet
@@ -0,0 +1,21 @@
+// 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.
+
+local wordcount = import "examples/jsonnet/wordcount.jsonnet";
+local intersection = import "examples/jsonnet/intersection.jsonnet";
+
+{
+  "wordcount-workflow.json": wordcount,
+  "intersection-workflow.json": intersection,
+}
diff --git a/examples/jsonnet/wordcount.jsonnet b/examples/jsonnet/wordcount.jsonnet
new file mode 100644
index 0000000..e816d44
--- /dev/null
+++ b/examples/jsonnet/wordcount.jsonnet
@@ -0,0 +1,59 @@
+// 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.
+
+local workflow = import "examples/jsonnet/workflow.jsonnet";
+
+// Workflow that performs a wordcount using shell commands.
+{
+  wordcount: workflow.Workflow {
+    retries: 12,
+    schedule: workflow.Schedule {
+      start_date: "2015-11-15",
+      start_time: "17:30",
+      repeat_frequency: 1,
+      repeat_type: "week",
+    },
+    jobs: {
+      local input_file = "/tmp/passage_test",
+      local tokens_file = "/tmp/tokens",
+      local sorted_tokens_file = "/tmp/sorted_tokens",
+      local counts_file = "/tmp/counts",
+
+      // Reads the input file and produces an output file with one word per
+      // line.
+      tokenize: workflow.ShJob {
+        command: "tr ' ' '\n' < %s > %s" % [input_file, tokens_file],
+        inputs: [input_file],
+        outputs: [tokens_file],
+      },
+
+      // Takes the tokens file and produces a file with the tokens sorted.
+      sort: workflow.ShJob {
+        deps: [":tokenize"],
+        command: "sort %s > %s" % [tokens_file, sorted_tokens_file],
+        inputs: [tokens_file],
+        outputs: [sorted_tokens_file],
+      },
+
+      // Takes the file containing sorted tokens and produces a file containing
+      // the counts for each word.
+      count: workflow.ShJob {
+        deps: [":sort"],
+        command: "uniq -c %s > %s" % [sorted_tokens_file, counts_file],
+        inputs: [sorted_tokens_file],
+        outputs: [counts_file],
+      },
+    }
+  }
+}
diff --git a/examples/jsonnet/workflow.jsonnet b/examples/jsonnet/workflow.jsonnet
new file mode 100644
index 0000000..bca2bd7
--- /dev/null
+++ b/examples/jsonnet/workflow.jsonnet
@@ -0,0 +1,46 @@
+// 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.
+
+// Configuration for a hypothetical workflow scheduling system.
+{
+  // Configuration for a workflow.
+  Workflow:: {
+    schedule: {},
+    retries: 5,
+    jobs: {},
+  },
+
+  // Scheduling configuration for a workflow.
+  Schedule:: {
+    start_date: "",
+    start_time: "",
+    repeat_frequency: 0,
+    repeat_type: "",
+  },
+
+  // Base configuration for a Job in a workflow.
+  Job:: {
+    type: "base",
+    deps: [],
+    inputs: [],
+    outputs: [],
+  },
+
+  // Configuration for a job that runs a shell command.
+  ShJob:: self.Job {
+    type: "sh",
+    command: "",
+    vars: {},
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm b/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm
index 97f5303..e10b1b5 100644
--- a/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm
+++ b/src/main/java/com/google/devtools/build/docgen/templates/be-header.vm
@@ -137,26 +137,30 @@
 
 <h4>Rules implemented as Skylark extensions</h4>
 
+<p>
 The Bazel team provides a set of supported build rules written using the
 <a href="/docs/skylark/index.html">Skylark</a> rules framework. These rules
 should be explicitly <a href="#load">load</a>ed. They allow you to build the
 following:
+</p>
 
 <ul>
 <li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_rules/closure">
-    Closure libraries</a>
+    Closure libraries</a></li>
 <li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_defs/docker">
-    Docker images</a>
+    Docker images</a></li>
 <li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_defs/groovy">
-    Groovy projects</a>
+    Groovy projects</a></li>
 <li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_rules/appengine">
-    Java App Engine applications</a>
+    Java App Engine applications</a></li>
 <li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_defs/d">
-    D projects</a>
+    D projects</a></li>
 <li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_rules/rust">
-    Rust projects</a>
+    Rust projects</a></li>
+<li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_defs/jsonnet">
+    Jsonnet files</a></li>
 <li> <a href="https://github.com/bazelbuild/bazel/tree/master/tools/build_defs/scala">
-    Scala projects</a> - experimental
+    Scala projects</a> - experimental</li>
 </ul>
 
 <h2 id="common-definitions">Common definitions</h2>
diff --git a/tools/BUILD b/tools/BUILD
index a2df391..f370eb3 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -23,6 +23,7 @@
     name = "package-srcs",
     srcs = glob(["**"]) + [
         "//tools/build_defs/d:srcs",
+        "//tools/build_defs/jsonnet:srcs",
         "//tools/build_defs/docker:srcs",
         "//tools/build_rules/appengine:srcs",
         "//tools/build_rules/closure:srcs",
diff --git a/tools/build_defs/jsonnet/BUILD b/tools/build_defs/jsonnet/BUILD
new file mode 100644
index 0000000..7f38b97
--- /dev/null
+++ b/tools/build_defs/jsonnet/BUILD
@@ -0,0 +1,16 @@
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "srcs",
+    srcs = glob(["**"]),
+)
+
+filegroup(
+    name = "jsonnet",
+    srcs = ["@jsonnet//:jsonnet"],
+)
+
+filegroup(
+    name = "std",
+    srcs = ["@jsonnet//:std"],
+)
diff --git a/tools/build_defs/jsonnet/README.md b/tools/build_defs/jsonnet/README.md
new file mode 100644
index 0000000..6d40df7
--- /dev/null
+++ b/tools/build_defs/jsonnet/README.md
@@ -0,0 +1,310 @@
+# Jsonnet Rules
+
+## Rules
+
+* [`jsonnet_library`](#jsonnet_library)
+* [`jsonnet_to_json`](#jsonnet_to_json)
+
+## Overview
+
+These are build rules for working with [Jsonnet][jsonnet] files with Bazel.
+
+[jsonnet]: http://google.github.io/jsonnet/doc/
+
+## Setup
+
+To use the Jsonnet rules, simply copy the contents of `jsonnet.WORKSPACE` into
+your `WORKSPACE` file.
+
+<a name="#jsonnet_library"></a>
+## jsonnet_library
+
+```python
+jsonnet_library(name, srcs, deps, imports)
+```
+
+<table>
+  <thead>
+    <tr>
+      <th>Attribute</th>
+      <th>Description</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>name</code></td>
+      <td>
+        <code>Name, required</code>
+        <p>A unique name for this rule.</p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>srcs</code></td>
+      <td>
+        <code>List of Labels, required</code>
+        <p>
+          List of <code>.jsonnet</code> files that comprises this Jsonnet
+          library.
+        </p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>deps</code></td>
+      <td>
+        <code>List of labels, optional</code>
+        <p>
+          List of targets that are required by the <code>srcs</code> Jsonnet
+          files.
+        </p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>imports</code></td>
+      <td>
+        <code>List of strings, optional</code>
+        <p>
+          List of import <code>-J</code> flags to be passed to the
+          <code>jsonnet</code> compiler.
+        </p>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+### Example
+
+Suppose you have the following directory structure:
+
+```
+[workspace]/
+    WORKSPACE
+    configs/
+      BUILD
+      backend.jsonnet
+      frontend.jsonnet
+```
+
+You can use the `jsonnet_library` rule to build a collection of `.jsonnet`
+files that can be imported by other `.jsonnet` files as dependencies:
+
+`configs/BUILD`:
+
+```python
+load("/tools/build_defs/jsonnet/jsonnet", "jsonnet_library")
+
+jsonnet_library(
+    name = "configs",
+    srcs = [
+        "backend.jsonnet",
+        "frontend.jsonnet",
+    ],
+)
+```
+
+<a name="#jsonnet_to_json"></a>
+## jsonnet_to_json
+
+```python
+jsonnet_to_json(name, src, deps, outs, multiple_outputs, imports)
+```
+
+<table>
+  <thead>
+    <tr>
+      <th>Attribute</th>
+      <th>Description</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr>
+      <td><code>name</code></td>
+      <td>
+        <code>Name, required</code>
+        <p>A unique name for this rule.</p>
+        <p>
+          This name will be used as the name of the JSON file generated by this
+          rule.
+        </p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>src</code></td>
+      <td>
+        <code>Label, required</code>
+        <p>
+          The <code>.jsonnet</code> file to convert to JSON.
+        </p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>deps</code></td>
+      <td>
+        <code>List of labels, optional</code>
+        <p>
+          List of targets that are required by the <code>src</code> Jsonnet
+          file.
+        </p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>outs</code></td>
+      <td>
+        <code>List of Filenames, optional</code>
+        <p>
+          Names of the output .json files to be generated by this rule.
+        </p>
+        <p>
+          If you are generating only a single JSON file and are not using
+          jsonnet multiple output files, then this attribute should only
+          contain the file name of the JSON file you are generating.
+        </p>
+        <p>
+          If you are generating multiple JSON files using jsonnet multiple file
+          output (<code>jsonnet -m</code>), then list the file names of all the
+          JSON files to be generated. The file names specified here must match
+          the file names specified in your <code>src</code> Jsonnet file.
+        </p>
+        <p>
+          For the case where multiple file output is used but only for
+          generating one output file, set the <code>multiple_outputs</code>
+          attribute to 1 to explicitly enable the <code>-m</code> flag for
+          multiple file output.
+        </p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>multiple_outputs</code></td>
+      <td>
+        <code>bool, optional, default 0</code>
+        <p>
+          Set to 1 to explicitly enable multiple file output via the
+          <code>jsonnet -m</code> flag.
+        </p>
+        <p>
+          This is used for the case where multiple file output is used but only
+          for generating a single output file. For example:
+        </p>
+<pre>
+local foo = import "foo.jsonnet";
+
+{
+  "foo.json": foo,
+}
+</pre>
+        </p>
+      </td>
+    </tr>
+    <tr>
+      <td><code>imports</code></td>
+      <td>
+        <code>List of strings, optional</code>
+        <p>
+          List of import <code>-J</code> flags to be passed to the
+          <code>jsonnet</code> compiler.
+        </p>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+### Example
+
+Suppose you have the following directory structure:
+
+```
+[workspace]/
+    WORKSPACE
+    workflows/
+        BUILD
+        workflow.jsonnet
+        wordcount.jsonnet
+        intersection.jsonnet
+```
+
+Say that `workflow.jsonnet` is a base configuration library for a workflow
+scheduling system and `wordcount.jsonnet` and `intersection.jsonnet` both
+import `workflow.jsonnet` to define workflows for performing a wordcount and
+intersection of two files, respectively.
+
+First, create a `jsonnet_library` target with `workflow.jsonnet`:
+
+`workflows/BUILD`:
+
+```python
+load("/tools/build_defs/jsonnet/jsonnet", "jsonnet_library")
+
+jsonnet_library(
+    name = "workflow",
+    srcs = ["workflow.jsonnet"],
+)
+```
+
+To compile `wordcount.jsonnet` and `intersection.jsonnet` to JSON, define two
+`jsonnet_to_json` targets:
+
+```python
+jsonnet_to_json(
+    name = "wordcount",
+    src = "wordcount.jsonnet",
+    outs = ["wordcount.json"],
+    deps = [":workflow"],
+)
+
+jsonnet_to_json(
+    name = "intersection",
+    src = "intersection.jsonnet",
+    outs = ["intersection.json"],
+    deps = [":workflow"],
+)
+```
+
+### Example: Multiple output files
+
+To use Jsonnet's [multiple output files][multiple-output-files], suppose you
+add a file `shell-workflows.jsonnet` that imports `wordcount.jsonnet` and
+`intersection.jsonnet`:
+
+`workflows/shell-workflows.jsonnet`:
+
+```
+local wordcount = import "workflows/wordcount.jsonnet";
+local intersection = import "workflows/intersection.jsonnet";
+
+{
+  "wordcount-workflow.json": wordcount,
+  "intersection-workflow.json": intersection,
+}
+```
+
+To compile `shell-workflows.jsonnet` into the two JSON files,
+`wordcount-workflow.json` and `intersection-workflow.json`, first create a
+`jsonnet_library` target containing the two files that
+`shell-workflows.jsonnet` depends on:
+
+```python
+jsonnet_library(
+    name = "shell-workflows-lib",
+    srcs = [
+        "wordcount.jsonnet",
+        "intersection.jsonnet",
+    ],
+    deps = [":workflow"],
+)
+```
+
+Then, create a `jsonnet_to_json` target and set `outs` to the list of output
+files to indicate that multiple output JSON files are generated:
+
+```python
+jsonnet_to_json(
+    name = "shell-workflows",
+    src = "shell-workflows.jsonnet",
+    deps = [":shell-workflows-lib"],
+    outs = [
+        "wordcount-workflow.jsonnet",
+        "intersection-workflow.jsonnet",
+    ],
+)
+```
+
+[multiple-output-files]: http://google.github.io/jsonnet/doc/commandline.html
diff --git a/tools/build_defs/jsonnet/jsonnet.BUILD b/tools/build_defs/jsonnet/jsonnet.BUILD
new file mode 100644
index 0000000..4b7dca6
--- /dev/null
+++ b/tools/build_defs/jsonnet/jsonnet.BUILD
@@ -0,0 +1,80 @@
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "std",
+    srcs = ["std.jsonnet"],
+)
+
+genrule(
+    name = "gen-std-jsonnet-h",
+    srcs = ["std.jsonnet"],
+    outs = ["std.jsonnet.h"],
+    cmd = "((od -v -Anone -t u1 $< | tr \" \" \"\n\" | grep -v \"^$$\" " +
+          "| tr \"\n\" \",\" ) && echo \"0\") > $@; " +
+          "echo >> $@",
+)
+
+cc_library(
+    name = "jsonnet-common",
+    srcs = [
+        "lexer.cpp",
+        "parser.cpp",
+        "static_analysis.cpp",
+        "vm.cpp",
+        "std.jsonnet.h",
+    ],
+    hdrs = [
+        "lexer.h",
+        "parser.h",
+        "static_analysis.h",
+        "static_error.h",
+        "vm.h",
+    ],
+    linkopts = ["-lm"],
+    includes = ["."],
+)
+
+cc_library(
+    name = "libjsonnet",
+    srcs = ["libjsonnet.cpp"],
+    hdrs = ["libjsonnet.h"],
+    deps = [":jsonnet-common"],
+    includes = ["."],
+)
+
+cc_binary(
+    name = "jsonnet",
+    srcs = ["jsonnet.cpp"],
+    deps = [":libjsonnet"],
+    includes = ["."],
+)
+
+cc_binary(
+    name = "libjsonnet_test_snippet",
+    srcs = ["libjsonnet_test_snippet.c"],
+    deps = [":libjsonnet"],
+    includes = ["."],
+)
+
+cc_binary(
+    name = "libjsonnet_test_file",
+    srcs = ["libjsonnet_test_file.c"],
+    deps = [":libjsonnet"],
+    includes = ["."],
+)
+
+filegroup(
+    name = "object_jsonnet",
+    srcs = ["test_suite/object.jsonnet"],
+)
+
+sh_test(
+    name = "libjsonnet_test",
+    srcs = ["libjsonnet_test.sh"],
+    data = [
+        ":jsonnet",
+        ":libjsonnet_test_snippet",
+        ":libjsonnet_test_file",
+        ":object_jsonnet",
+    ],
+)
diff --git a/tools/build_defs/jsonnet/jsonnet.WORKSPACE b/tools/build_defs/jsonnet/jsonnet.WORKSPACE
new file mode 100644
index 0000000..e6da943
--- /dev/null
+++ b/tools/build_defs/jsonnet/jsonnet.WORKSPACE
@@ -0,0 +1,6 @@
+new_git_repository(
+    name = "jsonnet",
+    remote = "https://github.com/google/jsonnet.git",
+    tag = "v0.8.0",
+    build_file = "tools/build_defs/jsonnet/jsonnet.BUILD",
+)
diff --git a/tools/build_defs/jsonnet/jsonnet.bzl b/tools/build_defs/jsonnet/jsonnet.bzl
new file mode 100644
index 0000000..13a1ba5
--- /dev/null
+++ b/tools/build_defs/jsonnet/jsonnet.bzl
@@ -0,0 +1,139 @@
+# 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.
+
+"""Jsonnet rules for Bazel."""
+
+JSONNET_FILETYPE = FileType([".jsonnet"])
+
+def _setup_deps(deps):
+  """Collects source files and import flags of transitive dependencies.
+
+  Args:
+    deps: List of deps labels from ctx.attr.deps.
+
+  Returns:
+    Returns a struct containing the following fields:
+      transitive_sources: List of Files containing sources of transitive
+          dependencies
+      imports: List of Strings containing import flags set by transitive
+          dependency targets.
+  """
+  transitive_sources = set(order="compile")
+  imports = set()
+  for dep in deps:
+    transitive_sources += dep.transitive_jsonnet_files
+    imports += dep.imports
+
+  return struct(
+      transitive_sources = transitive_sources,
+      imports = imports)
+
+def _jsonnet_library_impl(ctx):
+  """Implementation of the jsonnet_library rule."""
+  depinfo = _setup_deps(ctx.attr.deps)
+  sources = depinfo.transitive_sources + ctx.files.srcs
+  imports = depinfo.imports + ctx.attr.imports
+  return struct(files = set(),
+                transitive_jsonnet_files = sources,
+                imports = imports)
+
+def _jsonnet_toolchain(ctx):
+  return struct(
+      jsonnet_path = ctx.file._jsonnet.path,
+      imports = ["-J %s" % ctx.file._std.dirname])
+
+def _jsonnet_to_json_impl(ctx):
+  """Implementation of the jsonnet_to_json rule."""
+  depinfo = _setup_deps(ctx.attr.deps)
+  toolchain = _jsonnet_toolchain(ctx)
+  command = (
+      [
+          "set -e;",
+          toolchain.jsonnet_path,
+      ] +
+      toolchain.imports +
+      ctx.attr.imports +
+      list(depinfo.imports) +
+      ["-J ."])
+
+  outputs = []
+  # If multiple_outputs is set to true, then jsonnet will be invoked with the
+  # -m flag for multiple outputs. Otherwise, jsonnet will write the resulting
+  # JSON to stdout, which is redirected into a single JSON output file.
+  if len(ctx.attr.outs) > 1 or ctx.attr.multiple_outputs:
+    output_json_files = [ctx.new_file(ctx.configuration.bin_dir, out.name)
+                         for out in ctx.attr.outs]
+    outputs += output_json_files
+    command += ["-m", ctx.file.src.path]
+    # Currently, jsonnet -m creates the output files in the current working
+    # directory. Append mv commands to move the output files into their
+    # correct output directories.
+    # TODO(dzc): Remove this hack when jsonnet supports a flag for setting
+    # an output directory.
+    for json_file in output_json_files:
+      command += ["; mv %s %s" % (json_file.basename, json_file.path)]
+  else:
+    if len(ctx.attr.outs) > 1:
+      fail("Only one file can be specified in outs if multiple_outputs is " +
+           "not set.")
+
+    compiled_json = ctx.new_file(ctx.configuration.bin_dir,
+                                 ctx.attr.outs[0].name)
+    outputs += [compiled_json]
+    command += [ctx.file.src.path, "> %s" % compiled_json.path]
+
+  compile_inputs = (
+      [ctx.file.src, ctx.file._jsonnet, ctx.file._std] +
+      list(depinfo.transitive_sources))
+
+  ctx.action(
+      inputs = compile_inputs,
+      outputs = outputs,
+      mnemonic = "Jsonnet",
+      command = " ".join(command),
+      use_default_shell_env = True,
+      progress_message = "Compiling Jsonnet to JSON for " + ctx.label.name);
+
+_jsonnet_common_attrs = {
+    "deps": attr.label_list(providers = ["transitive_jsonnet_files"],
+                            allow_files = False),
+    "imports": attr.string_list(),
+    "_jsonnet": attr.label(
+        default = Label("//tools/build_defs/jsonnet:jsonnet"),
+        executable = True,
+        single_file = True),
+    "_std": attr.label(default = Label("//tools/build_defs/jsonnet:std"),
+                       single_file = True),
+}
+
+_jsonnet_library_attrs = {
+    "srcs": attr.label_list(allow_files = JSONNET_FILETYPE),
+}
+
+jsonnet_library = rule(
+    _jsonnet_library_impl,
+    attrs = _jsonnet_library_attrs + _jsonnet_common_attrs,
+)
+
+_jsonnet_to_json_attrs = {
+    "src": attr.label(allow_files = JSONNET_FILETYPE,
+                      single_file = True),
+    "outs": attr.output_list(mandatory = True),
+    "multiple_outputs": attr.bool(),
+}
+
+jsonnet_to_json = rule(
+    _jsonnet_to_json_impl,
+    attrs = _jsonnet_to_json_attrs + _jsonnet_common_attrs,
+)