| """Starlark utilities for working with other build systems.""" | |
| load("@bazel_skylib//lib:paths.bzl", "paths") | |
| load("@rules_pkg//pkg:providers.bzl", "PackageFilegroupInfo", "PackageFilesInfo") | |
| load("//bazel/common:proto_info.bzl", "ProtoInfo") | |
| load(":cc_dist_library.bzl", "CcFileList") | |
| ################################################################################ | |
| # Macro to create CMake and Automake source lists. | |
| ################################################################################ | |
| def gen_file_lists(name, out_stem, testonly = False, **kwargs): | |
| gen_cmake_file_lists( | |
| name = name + "_cmake", | |
| out = out_stem + ".cmake", | |
| source_prefix = "${protobuf_SOURCE_DIR}/", | |
| testonly = testonly, | |
| **kwargs | |
| ) | |
| native.filegroup( | |
| name = name, | |
| srcs = [ | |
| out_stem + ".cmake", | |
| ], | |
| visibility = ["//src:__pkg__"], | |
| testonly = testonly, | |
| ) | |
| ################################################################################ | |
| # Aspect that extracts srcs, hdrs, etc. | |
| ################################################################################ | |
| ProtoFileList = provider( | |
| doc = "List of proto files and generated code to be built into a library.", | |
| fields = { | |
| # Proto files: | |
| "proto_srcs": "proto file sources", | |
| # Generated sources: | |
| "hdrs": "header files that are expected to be generated", | |
| "srcs": "source files that are expected to be generated", | |
| }, | |
| ) | |
| def _flatten_target_files(targets): | |
| files = [] | |
| for target in targets: | |
| for tfile in target.files.to_list(): | |
| files.append(tfile) | |
| return files | |
| def _file_list_aspect_impl(target, ctx): | |
| # We're going to reach directly into the attrs on the traversed rule. | |
| rule_attr = ctx.rule.attr | |
| providers = [] | |
| # Extract sources from a `proto_library`: | |
| if ProtoInfo in target: | |
| proto_srcs = [] | |
| srcs = [] | |
| hdrs = [] | |
| for src in _flatten_target_files(rule_attr.srcs): | |
| proto_srcs.append(src) | |
| srcs.append("%s/%s.pb.cc" % (src.dirname, src.basename)) | |
| hdrs.append("%s/%s.pb.h" % (src.dirname, src.basename)) | |
| providers.append(ProtoFileList( | |
| proto_srcs = proto_srcs, | |
| srcs = srcs, | |
| hdrs = hdrs, | |
| )) | |
| return providers | |
| file_list_aspect = aspect( | |
| doc = """ | |
| Aspect to provide the list of sources and headers from a rule. | |
| Output is CcFileList and/or ProtoFileList. Example: | |
| cc_library( | |
| name = "foo", | |
| srcs = [ | |
| "foo.cc", | |
| "foo_internal.h", | |
| ], | |
| hdrs = ["foo.h"], | |
| textual_hdrs = ["foo_inl.inc"], | |
| ) | |
| # produces: | |
| # CcFileList( | |
| # hdrs = [File("foo.h")], | |
| # textual_hdrs = [File("foo_inl.inc")], | |
| # internal_hdrs = [File("foo_internal.h")], | |
| # srcs = [File("foo.cc")], | |
| # ) | |
| proto_library( | |
| name = "bar_proto", | |
| srcs = ["bar.proto"], | |
| ) | |
| # produces: | |
| # ProtoFileList( | |
| # proto_srcs = ["bar.proto"], | |
| # # Generated filenames are synthesized: | |
| # hdrs = ["bar.pb.h"], | |
| # srcs = ["bar.pb.cc"], | |
| # ) | |
| """, | |
| implementation = _file_list_aspect_impl, | |
| ) | |
| ################################################################################ | |
| # Generic source lists generation | |
| # | |
| # This factory creates a rule implementation that is parameterized by a | |
| # fragment generator function. | |
| ################################################################################ | |
| def _create_file_list_impl(ctx, fragment_generator): | |
| # `fragment_generator` is a function like: | |
| # def fn(originating_rule: Label, | |
| # varname: str, | |
| # source_prefix: str, | |
| # path_strings: [str]) -> str | |
| # | |
| # It returns a string that defines `varname` to `path_strings`, each | |
| # prepended with `source_prefix`. | |
| # | |
| # When dealing with `File` objects, the `short_path` is used to strip | |
| # the output prefix for generated files. | |
| out = ctx.outputs.out | |
| fragments = [] | |
| for srcrule, value in ctx.attr.src_libs.items(): | |
| split_value = value.split(",") | |
| libname = split_value[0] | |
| gencode_dir = split_value[1] if len(split_value) == 2 else "" | |
| if CcFileList in srcrule: | |
| cc_file_list = srcrule[CcFileList] | |
| # Turn depsets of files into sorted lists. | |
| srcs = sorted(cc_file_list.srcs.to_list()) | |
| hdrs = sorted( | |
| depset(transitive = [ | |
| cc_file_list.textual_hdrs, | |
| cc_file_list.hdrs, | |
| ]).to_list(), | |
| ) | |
| fragments.extend([ | |
| fragment_generator( | |
| srcrule.label, | |
| libname + "_srcs", | |
| ctx.attr.source_prefix, | |
| [f.short_path for f in srcs], | |
| ), | |
| fragment_generator( | |
| srcrule.label, | |
| libname + "_hdrs", | |
| ctx.attr.source_prefix, | |
| [f.short_path for f in hdrs], | |
| ), | |
| ]) | |
| if ProtoFileList in srcrule: | |
| proto_file_list = srcrule[ProtoFileList] | |
| fragments.extend([ | |
| fragment_generator( | |
| srcrule.label, | |
| libname + "_proto_srcs", | |
| ctx.attr.source_prefix, | |
| [f.short_path for f in proto_file_list.proto_srcs], | |
| ), | |
| fragment_generator( | |
| srcrule.label, | |
| libname + "_srcs", | |
| ctx.attr.source_prefix, | |
| [gencode_dir + paths.basename(s) if gencode_dir else s for s in proto_file_list.srcs], | |
| ), | |
| fragment_generator( | |
| srcrule.label, | |
| libname + "_hdrs", | |
| ctx.attr.source_prefix, | |
| [gencode_dir + paths.basename(s) if gencode_dir else s for s in proto_file_list.hdrs], | |
| ), | |
| ]) | |
| files = {} | |
| if PackageFilegroupInfo in srcrule: | |
| for pkg_files_info, origin in srcrule[PackageFilegroupInfo].pkg_files: | |
| # keys are the destination path: | |
| files.update(pkg_files_info.dest_src_map) | |
| if PackageFilesInfo in srcrule: | |
| # keys are the destination: | |
| files.update(srcrule[PackageFilesInfo].dest_src_map) | |
| if files == {} and DefaultInfo in srcrule and CcFileList not in srcrule: | |
| # This could be an individual file or filegroup. | |
| # We explicitly ignore rules with CcInfo, since their | |
| # output artifacts are libraries or binaries. | |
| files.update( | |
| { | |
| f.short_path: 1 | |
| for f in srcrule[DefaultInfo].files.to_list() | |
| }, | |
| ) | |
| if files: | |
| fragments.append( | |
| fragment_generator( | |
| srcrule.label, | |
| libname + "_files", | |
| ctx.attr.source_prefix, | |
| sorted(files.keys()), | |
| ), | |
| ) | |
| generator_label = "@//%s:%s" % (ctx.label.package, ctx.label.name) | |
| ctx.actions.write( | |
| output = out, | |
| content = (ctx.attr._header % generator_label) + "\n".join(fragments), | |
| ) | |
| return [DefaultInfo(files = depset([out]))] | |
| # Common rule attrs for rules that use `_create_file_list_impl`: | |
| # (note that `_header` is also required) | |
| _source_list_common_attrs = { | |
| "out": attr.output( | |
| doc = ( | |
| "The generated filename. This should usually have a build " + | |
| "system-specific extension, like `out.am` or `out.cmake`." | |
| ), | |
| mandatory = True, | |
| ), | |
| "src_libs": attr.label_keyed_string_dict( | |
| doc = ( | |
| "A dict, {target: libname[,gencode_dir]} of libraries to include. " + | |
| "Targets can be C++ rules (like `cc_library` or `cc_test`), " + | |
| "`proto_library` rules, files, `filegroup` rules, `pkg_files` " + | |
| "rules, or `pkg_filegroup` rules. " + | |
| "The libname is a string, and used to construct the variable " + | |
| "name in the `out` file holding the target's sources. " + | |
| "For generated files, the output root (like `bazel-bin/`) is not " + | |
| "included. gencode_dir is used instead of target's location if provided." + | |
| "For `pkg_files` and `pkg_filegroup` rules, the destination path " + | |
| "is used." | |
| ), | |
| mandatory = True, | |
| providers = [ | |
| [CcFileList], | |
| [DefaultInfo], | |
| [PackageFilegroupInfo], | |
| [PackageFilesInfo], | |
| [ProtoFileList], | |
| ], | |
| aspects = [file_list_aspect], | |
| ), | |
| "source_prefix": attr.string( | |
| doc = "String to prepend to each source path.", | |
| ), | |
| } | |
| ################################################################################ | |
| # CMake source lists generation | |
| ################################################################################ | |
| def _cmake_var_fragment(owner, varname, prefix, entries): | |
| """Returns a single `set(varname ...)` fragment (CMake syntax). | |
| Args: | |
| owner: Label, the rule that owns these srcs. | |
| varname: str, the var name to set. | |
| prefix: str, prefix to prepend to each of `entries`. | |
| entries: [str], the entries in the list. | |
| Returns: | |
| A string. | |
| """ | |
| return ( | |
| "# @//{package}:{name}\n" + | |
| "set({varname}\n" + | |
| "{entries}\n" + | |
| ")\n" | |
| ).format( | |
| package = owner.package, | |
| name = owner.name, | |
| varname = varname, | |
| # Strip out "wkt/google/protobuf/" from the well-known type file paths. | |
| # This is currently necessary to allow checked-in and generated | |
| # versions of the well-known type generated code to coexist. | |
| entries = "\n".join([" %s%s" % (prefix, f.replace("wkt/google/protobuf/", "")) for f in entries]), | |
| ) | |
| def _cmake_file_list_impl(ctx): | |
| _create_file_list_impl(ctx, _cmake_var_fragment) | |
| gen_cmake_file_lists = rule( | |
| doc = """ | |
| Generates a CMake-syntax file with lists of files. | |
| The generated file defines variables with lists of files from `srcs`. The | |
| intent is for these files to be included from a non-generated CMake file | |
| which actually defines the libraries based on these lists. | |
| For C++ rules, the following are generated: | |
| {libname}_srcs: contains srcs. | |
| {libname}_hdrs: contains hdrs and textual_hdrs. | |
| For proto_library, the following are generated: | |
| {libname}_proto_srcs: contains the srcs from the `proto_library` rule. | |
| {libname}_srcs: contains synthesized paths for generated C++ sources. | |
| {libname}_hdrs: contains synthesized paths for generated C++ headers. | |
| """, | |
| implementation = _cmake_file_list_impl, | |
| attrs = dict( | |
| _source_list_common_attrs, | |
| _header = attr.string( | |
| default = """\ | |
| # Auto-generated by %s | |
| # | |
| # This file contains lists of sources based on Bazel rules. It should | |
| # be included from a hand-written CMake file that defines targets. | |
| # | |
| # Changes to this file will be overwritten based on Bazel definitions. | |
| if(${CMAKE_VERSION} VERSION_GREATER 3.10 OR ${CMAKE_VERSION} VERSION_EQUAL 3.10) | |
| include_guard() | |
| endif() | |
| """, | |
| ), | |
| ), | |
| ) |