1# This file is licensed under the Apache License v2.0 with LLVM Exceptions. 2# See https://llvm.org/LICENSE.txt for license information. 3# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 4"""BUILD extensions for MLIR table generation.""" 5 6load("@bazel_skylib//lib:paths.bzl", "paths") 7 8TdInfo = provider( 9 "Holds TableGen files and the dependencies and include paths necessary to" + 10 " build them.", 11 fields = { 12 "transitive_sources": "td files transitively used by this rule.", 13 "transitive_includes": ( 14 "include arguments to add to the final TableGen invocation. These" + 15 " are the absolute directory paths that will be added with '-I'." 16 ), 17 }, 18) 19 20# For now we allow anything that provides DefaultInfo to just forward its files. 21# In particular, this allows filegroups to be used. This is mostly to ease 22# transition. In the future, the TdInfo provider will be required. 23# TODO(gcmn): Switch to enforcing TdInfo provider. 24def _get_dep_transitive_srcs(dep): 25 """Extract TdInfo.transitive_sources, falling back to DefaultInfo.files.""" 26 if TdInfo in dep: 27 return dep[TdInfo].transitive_sources 28 return dep[DefaultInfo].files 29 30def _get_dep_transitive_includes(dep): 31 """Extract TdInfo.transitive_includes, falling back to an empty depset().""" 32 if TdInfo in dep: 33 return dep[TdInfo].transitive_includes 34 return depset() 35 36def _get_transitive_srcs(srcs, deps): 37 """Obtain the source files for a target and its transitive dependencies. 38 39 Args: 40 srcs: a list of source files 41 deps: a list of targets that are direct dependencies 42 Returns: 43 a collection of the transitive sources 44 """ 45 return depset( 46 direct = srcs, 47 transitive = [_get_dep_transitive_srcs(dep) for dep in deps], 48 ) 49 50def _get_transitive_includes(includes, deps): 51 """Obtain the includes paths for a target and its transitive dependencies. 52 53 Args: 54 includes: a list of include paths 55 deps: a list of targets that are direct dependencies 56 Returns: 57 a collection of the transitive include paths 58 """ 59 return depset( 60 direct = includes, 61 transitive = [_get_dep_transitive_includes(dep) for dep in deps], 62 ) 63 64def _prefix_roots(ctx, includes): 65 """Map the given includes to be relative to all root directories. 66 67 This will expand them to be relative to all the root directories available 68 in the execution environment for ctx.run (bin and genfiles in addition to 69 the normal source root) 70 """ 71 prefixed_includes = [] 72 for include in includes: 73 prefixed_includes.append(include) 74 prefixed_includes.append(paths.join(ctx.genfiles_dir.path, include)) 75 prefixed_includes.append(paths.join(ctx.bin_dir.path, include)) 76 return prefixed_includes 77 78def _resolve_includes(ctx, includes): 79 """Resolves include paths to paths relative to the execution root. 80 81 Relative paths are interpreted as relative to the current label's package. 82 Absolute paths are interpreted as relative to the current label's workspace 83 root.""" 84 package = ctx.label.package 85 workspace_root = ctx.label.workspace_root 86 workspace_root = workspace_root if workspace_root else "." 87 resolved_includes = [] 88 for include in includes: 89 if paths.is_absolute(include): 90 include = include.lstrip("/") 91 else: 92 include = paths.join(package, include) 93 include = paths.join(workspace_root, include) 94 resolved_includes.extend(_prefix_roots(ctx, [include])) 95 return resolved_includes 96 97def _td_library_impl(ctx): 98 trans_srcs = _get_transitive_srcs(ctx.files.srcs, ctx.attr.deps) 99 trans_includes = _get_transitive_includes( 100 _resolve_includes(ctx, ctx.attr.includes), 101 ctx.attr.deps, 102 ) 103 104 # Note that we include srcs in runfiles. A td_library doesn't compile to 105 # produce an output: it's just a depset of source files and include 106 # directories. So if it is needed for execution of some rule (likely 107 # something running tblgen as a test action), the files needed are the same 108 # as the source files. 109 # Note: not using merge_all, as that is not available in Bazel 4.0 110 runfiles = ctx.runfiles(ctx.files.srcs) 111 for src in ctx.attr.srcs: 112 runfiles = runfiles.merge(src[DefaultInfo].default_runfiles) 113 for dep in ctx.attr.deps: 114 runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) 115 116 return [ 117 DefaultInfo(files = trans_srcs, runfiles = runfiles), 118 TdInfo( 119 transitive_sources = trans_srcs, 120 transitive_includes = trans_includes, 121 ), 122 ] 123 124td_library = rule( 125 _td_library_impl, 126 attrs = { 127 "srcs": attr.label_list(allow_files = True), 128 "includes": attr.string_list( 129 doc = "Include paths to be added to the final TableGen tool" + 130 " invocation. Relative paths are interpreted as relative to" + 131 " the current label's package. Absolute paths are" + 132 " interpreted as relative to the current label's workspace", 133 ), 134 # TODO(gcmn): limit to TdInfo providers. 135 "deps": attr.label_list( 136 doc = "Dependencies providing TableGen source files and include" + 137 " paths.", 138 ), 139 }, 140) 141 142def _gentbl_rule_impl(ctx): 143 td_file = ctx.file.td_file 144 145 trans_srcs = _get_transitive_srcs( 146 ctx.files.td_srcs + [td_file], 147 ctx.attr.deps, 148 ) 149 150 # Note that the td_file.dirname is already relative to the execution root, 151 # i.e. may contain an `external/<workspace_name>` prefix if the current 152 # workspace is not the main workspace. Therefore it is not included in the 153 # _resolve_includes call that prepends this prefix. 154 trans_includes = _get_transitive_includes( 155 _resolve_includes(ctx, ctx.attr.includes + ["/"]) + 156 _prefix_roots(ctx, [td_file.dirname]), 157 ctx.attr.deps, 158 ) 159 160 args = ctx.actions.args() 161 args.add_all(ctx.attr.opts) 162 args.add(td_file) 163 args.add_all(trans_includes, before_each = "-I") 164 165 args.add("-o", ctx.outputs.out.path) 166 167 ctx.actions.run( 168 outputs = [ctx.outputs.out], 169 inputs = trans_srcs, 170 executable = ctx.executable.tblgen, 171 arguments = [args], 172 # Make sure action_env settings are honored so the env is the same as 173 # when the tool was built. Important for locating shared libraries with 174 # a custom LD_LIBRARY_PATH. 175 use_default_shell_env = True, 176 mnemonic = "TdGenerate", 177 ) 178 179 return [DefaultInfo()] 180 181gentbl_rule = rule( 182 _gentbl_rule_impl, 183 doc = "Generates tabular code from a table definition file.", 184 # Match genrule behavior 185 output_to_genfiles = True, 186 attrs = { 187 "tblgen": attr.label( 188 doc = "The TableGen executable with which to generate `out`.", 189 executable = True, 190 cfg = "exec", 191 ), 192 "td_file": attr.label( 193 doc = "The TableGen file to run through `tblgen`.", 194 allow_single_file = True, 195 mandatory = True, 196 ), 197 "td_srcs": attr.label_list( 198 doc = "Additional TableGen files included by `td_file`. It is not" + 199 " necessary to list td_file here (though not an error).", 200 allow_files = True, 201 ), 202 # TODO(gcmn): limit to TdInfo providers. 203 "deps": attr.label_list( 204 doc = "Dependencies providing TableGen source files and include" + 205 " paths.", 206 ), 207 "out": attr.output( 208 doc = "The output file for the TableGen invocation.", 209 mandatory = True, 210 ), 211 "opts": attr.string_list( 212 doc = "Additional command line options to add to the TableGen" + 213 " invocation. For include arguments, prefer to use" + 214 " `includes`.", 215 ), 216 "includes": attr.string_list( 217 doc = "Include paths to be added to the final TableGen tool" + 218 " invocation. Relative paths are interpreted as relative to" + 219 " the current label's package. Absolute paths are" + 220 " interpreted as relative to the current label's workspace." + 221 " Includes are applied from all roots available in the" + 222 " execution environment (source, genfiles, and bin" + 223 " directories). The execution roots themselves and the " + 224 " directory of td_file are always added.", 225 ), 226 }, 227) 228 229# TODO(gcmn): Figure out how to reduce duplication with _gentbl_rule_impl 230def _gentbl_test_impl(ctx): 231 td_file = ctx.file.td_file 232 233 # Note that the td_file.dirname is already relative to the execution root, 234 # i.e. may contain an `external/<workspace_name>` prefix if the current 235 # workspace is not the main workspace. Therefore it is not included in the 236 # _resolve_includes call that prepends this prefix. 237 trans_includes = _get_transitive_includes( 238 _resolve_includes(ctx, ctx.attr.includes + ["/"]) + 239 _prefix_roots(ctx, [td_file.dirname]), 240 ctx.attr.deps, 241 ) 242 243 test_args = [ctx.executable.tblgen.short_path] 244 test_args.extend(ctx.attr.opts) 245 test_args.append(td_file.path) 246 test_args.extend(["-I " + include for include in trans_includes.to_list()]) 247 248 test_args.extend(["-o", "/dev/null"]) 249 250 ctx.actions.write( 251 ctx.outputs.executable, 252 content = " ".join(test_args), 253 is_executable = True, 254 ) 255 256 # Note: not using merge_all, as that is not available in Bazel 4.0 257 runfiles = ctx.runfiles( 258 files = [ctx.executable.tblgen], 259 transitive_files = _get_transitive_srcs( 260 ctx.files.td_srcs + [td_file], 261 ctx.attr.deps, 262 ), 263 ) 264 for src in ctx.attr.td_srcs: 265 runfiles = runfiles.merge(src[DefaultInfo].default_runfiles) 266 for dep in ctx.attr.deps: 267 runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles) 268 269 return [ 270 coverage_common.instrumented_files_info( 271 ctx, 272 source_attributes = ["td_file", "td_srcs"], 273 dependency_attributes = ["tblgen", "deps"], 274 ), 275 DefaultInfo(runfiles = runfiles), 276 ] 277 278gentbl_test = rule( 279 _gentbl_test_impl, 280 test = True, 281 doc = "A shell test that tests the given TablegGen invocation. Note" + 282 " that unlike gentbl_rule, this builds and invokes `tblgen` in the" + 283 " target configuration. Takes all the same arguments as gentbl_rule" + 284 " except for `out` (as it does not generate any output)", 285 attrs = { 286 "tblgen": attr.label( 287 doc = "The TableGen executable run in the shell command. Note" + 288 " that this is built in the target configuration.", 289 executable = True, 290 cfg = "target", 291 ), 292 "td_file": attr.label( 293 doc = "See gentbl_rule.td_file", 294 allow_single_file = True, 295 mandatory = True, 296 ), 297 "td_srcs": attr.label_list( 298 doc = "See gentbl_rule.td_srcs", 299 allow_files = True, 300 ), 301 "deps": attr.label_list(doc = "See gentbl_rule.deps"), 302 "opts": attr.string_list(doc = "See gentbl_rule.opts"), 303 "includes": attr.string_list(doc = "See gentbl_rule.includes"), 304 }, 305) 306 307def gentbl_filegroup( 308 name, 309 tblgen, 310 td_file, 311 tbl_outs, 312 td_srcs = [], 313 includes = [], 314 deps = [], 315 test = False, 316 skip_opts = [], 317 **kwargs): 318 """Create multiple TableGen generated files using the same tool and input. 319 320 All generated outputs are bundled in a file group with the given name. 321 322 Args: 323 name: The name of the generated filegroup rule for use in dependencies. 324 tblgen: The binary used to produce the output. 325 td_file: The primary table definitions file. 326 tbl_outs: A list of tuples ([opts], out), where each 'opts' is a list of 327 options passed to tblgen, each option being a string, and 'out' is the 328 corresponding output file produced. 329 td_srcs: See gentbl_rule.td_srcs 330 includes: See gentbl_rule.includes 331 deps: See gentbl_rule.deps 332 test: Whether to create a shell test that invokes the tool too. 333 skip_opts: Files generated using these opts in tbl_outs will be excluded 334 from the generated filegroup. 335 **kwargs: Extra keyword arguments to pass to all generated rules. 336 """ 337 338 for (opts, out) in tbl_outs: 339 first_opt = opts[0] if opts else "" 340 rule_suffix = "_{}_{}".format( 341 first_opt.replace("-", "_").replace("=", "_"), 342 str(hash(" ".join(opts))), 343 ) 344 gentbl_name = "%s_%s_genrule" % (name, rule_suffix) 345 gentbl_rule( 346 name = gentbl_name, 347 td_file = td_file, 348 tblgen = tblgen, 349 opts = opts, 350 td_srcs = td_srcs, 351 deps = deps, 352 includes = includes, 353 out = out, 354 **kwargs 355 ) 356 357 if test: 358 # Also run the generator in the target configuration as a test. This 359 # means it gets run with asserts and sanitizers and such when they 360 # are enabled and is counted in coverage. 361 gentbl_test( 362 name = "%s_test" % (gentbl_name,), 363 td_file = td_file, 364 tblgen = tblgen, 365 opts = opts, 366 td_srcs = td_srcs, 367 deps = deps, 368 includes = includes, 369 # Shell files not executable on Windows. 370 # TODO(gcmn): Support windows. 371 tags = ["no_windows"], 372 **kwargs 373 ) 374 375 included_srcs = [f for (opts, f) in tbl_outs if not any([skip_opt in opts for skip_opt in skip_opts])] 376 native.filegroup( 377 name = name, 378 srcs = included_srcs, 379 **kwargs 380 ) 381 382def gentbl_cc_library( 383 name, 384 tblgen, 385 td_file, 386 tbl_outs, 387 td_srcs = [], 388 includes = [], 389 deps = [], 390 strip_include_prefix = None, 391 test = False, 392 **kwargs): 393 """Create multiple TableGen generated files using the same tool and input. 394 395 All generated outputs are bundled in a cc_library rule. 396 397 Args: 398 name: The name of the generated cc_library rule for use in dependencies. 399 tblgen: The binary used to produce the output. 400 td_file: The primary table definitions file. 401 tbl_outs: A list of tuples ([opts], out), where each 'opts' is a list of 402 options passed to tblgen, each option being a string, and 'out' is the 403 corresponding output file produced. 404 td_srcs: See gentbl_rule.td_srcs 405 includes: See gentbl_rule.includes 406 deps: See gentbl_rule.deps 407 strip_include_prefix: attribute to pass through to cc_library. 408 test: whether to create a shell test that invokes the tool too. 409 **kwargs: Extra keyword arguments to pass to all generated rules. 410 """ 411 412 filegroup_name = name + "_filegroup" 413 gentbl_filegroup( 414 name = filegroup_name, 415 tblgen = tblgen, 416 td_file = td_file, 417 tbl_outs = tbl_outs, 418 td_srcs = td_srcs, 419 includes = includes, 420 deps = deps, 421 test = test, 422 skip_opts = ["-gen-op-doc"], 423 **kwargs 424 ) 425 native.cc_library( 426 name = name, 427 # strip_include_prefix does not apply to textual_hdrs. 428 # https://github.com/bazelbuild/bazel/issues/12424 429 hdrs = [":" + filegroup_name] if strip_include_prefix else [], 430 strip_include_prefix = strip_include_prefix, 431 textual_hdrs = [":" + filegroup_name], 432 **kwargs 433 ) 434