Damien Martin-Guillerez | f88f4d8 | 2015-09-25 13:56:55 +0000 | [diff] [blame] | 1 | # Copyright 2015 The Bazel Authors. All rights reserved. |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | |
| 15 | """Jsonnet rules for Bazel.""" |
| 16 | |
David Chen | 854b72d | 2015-10-22 09:35:11 +0000 | [diff] [blame] | 17 | _JSONNET_FILETYPE = FileType([".jsonnet"]) |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 18 | |
| 19 | def _setup_deps(deps): |
| 20 | """Collects source files and import flags of transitive dependencies. |
| 21 | |
| 22 | Args: |
| 23 | deps: List of deps labels from ctx.attr.deps. |
| 24 | |
| 25 | Returns: |
| 26 | Returns a struct containing the following fields: |
| 27 | transitive_sources: List of Files containing sources of transitive |
| 28 | dependencies |
| 29 | imports: List of Strings containing import flags set by transitive |
| 30 | dependency targets. |
| 31 | """ |
| 32 | transitive_sources = set(order="compile") |
| 33 | imports = set() |
| 34 | for dep in deps: |
| 35 | transitive_sources += dep.transitive_jsonnet_files |
David Chen | 029af28 | 2015-09-19 19:37:37 +0000 | [diff] [blame] | 36 | imports += ["%s/%s" % (dep.label.package, im) for im in dep.imports] |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 37 | |
| 38 | return struct( |
| 39 | transitive_sources = transitive_sources, |
| 40 | imports = imports) |
| 41 | |
| 42 | def _jsonnet_library_impl(ctx): |
| 43 | """Implementation of the jsonnet_library rule.""" |
| 44 | depinfo = _setup_deps(ctx.attr.deps) |
| 45 | sources = depinfo.transitive_sources + ctx.files.srcs |
| 46 | imports = depinfo.imports + ctx.attr.imports |
| 47 | return struct(files = set(), |
| 48 | transitive_jsonnet_files = sources, |
| 49 | imports = imports) |
| 50 | |
| 51 | def _jsonnet_toolchain(ctx): |
| 52 | return struct( |
| 53 | jsonnet_path = ctx.file._jsonnet.path, |
| 54 | imports = ["-J %s" % ctx.file._std.dirname]) |
| 55 | |
| 56 | def _jsonnet_to_json_impl(ctx): |
| 57 | """Implementation of the jsonnet_to_json rule.""" |
| 58 | depinfo = _setup_deps(ctx.attr.deps) |
| 59 | toolchain = _jsonnet_toolchain(ctx) |
David Chen | 3c45db0 | 2015-10-13 13:25:47 +0000 | [diff] [blame] | 60 | jsonnet_vars = ctx.attr.vars |
| 61 | jsonnet_code_vars = ctx.attr.code_vars |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 62 | command = ( |
| 63 | [ |
| 64 | "set -e;", |
| 65 | toolchain.jsonnet_path, |
| 66 | ] + |
David Chen | 029af28 | 2015-09-19 19:37:37 +0000 | [diff] [blame] | 67 | ["-J %s/%s" % (ctx.label.package, im) for im in ctx.attr.imports] + |
| 68 | ["-J %s" % im for im in depinfo.imports] + |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 69 | toolchain.imports + |
David Chen | 3c45db0 | 2015-10-13 13:25:47 +0000 | [diff] [blame] | 70 | ["-J ."] + |
| 71 | ["--var '%s'='%s'" |
| 72 | % (var, jsonnet_vars[var]) for var in jsonnet_vars.keys()] + |
| 73 | ["--code-var '%s'='%s'" |
Googler | bc08fac | 2015-10-30 10:38:50 +0000 | [diff] [blame] | 74 | % (var, jsonnet_code_vars[var]) for var in jsonnet_code_vars.keys()]) |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 75 | |
| 76 | outputs = [] |
| 77 | # If multiple_outputs is set to true, then jsonnet will be invoked with the |
| 78 | # -m flag for multiple outputs. Otherwise, jsonnet will write the resulting |
| 79 | # JSON to stdout, which is redirected into a single JSON output file. |
| 80 | if len(ctx.attr.outs) > 1 or ctx.attr.multiple_outputs: |
| 81 | output_json_files = [ctx.new_file(ctx.configuration.bin_dir, out.name) |
| 82 | for out in ctx.attr.outs] |
| 83 | outputs += output_json_files |
David Chen | ea98701 | 2015-10-13 12:51:50 +0000 | [diff] [blame] | 84 | command += ["-m", output_json_files[0].dirname, ctx.file.src.path] |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 85 | else: |
| 86 | if len(ctx.attr.outs) > 1: |
| 87 | fail("Only one file can be specified in outs if multiple_outputs is " + |
| 88 | "not set.") |
| 89 | |
| 90 | compiled_json = ctx.new_file(ctx.configuration.bin_dir, |
| 91 | ctx.attr.outs[0].name) |
| 92 | outputs += [compiled_json] |
David Chen | ea98701 | 2015-10-13 12:51:50 +0000 | [diff] [blame] | 93 | command += [ctx.file.src.path, "-o", compiled_json.path] |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 94 | |
| 95 | compile_inputs = ( |
| 96 | [ctx.file.src, ctx.file._jsonnet, ctx.file._std] + |
| 97 | list(depinfo.transitive_sources)) |
| 98 | |
| 99 | ctx.action( |
| 100 | inputs = compile_inputs, |
| 101 | outputs = outputs, |
| 102 | mnemonic = "Jsonnet", |
| 103 | command = " ".join(command), |
| 104 | use_default_shell_env = True, |
| 105 | progress_message = "Compiling Jsonnet to JSON for " + ctx.label.name); |
| 106 | |
David Chen | 854b72d | 2015-10-22 09:35:11 +0000 | [diff] [blame] | 107 | _EXIT_CODE_COMPARE_COMMAND = """ |
| 108 | EXIT_CODE=$? |
| 109 | EXPECTED_EXIT_CODE=%d |
| 110 | if [ $EXIT_CODE -ne $EXPECTED_EXIT_CODE ] ; then |
| 111 | echo "FAIL (exit code): %s" |
| 112 | echo "Expected: $EXPECTED_EXIT_CODE" |
| 113 | echo "Actual: $EXIT_CODE" |
| 114 | echo "Output: $OUTPUT" |
| 115 | exit 1 |
| 116 | fi |
| 117 | """ |
| 118 | |
| 119 | _DIFF_COMMAND = """ |
| 120 | GOLDEN=$(cat %s) |
| 121 | if [ "$OUTPUT" != "$GOLDEN" ]; then |
| 122 | echo "FAIL (output mismatch): %s" |
| 123 | echo "Diff:" |
| 124 | diff <(echo $GOLDEN) <(echo $OUTPUT) |
| 125 | echo "Expected: $GOLDEN" |
| 126 | echo "Actual: $OUTPUT" |
| 127 | exit 1 |
| 128 | fi |
| 129 | """ |
| 130 | |
| 131 | _REGEX_DIFF_COMMAND = """ |
| 132 | GOLDEN_REGEX=$(cat %s) |
| 133 | if [[ ! "$OUTPUT" =~ $GOLDEN_REGEX ]]; then |
| 134 | echo "FAIL (regex mismatch): %s" |
| 135 | echo "Output: $OUTPUT" |
| 136 | exit 1 |
| 137 | fi |
| 138 | """ |
| 139 | |
| 140 | def _jsonnet_to_json_test_impl(ctx): |
| 141 | """Implementation of the jsonnet_to_json_test rule.""" |
| 142 | depinfo = _setup_deps(ctx.attr.deps) |
| 143 | toolchain = _jsonnet_toolchain(ctx) |
| 144 | |
| 145 | golden_files = [] |
| 146 | if ctx.file.golden: |
| 147 | golden_files += [ctx.file.golden] |
| 148 | if ctx.attr.regex: |
| 149 | diff_command = _REGEX_DIFF_COMMAND % (ctx.file.golden.short_path, |
| 150 | ctx.label.name) |
| 151 | else: |
| 152 | diff_command = _DIFF_COMMAND % (ctx.file.golden.short_path, |
| 153 | ctx.label.name) |
| 154 | |
| 155 | jsonnet_vars = ctx.attr.vars |
| 156 | jsonnet_code_vars = ctx.attr.code_vars |
| 157 | jsonnet_command = " ".join( |
| 158 | ["OUTPUT=$(%s" % ctx.file._jsonnet.short_path] + |
| 159 | ["-J %s/%s" % (ctx.label.package, im) for im in ctx.attr.imports] + |
| 160 | ["-J %s" % im for im in depinfo.imports] + |
| 161 | toolchain.imports + |
| 162 | ["-J ."] + |
| 163 | ["--var %s=%s" |
| 164 | % (var, jsonnet_vars[var]) for var in jsonnet_vars.keys()] + |
| 165 | ["--code-var %s=%s" |
Googler | bc08fac | 2015-10-30 10:38:50 +0000 | [diff] [blame] | 166 | % (var, jsonnet_code_vars[var]) for var in jsonnet_code_vars.keys()] + |
David Chen | 854b72d | 2015-10-22 09:35:11 +0000 | [diff] [blame] | 167 | [ |
| 168 | ctx.file.src.path, |
| 169 | "2>&1)", |
| 170 | ]) |
| 171 | |
| 172 | command = "\n".join([ |
| 173 | "#!/bin/bash", |
| 174 | jsonnet_command, |
| 175 | _EXIT_CODE_COMPARE_COMMAND % (ctx.attr.error, ctx.label.name), |
| 176 | diff_command]) |
| 177 | |
| 178 | ctx.file_action(output = ctx.outputs.executable, |
| 179 | content = command, |
| 180 | executable = True); |
| 181 | |
| 182 | test_inputs = ( |
| 183 | [ctx.file.src, ctx.file._jsonnet, ctx.file._std] + |
| 184 | golden_files + |
| 185 | list(depinfo.transitive_sources)) |
| 186 | |
| 187 | return struct( |
| 188 | runfiles = ctx.runfiles(files = test_inputs, collect_data = True)) |
| 189 | |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 190 | _jsonnet_common_attrs = { |
| 191 | "deps": attr.label_list(providers = ["transitive_jsonnet_files"], |
| 192 | allow_files = False), |
| 193 | "imports": attr.string_list(), |
| 194 | "_jsonnet": attr.label( |
| 195 | default = Label("//tools/build_defs/jsonnet:jsonnet"), |
| 196 | executable = True, |
| 197 | single_file = True), |
| 198 | "_std": attr.label(default = Label("//tools/build_defs/jsonnet:std"), |
| 199 | single_file = True), |
| 200 | } |
| 201 | |
| 202 | _jsonnet_library_attrs = { |
David Chen | 854b72d | 2015-10-22 09:35:11 +0000 | [diff] [blame] | 203 | "srcs": attr.label_list(allow_files = _JSONNET_FILETYPE), |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 204 | } |
| 205 | |
| 206 | jsonnet_library = rule( |
| 207 | _jsonnet_library_impl, |
| 208 | attrs = _jsonnet_library_attrs + _jsonnet_common_attrs, |
| 209 | ) |
| 210 | |
David Chen | 3c45db0 | 2015-10-13 13:25:47 +0000 | [diff] [blame] | 211 | _jsonnet_compile_attrs = { |
David Chen | 854b72d | 2015-10-22 09:35:11 +0000 | [diff] [blame] | 212 | "src": attr.label(allow_files = _JSONNET_FILETYPE, |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 213 | single_file = True), |
David Chen | 3c45db0 | 2015-10-13 13:25:47 +0000 | [diff] [blame] | 214 | "vars": attr.string_dict(), |
| 215 | "code_vars": attr.string_dict(), |
| 216 | } |
| 217 | |
| 218 | _jsonnet_to_json_attrs = _jsonnet_compile_attrs + { |
David Chen | 361d2e2 | 2015-09-12 01:32:26 +0000 | [diff] [blame] | 219 | "outs": attr.output_list(mandatory = True), |
| 220 | "multiple_outputs": attr.bool(), |
| 221 | } |
| 222 | |
| 223 | jsonnet_to_json = rule( |
| 224 | _jsonnet_to_json_impl, |
| 225 | attrs = _jsonnet_to_json_attrs + _jsonnet_common_attrs, |
| 226 | ) |
David Chen | 854b72d | 2015-10-22 09:35:11 +0000 | [diff] [blame] | 227 | |
| 228 | _jsonnet_to_json_test_attrs = _jsonnet_compile_attrs + { |
| 229 | "golden": attr.label(allow_files = True, single_file = True), |
| 230 | "error": attr.int(), |
| 231 | "regex": attr.bool(), |
| 232 | } |
| 233 | |
| 234 | jsonnet_to_json_test = rule( |
| 235 | _jsonnet_to_json_test_impl, |
| 236 | attrs = _jsonnet_to_json_test_attrs + _jsonnet_common_attrs, |
| 237 | executable = True, |
| 238 | test = True, |
| 239 | ) |
David Chen | 7019ff9 | 2015-12-04 13:25:38 +0000 | [diff] [blame] | 240 | |
| 241 | def jsonnet_repositories(): |
| 242 | native.git_repository( |
| 243 | name = "jsonnet", |
| 244 | remote = "https://github.com/google/jsonnet.git", |
| 245 | tag = "v0.8.1", |
| 246 | ) |