• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Starlark utilities for working with other build systems."""
2
3load("@bazel_skylib//lib:paths.bzl", "paths")
4load("@rules_pkg//pkg:providers.bzl", "PackageFilegroupInfo", "PackageFilesInfo")
5load("//bazel/common:proto_info.bzl", "ProtoInfo")
6load(":cc_dist_library.bzl", "CcFileList")
7
8################################################################################
9# Macro to create CMake and Automake source lists.
10################################################################################
11
12def gen_file_lists(name, out_stem, **kwargs):
13    gen_cmake_file_lists(
14        name = name + "_cmake",
15        out = out_stem + ".cmake",
16        source_prefix = "${protobuf_SOURCE_DIR}/",
17        **kwargs
18    )
19    native.filegroup(
20        name = name,
21        srcs = [
22            out_stem + ".cmake",
23        ],
24        visibility = ["//src:__pkg__"],
25    )
26
27################################################################################
28# Aspect that extracts srcs, hdrs, etc.
29################################################################################
30
31ProtoFileList = provider(
32    doc = "List of proto files and generated code to be built into a library.",
33    fields = {
34        # Proto files:
35        "proto_srcs": "proto file sources",
36
37        # Generated sources:
38        "hdrs": "header files that are expected to be generated",
39        "srcs": "source files that are expected to be generated",
40    },
41)
42
43def _flatten_target_files(targets):
44    files = []
45    for target in targets:
46        for tfile in target.files.to_list():
47            files.append(tfile)
48    return files
49
50def _file_list_aspect_impl(target, ctx):
51    # We're going to reach directly into the attrs on the traversed rule.
52    rule_attr = ctx.rule.attr
53    providers = []
54
55    # Extract sources from a `proto_library`:
56    if ProtoInfo in target:
57        proto_srcs = []
58        srcs = []
59        hdrs = []
60        for src in _flatten_target_files(rule_attr.srcs):
61            proto_srcs.append(src)
62            srcs.append("%s/%s.pb.cc" % (src.dirname, src.basename))
63            hdrs.append("%s/%s.pb.h" % (src.dirname, src.basename))
64
65        providers.append(ProtoFileList(
66            proto_srcs = proto_srcs,
67            srcs = srcs,
68            hdrs = hdrs,
69        ))
70
71    return providers
72
73file_list_aspect = aspect(
74    doc = """
75Aspect to provide the list of sources and headers from a rule.
76
77Output is CcFileList and/or ProtoFileList. Example:
78
79  cc_library(
80      name = "foo",
81      srcs = [
82          "foo.cc",
83          "foo_internal.h",
84      ],
85      hdrs = ["foo.h"],
86      textual_hdrs = ["foo_inl.inc"],
87  )
88  # produces:
89  # CcFileList(
90  #     hdrs = [File("foo.h")],
91  #     textual_hdrs = [File("foo_inl.inc")],
92  #     internal_hdrs = [File("foo_internal.h")],
93  #     srcs = [File("foo.cc")],
94  # )
95
96  proto_library(
97      name = "bar_proto",
98      srcs = ["bar.proto"],
99  )
100  # produces:
101  # ProtoFileList(
102  #     proto_srcs = ["bar.proto"],
103  #     # Generated filenames are synthesized:
104  #     hdrs = ["bar.pb.h"],
105  #     srcs = ["bar.pb.cc"],
106  # )
107""",
108    implementation = _file_list_aspect_impl,
109)
110
111################################################################################
112# Generic source lists generation
113#
114# This factory creates a rule implementation that is parameterized by a
115# fragment generator function.
116################################################################################
117
118def _create_file_list_impl(ctx, fragment_generator):
119    # `fragment_generator` is a function like:
120    #     def fn(originating_rule: Label,
121    #            varname: str,
122    #            source_prefix: str,
123    #            path_strings: [str]) -> str
124    #
125    # It returns a string that defines `varname` to `path_strings`, each
126    # prepended with `source_prefix`.
127    #
128    # When dealing with `File` objects, the `short_path` is used to strip
129    # the output prefix for generated files.
130
131    out = ctx.outputs.out
132
133    fragments = []
134    for srcrule, value in ctx.attr.src_libs.items():
135        split_value = value.split(",")
136        libname = split_value[0]
137        gencode_dir = split_value[1] if len(split_value) == 2 else ""
138        if CcFileList in srcrule:
139            cc_file_list = srcrule[CcFileList]
140
141            # Turn depsets of files into sorted lists.
142            srcs = sorted(cc_file_list.srcs.to_list())
143            hdrs = sorted(
144                depset(transitive = [
145                    cc_file_list.textual_hdrs,
146                    cc_file_list.hdrs,
147                ]).to_list(),
148            )
149
150            fragments.extend([
151                fragment_generator(
152                    srcrule.label,
153                    libname + "_srcs",
154                    ctx.attr.source_prefix,
155                    [f.short_path for f in srcs],
156                ),
157                fragment_generator(
158                    srcrule.label,
159                    libname + "_hdrs",
160                    ctx.attr.source_prefix,
161                    [f.short_path for f in hdrs],
162                ),
163            ])
164
165        if ProtoFileList in srcrule:
166            proto_file_list = srcrule[ProtoFileList]
167            fragments.extend([
168                fragment_generator(
169                    srcrule.label,
170                    libname + "_proto_srcs",
171                    ctx.attr.source_prefix,
172                    [f.short_path for f in proto_file_list.proto_srcs],
173                ),
174                fragment_generator(
175                    srcrule.label,
176                    libname + "_srcs",
177                    ctx.attr.source_prefix,
178                    [gencode_dir + paths.basename(s) if gencode_dir else s for s in proto_file_list.srcs],
179                ),
180                fragment_generator(
181                    srcrule.label,
182                    libname + "_hdrs",
183                    ctx.attr.source_prefix,
184                    [gencode_dir + paths.basename(s) if gencode_dir else s for s in proto_file_list.hdrs],
185                ),
186            ])
187
188        files = {}
189
190        if PackageFilegroupInfo in srcrule:
191            for pkg_files_info, origin in srcrule[PackageFilegroupInfo].pkg_files:
192                # keys are the destination path:
193                files.update(pkg_files_info.dest_src_map)
194
195        if PackageFilesInfo in srcrule:
196            # keys are the destination:
197            files.update(srcrule[PackageFilesInfo].dest_src_map)
198
199        if files == {} and DefaultInfo in srcrule and CcFileList not in srcrule:
200            # This could be an individual file or filegroup.
201            # We explicitly ignore rules with CcInfo, since their
202            # output artifacts are libraries or binaries.
203            files.update(
204                {
205                    f.short_path: 1
206                    for f in srcrule[DefaultInfo].files.to_list()
207                },
208            )
209
210        if files:
211            fragments.append(
212                fragment_generator(
213                    srcrule.label,
214                    libname + "_files",
215                    ctx.attr.source_prefix,
216                    sorted(files.keys()),
217                ),
218            )
219
220    generator_label = "@//%s:%s" % (ctx.label.package, ctx.label.name)
221    ctx.actions.write(
222        output = out,
223        content = (ctx.attr._header % generator_label) + "\n".join(fragments),
224    )
225
226    return [DefaultInfo(files = depset([out]))]
227
228# Common rule attrs for rules that use `_create_file_list_impl`:
229# (note that `_header` is also required)
230_source_list_common_attrs = {
231    "out": attr.output(
232        doc = (
233            "The generated filename. This should usually have a build " +
234            "system-specific extension, like `out.am` or `out.cmake`."
235        ),
236        mandatory = True,
237    ),
238    "src_libs": attr.label_keyed_string_dict(
239        doc = (
240            "A dict, {target: libname[,gencode_dir]} of libraries to include. " +
241            "Targets can be C++ rules (like `cc_library` or `cc_test`), " +
242            "`proto_library` rules, files, `filegroup` rules, `pkg_files` " +
243            "rules, or `pkg_filegroup` rules. " +
244            "The libname is a string, and used to construct the variable " +
245            "name in the `out` file holding the target's sources. " +
246            "For generated files, the output root (like `bazel-bin/`) is not " +
247            "included. gencode_dir is used instead of target's location if provided." +
248            "For `pkg_files` and `pkg_filegroup` rules, the destination path " +
249            "is used."
250        ),
251        mandatory = True,
252        providers = [
253            [CcFileList],
254            [DefaultInfo],
255            [PackageFilegroupInfo],
256            [PackageFilesInfo],
257            [ProtoFileList],
258        ],
259        aspects = [file_list_aspect],
260    ),
261    "source_prefix": attr.string(
262        doc = "String to prepend to each source path.",
263    ),
264}
265
266################################################################################
267# CMake source lists generation
268################################################################################
269
270def _cmake_var_fragment(owner, varname, prefix, entries):
271    """Returns a single `set(varname ...)` fragment (CMake syntax).
272
273    Args:
274      owner: Label, the rule that owns these srcs.
275      varname: str, the var name to set.
276      prefix: str, prefix to prepend to each of `entries`.
277      entries: [str], the entries in the list.
278
279    Returns:
280      A string.
281    """
282    return (
283        "# @//{package}:{name}\n" +
284        "set({varname}\n" +
285        "{entries}\n" +
286        ")\n"
287    ).format(
288        package = owner.package,
289        name = owner.name,
290        varname = varname,
291        # Strip out "wkt/google/protobuf/" from the well-known type file paths.
292        # This is currently necessary to allow checked-in and generated
293        # versions of the well-known type generated code to coexist.
294        entries = "\n".join(["  %s%s" % (prefix, f.replace("wkt/google/protobuf/", "")) for f in entries]),
295    )
296
297def _cmake_file_list_impl(ctx):
298    _create_file_list_impl(ctx, _cmake_var_fragment)
299
300gen_cmake_file_lists = rule(
301    doc = """
302Generates a CMake-syntax file with lists of files.
303
304The generated file defines variables with lists of files from `srcs`. The
305intent is for these files to be included from a non-generated CMake file
306which actually defines the libraries based on these lists.
307
308For C++ rules, the following are generated:
309    {libname}_srcs: contains srcs.
310    {libname}_hdrs: contains hdrs and textual_hdrs.
311
312For proto_library, the following are generated:
313    {libname}_proto_srcs: contains the srcs from the `proto_library` rule.
314    {libname}_srcs: contains synthesized paths for generated C++ sources.
315    {libname}_hdrs: contains synthesized paths for generated C++ headers.
316
317""",
318    implementation = _cmake_file_list_impl,
319    attrs = dict(
320        _source_list_common_attrs,
321        _header = attr.string(
322            default = """\
323# Auto-generated by %s
324#
325# This file contains lists of sources based on Bazel rules. It should
326# be included from a hand-written CMake file that defines targets.
327#
328# Changes to this file will be overwritten based on Bazel definitions.
329
330if(${CMAKE_VERSION} VERSION_GREATER 3.10 OR ${CMAKE_VERSION} VERSION_EQUAL 3.10)
331  include_guard()
332endif()
333
334""",
335        ),
336    ),
337)
338