blob: 29360243998cb49f3fd7d10ac5bb1a74cc0a8cce [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001# Copyright 2015 The Bazel Authors. All rights reserved.
David Chen361d2e22015-09-12 01:32:26 +00002#
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 Chen854b72d2015-10-22 09:35:11 +000017_JSONNET_FILETYPE = FileType([".jsonnet"])
David Chen361d2e22015-09-12 01:32:26 +000018
19def _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 Chen029af282015-09-19 19:37:37 +000036 imports += ["%s/%s" % (dep.label.package, im) for im in dep.imports]
David Chen361d2e22015-09-12 01:32:26 +000037
38 return struct(
39 transitive_sources = transitive_sources,
40 imports = imports)
41
42def _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
51def _jsonnet_toolchain(ctx):
52 return struct(
53 jsonnet_path = ctx.file._jsonnet.path,
54 imports = ["-J %s" % ctx.file._std.dirname])
55
56def _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 Chen3c45db02015-10-13 13:25:47 +000060 jsonnet_vars = ctx.attr.vars
61 jsonnet_code_vars = ctx.attr.code_vars
David Chen361d2e22015-09-12 01:32:26 +000062 command = (
63 [
64 "set -e;",
65 toolchain.jsonnet_path,
66 ] +
David Chen029af282015-09-19 19:37:37 +000067 ["-J %s/%s" % (ctx.label.package, im) for im in ctx.attr.imports] +
68 ["-J %s" % im for im in depinfo.imports] +
David Chen361d2e22015-09-12 01:32:26 +000069 toolchain.imports +
David Chen3c45db02015-10-13 13:25:47 +000070 ["-J ."] +
71 ["--var '%s'='%s'"
72 % (var, jsonnet_vars[var]) for var in jsonnet_vars.keys()] +
73 ["--code-var '%s'='%s'"
Googlerbc08fac2015-10-30 10:38:50 +000074 % (var, jsonnet_code_vars[var]) for var in jsonnet_code_vars.keys()])
David Chen361d2e22015-09-12 01:32:26 +000075
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 Chenea987012015-10-13 12:51:50 +000084 command += ["-m", output_json_files[0].dirname, ctx.file.src.path]
David Chen361d2e22015-09-12 01:32:26 +000085 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 Chenea987012015-10-13 12:51:50 +000093 command += [ctx.file.src.path, "-o", compiled_json.path]
David Chen361d2e22015-09-12 01:32:26 +000094
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 Chen854b72d2015-10-22 09:35:11 +0000107_EXIT_CODE_COMPARE_COMMAND = """
108EXIT_CODE=$?
109EXPECTED_EXIT_CODE=%d
110if [ $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
116fi
117"""
118
119_DIFF_COMMAND = """
120GOLDEN=$(cat %s)
121if [ "$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
128fi
129"""
130
131_REGEX_DIFF_COMMAND = """
132GOLDEN_REGEX=$(cat %s)
133if [[ ! "$OUTPUT" =~ $GOLDEN_REGEX ]]; then
134 echo "FAIL (regex mismatch): %s"
135 echo "Output: $OUTPUT"
136 exit 1
137fi
138"""
139
140def _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"
Googlerbc08fac2015-10-30 10:38:50 +0000166 % (var, jsonnet_code_vars[var]) for var in jsonnet_code_vars.keys()] +
David Chen854b72d2015-10-22 09:35:11 +0000167 [
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 Chen361d2e22015-09-12 01:32:26 +0000190_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 Chen854b72d2015-10-22 09:35:11 +0000203 "srcs": attr.label_list(allow_files = _JSONNET_FILETYPE),
David Chen361d2e22015-09-12 01:32:26 +0000204}
205
206jsonnet_library = rule(
207 _jsonnet_library_impl,
208 attrs = _jsonnet_library_attrs + _jsonnet_common_attrs,
209)
210
David Chen3c45db02015-10-13 13:25:47 +0000211_jsonnet_compile_attrs = {
David Chen854b72d2015-10-22 09:35:11 +0000212 "src": attr.label(allow_files = _JSONNET_FILETYPE,
David Chen361d2e22015-09-12 01:32:26 +0000213 single_file = True),
David Chen3c45db02015-10-13 13:25:47 +0000214 "vars": attr.string_dict(),
215 "code_vars": attr.string_dict(),
216}
217
218_jsonnet_to_json_attrs = _jsonnet_compile_attrs + {
David Chen361d2e22015-09-12 01:32:26 +0000219 "outs": attr.output_list(mandatory = True),
220 "multiple_outputs": attr.bool(),
221}
222
223jsonnet_to_json = rule(
224 _jsonnet_to_json_impl,
225 attrs = _jsonnet_to_json_attrs + _jsonnet_common_attrs,
226)
David Chen854b72d2015-10-22 09:35:11 +0000227
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
234jsonnet_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 Chen7019ff92015-12-04 13:25:38 +0000240
241def jsonnet_repositories():
242 native.git_repository(
243 name = "jsonnet",
244 remote = "https://github.com/google/jsonnet.git",
245 tag = "v0.8.1",
246 )