"""BUILD rules for generating flatbuffer files."""
load("@build_bazel_rules_android//android:rules.bzl", "android_library")
load("@rules_java//java:defs.bzl", "java_library")
flatc_path = "@flatbuffers//:flatc"
zip_files = "@org_tensorflow//tensorflow_lite_support/tools:zip_files"
DEFAULT_INCLUDE_PATHS = [
"./",
"$(GENDIR)",
"$(BINDIR)",
]
DEFAULT_FLATC_ARGS = [
"--no-union-value-namespacing",
"--gen-object-api",
]
def flatbuffer_library_public(
name,
srcs,
outs,
language_flag,
out_prefix = "",
includes = [],
include_paths = [],
compatible_with = [],
flatc_args = DEFAULT_FLATC_ARGS,
reflection_name = "",
reflection_visibility = None, # buildifier: disable=unused-variable
output_to_bindir = False):
"""Generates code files for reading/writing the given flatbuffers in the requested language using the public compiler.
Outs:
filegroup(name): all generated source files.
Fileset([reflection_name]): (Optional) all generated reflection binaries.
Args:
name: Rule name.
srcs: Source .fbs files. Sent in order to the compiler.
outs: Output files from flatc.
language_flag: Target language flag. One of [-c, -j, -js].
out_prefix: Prepend this path to the front of all generated files except on
single source targets. Usually is a directory name.
includes: Optional, list of filegroups of schemas that the srcs depend on.
include_paths: Optional, list of paths the includes files can be found in.
compatible_with: Optional, passed to genrule for environments this rule
can be built for.
flatc_args: Optional, list of additional arguments to pass to flatc.
reflection_name: Optional, if set this will generate the flatbuffer
reflection binaries for the schemas.
reflection_visibility: The visibility of the generated reflection Fileset.
output_to_bindir: Passed to genrule for output to bin directory.
"""
include_paths_cmd = ["-I %s" % (s) for s in include_paths]
# '$(@D)' when given a single source target will give the appropriate
# directory. Appending 'out_prefix' is only necessary when given a build
# target with multiple sources.
output_directory = (
("-o $(@D)/%s" % (out_prefix)) if len(srcs) > 1 else ("-o $(@D)")
)
genrule_cmd = " ".join([
"for f in $(SRCS); do",
"$(location %s)" % (flatc_path),
" ".join(flatc_args),
" ".join(include_paths_cmd),
language_flag,
output_directory,
"$$f;",
"done",
])
native.genrule(
name = name,
srcs = srcs,
outs = outs,
output_to_bindir = output_to_bindir,
compatible_with = compatible_with,
tools = includes + [flatc_path],
cmd = genrule_cmd,
message = "Generating flatbuffer files for %s:" % (name),
)
if reflection_name:
reflection_genrule_cmd = " ".join([
"for f in $(SRCS); do",
"$(location %s)" % (flatc_path),
"-b --schema",
" ".join(flatc_args),
" ".join(include_paths_cmd),
language_flag,
output_directory,
"$$f;",
"done",
])
reflection_outs = [
(out_prefix + "%s.bfbs") % (s.replace(".fbs", "").split("/")[-1])
for s in srcs
]
native.genrule(
name = "%s_srcs" % reflection_name,
srcs = srcs,
outs = reflection_outs,
output_to_bindir = output_to_bindir,
compatible_with = compatible_with,
tools = includes + [flatc_path],
cmd = reflection_genrule_cmd,
message = "Generating flatbuffer reflection binary for %s:" % (name),
)
# TODO: Make bazel rules proper and supported by flatbuffer
# Have to comment this since FilesetEntry is not supported in bazel
# starlark.
# native.Fileset(
# name = reflection_name,
# out = "%s_out" % reflection_name,
# entries = [
# native.FilesetEntry(files = reflection_outs),
# ],
# visibility = reflection_visibility,
# compatible_with = compatible_with,
# )
def flatbuffer_cc_library(
name,
srcs,
srcs_filegroup_name = "",
out_prefix = "",
includes = [],
include_paths = [],
compatible_with = [],
flatc_args = DEFAULT_FLATC_ARGS,
visibility = None,
srcs_filegroup_visibility = None,
gen_reflections = False):
'''A cc_library with the generated reader/writers for the given flatbuffer definitions.
Outs:
filegroup([name]_srcs): all generated .h files.
filegroup(srcs_filegroup_name if specified, or [name]_includes if not):
Other flatbuffer_cc_library's can pass this in for their `includes`
parameter, if they depend on the schemas in this library.
Fileset([name]_reflection): (Optional) all generated reflection binaries.
cc_library([name]): library with sources and flatbuffers deps.
Remarks:
** Because the genrule used to call flatc does not have any trivial way of
computing the output list of files transitively generated by includes and
--gen-includes (the default) being defined for flatc, the --gen-includes
flag will not work as expected. The way around this is to add a dependency
to the flatbuffer_cc_library defined alongside the flatc included Fileset.
For example you might define:
flatbuffer_cc_library(
name = "my_fbs",
srcs = [ "schemas/foo.fbs" ],
includes = [ "//third_party/bazz:bazz_fbs_includes" ],
)
In which foo.fbs includes a few files from the Fileset defined at
//third_party/bazz:bazz_fbs_includes. When compiling the library that
includes foo_generated.h, and therefore has my_fbs as a dependency, it
will fail to find any of the bazz *_generated.h files unless you also
add bazz's flatbuffer_cc_library to your own dependency list, e.g.:
cc_library(
name = "my_lib",
deps = [
":my_fbs",
"//third_party/bazz:bazz_fbs"
],
)
Happy dependent Flatbuffering!
Args:
name: Rule name.
srcs: Source .fbs files. Sent in order to the compiler.
srcs_filegroup_name: Name of the output filegroup that holds srcs. Pass this
filegroup into the `includes` parameter of any other
flatbuffer_cc_library that depends on this one's schemas.
out_prefix: Prepend this path to the front of all generated files. Usually
is a directory name.
includes: Optional, list of filegroups of schemas that the srcs depend on.
** SEE REMARKS BELOW **
include_paths: Optional, list of paths the includes files can be found in.
compatible_with: Optional, passed to genrule for environments this rule
can be built for
flatc_args: Optional list of additional arguments to pass to flatc
(e.g. --gen-mutable).
visibility: The visibility of the generated cc_library. By default, use the
default visibility of the project.
srcs_filegroup_visibility: The visibility of the generated srcs filegroup.
By default, use the value of the visibility parameter above.
gen_reflections: Optional, if true this will generate the flatbuffer
reflection binaries for the schemas.
'''
output_headers = [
(out_prefix + "%s_generated.h") % (s.replace(".fbs", "").split("/")[-1].split(":")[-1])
for s in srcs
]
reflection_name = "%s_reflection" % name if gen_reflections else ""
flatbuffer_library_public(
name = "%s_srcs" % (name),
srcs = srcs,
outs = output_headers,
language_flag = "-c",
out_prefix = out_prefix,
includes = includes,
include_paths = include_paths,
compatible_with = compatible_with,
flatc_args = flatc_args,
reflection_name = reflection_name,
reflection_visibility = visibility,
)
native.cc_library(
name = name,
hdrs = output_headers,
srcs = output_headers,
features = [
"-parse_headers",
],
deps = [
"@flatbuffers//:runtime_cc",
],
includes = ["."],
linkstatic = 1,
visibility = visibility,
compatible_with = compatible_with,
)
# A filegroup for the `srcs`. That is, all the schema files for this
# Flatbuffer set.
native.filegroup(
name = srcs_filegroup_name if srcs_filegroup_name else "%s_includes" % (name),
srcs = srcs,
visibility = srcs_filegroup_visibility if srcs_filegroup_visibility != None else visibility,
compatible_with = compatible_with,
)
FlatbufferInfo = provider(
"Custom provider to track dependencies transitively.",
fields = {
"transitive_srcs": "flatbuffer schema definitions.",
},
)
def _flatbuffer_schemas_aspect_impl(target, ctx):
_ignore = [target]
transitive_srcs = depset()
if hasattr(ctx.rule.attr, "deps"):
for dep in ctx.rule.attr.deps:
if FlatbufferInfo in dep:
transitive_srcs = depset(dep[FlatbufferInfo].transitive_srcs, transitive = [transitive_srcs]) # buildifier: disable=overly-nested-depset
if hasattr(ctx.rule.attr, "srcs"):
for src in ctx.rule.attr.srcs:
if FlatbufferInfo in src:
transitive_srcs = depset(src[FlatbufferInfo].transitive_srcs, transitive = [transitive_srcs]) # buildifier: disable=overly-nested-depset
for f in src.files:
if f.extension == "fbs":
transitive_srcs = depset([f], transitive = [transitive_srcs]) # buildifier: disable=overly-nested-depset
return [FlatbufferInfo(transitive_srcs = transitive_srcs)]
# An aspect that runs over all dependencies and transitively collects
# flatbuffer schema files.
_flatbuffer_schemas_aspect = aspect(
attr_aspects = [
"deps",
"srcs",
],
implementation = _flatbuffer_schemas_aspect_impl,
)
# Rule to invoke the flatbuffer compiler.
def _gen_flatbuffer_srcs_impl(ctx):
outputs = ctx.attr.outputs
include_paths = ctx.attr.include_paths
if ctx.attr.no_includes:
no_includes_statement = ["--no-includes"]
else:
no_includes_statement = []
if ctx.attr.language_flag == "--python":
onefile_statement = ["--gen-onefile"]
else:
onefile_statement = []
# Need to generate all files in a directory.
if not outputs:
outputs = [ctx.actions.declare_directory("{}_all".format(ctx.attr.name))]
output_directory = outputs[0].path
else:
outputs = [ctx.actions.declare_file(output) for output in outputs]
output_directory = outputs[0].dirname
deps = depset(ctx.files.srcs + ctx.files.deps, transitive = [
dep[FlatbufferInfo].transitive_srcs
for dep in ctx.attr.deps
if FlatbufferInfo in dep
])
include_paths_cmd_line = []
for s in include_paths:
include_paths_cmd_line.extend(["-I", s])
for src in ctx.files.srcs:
ctx.actions.run(
inputs = deps,
outputs = outputs,
executable = ctx.executable._flatc,
arguments = [
ctx.attr.language_flag,
"-o",
output_directory,
# Allow for absolute imports and referencing of generated files.
"-I",
"./",
"-I",
ctx.genfiles_dir.path,
"-I",
ctx.bin_dir.path,
] + no_includes_statement +
onefile_statement +
include_paths_cmd_line + [
"--no-union-value-namespacing",
"--gen-object-api",
src.path,
],
progress_message = "Generating flatbuffer files for {}:".format(src),
)
return [
DefaultInfo(files = depset(outputs)),
]
_gen_flatbuffer_srcs = rule(
_gen_flatbuffer_srcs_impl,
attrs = {
"srcs": attr.label_list(
allow_files = [".fbs"],
mandatory = True,
),
"outputs": attr.string_list(
default = [],
mandatory = False,
),
"deps": attr.label_list(
default = [],
mandatory = False,
aspects = [_flatbuffer_schemas_aspect],
),
"include_paths": attr.string_list(
default = [],
mandatory = False,
),
"language_flag": attr.string(
mandatory = True,
),
"no_includes": attr.bool(
default = False,
mandatory = False,
),
"_flatc": attr.label(
default = Label("@flatbuffers//:flatc"),
executable = True,
cfg = "exec",
),
},
)
def flatbuffer_py_strip_prefix_srcs(name, srcs = [], strip_prefix = ""):
"""Strips path prefix.
Args:
name: Rule name. (required)
srcs: Source .py files. (required)
strip_prefix: Path that needs to be stripped from the srcs filepaths. (required)
"""
for src in srcs:
native.genrule(
name = name + "_" + src.replace(".", "_").replace("/", "_"),
srcs = [src],
outs = [src.replace(strip_prefix, "")],
cmd = "cp $< $@",
)
def _concat_flatbuffer_py_srcs_impl(ctx):
# Merge all generated python files. The files are concatenated and the
# import statements are removed. Finally we import the flatbuffer runtime
# library.
# IMPORTANT: Our Windows shell does not support "find ... -exec" properly.
# If changing the commandline here, please build wheels and run smoke tests
# on all the three operation systems.
command = "echo 'import flatbuffers\n' > %s; "
command += "for f in $(find %s -name '*.py'); do cat $f | sed '/import flatbuffers/d' >> %s; done "
ctx.actions.run_shell(
inputs = ctx.attr.deps[0].files,
outputs = [ctx.outputs.out],
command = command % (
ctx.outputs.out.path,
ctx.attr.deps[0].files.to_list()[0].path,
ctx.outputs.out.path,
),
)
_concat_flatbuffer_py_srcs = rule(
_concat_flatbuffer_py_srcs_impl,
attrs = {
"deps": attr.label_list(mandatory = True),
},
outputs = {"out": "%{name}.py"},
)
def flatbuffer_py_library(
name,
srcs,
deps = [],
include_paths = []):
"""A py_library with the generated reader/writers for the given schema.
This rule assumes that the schema files define non-conflicting names, so that
they can be merged in a single file. This is e.g. the case if only a single
namespace is used.
The rule call the flatbuffer compiler for all schema files and merges the
generated python files into a single file that is wrapped in a py_library.
Args:
name: Rule name. (required)
srcs: List of source .fbs files. (required)
deps: List of dependencies.
include_paths: Optional, list of paths the includes files can be found in.
"""
all_srcs = "{}_srcs".format(name)
_gen_flatbuffer_srcs(
name = all_srcs,
srcs = srcs,
language_flag = "--python",
deps = deps,
include_paths = include_paths,
)
all_srcs_no_include = "{}_srcs_no_include".format(name)
_gen_flatbuffer_srcs(
name = all_srcs_no_include,
srcs = srcs,
language_flag = "--python",
deps = deps,
no_includes = True,
include_paths = include_paths,
)
# TODO: Remove the concatnation rule with 2.0.6 update.
concat_py_srcs = "{}_generated".format(name)
_concat_flatbuffer_py_srcs(
name = concat_py_srcs,
deps = [
":{}".format(all_srcs_no_include),
],
)
native.py_library(
name = name,
srcs = [
":{}".format(concat_py_srcs),
],
srcs_version = "PY2AND3",
deps = deps,
)
def flatbuffer_java_library(
name,
srcs,
custom_package = "",
package_prefix = "",
include_paths = DEFAULT_INCLUDE_PATHS,
flatc_args = DEFAULT_FLATC_ARGS,
visibility = None):
"""A java library with the generated reader/writers for the given flatbuffer definitions.
Args:
name: Rule name. (required)
srcs: List of source .fbs files including all includes. (required)
custom_package: Package name of generated Java files. If not specified
namespace in the schema files will be used. (optional)
package_prefix: like custom_package, but prefixes to the existing
namespace. (optional)
include_paths: List of paths that includes files can be found in. (optional)
flatc_args: List of additional arguments to pass to flatc. (optional)
visibility: Visibility setting for the java_library rule. (optional)
"""
out_srcjar = "java_%s_all.srcjar" % name
flatbuffer_java_srcjar(
name = "%s_srcjar" % name,
srcs = srcs,
out = out_srcjar,
custom_package = custom_package,
flatc_args = flatc_args,
include_paths = include_paths,
package_prefix = package_prefix,
)
native.filegroup(
name = "%s.srcjar" % name,
srcs = [out_srcjar],
)
java_library(
name = name,
srcs = [out_srcjar],
javacopts = ["-source 7 -target 7"],
deps = [
"@flatbuffers//:runtime_java",
],
visibility = visibility,
)
def flatbuffer_java_srcjar(
name,
srcs,
out,
custom_package = "",
package_prefix = "",
include_paths = DEFAULT_INCLUDE_PATHS,
flatc_args = DEFAULT_FLATC_ARGS):
"""Generate flatbuffer Java source files.
Args:
name: Rule name. (required)
srcs: List of source .fbs files including all includes. (required)
out: Output file name. (required)
custom_package: Package name of generated Java files. If not specified
namespace in the schema files will be used. (optional)
package_prefix: like custom_package, but prefixes to the existing
namespace. (optional)
include_paths: List of paths that includes files can be found in. (optional)
flatc_args: List of additional arguments to pass to flatc. (optional)
"""
command_fmt = """set -e
tmpdir=$(@D)
schemas=$$tmpdir/schemas
java_root=$$tmpdir/java
rm -rf $$schemas
rm -rf $$java_root
mkdir -p $$schemas
mkdir -p $$java_root
for src in $(SRCS); do
dest=$$schemas/$$src
rm -rf $$(dirname $$dest)
mkdir -p $$(dirname $$dest)
if [ -z "{custom_package}" ] && [ -z "{package_prefix}" ]; then
cp -f $$src $$dest
else
if [ -z "{package_prefix}" ]; then
sed -e "s/namespace\\s.*/namespace {custom_package};/" $$src > $$dest
else
sed -e "s/namespace \\([^;]\\+\\);/namespace {package_prefix}.\\1;/" $$src > $$dest
fi
fi
done
flatc_arg_I="-I $$tmpdir/schemas"
for include_path in {include_paths}; do
flatc_arg_I="$$flatc_arg_I -I $$schemas/$$include_path"
done
flatc_additional_args=
for arg in {flatc_args}; do
flatc_additional_args="$$flatc_additional_args $$arg"
done
for src in $(SRCS); do
$(location {flatc_path}) $$flatc_arg_I --java $$flatc_additional_args -o $$java_root $$schemas/$$src
done
$(location {zip_files}) -export_zip_path=$@ -file_directory=$$java_root
"""
genrule_cmd = command_fmt.format(
package_name = native.package_name(),
custom_package = custom_package,
package_prefix = package_prefix,
flatc_path = flatc_path,
zip_files = zip_files,
include_paths = " ".join(include_paths),
flatc_args = " ".join(flatc_args),
)
native.genrule(
name = name,
srcs = srcs,
outs = [out],
tools = [flatc_path, zip_files],
cmd = genrule_cmd,
)
def flatbuffer_android_library(
name,
srcs,
custom_package = "",
package_prefix = "",
include_paths = DEFAULT_INCLUDE_PATHS,
flatc_args = DEFAULT_FLATC_ARGS,
visibility = None):
"""An android_library with the generated reader/writers for the given flatbuffer definitions.
Args:
name: Rule name. (required)
srcs: List of source .fbs files including all includes. (required)
custom_package: Package name of generated Java files. If not specified
namespace in the schema files will be used. (optional)
package_prefix: like custom_package, but prefixes to the existing
namespace. (optional)
include_paths: List of paths that includes files can be found in. (optional)
flatc_args: List of additional arguments to pass to flatc. (optional)
visibility: Visibility setting for the android_library rule. (optional)
"""
out_srcjar = "android_%s_all.srcjar" % name
flatbuffer_java_srcjar(
name = "%s_srcjar" % name,
srcs = srcs,
out = out_srcjar,
custom_package = custom_package,
flatc_args = flatc_args,
include_paths = include_paths,
package_prefix = package_prefix,
)
native.filegroup(
name = "%s.srcjar" % name,
srcs = [out_srcjar],
)
# To support org.checkerframework.dataflow.qual.Pure.
checkerframework_annotations = [
"@org_checkerframework_qual",
] if "--java-checkerframework" in flatc_args else []
android_library(
name = name,
srcs = [out_srcjar],
javacopts = ["-source 7 -target 7"],
visibility = visibility,
deps = [
"@flatbuffers//:runtime_android",
] + checkerframework_annotations,
)