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