• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""BUILD rules for generating flatbuffer files."""
2
3flatc_path = "@flatbuffers//:flatc"
4
5DEFAULT_FLATC_ARGS = [
6    "--no-union-value-namespacing",
7    "--gen-object-api",
8]
9
10def flatbuffer_library_public(
11        name,
12        srcs,
13        outs,
14        language_flag,
15        out_prefix = "",
16        includes = [],
17        include_paths = [],
18        flatc_args = DEFAULT_FLATC_ARGS,
19        reflection_name = "",
20        reflection_visibility = None,
21        output_to_bindir = False):
22    """Generates code files for reading/writing the given flatbuffers in the requested language using the public compiler.
23
24    Outs:
25      filegroup(name): all generated source files.
26      Fileset([reflection_name]): (Optional) all generated reflection binaries.
27
28    Args:
29      name: Rule name.
30      srcs: Source .fbs files. Sent in order to the compiler.
31      outs: Output files from flatc.
32      language_flag: Target language flag. One of [-c, -j, -js].
33      out_prefix: Prepend this path to the front of all generated files except on
34          single source targets. Usually is a directory name.
35      includes: Optional, list of filegroups of schemas that the srcs depend on.
36      include_paths: Optional, list of paths the includes files can be found in.
37      flatc_args: Optional, list of additional arguments to pass to flatc.
38      reflection_name: Optional, if set this will generate the flatbuffer
39        reflection binaries for the schemas.
40      reflection_visibility: The visibility of the generated reflection Fileset.
41      output_to_bindir: Passed to genrule for output to bin directory.
42    """
43    include_paths_cmd = ["-I %s" % (s) for s in include_paths]
44
45    # '$(@D)' when given a single source target will give the appropriate
46    # directory. Appending 'out_prefix' is only necessary when given a build
47    # target with multiple sources.
48    output_directory = (
49        ("-o $(@D)/%s" % (out_prefix)) if len(srcs) > 1 else ("-o $(@D)")
50    )
51    genrule_cmd = " ".join([
52        "for f in $(SRCS); do",
53        "$(location %s)" % (flatc_path),
54        " ".join(flatc_args),
55        " ".join(include_paths_cmd),
56        language_flag,
57        output_directory,
58        "$$f;",
59        "done",
60    ])
61    native.genrule(
62        name = name,
63        srcs = srcs,
64        outs = outs,
65        output_to_bindir = output_to_bindir,
66        tools = includes + [flatc_path],
67        cmd = genrule_cmd,
68        message = "Generating flatbuffer files for %s:" % (name),
69    )
70    if reflection_name:
71        reflection_genrule_cmd = " ".join([
72            "for f in $(SRCS); do",
73            "$(location %s)" % (flatc_path),
74            "-b --schema",
75            " ".join(flatc_args),
76            " ".join(include_paths_cmd),
77            language_flag,
78            output_directory,
79            "$$f;",
80            "done",
81        ])
82        reflection_outs = [
83            (out_prefix + "%s.bfbs") % (s.replace(".fbs", "").split("/")[-1])
84            for s in srcs
85        ]
86        native.genrule(
87            name = "%s_srcs" % reflection_name,
88            srcs = srcs,
89            outs = reflection_outs,
90            output_to_bindir = output_to_bindir,
91            tools = includes + [flatc_path],
92            cmd = reflection_genrule_cmd,
93            message = "Generating flatbuffer reflection binary for %s:" % (name),
94        )
95        # TODO(b/114456773): Make bazel rules proper and supported by flatbuffer
96        # Have to comment this since FilesetEntry is not supported in bazel
97        # skylark.
98        # native.Fileset(
99        #     name = reflection_name,
100        #     out = "%s_out" % reflection_name,
101        #     entries = [
102        #         native.FilesetEntry(files = reflection_outs),
103        #     ],
104        #     visibility = reflection_visibility,
105        # )
106
107def flatbuffer_cc_library(
108        name,
109        srcs,
110        srcs_filegroup_name = "",
111        out_prefix = "",
112        includes = [],
113        include_paths = [],
114        flatc_args = DEFAULT_FLATC_ARGS,
115        visibility = None,
116        srcs_filegroup_visibility = None,
117        gen_reflections = False):
118    '''A cc_library with the generated reader/writers for the given flatbuffer definitions.
119
120    Outs:
121      filegroup([name]_srcs): all generated .h files.
122      filegroup(srcs_filegroup_name if specified, or [name]_includes if not):
123          Other flatbuffer_cc_library's can pass this in for their `includes`
124          parameter, if they depend on the schemas in this library.
125      Fileset([name]_reflection): (Optional) all generated reflection binaries.
126      cc_library([name]): library with sources and flatbuffers deps.
127
128    Remarks:
129      ** Because the genrule used to call flatc does not have any trivial way of
130        computing the output list of files transitively generated by includes and
131        --gen-includes (the default) being defined for flatc, the --gen-includes
132        flag will not work as expected. The way around this is to add a dependency
133        to the flatbuffer_cc_library defined alongside the flatc included Fileset.
134        For example you might define:
135
136        flatbuffer_cc_library(
137            name = "my_fbs",
138            srcs = [ "schemas/foo.fbs" ],
139            includes = [ "//third_party/bazz:bazz_fbs_includes" ],
140        )
141
142        In which foo.fbs includes a few files from the Fileset defined at
143        //third_party/bazz:bazz_fbs_includes. When compiling the library that
144        includes foo_generated.h, and therefore has my_fbs as a dependency, it
145        will fail to find any of the bazz *_generated.h files unless you also
146        add bazz's flatbuffer_cc_library to your own dependency list, e.g.:
147
148        cc_library(
149            name = "my_lib",
150            deps = [
151                ":my_fbs",
152                "//third_party/bazz:bazz_fbs"
153            ],
154        )
155
156        Happy dependent Flatbuffering!
157
158    Args:
159      name: Rule name.
160      srcs: Source .fbs files. Sent in order to the compiler.
161      srcs_filegroup_name: Name of the output filegroup that holds srcs. Pass this
162          filegroup into the `includes` parameter of any other
163          flatbuffer_cc_library that depends on this one's schemas.
164      out_prefix: Prepend this path to the front of all generated files. Usually
165          is a directory name.
166      includes: Optional, list of filegroups of schemas that the srcs depend on.
167          ** SEE REMARKS BELOW **
168      include_paths: Optional, list of paths the includes files can be found in.
169      flatc_args: Optional list of additional arguments to pass to flatc
170          (e.g. --gen-mutable).
171      visibility: The visibility of the generated cc_library. By default, use the
172          default visibility of the project.
173      srcs_filegroup_visibility: The visibility of the generated srcs filegroup.
174          By default, use the value of the visibility parameter above.
175      gen_reflections: Optional, if true this will generate the flatbuffer
176        reflection binaries for the schemas.
177    '''
178    output_headers = [
179        (out_prefix + "%s_generated.h") % (s.replace(".fbs", "").split("/")[-1])
180        for s in srcs
181    ]
182    reflection_name = "%s_reflection" % name if gen_reflections else ""
183
184    flatbuffer_library_public(
185        name = "%s_srcs" % (name),
186        srcs = srcs,
187        outs = output_headers,
188        language_flag = "-c",
189        out_prefix = out_prefix,
190        includes = includes,
191        include_paths = include_paths,
192        flatc_args = flatc_args,
193        reflection_name = reflection_name,
194        reflection_visibility = visibility,
195    )
196    native.cc_library(
197        name = name,
198        hdrs = output_headers,
199        srcs = output_headers,
200        features = [
201            "-parse_headers",
202        ],
203        deps = [
204            "@flatbuffers//:runtime_cc",
205        ],
206        includes = ["."],
207        linkstatic = 1,
208        visibility = visibility,
209    )
210
211    # A filegroup for the `srcs`. That is, all the schema files for this
212    # Flatbuffer set.
213    native.filegroup(
214        name = srcs_filegroup_name if srcs_filegroup_name else "%s_includes" % (name),
215        srcs = srcs,
216        visibility = srcs_filegroup_visibility if srcs_filegroup_visibility != None else visibility,
217    )
218
219# Custom provider to track dependencies transitively.
220FlatbufferInfo = provider(
221    fields = {
222        "transitive_srcs": "flatbuffer schema definitions.",
223    },
224)
225
226def _flatbuffer_schemas_aspect_impl(target, ctx):
227    _ignore = [target]
228    transitive_srcs = depset()
229    if hasattr(ctx.rule.attr, "deps"):
230        for dep in ctx.rule.attr.deps:
231            if FlatbufferInfo in dep:
232                transitive_srcs = depset(dep[FlatbufferInfo].transitive_srcs, transitive = [transitive_srcs])
233    if hasattr(ctx.rule.attr, "srcs"):
234        for src in ctx.rule.attr.srcs:
235            if FlatbufferInfo in src:
236                transitive_srcs = depset(src[FlatbufferInfo].transitive_srcs, transitive = [transitive_srcs])
237            for f in src.files:
238                if f.extension == "fbs":
239                    transitive_srcs = depset([f], transitive = [transitive_srcs])
240    return [FlatbufferInfo(transitive_srcs = transitive_srcs)]
241
242# An aspect that runs over all dependencies and transitively collects
243# flatbuffer schema files.
244_flatbuffer_schemas_aspect = aspect(
245    attr_aspects = [
246        "deps",
247        "srcs",
248    ],
249    implementation = _flatbuffer_schemas_aspect_impl,
250)
251
252# Rule to invoke the flatbuffer compiler.
253def _gen_flatbuffer_srcs_impl(ctx):
254    outputs = ctx.attr.outputs
255    include_paths = ctx.attr.include_paths
256    if ctx.attr.no_includes:
257        no_includes_statement = ["--no-includes"]
258    else:
259        no_includes_statement = []
260
261    # Need to generate all files in a directory.
262    if not outputs:
263        outputs = [ctx.actions.declare_directory("{}_all".format(ctx.attr.name))]
264        output_directory = outputs[0].path
265    else:
266        outputs = [ctx.actions.declare_file(output) for output in outputs]
267        output_directory = outputs[0].dirname
268
269    deps = depset(ctx.files.srcs + ctx.files.deps, transitive = [
270        dep[FlatbufferInfo].transitive_srcs
271        for dep in ctx.attr.deps
272        if FlatbufferInfo in dep
273    ])
274
275    include_paths_cmd_line = []
276    for s in include_paths:
277        include_paths_cmd_line.extend(["-I", s])
278
279    for src in ctx.files.srcs:
280        ctx.actions.run(
281            inputs = deps,
282            outputs = outputs,
283            executable = ctx.executable._flatc,
284            arguments = [
285                            ctx.attr.language_flag,
286                            "-o",
287                            output_directory,
288                            # Allow for absolute imports and referencing of generated files.
289                            "-I",
290                            "./",
291                            "-I",
292                            ctx.genfiles_dir.path,
293                            "-I",
294                            ctx.bin_dir.path,
295                        ] + no_includes_statement +
296                        include_paths_cmd_line + [
297                "--no-union-value-namespacing",
298                "--gen-object-api",
299                src.path,
300            ],
301            progress_message = "Generating flatbuffer files for {}:".format(src),
302        )
303    return [
304        DefaultInfo(files = depset(outputs)),
305    ]
306
307_gen_flatbuffer_srcs = rule(
308    _gen_flatbuffer_srcs_impl,
309    attrs = {
310        "srcs": attr.label_list(
311            allow_files = [".fbs"],
312            mandatory = True,
313        ),
314        "outputs": attr.string_list(
315            default = [],
316            mandatory = False,
317        ),
318        "deps": attr.label_list(
319            default = [],
320            mandatory = False,
321            aspects = [_flatbuffer_schemas_aspect],
322        ),
323        "include_paths": attr.string_list(
324            default = [],
325            mandatory = False,
326        ),
327        "language_flag": attr.string(
328            mandatory = True,
329        ),
330        "no_includes": attr.bool(
331            default = False,
332            mandatory = False,
333        ),
334        "_flatc": attr.label(
335            default = Label("@flatbuffers//:flatc"),
336            executable = True,
337            cfg = "host",
338        ),
339    },
340    output_to_genfiles = True,
341)
342
343def _concat_flatbuffer_py_srcs_impl(ctx):
344    # Merge all generated python files. The files are concatenated and the
345    # import statements are removed. Finally we import the flatbuffer runtime
346    # library.
347    ctx.actions.run_shell(
348        inputs = ctx.attr.deps[0].files,
349        outputs = [ctx.outputs.out],
350        command = (
351            "find '%s' -name '*.py' -exec cat {} + |" +
352            "sed '/import flatbuffers/d' |" +
353            "sed 's/from flatbuffers." +
354            "/from flatbuffers.python.flatbuffers./' |" +
355            "sed '1s/^/from flatbuffers.python " +
356            "import flatbuffers\\n/' > %s"
357        ) % (
358            ctx.attr.deps[0].files.to_list()[0].path,
359            ctx.outputs.out.path,
360        ),
361    )
362
363_concat_flatbuffer_py_srcs = rule(
364    _concat_flatbuffer_py_srcs_impl,
365    attrs = {
366        "deps": attr.label_list(mandatory = True),
367    },
368    output_to_genfiles = True,
369    outputs = {"out": "%{name}.py"},
370)
371
372def flatbuffer_py_library(
373        name,
374        srcs,
375        deps = [],
376        include_paths = []):
377    """A py_library with the generated reader/writers for the given schema.
378
379    This rule assumes that the schema files define non-conflicting names, so that
380    they can be merged in a single file. This is e.g. the case if only a single
381    namespace is used.
382    The rule call the flatbuffer compiler for all schema files and merges the
383    generated python files into a single file that is wrapped in a py_library.
384
385    Args:
386      name: Rule name. (required)
387      srcs: List of source .fbs files. (required)
388      deps: List of dependencies.
389      include_paths: Optional, list of paths the includes files can be found in.
390    """
391    all_srcs = "{}_srcs".format(name)
392    _gen_flatbuffer_srcs(
393        name = all_srcs,
394        srcs = srcs,
395        language_flag = "--python",
396        deps = deps,
397        include_paths = include_paths,
398    )
399    all_srcs_no_include = "{}_srcs_no_include".format(name)
400    _gen_flatbuffer_srcs(
401        name = all_srcs_no_include,
402        srcs = srcs,
403        language_flag = "--python",
404        deps = deps,
405        no_includes = True,
406        include_paths = include_paths,
407    )
408    concat_py_srcs = "{}_generated".format(name)
409    _concat_flatbuffer_py_srcs(
410        name = concat_py_srcs,
411        deps = [
412            ":{}".format(all_srcs_no_include),
413        ],
414    )
415    native.py_library(
416        name = name,
417        srcs = [
418            ":{}".format(concat_py_srcs),
419        ],
420        srcs_version = "PY2AND3",
421        deps = deps + [
422            "@flatbuffers//:runtime_py",
423        ],
424    )
425