1# Copyright 2019 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Pigweed build environment for bazel.""" 15 16load("@bazel_skylib//lib:selects.bzl", "selects") 17load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain") 18load("@rules_cc//cc:action_names.bzl", "C_COMPILE_ACTION_NAME") 19load( 20 "//pw_build/bazel_internal:pigweed_internal.bzl", 21 _compile_cc = "compile_cc", 22 _link_cc = "link_cc", 23) 24 25def pw_facade(name, srcs = None, backend = None, **kwargs): 26 """Create a cc_library with a facade. 27 28 This macro simplifies instantiating Pigweed's facade pattern. It generates 29 two targets: 30 31 * cc_library with the label "name". This is the complete library target. 32 Users of the functionality provided by this library should depend on this 33 target. It has a public dependency on the "backend". 34 * cc_library with the label "name.facade". This library exposes only the 35 headers. Implementations of the backend should depend on it. 36 37 Args: 38 name: The name of the cc_library. 39 srcs: The source files of the cc_library. 40 backend: The backend for the facade. This should be a label_flag or other 41 target that allows swapping out the backend implementation at build 42 time. (In a downstream project an alias with an "actual = select(...)" 43 attribute may also be appropriate, but in upstream Pigweed use only a 44 label_flag.). 45 **kwargs: Passed on to cc_library. 46 """ 47 if type(backend) != "string": 48 fail( 49 "The 'backend' attribute must be a single label, " + 50 "got {} of type {}".format(backend, type(backend)), 51 ) 52 53 facade_kwargs = dict(**kwargs) 54 55 # A facade has no srcs, so it can only have public deps. Don't specify any 56 # implementation_deps on the facade target. 57 facade_kwargs.pop("implementation_deps", []) 58 native.cc_library( 59 name = name + ".facade", 60 **facade_kwargs 61 ) 62 63 kwargs["deps"] = kwargs.get("deps", []) + [backend] 64 native.cc_library( 65 name = name, 66 srcs = srcs, 67 **kwargs 68 ) 69 70 # For simplifying the migration to this macro only. Do not depend on this 71 # target from new code: depend directly on the .facade target instead. 72 native.alias( 73 name = name + "_facade", 74 actual = ":" + name + ".facade", 75 ) 76 77def pw_cc_binary(**kwargs): 78 """Wrapper for cc_binary providing some defaults. 79 80 Specifically, this wrapper adds deps on backend_impl targets for pw_assert 81 and pw_log. 82 83 Args: 84 **kwargs: Passed to cc_binary. 85 """ 86 87 # TODO: b/234877642 - Remove this implicit dependency once we have a better 88 # way to handle the facades without introducing a circular dependency into 89 # the build. 90 kwargs["deps"] = kwargs.get("deps", []) + ["@pigweed//pw_build:default_link_extra_lib"] 91 native.cc_binary(**kwargs) 92 93def pw_cc_test(**kwargs): 94 """Wrapper for cc_test providing some defaults. 95 96 Specifically, this wrapper, 97 98 * Adds a dep on the pw_assert backend. 99 * Adds a dep on //pw_unit_test:simple_printing_main 100 101 In addition, a .lib target is created that's a cc_library with the same 102 kwargs. Such library targets can be used as dependencies of firmware images 103 bundling multiple tests. The library target has alwayslink = 1, to support 104 dynamic registration (ensure the tests are baked into the image even though 105 there are no references to them, so that they can be found by RUN_ALL_TESTS 106 at runtime). 107 108 Args: 109 **kwargs: Passed to cc_test. 110 """ 111 112 # TODO: b/234877642 - Remove this implicit dependency once we have a better 113 # way to handle the facades without introducing a circular dependency into 114 # the build. 115 kwargs["deps"] = kwargs.get("deps", []) + ["@pigweed//pw_build:default_link_extra_lib"] 116 117 # Depend on the backend. E.g. to pull in gtest.h include paths. 118 kwargs["deps"] = kwargs["deps"] + ["@pigweed//pw_unit_test:backend"] 119 120 # Save the base set of deps minus pw_unit_test:main for the .lib target. 121 original_deps = kwargs["deps"] 122 123 # Add the unit test main label flag dep. 124 test_main = kwargs.pop("test_main", "@pigweed//pw_unit_test:main") 125 kwargs["deps"] = original_deps + [test_main] 126 127 native.cc_test(**kwargs) 128 129 kwargs["alwayslink"] = 1 130 131 # pw_cc_test deps may include testonly targets. 132 kwargs["testonly"] = True 133 134 # Remove any kwargs that cc_library would not recognize. 135 for arg in ( 136 "additional_linker_inputs", 137 "args", 138 "env", 139 "env_inherit", 140 "flaky", 141 "local", 142 "malloc", 143 "shard_count", 144 "size", 145 "stamp", 146 "timeout", 147 ): 148 kwargs.pop(arg, "") 149 150 # Reset the deps for the .lib target. 151 kwargs["deps"] = original_deps 152 native.cc_library(name = kwargs.pop("name") + ".lib", **kwargs) 153 154def pw_cc_perf_test(**kwargs): 155 """A Pigweed performance test. 156 157 This macro produces a cc_binary and, 158 159 * Adds a dep on the pw_assert backend. 160 * Adds a dep on //pw_perf_test:logging_main 161 162 Args: 163 **kwargs: Passed to cc_binary. 164 """ 165 kwargs["deps"] = kwargs.get("deps", []) + \ 166 ["@pigweed//pw_perf_test:logging_main"] 167 kwargs["deps"] = kwargs["deps"] + ["@pigweed//pw_assert:backend_impl"] 168 kwargs["testonly"] = True 169 native.cc_binary(**kwargs) 170 171def host_backend_alias(name, backend): 172 """An alias that resolves to the backend for host platforms.""" 173 native.alias( 174 name = name, 175 actual = selects.with_or({ 176 ( 177 "@platforms//os:android", 178 "@platforms//os:chromiumos", 179 "@platforms//os:linux", 180 "@platforms//os:macos", 181 "@platforms//os:windows", 182 ): backend, 183 "//conditions:default": "@pigweed//pw_build:unspecified_backend", 184 }), 185 ) 186 187CcBlobInfo = provider( 188 "Input to pw_cc_blob_library", 189 fields = { 190 "alignas": "If present, the byte array is aligned as specified. The " + 191 "value of this argument is used verbatim in an alignas() " + 192 "specifier for the blob byte array.", 193 "file_path": "The file path for the binary blob.", 194 "linker_section": "If present, places the byte array in the specified " + 195 "linker section.", 196 "symbol_name": "The C++ symbol for the byte array.", 197 }, 198) 199 200def _pw_cc_blob_info_impl(ctx): 201 return [CcBlobInfo( 202 symbol_name = ctx.attr.symbol_name, 203 file_path = ctx.file.file_path, 204 linker_section = ctx.attr.linker_section, 205 alignas = ctx.attr.alignas, 206 )] 207 208pw_cc_blob_info = rule( 209 implementation = _pw_cc_blob_info_impl, 210 attrs = { 211 "alignas": attr.string(default = ""), 212 "file_path": attr.label(allow_single_file = True), 213 "linker_section": attr.string(default = ""), 214 "symbol_name": attr.string(), 215 }, 216 provides = [CcBlobInfo], 217) 218 219def _pw_cc_blob_library_impl(ctx): 220 # Python tool takes a json file with info about blobs to generate. 221 blobs = [] 222 blob_paths = [] 223 for blob in ctx.attr.blobs: 224 blob_info = blob[CcBlobInfo] 225 blob_paths.append(blob_info.file_path) 226 blob_dict = { 227 "file_path": blob_info.file_path.path, 228 "linker_section": blob_info.linker_section, 229 "symbol_name": blob_info.symbol_name, 230 } 231 if (blob_info.alignas): 232 blob_dict["alignas"] = blob_info.alignas 233 blobs.append(blob_dict) 234 blob_json = ctx.actions.declare_file(ctx.label.name + "_blob.json") 235 ctx.actions.write(blob_json, json.encode(blobs)) 236 237 hdr = ctx.actions.declare_file(ctx.attr.out_header) 238 src = ctx.actions.declare_file(ctx.attr.out_header.removesuffix(".h") + ".cc") 239 240 if (not ctx.attr.namespace): 241 fail("namespace required for pw_cc_blob_library") 242 243 args = ctx.actions.args() 244 args.add("--blob-file={}".format(blob_json.path)) 245 args.add("--namespace={}".format(ctx.attr.namespace)) 246 args.add("--header-include={}".format(ctx.attr.out_header)) 247 args.add("--out-header={}".format(hdr.path)) 248 args.add("--out-source={}".format(src.path)) 249 250 ctx.actions.run( 251 inputs = depset(direct = blob_paths + [blob_json]), 252 progress_message = "Generating cc blob library for %s" % (ctx.label.name), 253 tools = [ 254 ctx.executable._generate_cc_blob_library, 255 ctx.executable._python_runtime, 256 ], 257 outputs = [hdr, src], 258 executable = ctx.executable._generate_cc_blob_library, 259 arguments = [args], 260 ) 261 262 include_path = ctx.bin_dir.path 263 264 # If workspace_root is set, this target is in an external workspace, so the 265 # generated file will be located under workspace_root. 266 if ctx.label.workspace_root: 267 include_path += "/" + ctx.label.workspace_root 268 269 # If target is not in root BUILD file of repo, append package name as that's 270 # where the generated file will end up. 271 if ctx.label.package: 272 include_path += "/" + ctx.label.package 273 274 return _compile_cc( 275 ctx, 276 [src], 277 [hdr], 278 deps = ctx.attr.deps, 279 alwayslink = ctx.attr.alwayslink, 280 includes = [include_path], 281 defines = [], 282 ) 283 284pw_cc_blob_library = rule( 285 implementation = _pw_cc_blob_library_impl, 286 doc = """Turns binary blobs into a C++ library of hard-coded byte arrays. 287 288 The byte arrays are constant initialized and are safe to access at any time, 289 including before main(). 290 291 Args: 292 ctx: Rule context. 293 blobs: A list of CcBlobInfo where each entry corresponds to a binary 294 blob to be transformed from file to byte array. This is a 295 required field. Blob fields include: 296 297 symbol_name [required]: The C++ symbol for the byte array. 298 299 file_path [required]: The file path for the binary blob. 300 301 linker_section [optional]: If present, places the byte array 302 in the specified linker section. 303 304 alignas [optional]: If present, the byte array is aligned as 305 specified. The value of this argument is used verbatim 306 in an alignas() specifier for the blob byte array. 307 308 out_header: The header file to generate. Users will include this file 309 exactly as it is written here to reference the byte arrays. 310 311 namespace: The C++ namespace in which to place the generated blobs. 312 alwayslink: Whether this library should always be linked. 313 """, 314 attrs = { 315 "alwayslink": attr.bool(default = False), 316 "blobs": attr.label_list(providers = [CcBlobInfo]), 317 "deps": attr.label_list(default = [Label("//pw_preprocessor")]), 318 "namespace": attr.string(), 319 "out_header": attr.string(), 320 "_generate_cc_blob_library": attr.label( 321 default = Label("//pw_build/py:generate_cc_blob_library"), 322 executable = True, 323 cfg = "exec", 324 ), 325 "_python_runtime": attr.label( 326 default = Label("//:python3_interpreter"), 327 allow_single_file = True, 328 executable = True, 329 cfg = "exec", 330 ), 331 }, 332 provides = [CcInfo], 333 fragments = ["cpp"], 334 toolchains = use_cpp_toolchain(), 335) 336 337def _pw_cc_binary_with_map_impl(ctx): 338 [cc_info] = _compile_cc( 339 ctx, 340 ctx.files.srcs, 341 [], 342 deps = ctx.attr.deps + [ctx.attr.link_extra_lib, ctx.attr.malloc], 343 includes = ctx.attr.includes, 344 defines = ctx.attr.defines, 345 local_defines = ctx.attr.local_defines, 346 ) 347 348 map_file = ctx.actions.declare_file(ctx.label.name + ".map") 349 map_flags = ["-Wl,-Map=" + map_file.path] 350 351 return _link_cc( 352 ctx, 353 [cc_info.linking_context], 354 ctx.attr.linkstatic, 355 ctx.attr.stamp, 356 user_link_flags = ctx.attr.linkopts + map_flags, 357 additional_outputs = [map_file], 358 ) 359 360pw_cc_binary_with_map = rule( 361 implementation = _pw_cc_binary_with_map_impl, 362 doc = """Links a binary like cc_binary does but generates a linker map file 363 and provides it as an output after the executable in the DefaultInfo list 364 returned by this rule. 365 366 This rule makes an effort to somewhat mimic cc_binary args and behavior but 367 doesn't fully support all options currently. Make variable substitution and 368 tokenization handling isn't implemented by this rule on any of it's attrs. 369 370 Args: 371 ctx: Rule context. 372 """, 373 attrs = { 374 "defines": attr.string_list( 375 doc = "List of defines to add to the compile line.", 376 ), 377 "deps": attr.label_list( 378 providers = [CcInfo], 379 doc = "The list of other libraries to be linked in to the binary target.", 380 ), 381 "includes": attr.string_list( 382 doc = "List of include dirs to add to the compile line.", 383 ), 384 "link_extra_lib": attr.label( 385 default = "@bazel_tools//tools/cpp:link_extra_lib", 386 doc = "Control linking of extra libraries.", 387 ), 388 "linkopts": attr.string_list( 389 doc = "Add these flags to the C++ linker command.", 390 ), 391 "linkstatic": attr.bool( 392 doc = "True if binary should be link statically", 393 ), 394 "local_defines": attr.string_list( 395 doc = "List of defines to add to the compile line.", 396 ), 397 "malloc": attr.label( 398 default = "@bazel_tools//tools/cpp:malloc", 399 doc = "Override the default dependency on malloc.", 400 ), 401 "srcs": attr.label_list( 402 allow_files = True, 403 doc = "The list of C and C++ files that are processed to create the target.", 404 ), 405 "stamp": attr.int( 406 default = -1, 407 doc = "Whether to encode build information into the binary.", 408 ), 409 }, 410 executable = True, 411 provides = [DefaultInfo], 412 fragments = ["cpp"], 413 toolchains = use_cpp_toolchain(), 414) 415 416def _preprocess_linker_script_impl(ctx): 417 cc_toolchain = find_cpp_toolchain(ctx) 418 output_script = ctx.actions.declare_file(ctx.label.name + ".ld") 419 feature_configuration = cc_common.configure_features( 420 ctx = ctx, 421 cc_toolchain = cc_toolchain, 422 requested_features = ctx.features, 423 unsupported_features = ctx.disabled_features, 424 ) 425 cxx_compiler_path = cc_common.get_tool_for_action( 426 feature_configuration = feature_configuration, 427 action_name = C_COMPILE_ACTION_NAME, 428 ) 429 compilation_context = cc_common.merge_compilation_contexts( 430 compilation_contexts = [dep[CcInfo].compilation_context for dep in ctx.attr.deps], 431 ) 432 c_compile_variables = cc_common.create_compile_variables( 433 feature_configuration = feature_configuration, 434 cc_toolchain = cc_toolchain, 435 user_compile_flags = ctx.fragments.cpp.copts + ctx.fragments.cpp.conlyopts, 436 include_directories = compilation_context.includes, 437 quote_include_directories = compilation_context.quote_includes, 438 system_include_directories = compilation_context.system_includes, 439 preprocessor_defines = depset(ctx.attr.defines, transitive = [compilation_context.defines]), 440 ) 441 cmd_line = cc_common.get_memory_inefficient_command_line( 442 feature_configuration = feature_configuration, 443 action_name = C_COMPILE_ACTION_NAME, 444 variables = c_compile_variables, 445 ) 446 env = cc_common.get_environment_variables( 447 feature_configuration = feature_configuration, 448 action_name = C_COMPILE_ACTION_NAME, 449 variables = c_compile_variables, 450 ) 451 ctx.actions.run( 452 outputs = [output_script], 453 inputs = depset( 454 [ctx.file.linker_script], 455 transitive = [compilation_context.headers, cc_toolchain.all_files], 456 ), 457 executable = cxx_compiler_path, 458 arguments = [ 459 "-E", 460 "-P", 461 "-xc", 462 ctx.file.linker_script.path, 463 "-o", 464 output_script.path, 465 ] + cmd_line, 466 env = env, 467 ) 468 linker_input = cc_common.create_linker_input( 469 owner = ctx.label, 470 user_link_flags = ["-T", output_script.path], 471 additional_inputs = depset(direct = [output_script]), 472 ) 473 linking_context = cc_common.create_linking_context( 474 linker_inputs = depset(direct = [linker_input]), 475 ) 476 return [ 477 DefaultInfo(files = depset([output_script])), 478 CcInfo(linking_context = linking_context), 479 ] 480 481pw_linker_script = rule( 482 _preprocess_linker_script_impl, 483 doc = """Create a linker script target. Supports preprocessing with the C 484 preprocessor and adding the resulting linker script to linkopts. Also 485 provides a DefaultInfo containing the processed linker script. 486 """, 487 attrs = { 488 "copts": attr.string_list(doc = "C compile options."), 489 "defines": attr.string_list(doc = "C preprocessor defines."), 490 "deps": attr.label_list( 491 providers = [CcInfo], 492 doc = """Dependencies of this linker script. Can be used to provide 493 header files and defines. Only the CompilationContext of 494 the provided dependencies are used.""", 495 ), 496 "linker_script": attr.label( 497 mandatory = True, 498 allow_single_file = True, 499 doc = "Linker script to preprocess.", 500 ), 501 "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")), 502 }, 503 toolchains = use_cpp_toolchain(), 504 fragments = ["cpp"], 505) 506