xref: /aosp_15_r20/external/cronet/third_party/protobuf/pkg/build_systems.bzl (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1# Starlark utilities for working with other build systems
2
3load("@rules_pkg//:providers.bzl", "PackageFilegroupInfo", "PackageFilesInfo")
4
5################################################################################
6# Macro to create CMake and Automake source lists.
7################################################################################
8
9def gen_file_lists(name, out_stem, **kwargs):
10    gen_cmake_file_lists(
11        name = name + "_cmake",
12        out = out_stem + ".cmake",
13        source_prefix = "${protobuf_SOURCE_DIR}/",
14        **kwargs
15    )
16    gen_automake_file_lists(
17        name = name + "_automake",
18        out = out_stem + ".am",
19        source_prefix = "$(top_srcdir)/",
20        **kwargs
21    )
22    native.filegroup(
23        name = name,
24        srcs = [
25            out_stem + ".cmake",
26            out_stem + ".am",
27        ],
28    )
29
30################################################################################
31# Aspect that extracts srcs, hdrs, etc.
32################################################################################
33
34CcFileList = provider(
35    doc = "List of files to be built into a library.",
36    fields = {
37        # As a rule of thumb, `hdrs` and `textual_hdrs` are the files that
38        # would be installed along with a prebuilt library.
39        "hdrs": "public header files, including those used by generated code",
40        "textual_hdrs": "files which are included but are not self-contained",
41
42        # The `internal_hdrs` are header files which appear in `srcs`.
43        # These are only used when compiling the library.
44        "internal_hdrs": "internal header files (only used to build .cc files)",
45        "srcs": "source files",
46    },
47)
48
49ProtoFileList = provider(
50    doc = "List of proto files and generated code to be built into a library.",
51    fields = {
52        # Proto files:
53        "proto_srcs": "proto file sources",
54
55        # Generated sources:
56        "hdrs": "header files that are expected to be generated",
57        "srcs": "source files that are expected to be generated",
58    },
59)
60
61def _flatten_target_files(targets):
62    files = []
63    for target in targets:
64        for tfile in target.files.to_list():
65            files.append(tfile)
66    return files
67
68def _combine_cc_file_lists(file_lists):
69    hdrs = {}
70    textual_hdrs = {}
71    internal_hdrs = {}
72    srcs = {}
73    for file_list in file_lists:
74        hdrs.update({f: 1 for f in file_list.hdrs})
75        textual_hdrs.update({f: 1 for f in file_list.textual_hdrs})
76        internal_hdrs.update({f: 1 for f in file_list.internal_hdrs})
77        srcs.update({f: 1 for f in file_list.srcs})
78    return CcFileList(
79        hdrs = sorted(hdrs.keys()),
80        textual_hdrs = sorted(textual_hdrs.keys()),
81        internal_hdrs = sorted(internal_hdrs.keys()),
82        srcs = sorted(srcs.keys()),
83    )
84
85def _file_list_aspect_impl(target, ctx):
86    # We're going to reach directly into the attrs on the traversed rule.
87    rule_attr = ctx.rule.attr
88    providers = []
89
90    # Extract sources from a `cc_library` (or similar):
91    if CcInfo in target:
92        # CcInfo is a proxy for what we expect this rule to look like.
93        # However, some deps may expose `CcInfo` without having `srcs`,
94        # `hdrs`, etc., so we use `getattr` to handle that gracefully.
95
96        internal_hdrs = []
97        srcs = []
98
99        # Filter `srcs` so it only contains source files. Headers will go
100        # into `internal_headers`.
101        for src in _flatten_target_files(getattr(rule_attr, "srcs", [])):
102            if src.extension.lower() in ["c", "cc", "cpp", "cxx"]:
103                srcs.append(src)
104            else:
105                internal_hdrs.append(src)
106
107        providers.append(CcFileList(
108            hdrs = _flatten_target_files(getattr(rule_attr, "hdrs", [])),
109            textual_hdrs = _flatten_target_files(getattr(
110                rule_attr,
111                "textual_hdrs",
112                [],
113            )),
114            internal_hdrs = internal_hdrs,
115            srcs = srcs,
116        ))
117
118    # Extract sources from a `proto_library`:
119    if ProtoInfo in target:
120        proto_srcs = []
121        srcs = []
122        hdrs = []
123        for src in _flatten_target_files(rule_attr.srcs):
124            proto_srcs.append(src)
125            srcs.append("%s/%s.pb.cc" % (src.dirname, src.basename))
126            hdrs.append("%s/%s.pb.h" % (src.dirname, src.basename))
127
128        providers.append(ProtoFileList(
129            proto_srcs = proto_srcs,
130            srcs = srcs,
131            hdrs = hdrs,
132        ))
133
134    return providers
135
136file_list_aspect = aspect(
137    doc = """
138Aspect to provide the list of sources and headers from a rule.
139
140Output is CcFileList and/or ProtoFileList. Example:
141
142  cc_library(
143      name = "foo",
144      srcs = [
145          "foo.cc",
146          "foo_internal.h",
147      ],
148      hdrs = ["foo.h"],
149      textual_hdrs = ["foo_inl.inc"],
150  )
151  # produces:
152  # CcFileList(
153  #     hdrs = [File("foo.h")],
154  #     textual_hdrs = [File("foo_inl.inc")],
155  #     internal_hdrs = [File("foo_internal.h")],
156  #     srcs = [File("foo.cc")],
157  # )
158
159  proto_library(
160      name = "bar_proto",
161      srcs = ["bar.proto"],
162  )
163  # produces:
164  # ProtoFileList(
165  #     proto_srcs = ["bar.proto"],
166  #     # Generated filenames are synthesized:
167  #     hdrs = ["bar.pb.h"],
168  #     srcs = ["bar.pb.cc"],
169  # )
170""",
171    implementation = _file_list_aspect_impl,
172)
173
174################################################################################
175# Generic source lists generation
176#
177# This factory creates a rule implementation that is parameterized by a
178# fragment generator function.
179################################################################################
180
181def _create_file_list_impl(fragment_generator):
182    # `fragment_generator` is a function like:
183    #     def fn(originating_rule: Label,
184    #            varname: str,
185    #            source_prefix: str,
186    #            path_strings: [str]) -> str
187    #
188    # It returns a string that defines `varname` to `path_strings`, each
189    # prepended with `source_prefix`.
190    #
191    # When dealing with `File` objects, the `short_path` is used to strip
192    # the output prefix for generated files.
193
194    def _impl(ctx):
195        out = ctx.outputs.out
196
197        fragments = []
198        for srcrule, libname in ctx.attr.src_libs.items():
199            if CcFileList in srcrule:
200                cc_file_list = srcrule[CcFileList]
201                fragments.extend([
202                    fragment_generator(
203                        srcrule.label,
204                        libname + "_srcs",
205                        ctx.attr.source_prefix,
206                        [f.short_path for f in cc_file_list.srcs],
207                    ),
208                    fragment_generator(
209                        srcrule.label,
210                        libname + "_hdrs",
211                        ctx.attr.source_prefix,
212                        [f.short_path for f in (cc_file_list.hdrs +
213                                                cc_file_list.textual_hdrs)],
214                    ),
215                ])
216
217            if ProtoFileList in srcrule:
218                proto_file_list = srcrule[ProtoFileList]
219                fragments.extend([
220                    fragment_generator(
221                        srcrule.label,
222                        libname + "_proto_srcs",
223                        ctx.attr.source_prefix,
224                        [f.short_path for f in proto_file_list.proto_srcs],
225                    ),
226                    fragment_generator(
227                        srcrule.label,
228                        libname + "_srcs",
229                        ctx.attr.source_prefix,
230                        proto_file_list.srcs,
231                    ),
232                    fragment_generator(
233                        srcrule.label,
234                        libname + "_hdrs",
235                        ctx.attr.source_prefix,
236                        proto_file_list.hdrs,
237                    ),
238                ])
239
240            files = {}
241
242            if PackageFilegroupInfo in srcrule:
243                for pkg_files_info, origin in srcrule[PackageFilegroupInfo].pkg_files:
244                    # keys are the destination path:
245                    files.update(pkg_files_info.dest_src_map)
246
247            if PackageFilesInfo in srcrule:
248                # keys are the destination:
249                files.update(srcrule[PackageFilesInfo].dest_src_map)
250
251            if files == {} and DefaultInfo in srcrule and CcInfo not in srcrule:
252                # This could be an individual file or filegroup.
253                # We explicitly ignore rules with CcInfo, since their
254                # output artifacts are libraries or binaries.
255                files.update(
256                    {
257                        f.short_path: 1
258                        for f in srcrule[DefaultInfo].files.to_list()
259                    },
260                )
261
262            if files:
263                fragments.append(
264                    fragment_generator(
265                        srcrule.label,
266                        libname + "_files",
267                        ctx.attr.source_prefix,
268                        sorted(files.keys()),
269                    ),
270                )
271
272        ctx.actions.write(
273            output = out,
274            content = (ctx.attr._header % ctx.label) + "\n".join(fragments),
275        )
276
277        return [DefaultInfo(files = depset([out]))]
278
279    return _impl
280
281# Common rule attrs for rules that use `_create_file_list_impl`:
282# (note that `_header` is also required)
283_source_list_common_attrs = {
284    "out": attr.output(
285        doc = (
286            "The generated filename. This should usually have a build " +
287            "system-specific extension, like `out.am` or `out.cmake`."
288        ),
289        mandatory = True,
290    ),
291    "src_libs": attr.label_keyed_string_dict(
292        doc = (
293            "A dict, {target: libname} of libraries to include. " +
294            "Targets can be C++ rules (like `cc_library` or `cc_test`), " +
295            "`proto_library` rules, files, `filegroup` rules, `pkg_files` " +
296            "rules, or `pkg_filegroup` rules. " +
297            "The libname is a string, and used to construct the variable " +
298            "name in the `out` file holding the target's sources. " +
299            "For generated files, the output root (like `bazel-bin/`) is not " +
300            "included. " +
301            "For `pkg_files` and `pkg_filegroup` rules, the destination path " +
302            "is used."
303        ),
304        mandatory = True,
305        providers = [
306            [CcFileList],
307            [DefaultInfo],
308            [PackageFilegroupInfo],
309            [PackageFilesInfo],
310            [ProtoFileList],
311        ],
312        aspects = [file_list_aspect],
313    ),
314    "source_prefix": attr.string(
315        doc = "String to prepend to each source path.",
316    ),
317}
318
319################################################################################
320# CMake source lists generation
321################################################################################
322
323def _cmake_var_fragment(owner, varname, prefix, entries):
324    """Returns a single `set(varname ...)` fragment (CMake syntax).
325
326    Args:
327      owner: Label, the rule that owns these srcs.
328      varname: str, the var name to set.
329      prefix: str, prefix to prepend to each of `entries`.
330      entries: [str], the entries in the list.
331
332    Returns:
333      A string.
334    """
335    return (
336        "# {owner}\n" +
337        "set({varname}\n" +
338        "{entries}\n" +
339        ")\n"
340    ).format(
341        owner = owner,
342        varname = varname,
343        entries = "\n".join(["  %s%s" % (prefix, f) for f in entries]),
344    )
345
346gen_cmake_file_lists = rule(
347    doc = """
348Generates a CMake-syntax file with lists of files.
349
350The generated file defines variables with lists of files from `srcs`. The
351intent is for these files to be included from a non-generated CMake file
352which actually defines the libraries based on these lists.
353
354For C++ rules, the following are generated:
355    {libname}_srcs: contains srcs.
356    {libname}_hdrs: contains hdrs and textual_hdrs.
357
358For proto_library, the following are generated:
359    {libname}_proto_srcs: contains the srcs from the `proto_library` rule.
360    {libname}_srcs: contains syntesized paths for generated C++ sources.
361    {libname}_hdrs: contains syntesized paths for generated C++ headers.
362
363""",
364    implementation = _create_file_list_impl(_cmake_var_fragment),
365    attrs = dict(
366        _source_list_common_attrs,
367        _header = attr.string(
368            default = """\
369# Auto-generated by %s
370#
371# This file contains lists of sources based on Bazel rules. It should
372# be included from a hand-written CMake file that defines targets.
373#
374# Changes to this file will be overwritten based on Bazel definitions.
375
376if(${CMAKE_VERSION} VERSION_GREATER 3.10 OR ${CMAKE_VERSION} VERSION_EQUAL 3.10)
377  include_guard()
378endif()
379
380""",
381        ),
382    ),
383)
384
385################################################################################
386# Automake source lists generation
387################################################################################
388
389def _automake_var_fragment(owner, varname, prefix, entries):
390    """Returns a single variable assignment fragment (Automake syntax).
391
392    Args:
393      owner: Label, the rule that owns these srcs.
394      varname: str, the var name to set.
395      prefix: str, prefix to prepend to each of `entries`.
396      entries: [str], the entries in the list.
397
398    Returns:
399      A string.
400    """
401    if len(entries) == 0:
402        # A backslash followed by a blank line is illegal. We still want
403        # to emit the variable, though.
404        return "# {owner}\n{varname} =\n".format(
405            owner = owner,
406            varname = varname,
407        )
408    fragment = (
409        "# {owner}\n" +
410        "{varname} = \\\n" +
411        "{entries}"
412    ).format(
413        owner = owner,
414        varname = varname,
415        entries = " \\\n".join(["  %s%s" % (prefix, f) for f in entries]),
416    )
417    return fragment.rstrip("\\ ") + "\n"
418
419gen_automake_file_lists = rule(
420    doc = """
421Generates an Automake-syntax file with lists of files.
422
423The generated file defines variables with lists of files from `srcs`. The
424intent is for these files to be included from a non-generated Makefile.am
425file which actually defines the libraries based on these lists.
426
427For C++ rules, the following are generated:
428    {libname}_srcs: contains srcs.
429    {libname}_hdrs: contains hdrs and textual_hdrs.
430
431For proto_library, the following are generated:
432    {libname}_proto_srcs: contains the srcs from the `proto_library` rule.
433    {libname}_srcs: contains syntesized paths for generated C++ sources.
434    {libname}_hdrs: contains syntesized paths for generated C++ headers.
435
436""",
437    implementation = _create_file_list_impl(_automake_var_fragment),
438    attrs = dict(
439        _source_list_common_attrs.items(),
440        _header = attr.string(
441            default = """\
442# Auto-generated by %s
443#
444# This file contains lists of sources based on Bazel rules. It should
445# be included from a hand-written Makefile.am that defines targets.
446#
447# Changes to this file will be overwritten based on Bazel definitions.
448
449""",
450        ),
451    ),
452)
453