blob: dcb308bf875ead0b0d4265a21815ce3f743f2731 [file] [log] [blame]
# Copyright 2015 The Bazel Authors. 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 += ["%s/%s" % (dep.label.package, im) for im in 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)
jsonnet_vars = ctx.attr.vars
jsonnet_code_vars = ctx.attr.code_vars
command = (
[
"set -e;",
toolchain.jsonnet_path,
] +
["-J %s/%s" % (ctx.label.package, im) for im in ctx.attr.imports] +
["-J %s" % im for im in depinfo.imports] +
toolchain.imports +
["-J ."] +
["--var '%s'='%s'"
% (var, jsonnet_vars[var]) for var in jsonnet_vars.keys()] +
["--code-var '%s'='%s'"
% (var, jsonnet_code_vars[var]) for var in jsonnet_code_vars.keys()])
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", output_json_files[0].dirname, ctx.file.src.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, "-o", 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);
_EXIT_CODE_COMPARE_COMMAND = """
EXIT_CODE=$?
EXPECTED_EXIT_CODE=%d
if [ $EXIT_CODE -ne $EXPECTED_EXIT_CODE ] ; then
echo "FAIL (exit code): %s"
echo "Expected: $EXPECTED_EXIT_CODE"
echo "Actual: $EXIT_CODE"
echo "Output: $OUTPUT"
exit 1
fi
"""
_DIFF_COMMAND = """
GOLDEN=$(cat %s)
if [ "$OUTPUT" != "$GOLDEN" ]; then
echo "FAIL (output mismatch): %s"
echo "Diff:"
diff <(echo $GOLDEN) <(echo $OUTPUT)
echo "Expected: $GOLDEN"
echo "Actual: $OUTPUT"
exit 1
fi
"""
_REGEX_DIFF_COMMAND = """
GOLDEN_REGEX=$(cat %s)
if [[ ! "$OUTPUT" =~ $GOLDEN_REGEX ]]; then
echo "FAIL (regex mismatch): %s"
echo "Output: $OUTPUT"
exit 1
fi
"""
def _jsonnet_to_json_test_impl(ctx):
"""Implementation of the jsonnet_to_json_test rule."""
depinfo = _setup_deps(ctx.attr.deps)
toolchain = _jsonnet_toolchain(ctx)
golden_files = []
if ctx.file.golden:
golden_files += [ctx.file.golden]
if ctx.attr.regex:
diff_command = _REGEX_DIFF_COMMAND % (ctx.file.golden.short_path,
ctx.label.name)
else:
diff_command = _DIFF_COMMAND % (ctx.file.golden.short_path,
ctx.label.name)
jsonnet_vars = ctx.attr.vars
jsonnet_code_vars = ctx.attr.code_vars
jsonnet_command = " ".join(
["OUTPUT=$(%s" % ctx.file.jsonnet.short_path] +
["-J %s/%s" % (ctx.label.package, im) for im in ctx.attr.imports] +
["-J %s" % im for im in depinfo.imports] +
toolchain.imports +
["-J ."] +
["--var %s=%s"
% (var, jsonnet_vars[var]) for var in jsonnet_vars.keys()] +
["--code-var %s=%s"
% (var, jsonnet_code_vars[var]) for var in jsonnet_code_vars.keys()] +
[
ctx.file.src.path,
"2>&1)",
])
command = "\n".join([
"#!/bin/bash",
jsonnet_command,
_EXIT_CODE_COMPARE_COMMAND % (ctx.attr.error, ctx.label.name),
diff_command])
ctx.file_action(output = ctx.outputs.executable,
content = command,
executable = True);
test_inputs = (
[ctx.file.src, ctx.file.jsonnet, ctx.file.std] +
golden_files +
list(depinfo.transitive_sources))
return struct(
runfiles = ctx.runfiles(files = test_inputs, collect_data = True))
_jsonnet_common_attrs = {
"deps": attr.label_list(providers = ["transitive_jsonnet_files"],
allow_files = False),
"imports": attr.string_list(),
"jsonnet": attr.label(
default = Label("@bazel_tools//tools/build_defs/jsonnet:jsonnet"),
executable = True,
single_file = True),
"std": attr.label(
default = Label("@bazel_tools//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_compile_attrs = {
"src": attr.label(allow_files = _JSONNET_FILETYPE,
single_file = True),
"vars": attr.string_dict(),
"code_vars": attr.string_dict(),
}
_jsonnet_to_json_attrs = _jsonnet_compile_attrs + {
"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,
)
_jsonnet_to_json_test_attrs = _jsonnet_compile_attrs + {
"golden": attr.label(allow_files = True, single_file = True),
"error": attr.int(),
"regex": attr.bool(),
}
jsonnet_to_json_test = rule(
_jsonnet_to_json_test_impl,
attrs = _jsonnet_to_json_test_attrs + _jsonnet_common_attrs,
executable = True,
test = True,
)
def jsonnet_repositories():
native.git_repository(
name = "jsonnet",
remote = "https://github.com/google/jsonnet.git",
tag = "v0.8.1",
)