| # Copyright (C) 2022 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Headers target for DDK.""" |
| |
| load("@bazel_skylib//lib:paths.bzl", "paths") |
| load("@bazel_skylib//lib:sets.bzl", "sets") |
| load("@rules_cc//cc/common:cc_common.bzl", "cc_common") |
| load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") |
| load( |
| ":common_providers.bzl", |
| "DdkHeadersInfo", |
| "DdkIncludeInfo", |
| ) |
| load(":ddk/ddk_config/ddk_config_info_subrule.bzl", "ddk_config_info_subrule") |
| |
| visibility("//build/kernel/kleaf/...") |
| |
| # At this time of writing (2022-11-01), this is what cc_library does; |
| # includes of this target, then includes of deps |
| DDK_INCLUDE_INFO_ORDER = "preorder" |
| |
| def get_extra_include_roots(headers): |
| """Given a list of headers, return a list of include roots. |
| |
| For each header in headers, drop short_path from path to get an include_root. |
| Then return all include_roots. |
| |
| Args: |
| headers: A list of headers. |
| Returns: |
| include roots to be prepended to include_dirs. |
| """ |
| |
| return sets.to_list(sets.make([header.root.path for header in headers])) |
| |
| def get_ddk_transitive_include_infos(deps): |
| """Returns a depset containing include directories from the list of dependencies. |
| |
| Args: |
| deps: A list of depended targets. If [`DdkHeadersInfo`](#DdkHeadersInfo) is in the target, |
| their `includes` are included in the returned depset. |
| Returns: |
| A depset containing include directories from the list of dependencies. |
| """ |
| |
| transitive = [] |
| for dep in deps: |
| if DdkHeadersInfo in dep: |
| transitive.append(dep[DdkHeadersInfo].include_infos) |
| return transitive |
| |
| def _check_includes(includes): |
| for include_dir in includes: |
| if paths.normalize(include_dir) != include_dir: |
| fail( |
| "include directory {} is not normalized to {}".format( |
| include_dir, |
| paths.normalize(include_dir), |
| ), |
| ) |
| if paths.is_absolute(include_dir): |
| fail("Absolute directories not allowed in includes: {}".format(include_dir)) |
| if include_dir == ".." or include_dir.startswith("../"): |
| fail("Invalid include directory: {}".format(include_dir)) |
| |
| def get_headers_depset(deps): |
| """Returns a depset containing headers from the list of dependencies |
| |
| Args: |
| deps: A list of depended targets. If [`DdkHeadersInfo`](#DdkHeadersInfo) is in the target, |
| `target[DdkHeadersInfo].files` are included in the returned depset. Otherwise |
| the default output files are included in the returned depset. |
| Returns: |
| A depset containing headers from the list of dependencies. |
| """ |
| transitive_deps = [] |
| |
| for dep in deps: |
| if DdkHeadersInfo in dep: |
| transitive_deps.append(dep[DdkHeadersInfo].files) |
| else: |
| transitive_deps.append(dep.files) |
| |
| return depset(transitive = transitive_deps) |
| |
| def ddk_headers_common_impl(label, hdrs, includes, linux_includes): |
| """Common implementation for rules that returns `DdkHeadersInfo`. |
| |
| Args: |
| label: Label of this target. |
| hdrs: The list of exported headers, e.g. [`ddk_headers.hdrs`](#ddk_headers-hdrs) |
| includes: The list of exported include directories, e.g. [`ddk_headers.includes`](#ddk_headers-includes) |
| linux_includes: Like `includes` but added to `LINUXINCLUDE`. |
| Returns: |
| DdkHeadersInfo |
| """ |
| |
| _check_includes(includes) |
| _check_includes(linux_includes) |
| |
| direct_include_infos = [] |
| if includes or linux_includes: |
| file_deps = [target.files for target in hdrs if DdkHeadersInfo not in target] |
| direct_include_infos.append(DdkIncludeInfo( |
| prefix = paths.join(label.workspace_root, label.package), |
| direct_files = depset(transitive = file_deps), |
| |
| # Turn lists into tuples because lists are mutable, making DdkIncludeInfo |
| # mutable and unable to be placed in a depset. |
| includes = tuple(includes), |
| linux_includes = tuple(linux_includes), |
| )) |
| |
| return DdkHeadersInfo( |
| files = get_headers_depset(hdrs), |
| include_infos = depset( |
| direct_include_infos, |
| transitive = get_ddk_transitive_include_infos(hdrs), |
| order = DDK_INCLUDE_INFO_ORDER, |
| ), |
| ) |
| |
| def ddk_headers_cc_info_transitively(label, hdrs, includes, linux_includes): |
| """Constructs CcInfo transitively from dependencies. |
| |
| Args: |
| label: Label of this target. |
| hdrs: List of header dependencies (targets). |
| includes: List of include directories relative to the current package. |
| linux_includes: Like `includes` but for `LINUXINCLUDE`. |
| Returns: |
| CcInfo provider. |
| """ |
| our_prefix = paths.join(label.workspace_root, label.package) |
| our_includes = [paths.join(our_prefix, inc) for inc in includes + linux_includes] |
| |
| transitive_includes = [] |
| transitive_headers = [] |
| |
| for dep in hdrs: |
| if CcInfo in dep: |
| transitive_includes.append(dep[CcInfo].compilation_context.includes) |
| transitive_headers.append(dep[CcInfo].compilation_context.headers) |
| else: |
| transitive_headers.append(dep.files) |
| |
| compilation_context = cc_common.create_compilation_context( |
| headers = depset(transitive = transitive_headers), |
| includes = depset(our_includes, transitive = transitive_includes), |
| ) |
| return CcInfo(compilation_context = compilation_context) |
| |
| def _ddk_headers_impl(ctx): |
| if get_headers_depset(ctx.attr.textual_hdrs): |
| # buildifier: disable=print |
| print("\nWARNING: textual_hdrs deprecated, use `hdrs` instead.") |
| |
| ddk_headers_info = ddk_headers_common_impl( |
| ctx.label, |
| ctx.attr.hdrs + ctx.attr.textual_hdrs, |
| ctx.attr.includes, |
| ctx.attr.linux_includes, |
| ) |
| |
| ddk_config_info = ddk_config_info_subrule( |
| kconfig_targets = ctx.attr.kconfigs, |
| defconfig_targets = ctx.attr.defconfigs, |
| deps = ctx.attr.hdrs + ctx.attr.textual_hdrs, |
| kernel_build_ddk_config_env = None, |
| ) |
| |
| cc_info = ddk_headers_cc_info_transitively( |
| ctx.label, |
| ctx.attr.hdrs + ctx.attr.textual_hdrs, |
| ctx.attr.includes, |
| ctx.attr.linux_includes, |
| ) |
| |
| return [ |
| DefaultInfo(files = ddk_headers_info.files), |
| ddk_headers_info, |
| ddk_config_info, |
| cc_info, |
| ] |
| |
| ddk_headers = rule( |
| implementation = _ddk_headers_impl, |
| doc = """A rule that exports a list of header files to be used in DDK. |
| |
| Example: |
| |
| ``` |
| ddk_headers( |
| name = "headers", |
| hdrs = ["include/module.h", "template.c"], |
| includes = ["include"], |
| ) |
| ``` |
| |
| `ddk_headers` can be chained; that is, a `ddk_headers` target can re-export |
| another `ddk_headers` target. For example: |
| |
| ``` |
| ddk_headers( |
| name = "foo", |
| hdrs = ["include_foo/foo.h"], |
| includes = ["include_foo"], |
| ) |
| ddk_headers( |
| name = "headers", |
| hdrs = [":foo", "include/module.h"], |
| includes = ["include"], |
| ) |
| ``` |
| """, |
| attrs = { |
| # allow_files = True because https://github.com/bazelbuild/bazel/issues/7516 |
| "hdrs": attr.label_list(allow_files = True, doc = """One of the following: |
| |
| - Local header files to be exported. You may also need to set the `includes` attribute. |
| - Other `ddk_headers` targets to be re-exported. |
| """), |
| # TODO: remove textual_hdrs in future |
| "textual_hdrs": attr.label_list( |
| allow_files = True, |
| doc = """DEPRECATED. Use `hdrs` instead. |
| |
| The list of header files to be textually included by sources. |
| |
| This is the location for declaring header files that cannot be compiled on their own; |
| that is, they always need to be textually included by other source files to build valid code. |
| """, |
| ), |
| "includes": attr.string_list( |
| doc = """A list of directories, relative to the current package, that are re-exported as include directories. |
| |
| [`ddk_module`](#ddk_module) with `deps` including this target automatically |
| adds the given include directory in the generated `Kbuild` files. |
| |
| You still need to add the actual header files to `hdrs`. |
| """, |
| ), |
| "linux_includes": attr.string_list( |
| doc = """Like `includes` but specified in `LINUXINCLUDES` instead. |
| |
| Setting this attribute allows you to override headers from `${KERNEL_DIR}`. See "Order of includes" |
| in [`ddk_module`](#ddk_module) for details. |
| |
| Like `includes`, `linux_includes` is applied to dependent `ddk_module`s. |
| """, |
| ), |
| "kconfigs": attr.label_list( |
| allow_files = True, |
| doc = """Kconfig files. |
| |
| See |
| [`Documentation/kbuild/kconfig-language.rst`](https://www.kernel.org/doc/html/latest/kbuild/kconfig.html) |
| for its format. |
| |
| Kconfig is optional for a `ddk_module`. The final Kconfig known by |
| this module consists of the following: |
| |
| - Kconfig from `kernel_build` |
| - Kconfig from dependent modules, if any |
| - Kconfig of this module, if any |
| """, |
| ), |
| "defconfigs": attr.label_list( |
| allow_files = True, |
| doc = """`defconfig` files. |
| |
| Items must already be declared in `kconfigs`. An item not declared |
| in Kconfig and inherited Kconfig files is silently dropped. |
| |
| An item declared in `kconfigs` without a specific value in `defconfigs` |
| uses default value specified in `kconfigs`. |
| """, |
| ), |
| }, |
| subrules = [ddk_config_info_subrule], |
| ) |