• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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