Switch `emboss_cc_library` to use real Starlark rules.
This change creates the `emboss_library` and `cc_emboss_library`
Starlark rules, which mirror `proto_library` and `cc_proto_library`.
This also fixes issues with `.emb` files that import `.emb` files that
import `.emb` files, where imports-of-imports were not always included
in the compilation.
This also required new command-line arguments to `emboss_front_end`
(`--output-file`) and `emboss_codegen_cpp` (`--input-file` and
`--output-file`).
diff --git a/build_defs.bzl b/build_defs.bzl
index fd16ebd..1dc3de9 100644
--- a/build_defs.bzl
+++ b/build_defs.bzl
@@ -16,10 +16,16 @@
# vim:set ft=blazebuild:
"""Build defs for Emboss.
-This file exports the emboss_cc_library rule, which accepts an .emb file and
-produces a corresponding C++ library.
+This file exports emboss_library, which creates an Emboss library, and
+cc_emboss_library, which creates a header file and can be used as a dep in a
+`cc_library`, `cc_binary`, or `cc_test` rule.
+
+There is also a convenience macro, `emboss_cc_library()`, which creates an
+`emboss_library` and a `cc_emboss_library` based on it.
"""
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
+
def emboss_cc_library(name, srcs, deps = [], visibility = None):
"""Constructs a C++ library from an .emb file."""
if len(srcs) != 1:
@@ -28,47 +34,204 @@
"srcs",
)
- native.filegroup(
- # The original .emb file must be visible to any other emboss_cc_library
- # that specifies this emboss_cc_library in its deps. This rule makes the
- # original .emb available to dependent rules.
- # TODO(bolms): As an optimization, use the precompiled IR instead of
- # reparsing the raw .embs.
- name = name + "__emb",
+ emboss_library(
+ name = name + "_ir",
srcs = srcs,
- visibility = visibility,
+ deps = [dep + "_ir" for dep in deps],
)
- native.genrule(
- # The generated header may be used in non-cc_library rules.
- name = name + "_header",
- tools = [
- # TODO(bolms): Make "emboss" driver program.
- "@com_google_emboss//compiler/front_end:emboss_front_end",
- "@com_google_emboss//compiler/back_end/cpp:emboss_codegen_cpp",
- ],
- srcs = srcs + [dep + "__emb" for dep in deps],
- cmd = ("$(location @com_google_emboss//compiler/front_end:emboss_front_end) " +
- "--output-ir-to-stdout " +
- "--import-dir=. " +
- "--import-dir='$(GENDIR)' " +
- "$(location {}) > $(@D)/$$(basename $(OUTS) .h).ir; " +
- "$(location @com_google_emboss//compiler/back_end/cpp:emboss_codegen_cpp) " +
- "< $(@D)/$$(basename $(OUTS) .h).ir > " +
- "$(OUTS); " +
- "rm $(@D)/$$(basename $(OUTS) .h).ir").format(") $location( ".join(srcs)),
- outs = [src + ".h" for src in srcs],
- # This rule should only be visible to the following rule.
- visibility = ["//visibility:private"],
- )
-
- native.cc_library(
+ cc_emboss_library(
name = name,
- hdrs = [
- ":" + name + "_header",
- ],
- deps = deps + [
- "@com_google_emboss//runtime/cpp:cpp_utils",
- ],
+ deps = [":" + name + "_ir"],
visibility = visibility,
)
+
+# Full Starlark rules for emboss_library and cc_emboss_library.
+#
+# This implementation is loosely based on the proto_library and
+# cc_proto_library rules that are included with Bazel.
+
+EmbossInfo = provider(
+ doc = "Encapsulates information provided by a `emboss_library.`",
+ fields = {
+ "direct_source": "(File) The `.emb` source files from the `srcs`" +
+ " attribute.",
+ "transitive_sources": "(depset[File]) The `.emb` files from `srcs` " +
+ "and all `deps`.",
+ "transitive_roots": "(list[str]) The root paths for all " +
+ "transitive_sources.",
+ "direct_ir": "(list[File]) The `.emb.ir` files generated from the " +
+ "`srcs`.",
+ "transitive_ir": "(depset[File]) The `.emb.ir` files generated from " +
+ "transitive_srcs.",
+ },
+)
+
+def _emboss_library_impl(ctx):
+ deps = [dep[EmbossInfo] for dep in ctx.attr.deps]
+ outs = []
+ if len(ctx.attr.srcs) != 1:
+ fail("`srcs` attribute must contain exactly one label.", attr = "srcs")
+ src = ctx.files.srcs[0]
+ out = ctx.actions.declare_file(src.basename + ".ir", sibling = src)
+ outs.append(out)
+ inputs = depset(
+ direct = [src],
+ transitive = [dep.transitive_sources for dep in deps],
+ )
+ dep_roots = depset(
+ direct = [src.root],
+ transitive = [dep.transitive_roots for dep in deps],
+ )
+ imports = ["--import-dir=" + root.path for root in dep_roots.to_list()]
+ ctx.actions.run(
+ inputs = inputs.to_list(),
+ outputs = [out],
+ arguments = [src.path, "--output-file=" + out.path] + imports,
+ executable = ctx.executable._emboss_compiler,
+ )
+ transitive_sources = depset(
+ direct = [src],
+ transitive = [dep.transitive_sources for dep in deps],
+ )
+ transitive_ir = depset(
+ direct = outs,
+ transitive = [dep.transitive_ir for dep in deps],
+ )
+ transitive_roots = depset(
+ direct = [src.root],
+ transitive = [dep.transitive_roots for dep in deps],
+ )
+ return [
+ EmbossInfo(
+ direct_source = src,
+ transitive_sources = transitive_sources,
+ transitive_roots = transitive_roots,
+ direct_ir = outs,
+ transitive_ir = transitive_ir,
+ ),
+ DefaultInfo(
+ files = depset(outs),
+ ),
+ ]
+
+emboss_library = rule(
+ _emboss_library_impl,
+ attrs = {
+ "srcs": attr.label_list(
+ allow_files = [".emb"],
+ ),
+ "deps": attr.label_list(
+ providers = [EmbossInfo],
+ ),
+ "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+ "_emboss_compiler": attr.label(
+ executable = True,
+ cfg = "exec",
+ allow_files = True,
+ default = Label(
+ "@com_google_emboss//compiler/front_end:emboss_front_end",
+ ),
+ ),
+ },
+ provides = [EmbossInfo],
+)
+
+EmbossCcHeaderInfo = provider(
+ fields = {
+ "headers": "(list[File]) The `.emb.h` headers from this rule.",
+ "transitive_headers": "(list[File]) The `.emb.h` headers from this " +
+ "rule and all dependencies.",
+ },
+ doc = "Provide cc emboss headers.",
+)
+
+def _cc_emboss_aspect_impl(target, ctx):
+ cc_toolchain = find_cpp_toolchain(ctx, mandatory = True)
+ emboss_cc_compiler = ctx.executable._emboss_cc_compiler
+ emboss_info = target[EmbossInfo]
+ feature_configuration = cc_common.configure_features(
+ ctx = ctx,
+ cc_toolchain = cc_toolchain,
+ requested_features = list(ctx.features),
+ unsupported_features = list(ctx.disabled_features),
+ )
+ src = target[EmbossInfo].direct_source
+ headers = [ ctx.actions.declare_file( src.basename + ".h", sibling = src) ]
+ args = ctx.actions.args()
+ args.add("--input-file")
+ args.add_all(emboss_info.direct_ir)
+ args.add("--output-file")
+ args.add_all(headers)
+ ctx.actions.run(
+ executable = emboss_cc_compiler,
+ arguments = [args],
+ inputs = emboss_info.direct_ir,
+ outputs = headers,
+ )
+ runtime_cc_info = ctx.attr._emboss_cc_runtime[CcInfo]
+ transitive_headers = depset(
+ direct = headers,
+ transitive = [
+ dep[EmbossCcHeaderInfo].transitive_headers
+ for dep in ctx.rule.attr.deps
+ ] +
+ [runtime_cc_info.compilation_context.headers],
+ )
+ (cc_compilation_context, cc_compilation_outputs) = cc_common.compile(
+ name = ctx.label.name,
+ actions = ctx.actions,
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ public_hdrs = headers,
+ private_hdrs = transitive_headers.to_list(),
+ )
+ return [
+ CcInfo(compilation_context = cc_compilation_context),
+ EmbossCcHeaderInfo(
+ headers = depset(headers),
+ transitive_headers = transitive_headers,
+ ),
+ ]
+
+_cc_emboss_aspect = aspect(
+ implementation = _cc_emboss_aspect_impl,
+ attr_aspects = ["deps"],
+ fragments = ["cpp"],
+ required_providers = [EmbossInfo],
+ attrs = {
+ "_cc_toolchain": attr.label(
+ default = "@bazel_tools//tools/cpp:current_cc_toolchain",
+ ),
+ "_emboss_cc_compiler": attr.label(
+ executable = True,
+ cfg = "exec",
+ default = "@com_google_emboss//compiler/back_end/cpp:emboss_codegen_cpp",
+ ),
+ "_emboss_cc_runtime": attr.label(
+ default = "@com_google_emboss//runtime/cpp:cpp_utils",
+ ),
+ },
+)
+
+def _cc_emboss_library_impl(ctx):
+ if len(ctx.attr.deps) != 1:
+ fail("`deps` attribute must contain exactly one label.", attr = "deps")
+ dep = ctx.attr.deps[0]
+ return [
+ dep[CcInfo],
+ dep[EmbossInfo],
+ DefaultInfo(files = dep[EmbossCcHeaderInfo].headers),
+ ]
+
+cc_emboss_library = rule(
+ implementation = _cc_emboss_library_impl,
+ attrs = {
+ "deps": attr.label_list(
+ aspects = [_cc_emboss_aspect],
+ allow_rules = ["emboss_library"],
+ allow_files = False,
+ ),
+ },
+ provides = [CcInfo, EmbossInfo],
+)
diff --git a/compiler/back_end/cpp/BUILD b/compiler/back_end/cpp/BUILD
index fe532a1..c9e879c 100644
--- a/compiler/back_end/cpp/BUILD
+++ b/compiler/back_end/cpp/BUILD
@@ -126,6 +126,17 @@
)
emboss_cc_test(
+ name = "importer2_test",
+ srcs = [
+ "testcode/importer2_test.cc",
+ ],
+ deps = [
+ "//testdata:importer2_emboss",
+ "@com_google_googletest//:gtest_main",
+ ],
+)
+
+emboss_cc_test(
name = "uint_sizes_test",
srcs = [
"testcode/uint_sizes_test.cc",
diff --git a/compiler/back_end/cpp/emboss_codegen_cpp.py b/compiler/back_end/cpp/emboss_codegen_cpp.py
index b1d0f72..73a1450 100644
--- a/compiler/back_end/cpp/emboss_codegen_cpp.py
+++ b/compiler/back_end/cpp/emboss_codegen_cpp.py
@@ -20,18 +20,41 @@
from __future__ import print_function
+import argparse
import sys
from compiler.back_end.cpp import header_generator
from compiler.util import ir_pb2
-def main(argv):
- del argv # Unused.
- ir = ir_pb2.EmbossIr.from_json(sys.stdin.read())
- print(header_generator.generate_header(ir))
+def _parse_command_line(argv):
+ """Parses the given command-line arguments."""
+ parser = argparse.ArgumentParser(description="Emboss compiler C++ back end.",
+ prog=argv[0])
+ parser.add_argument("--input-file",
+ type=str,
+ help=".emb.ir file to compile.")
+ parser.add_argument("--output-file",
+ type=str,
+ help="Write header to file. If not specified, write " +
+ "header to stdout.")
+ return parser.parse_args(argv[1:])
+
+
+def main(flags):
+ if flags.input_file:
+ with open(flags.input_file) as f:
+ ir = ir_pb2.EmbossIr.from_json(f.read())
+ else:
+ ir = ir_pb2.EmbossIr.from_json(sys.stdin.read())
+ header = header_generator.generate_header(ir)
+ if flags.output_file:
+ with open(flags.output_file, "w") as f:
+ f.write(header)
+ else:
+ print(header)
return 0
-if __name__ == '__main__':
- sys.exit(main(sys.argv))
+if __name__ == "__main__":
+ sys.exit(main(_parse_command_line(sys.argv)))
diff --git a/compiler/back_end/cpp/testcode/importer2_test.cc b/compiler/back_end/cpp/testcode/importer2_test.cc
new file mode 100644
index 0000000..100435d
--- /dev/null
+++ b/compiler/back_end/cpp/testcode/importer2_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2019 Google LLC
+//
+// 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
+//
+// https://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.
+
+// Tests for using types that are imported from imports.
+
+#include <stdint.h>
+
+#include <vector>
+
+#include "gtest/gtest.h"
+#include "testdata/importer2.emb.h"
+
+namespace emboss {
+namespace test {
+namespace {
+
+const ::std::uint8_t kOuter2[16] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // inner
+ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, // inner_gen
+};
+
+TEST(Importer, CanAccessInner) {
+ auto view = Outer2View(kOuter2, sizeof kOuter2);
+ EXPECT_EQ(0x0807060504030201UL, view.outer().inner().value().Read());
+ EXPECT_EQ(0x100f0e0d0c0b0a09UL, view.outer().inner_gen().value().Read());
+}
+
+} // namespace
+} // namespace test
+} // namespace emboss
diff --git a/compiler/front_end/emboss_front_end.py b/compiler/front_end/emboss_front_end.py
index aa74eb7..ed435c2 100644
--- a/compiler/front_end/emboss_front_end.py
+++ b/compiler/front_end/emboss_front_end.py
@@ -64,6 +64,9 @@
parser.add_argument("--output-ir-to-stdout",
action="store_true",
help="Dump serialized IR to stdout.")
+ parser.add_argument("--output-file",
+ type=str,
+ help="Write serialized IR to file.")
parser.add_argument("--no-debug-show-header-lines",
dest="debug_show_header_lines",
action="store_false",
@@ -159,6 +162,9 @@
set(module_ir.PRODUCTIONS) - main_module_debug_info.used_productions))
if flags.output_ir_to_stdout:
print(ir.to_json())
+ if flags.output_file:
+ with open(flags.output_file, "w") as f:
+ f.write(ir.to_json())
return 0
diff --git a/testdata/BUILD b/testdata/BUILD
index 9c68b22..083298c 100644
--- a/testdata/BUILD
+++ b/testdata/BUILD
@@ -14,7 +14,11 @@
# Shared test data for Emboss.
-load("//:build_defs.bzl", "emboss_cc_library")
+load("//:build_defs.bzl",
+ "emboss_cc_library",
+ "emboss_library",
+ "cc_emboss_library"
+)
package(
default_visibility = ["//:__subpackages__"],
@@ -67,13 +71,20 @@
srcs = glob(["format/**"]),
)
-emboss_cc_library(
- name = "span_se_log_file_status_emboss",
+emboss_library(
+ name = "span_se_log_file_status_emb_ir",
srcs = [
"golden/span_se_log_file_status.emb",
],
)
+cc_emboss_library(
+ name = "span_se_log_file_status_emboss",
+ deps = [
+ ":span_se_log_file_status_emb_ir",
+ ],
+)
+
emboss_cc_library(
name = "nested_structure_emboss",
srcs = [
@@ -147,6 +158,16 @@
)
emboss_cc_library(
+ name = "importer2_emboss",
+ srcs = [
+ "importer2.emb",
+ ],
+ deps = [
+ ":importer_emboss",
+ ],
+)
+
+emboss_cc_library(
name = "float_emboss",
srcs = [
"float.emb",
diff --git a/testdata/importer2.emb b/testdata/importer2.emb
new file mode 100644
index 0000000..5d9c769
--- /dev/null
+++ b/testdata/importer2.emb
@@ -0,0 +1,30 @@
+# Copyright 2019 Google LLC
+#
+# 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
+#
+# https://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.
+
+# Test .emb to ensure that the import system works.
+#
+# The file imported_genfiles.emb is identical to imported.emb except for the
+# [(cpp) namespace] attribute; it is used to ensure that generated .embs can be
+# used by the emboss_cc_library build rule.
+
+# These imports intentionally use names that do not match the file names, as a
+# test that the file names aren't being used.
+
+import "testdata/importer.emb" as imp
+
+[(cpp) namespace: "emboss::test"]
+
+
+struct Outer2:
+ 0 [+16] imp.Outer outer