| # Copyright (C) 2021 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. |
| |
| load("@bazel_skylib//lib:paths.bzl", "paths") |
| load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") |
| load("@bazel_skylib//rules:copy_file.bzl", "copy_file") |
| load("@kernel_toolchain_info//:dict.bzl", "CLANG_VERSION") |
| load("//build/bazel_common_rules/dist:dist.bzl", "copy_to_dist_dir") |
| load("//build/bazel_common_rules/exec:exec.bzl", "exec") |
| load(":constants.bzl", "TOOLCHAIN_VERSION_FILENAME") |
| load(":directory_with_structure.bzl", dws = "directory_with_structure") |
| load(":hermetic_tools.bzl", "HermeticToolsInfo") |
| load(":update_source_file.bzl", "update_source_file") |
| load( |
| ":utils.bzl", |
| "find_file", |
| "find_files", |
| "getoptattr", |
| "reverse_dict", |
| "utils", |
| ) |
| load( |
| "//build/kernel/kleaf/tests:kernel_test.bzl", |
| "kernel_build_test", |
| "kernel_module_test", |
| ) |
| |
| # Outputs of a kernel_build rule needed to build kernel_module's |
| _kernel_build_internal_outs = [ |
| "Module.symvers", |
| "include/config/kernel.release", |
| ] |
| |
| _sibling_names = [ |
| "notrim", |
| "with_vmlinux", |
| ] |
| |
| def _debug_trap(): |
| return """set -x |
| trap '>&2 /bin/date' DEBUG""" |
| |
| def _debug_print_scripts(ctx, command, what = None): |
| if ctx.attr._debug_print_scripts[BuildSettingInfo].value: |
| print(""" |
| # Script that runs %s%s:%s""" % (ctx.label, (" " + what if what else ""), command)) |
| |
| def _filter_module_srcs(files): |
| """Create the list of `module_srcs` for a [`kernel_build`] or similar.""" |
| return [ |
| s |
| for s in files |
| if s.path.endswith(".h") or any([token in s.path for token in [ |
| "Makefile", |
| "scripts/", |
| ]]) |
| ] |
| |
| def _kernel_build_config_impl(ctx): |
| out_file = ctx.actions.declare_file(ctx.attr.name + ".generated") |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| cat {srcs} > {out_file} |
| """.format( |
| srcs = " ".join([src.path for src in ctx.files.srcs]), |
| out_file = out_file.path, |
| ) |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelBuildConfig", |
| inputs = ctx.files.srcs + ctx.attr._hermetic_tools[HermeticToolsInfo].deps, |
| outputs = [out_file], |
| command = command, |
| progress_message = "Generating build config {}".format(ctx.label), |
| ) |
| return DefaultInfo(files = depset([out_file])) |
| |
| kernel_build_config = rule( |
| implementation = _kernel_build_config_impl, |
| doc = "Create a build.config file by concatenating build config fragments.", |
| attrs = { |
| "srcs": attr.label_list( |
| allow_files = True, |
| doc = """List of build config fragments. |
| |
| Order matters. To prevent buildifier from sorting the list, use the |
| `# do not sort` magic line. For example: |
| |
| ``` |
| kernel_build_config( |
| name = "build.config.foo.mixed", |
| srcs = [ |
| # do not sort |
| "build.config.mixed", |
| "build.config.foo", |
| ], |
| ) |
| ``` |
| |
| """, |
| ), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _transform_kernel_build_outs(name, what, outs): |
| """Transform `*outs` attributes for `kernel_build`. |
| |
| - If `outs` is a list, return it directly. |
| - If `outs` is a dict, return `select(outs)`. |
| - Otherwise fail |
| """ |
| if outs == None: |
| return None |
| if type(outs) == type([]): |
| return outs |
| elif type(outs) == type({}): |
| return select(outs) |
| else: |
| fail("{}: Invalid type for {}: {}".format(name, what, type(outs))) |
| |
| def _kernel_build_outs_add_vmlinux(name, outs): |
| files_to_add = ("vmlinux", "System.map") |
| outs_changed = False |
| if outs == None: |
| outs = ["vmlinux"] |
| outs_changed = True |
| if type(outs) == type([]): |
| for file in files_to_add: |
| if file not in outs: |
| # don't use append to avoid changing outs |
| outs = outs + [file] |
| outs_changed = True |
| elif type(outs) == type({}): |
| outs_new = {} |
| for k, v in outs.items(): |
| for file in files_to_add: |
| if file not in v: |
| # don't use append to avoid changing outs |
| v = v + [file] |
| outs_changed = True |
| outs_new[k] = v |
| outs = outs_new |
| else: |
| fail("{}: Invalid type for outs: {}".format(name, type(outs))) |
| return outs, outs_changed |
| |
| def kernel_build( |
| name, |
| build_config, |
| outs, |
| srcs = None, |
| module_outs = None, |
| implicit_outs = None, |
| generate_vmlinux_btf = None, |
| deps = None, |
| base_kernel = None, |
| kconfig_ext = None, |
| dtstree = None, |
| kmi_symbol_list = None, |
| additional_kmi_symbol_lists = None, |
| trim_nonlisted_kmi = None, |
| kmi_symbol_list_strict_mode = None, |
| collect_unstripped_modules = None, |
| enable_interceptor = None, |
| toolchain_version = None, |
| **kwargs): |
| """Defines a kernel build target with all dependent targets. |
| |
| It uses a `build_config` to construct a deterministic build environment (e.g. |
| `common/build.config.gki.aarch64`). The kernel sources need to be declared |
| via srcs (using a `glob()`). outs declares the output files that are surviving |
| the build. The effective output file names will be |
| `$(name)/$(output_file)`. Any other artifact is not guaranteed to be |
| accessible after the rule has run. The default `toolchain_version` is defined |
| with the value in `common/build.config.constants`, but can be overriden. |
| |
| A few additional labels are generated. |
| For example, if name is `"kernel_aarch64"`: |
| - `kernel_aarch64_uapi_headers` provides the UAPI kernel headers. |
| - `kernel_aarch64_headers` provides the kernel headers. |
| |
| Args: |
| name: The final kernel target name, e.g. `"kernel_aarch64"`. |
| build_config: Label of the build.config file, e.g. `"build.config.gki.aarch64"`. |
| kconfig_ext: Label of an external Kconfig.ext file sourced by the GKI kernel. |
| srcs: The kernel sources (a `glob()`). If unspecified or `None`, it is the following: |
| ``` |
| glob( |
| ["**"], |
| exclude = [ |
| "**/.*", # Hidden files |
| "**/.*/**", # Files in hidden directories |
| "**/BUILD.bazel", # build files |
| "**/*.bzl", # build files |
| ], |
| ) |
| ``` |
| base_kernel: A label referring the base kernel build. |
| |
| If set, the list of files specified in the `DefaultInfo` of the rule specified in |
| `base_kernel` is copied to a directory, and `KBUILD_MIXED_TREE` is set to the directory. |
| Setting `KBUILD_MIXED_TREE` effectively enables mixed build. |
| |
| To set additional flags for mixed build, change `build_config` to a `kernel_build_config` |
| rule, with a build config fragment that contains the additional flags. |
| |
| The label specified by `base_kernel` must produce a list of files similar |
| to what a `kernel_build` rule does. Usually, this points to one of the following: |
| - `//common:kernel_{arch}` |
| - A `kernel_filegroup` rule, e.g. |
| ``` |
| load("//build/kernel/kleaf:constants.bzl, "aarch64_outs") |
| kernel_filegroup( |
| name = "my_kernel_filegroup", |
| srcs = aarch64_outs, |
| ) |
| ``` |
| |
| generate_vmlinux_btf: If `True`, generates `vmlinux.btf` that is stripped of any debug |
| symbols, but contains type and symbol information within a .BTF section. |
| This is suitable for ABI analysis through BTF. |
| |
| Requires that `"vmlinux"` is in `outs`. |
| deps: Additional dependencies to build this kernel. |
| module_outs: A list of in-tree drivers. Similar to `outs`, but for `*.ko` files. |
| |
| If a `*.ko` kernel module should not be copied to `${DIST_DIR}`, it must be |
| included `implicit_outs` instead of `module_outs`. The list `implicit_outs + module_outs` |
| must include **all** `*.ko` files in `${OUT_DIR}`. If not, a build error is raised. |
| |
| Like `outs`, `module_outs` are part of the |
| [`DefaultInfo`](https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html) |
| that this `kernel_build` returns. For example: |
| ``` |
| kernel_build(name = "kernel", module_outs = ["foo.ko"], ...) |
| copy_to_dist_dir(name = "kernel_dist", data = [":kernel"]) |
| ``` |
| `foo.ko` will be included in the distribution. |
| |
| Like `outs`, this may be a `dict`. If so, it is wrapped in |
| [`select()`](https://docs.bazel.build/versions/main/configurable-attributes.html). See |
| documentation for `outs` for more details. |
| outs: The expected output files. |
| |
| Note: in-tree modules should be specified in `module_outs` instead. |
| |
| This attribute must be either a `dict` or a `list`. If it is a `list`, for each item |
| in `out`: |
| |
| - If `out` does not contain a slash, the build rule |
| automatically finds a file with name `out` in the kernel |
| build output directory `${OUT_DIR}`. |
| ``` |
| find ${OUT_DIR} -name {out} |
| ``` |
| There must be exactly one match. |
| The file is copied to the following in the output directory |
| `{name}/{out}` |
| |
| Example: |
| ``` |
| kernel_build(name = "kernel_aarch64", outs = ["vmlinux"]) |
| ``` |
| The bulid system copies `${OUT_DIR}/[<optional subdirectory>/]vmlinux` |
| to `kernel_aarch64/vmlinux`. |
| `kernel_aarch64/vmlinux` is the label to the file. |
| |
| - If `out` contains a slash, the build rule locates the file in the |
| kernel build output directory `${OUT_DIR}` with path `out` |
| The file is copied to the following in the output directory |
| 1. `{name}/{out}` |
| 2. `{name}/$(basename {out})` |
| |
| Example: |
| ``` |
| kernel_build( |
| name = "kernel_aarch64", |
| outs = ["arch/arm64/boot/vmlinux"]) |
| ``` |
| The bulid system copies |
| `${OUT_DIR}/arch/arm64/boot/vmlinux` |
| to: |
| - `kernel_aarch64/arch/arm64/boot/vmlinux` |
| - `kernel_aarch64/vmlinux` |
| They are also the labels to the output files, respectively. |
| |
| See `search_and_cp_output.py` for details. |
| |
| Files in `outs` are part of the |
| [`DefaultInfo`](https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html) |
| that this `kernel_build` returns. For example: |
| ``` |
| kernel_build(name = "kernel", outs = ["vmlinux"], ...) |
| copy_to_dist_dir(name = "kernel_dist", data = [":kernel"]) |
| ``` |
| `vmlinux` will be included in the distribution. |
| |
| If it is a `dict`, it is wrapped in |
| [`select()`](https://docs.bazel.build/versions/main/configurable-attributes.html). |
| |
| Example: |
| ``` |
| kernel_build( |
| name = "kernel_aarch64", |
| outs = {"config_foo": ["vmlinux"]}) |
| ``` |
| If conditions in `config_foo` is met, the rule is equivalent to |
| ``` |
| kernel_build( |
| name = "kernel_aarch64", |
| outs = ["vmlinux"]) |
| ``` |
| As explained above, the bulid system copies `${OUT_DIR}/[<optional subdirectory>/]vmlinux` |
| to `kernel_aarch64/vmlinux`. |
| `kernel_aarch64/vmlinux` is the label to the file. |
| |
| Note that a `select()` may not be passed into `kernel_build()` because |
| [`select()` cannot be evaluated in macros](https://docs.bazel.build/versions/main/configurable-attributes.html#why-doesnt-select-work-in-macros). |
| Hence: |
| - [combining `select()`s](https://docs.bazel.build/versions/main/configurable-attributes.html#combining-selects) |
| is not allowed. Instead, expand the cartesian product. |
| - To use |
| [`AND` chaining](https://docs.bazel.build/versions/main/configurable-attributes.html#or-chaining) |
| or |
| [`OR` chaining](https://docs.bazel.build/versions/main/configurable-attributes.html#selectsconfig_setting_group), |
| use `selects.config_setting_group()`. |
| |
| implicit_outs: Like `outs`, but not copied to the distribution directory. |
| |
| Labels are created for each item in `implicit_outs` as in `outs`. |
| kmi_symbol_list: A label referring to the main KMI symbol list file. See `additional_kmi_symbol_list`. |
| |
| This is the Bazel equivalent of `ADDTIONAL_KMI_SYMBOL_LISTS`. |
| additional_kmi_symbol_list: A list of labels referring to additional KMI symbol list files. |
| |
| This is the Bazel equivalent of `ADDTIONAL_KMI_SYMBOL_LISTS`. |
| |
| Let |
| ``` |
| all_kmi_symbol_lists = [kmi_symbol_list] + additional_kmi_symbol_list |
| ``` |
| |
| If `all_kmi_symbol_lists` is a non-empty list, `abi_symbollist` and |
| `abi_symbollist.report` are created and added to the |
| [`DefaultInfo`](https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html), |
| and copied to `DIST_DIR` during distribution. |
| |
| If `all_kmi_symbol_lists` is `None` or an empty list, `abi_symbollist` and |
| `abi_symbollist.report` are not created. |
| |
| It is possible to use a `glob()` to determine whether `abi_symbollist` |
| and `abi_symbollist.report` should be generated at build time. |
| For example: |
| ``` |
| kmi_symbol_list = "android/abi_gki_aarch64", |
| additional_kmi_symbol_lists = glob(["android/abi_gki_aarch64*"], exclude = ["android/abi_gki_aarch64"]), |
| ``` |
| trim_nonlisted_kmi: If `True`, trim symbols not listed in |
| `kmi_symbol_list` and `additional_kmi_symbol_lists`. |
| This is the Bazel equivalent of `TRIM_NONLISTED_KMI`. |
| |
| Requires `all_kmi_symbol_lists` to be non-empty. If `kmi_symbol_list` |
| or `additional_kmi_symbol_lists` |
| is a `glob()`, it is possible to set `trim_nonlisted_kmi` to be a |
| value based on that `glob()`. For example: |
| ``` |
| trim_nonlisted_kmi = len(glob(["android/abi_gki_aarch64*"])) > 0 |
| ``` |
| kmi_symbol_list_strict_mode: If `True`, add a build-time check between |
| `[kmi_symbol_list] + additional_kmi_symbol_lists` |
| and the KMI resulting from the build, to ensure |
| they match 1-1. |
| collect_unstripped_modules: If `True`, provide all unstripped in-tree. |
| |
| Approximately equivalent to `UNSTRIPPED_MODULES=*` in `build.sh`. |
| enable_interceptor: If set to `True`, enable interceptor so it can be |
| used in [`kernel_compile_commands`](#kernel_compile_commands). |
| toolchain_version: The toolchain version to depend on. |
| kwargs: Additional attributes to the internal rule, e.g. |
| [`visibility`](https://docs.bazel.build/versions/main/visibility.html). |
| See complete list |
| [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes). |
| |
| These arguments applies on the target with `{name}`, `{name}_headers`, `{name}_uapi_headers`, and `{name}_vmlinux_btf`. |
| """ |
| env_target_name = name + "_env" |
| config_target_name = name + "_config" |
| modules_prepare_target_name = name + "_modules_prepare" |
| uapi_headers_target_name = name + "_uapi_headers" |
| headers_target_name = name + "_headers" |
| kmi_symbol_list_target_name = name + "_kmi_symbol_list" |
| abi_symbollist_target_name = name + "_kmi_symbol_list_abi_symbollist" |
| raw_kmi_symbol_list_target_name = name + "_raw_kmi_symbol_list" |
| |
| if srcs == None: |
| srcs = native.glob( |
| ["**"], |
| exclude = [ |
| "**/.*", |
| "**/.*/**", |
| "**/BUILD.bazel", |
| "**/*.bzl", |
| ], |
| ) |
| |
| _kernel_env( |
| name = env_target_name, |
| build_config = build_config, |
| kconfig_ext = kconfig_ext, |
| dtstree = dtstree, |
| srcs = srcs, |
| toolchain_version = toolchain_version, |
| ) |
| |
| all_kmi_symbol_lists = [] |
| if kmi_symbol_list: |
| all_kmi_symbol_lists.append(kmi_symbol_list) |
| if additional_kmi_symbol_lists: |
| all_kmi_symbol_lists += additional_kmi_symbol_lists |
| |
| _kmi_symbol_list( |
| name = kmi_symbol_list_target_name, |
| env = env_target_name, |
| srcs = all_kmi_symbol_lists, |
| ) |
| |
| native.filegroup( |
| name = abi_symbollist_target_name, |
| srcs = [kmi_symbol_list_target_name], |
| output_group = "abi_symbollist", |
| ) |
| |
| _raw_kmi_symbol_list( |
| name = raw_kmi_symbol_list_target_name, |
| env = env_target_name, |
| src = abi_symbollist_target_name if all_kmi_symbol_lists else None, |
| ) |
| |
| _kernel_config( |
| name = config_target_name, |
| env = env_target_name, |
| srcs = srcs, |
| config = config_target_name + "/.config", |
| trim_nonlisted_kmi = trim_nonlisted_kmi, |
| raw_kmi_symbol_list = raw_kmi_symbol_list_target_name if all_kmi_symbol_lists else None, |
| ) |
| |
| _modules_prepare( |
| name = modules_prepare_target_name, |
| config = config_target_name, |
| srcs = srcs, |
| outdir_tar_gz = modules_prepare_target_name + "/modules_prepare_outdir.tar.gz", |
| ) |
| |
| _kernel_build( |
| name = name, |
| config = config_target_name, |
| srcs = srcs, |
| outs = _transform_kernel_build_outs(name, "outs", outs), |
| module_outs = _transform_kernel_build_outs(name, "module_outs", module_outs), |
| implicit_outs = _transform_kernel_build_outs(name, "implicit_outs", implicit_outs), |
| internal_outs = _transform_kernel_build_outs(name, "internal_outs", _kernel_build_internal_outs), |
| deps = deps, |
| base_kernel = base_kernel, |
| modules_prepare = modules_prepare_target_name, |
| kmi_symbol_list_strict_mode = kmi_symbol_list_strict_mode, |
| raw_kmi_symbol_list = raw_kmi_symbol_list_target_name if all_kmi_symbol_lists else None, |
| kernel_uapi_headers = uapi_headers_target_name, |
| collect_unstripped_modules = collect_unstripped_modules, |
| combined_abi_symbollist = abi_symbollist_target_name if all_kmi_symbol_lists else None, |
| enable_interceptor = enable_interceptor, |
| **kwargs |
| ) |
| |
| # key = attribute name, value = a list of labels for that attribute |
| real_outs = {} |
| |
| for out_name, out_attr_val in ( |
| ("outs", outs), |
| ("module_outs", module_outs), |
| ("implicit_outs", implicit_outs), |
| # internal_outs are opaque to the user, hence we don't create a alias (filegroup) for them. |
| ): |
| if out_attr_val == None: |
| continue |
| if type(out_attr_val) == type([]): |
| for out in out_attr_val: |
| native.filegroup(name = name + "/" + out, srcs = [":" + name], output_group = out) |
| real_outs[out_name] = [name + "/" + out for out in out_attr_val] |
| elif type(out_attr_val) == type({}): |
| # out_attr_val = {config_setting: [out, ...], ...} |
| # => reverse_dict = {out: [config_setting, ...], ...} |
| for out, config_settings in reverse_dict(out_attr_val).items(): |
| native.filegroup( |
| name = name + "/" + out, |
| # Use a select() to prevent this rule to build when config_setting is not fulfilled. |
| srcs = select({ |
| config_setting: [":" + name] |
| for config_setting in config_settings |
| }), |
| output_group = out, |
| # Use "manual" tags to prevent it to be built with ... |
| tags = ["manual"], |
| ) |
| real_outs[out_name] = [name + "/" + out for out, _ in reverse_dict(out_attr_val).items()] |
| else: |
| fail("Unexpected type {} for {}: {}".format(type(out_attr_val), out_name, out_attr_val)) |
| |
| _kernel_uapi_headers( |
| name = uapi_headers_target_name, |
| config = config_target_name, |
| srcs = srcs, |
| **kwargs |
| ) |
| |
| _kernel_headers( |
| name = headers_target_name, |
| kernel_build = name, |
| env = env_target_name, |
| # TODO: We need arch/ and include/ only. |
| srcs = srcs, |
| **kwargs |
| ) |
| |
| if generate_vmlinux_btf: |
| vmlinux_btf_name = name + "_vmlinux_btf" |
| _vmlinux_btf( |
| name = vmlinux_btf_name, |
| vmlinux = name + "/vmlinux", |
| env = env_target_name, |
| **kwargs |
| ) |
| |
| kernel_build_test( |
| name = name + "_test", |
| target = name, |
| ) |
| kernel_module_test( |
| name = name + "_modules_test", |
| modules = real_outs.get("module_outs"), |
| ) |
| |
| _DtsTreeInfo = provider(fields = { |
| "srcs": "DTS tree sources", |
| "makefile": "DTS tree makefile", |
| }) |
| |
| def _kernel_dtstree_impl(ctx): |
| return _DtsTreeInfo( |
| srcs = ctx.files.srcs, |
| makefile = ctx.file.makefile, |
| ) |
| |
| _kernel_dtstree = rule( |
| implementation = _kernel_dtstree_impl, |
| attrs = { |
| "srcs": attr.label_list(doc = "kernel device tree sources", allow_files = True), |
| "makefile": attr.label(mandatory = True, allow_single_file = True), |
| }, |
| ) |
| |
| def kernel_dtstree( |
| name, |
| srcs = None, |
| makefile = None, |
| **kwargs): |
| """Specify a kernel DTS tree. |
| |
| Args: |
| srcs: sources of the DTS tree. Default is |
| |
| ``` |
| glob(["**"], exclude = [ |
| "**/.*", |
| "**/.*/**", |
| "**/BUILD.bazel", |
| "**/*.bzl", |
| ]) |
| ``` |
| makefile: Makefile of the DTS tree. Default is `:Makefile`, i.e. the `Makefile` |
| at the root of the package. |
| kwargs: Additional attributes to the internal rule, e.g. |
| [`visibility`](https://docs.bazel.build/versions/main/visibility.html). |
| See complete list |
| [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes). |
| """ |
| if srcs == None: |
| srcs = native.glob( |
| ["**"], |
| exclude = [ |
| "**/.*", |
| "**/.*/**", |
| "**/BUILD.bazel", |
| "**/*.bzl", |
| ], |
| ) |
| if makefile == None: |
| makefile = ":Makefile" |
| |
| kwargs.update( |
| # This should be the exact list of arguments of kernel_dtstree. |
| name = name, |
| srcs = srcs, |
| makefile = makefile, |
| ) |
| _kernel_dtstree(**kwargs) |
| |
| def _get_status_cmd(ctx, status_file, var): |
| return """cat {status} | ( grep -e "^{var} " || true ) | cut -f2- -d' '""".format( |
| status = status_file.path, |
| var = var, |
| ) |
| |
| def _get_stable_status_cmd(ctx, var): |
| return _get_status_cmd(ctx, ctx.info_file, var) |
| |
| def _get_volatile_status_cmd(ctx, var): |
| return _get_status_cmd(ctx, ctx.version_file, var) |
| |
| def _get_scmversion_cmd(srctree, scmversion): |
| """Return a shell script that sets up .scmversion file in the source tree conditionally. |
| |
| Args: |
| srctree: Path to the source tree where `setlocalversion` were supposed to run with. |
| scmversion: The result of executing `setlocalversion` if it were executed on `srctree`. |
| """ |
| return """ |
| # Set up scm version |
| ( |
| # Save scmversion to .scmversion if .scmversion does not already exist. |
| # If it does exist, then it is part of "srcs", so respect its value. |
| # If .git exists, we are not in sandbox. _kernel_config disables |
| # CONFIG_LOCALVERSION_AUTO in this case. |
| if [[ ! -d {srctree}/.git ]] && [[ ! -f {srctree}/.scmversion ]]; then |
| scmversion={scmversion} |
| if [[ -n "${{scmversion}}" ]]; then |
| mkdir -p {srctree} |
| echo $scmversion > {srctree}/.scmversion |
| fi |
| fi |
| ) |
| """.format( |
| srctree = srctree, |
| scmversion = scmversion, |
| ) |
| |
| _KernelEnvInfo = provider(fields = { |
| "dependencies": "dependencies required to use this environment setup", |
| "setup": "setup script to initialize the environment", |
| }) |
| |
| def _sanitize_label_as_filename(label): |
| """Sanitize a Bazel label so it is safe to be used as a filename.""" |
| label_text = str(label) |
| return "".join([c if c.isalnum() else "_" for c in label_text.elems()]) |
| |
| def _remove_suffix(s, suffix): |
| if s.endswith(suffix): |
| return s[:-len(suffix)] |
| return s |
| |
| def _kernel_env_impl(ctx): |
| if ctx.attr._config_is_local[BuildSettingInfo].value and ctx.attr._config_is_stamp[BuildSettingInfo].value: |
| fail("--config=local cannot be set with --config=stamp. " + |
| "SCM version cannot be embedded without sandboxing. " + |
| "See build/kernel/kleaf/sandbox.md.") |
| |
| srcs = [ |
| s |
| for s in ctx.files.srcs |
| if "/build.config" in s.path or s.path.startswith("build.config") |
| ] |
| |
| build_config = ctx.file.build_config |
| kconfig_ext = ctx.file.kconfig_ext |
| dtstree_makefile = None |
| dtstree_srcs = [] |
| if ctx.attr.dtstree != None: |
| dtstree_makefile = ctx.attr.dtstree[_DtsTreeInfo].makefile |
| dtstree_srcs = ctx.attr.dtstree[_DtsTreeInfo].srcs |
| |
| setup_env = ctx.file.setup_env |
| preserve_env = ctx.file.preserve_env |
| out_file = ctx.actions.declare_file("%s.sh" % ctx.attr.name) |
| |
| inputs = [ |
| ctx.file._build_utils_sh, |
| build_config, |
| setup_env, |
| preserve_env, |
| ] |
| inputs += srcs |
| inputs += ctx.attr._hermetic_tools[HermeticToolsInfo].deps |
| |
| command = "" |
| command += ctx.attr._hermetic_tools[HermeticToolsInfo].setup |
| if ctx.attr._debug_annotate_scripts[BuildSettingInfo].value: |
| command += _debug_trap() |
| |
| if kconfig_ext: |
| command += """ |
| export KCONFIG_EXT={kconfig_ext} |
| """.format( |
| kconfig_ext = kconfig_ext.short_path, |
| ) |
| if dtstree_makefile: |
| command += """ |
| export DTSTREE_MAKEFILE={dtstree} |
| """.format( |
| dtstree = dtstree_makefile.short_path, |
| ) |
| |
| command += """ |
| # error on failures |
| set -e |
| set -o pipefail |
| """ |
| |
| if ctx.attr._debug_annotate_scripts[BuildSettingInfo].value: |
| command += """ |
| export MAKEFLAGS="${MAKEFLAGS} V=1" |
| """ |
| else: |
| command += """ |
| # Run Make in silence mode to suppress most of the info output |
| export MAKEFLAGS="${MAKEFLAGS} -s" |
| """ |
| |
| # If multiple targets have the same KERNEL_DIR are built simultaneously |
| # with --spawn_strategy=local, try to isolate their OUT_DIRs. |
| command += """ |
| export OUT_DIR_SUFFIX={name} |
| """.format(name = _remove_suffix(_sanitize_label_as_filename(ctx.label), "_env")) |
| |
| if ctx.attr._config_is_stamp[BuildSettingInfo].value: |
| command += """ |
| export SOURCE_DATE_EPOCH=$({source_date_epoch_cmd}) |
| """.format(source_date_epoch_cmd = _get_stable_status_cmd(ctx, "STABLE_SOURCE_DATE_EPOCH")) |
| inputs.append(ctx.info_file) |
| else: |
| command += """ |
| export SOURCE_DATE_EPOCH=0 |
| """ |
| |
| command += """ |
| # create a build environment |
| source {build_utils_sh} |
| export BUILD_CONFIG={build_config} |
| source {setup_env} |
| # capture it as a file to be sourced in downstream rules |
| {preserve_env} > {out} |
| """.format( |
| build_utils_sh = ctx.file._build_utils_sh.path, |
| build_config = build_config.path, |
| setup_env = setup_env.path, |
| preserve_env = preserve_env.path, |
| out = out_file.path, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelEnv", |
| inputs = inputs, |
| outputs = [out_file], |
| progress_message = "Creating build environment for %s" % ctx.attr.name, |
| command = command, |
| ) |
| |
| setup = "" |
| setup += ctx.attr._hermetic_tools[HermeticToolsInfo].setup |
| if ctx.attr._debug_annotate_scripts[BuildSettingInfo].value: |
| setup += _debug_trap() |
| |
| set_up_jobs_cmd = """ |
| # Increase parallelism # TODO(b/192655643): do not use -j anymore |
| export MAKEFLAGS="${{MAKEFLAGS}} -j$( |
| make_jobs="$({get_make_jobs_cmd})" |
| if [[ -n "$make_jobs" ]]; then |
| echo "$make_jobs" |
| else |
| nproc |
| fi |
| )" |
| """.format( |
| get_make_jobs_cmd = _get_volatile_status_cmd(ctx, "MAKE_JOBS"), |
| ) |
| |
| # For non-release builds, CONFIG_LOCALVERSION_AUTO is disabled. There's no |
| # need to set up scmversion. |
| set_up_scmversion_cmd = "" |
| if ctx.attr._config_is_stamp[BuildSettingInfo].value: |
| # workspace_status.py does not prepend BRANCH and KMI_GENERATION before |
| # STABLE_SCMVERSION because their values aren't known at that point. |
| # Hence, mimic the logic in setlocalversion to prepend them. |
| stable_scmversion_cmd = _get_stable_status_cmd(ctx, "STABLE_SCMVERSION") |
| |
| # TODO(b/227520025): Remove the following logic in setlocalversion. |
| # Right now, we need this logic for sandboxed builds. Local builds do not have |
| # CONFIG_LOCALVERSION_AUTO, so the following logic in setlocalversion is not necessary. |
| set_up_scmversion_cmd = """ |
| ( |
| # Extract the Android release version. If there is no match, then return 255 |
| # and clear the variable $android_release |
| set +e |
| android_release=$(echo "$BRANCH" | sed -e '/android[0-9]\\{{2,\\}}/!{{q255}}; s/^\\(android[0-9]\\{{2,\\}}\\)-.*/\\1/') |
| if [[ $? -ne 0 ]]; then |
| android_release= |
| fi |
| set -e |
| if [[ -n "$KMI_GENERATION" ]] && [[ $(expr $KMI_GENERATION : '^[0-9]\\+$') -eq 0 ]]; then |
| echo "Invalid KMI_GENERATION $KMI_GENERATION" >&2 |
| exit 1 |
| fi |
| scmversion="" |
| stable_scmversion=$({stable_scmversion_cmd}) |
| if [[ -n "$stable_scmversion" ]]; then |
| scmversion_prefix= |
| if [[ -n "$android_release" ]] && [[ -n "$KMI_GENERATION" ]]; then |
| scmversion_prefix="-$android_release-$KMI_GENERATION" |
| elif [[ -n "$android_release" ]]; then |
| scmversion_prefix="-$android_release" |
| fi |
| scmversion="${{scmversion_prefix}}${{stable_scmversion}}" |
| fi |
| {setup_cmd} |
| ) |
| """.format( |
| stable_scmversion_cmd = stable_scmversion_cmd, |
| setup_cmd = _get_scmversion_cmd( |
| srctree = "${ROOT_DIR}/${KERNEL_DIR}", |
| scmversion = "${scmversion}", |
| ), |
| ) |
| |
| setup += """ |
| # error on failures |
| set -e |
| set -o pipefail |
| # utility functions |
| source {build_utils_sh} |
| # source the build environment |
| source {env} |
| {set_up_jobs_cmd} |
| # re-setup the PATH to also include the hermetic tools, because env completely overwrites |
| # PATH with HERMETIC_TOOLCHAIN=1 |
| {hermetic_tools_additional_setup} |
| # setup LD_LIBRARY_PATH for prebuilts |
| export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${{ROOT_DIR}}/{linux_x86_libs_path} |
| {set_up_scmversion_cmd} |
| # Set up KCONFIG_EXT |
| if [ -n "${{KCONFIG_EXT}}" ]; then |
| export KCONFIG_EXT_PREFIX=$(rel_path $(realpath $(dirname ${{KCONFIG_EXT}})) ${{ROOT_DIR}}/${{KERNEL_DIR}})/ |
| fi |
| if [ -n "${{DTSTREE_MAKEFILE}}" ]; then |
| export dtstree=$(rel_path $(realpath $(dirname ${{DTSTREE_MAKEFILE}})) ${{ROOT_DIR}}/${{KERNEL_DIR}}) |
| fi |
| # Set up KCPPFLAGS |
| # For Kleaf local (non-sandbox) builds, $ROOT_DIR is under execroot but |
| # $ROOT_DIR/$KERNEL_DIR is a symlink to the real source tree under |
| # workspace root, making $abs_srctree not under $ROOT_DIR. |
| if [[ "$(realpath ${{ROOT_DIR}}/${{KERNEL_DIR}})" != "${{ROOT_DIR}}/${{KERNEL_DIR}}" ]]; then |
| export KCPPFLAGS="$KCPPFLAGS -ffile-prefix-map=$(realpath ${{ROOT_DIR}}/${{KERNEL_DIR}})/=" |
| fi |
| """.format( |
| hermetic_tools_additional_setup = ctx.attr._hermetic_tools[HermeticToolsInfo].additional_setup, |
| env = out_file.path, |
| build_utils_sh = ctx.file._build_utils_sh.path, |
| linux_x86_libs_path = ctx.files._linux_x86_libs[0].dirname, |
| set_up_scmversion_cmd = set_up_scmversion_cmd, |
| set_up_jobs_cmd = set_up_jobs_cmd, |
| ) |
| |
| dependencies = ctx.files._tools + ctx.attr._hermetic_tools[HermeticToolsInfo].deps |
| dependencies += [ |
| out_file, |
| ctx.file._build_utils_sh, |
| ctx.version_file, |
| ] |
| if ctx.attr._config_is_stamp[BuildSettingInfo].value: |
| dependencies.append(ctx.info_file) |
| if kconfig_ext: |
| dependencies.append(kconfig_ext) |
| dependencies += dtstree_srcs |
| return [ |
| _KernelEnvInfo( |
| dependencies = dependencies, |
| setup = setup, |
| ), |
| DefaultInfo(files = depset([out_file])), |
| ] |
| |
| def _get_tools(toolchain_version): |
| return [ |
| Label(e) |
| for e in ( |
| "//build/kernel:kernel-build-scripts", |
| "//prebuilts/clang/host/linux-x86/clang-%s:binaries" % toolchain_version, |
| ) |
| ] |
| |
| _KernelToolchainInfo = provider(fields = { |
| "toolchain_version": "The toolchain version", |
| "toolchain_version_file": "A file containing the toolchain version", |
| }) |
| |
| def _kernel_toolchain_aspect_impl(target, ctx): |
| if ctx.rule.kind == "_kernel_build": |
| return ctx.rule.attr.config[_KernelToolchainInfo] |
| if ctx.rule.kind == "_kernel_config": |
| return ctx.rule.attr.env[_KernelToolchainInfo] |
| if ctx.rule.kind == "_kernel_env": |
| return _KernelToolchainInfo(toolchain_version = ctx.rule.attr.toolchain_version) |
| |
| if ctx.rule.kind == "kernel_filegroup": |
| # Create a depset that contains all files referenced by "srcs" |
| all_srcs = depset([], transitive = [src.files for src in ctx.rule.attr.srcs]) |
| |
| # Traverse this depset and look for a file named "toolchain_version". |
| # If no file matches, leave it as None so that _kernel_build_check_toolchain prints a |
| # warning. |
| toolchain_version_file = find_file(name = TOOLCHAIN_VERSION_FILENAME, files = all_srcs.to_list(), what = ctx.label) |
| return _KernelToolchainInfo(toolchain_version_file = toolchain_version_file) |
| |
| fail("{label}: Unable to get toolchain info because {kind} is not supported.".format( |
| kind = ctx.rule.kind, |
| label = ctx.label, |
| )) |
| |
| _kernel_toolchain_aspect = aspect( |
| implementation = _kernel_toolchain_aspect_impl, |
| doc = "An aspect describing the toolchain of a `_kernel_build`, `_kernel_config`, or `_kernel_env` rule.", |
| attr_aspects = [ |
| "config", |
| "env", |
| ], |
| ) |
| |
| _kernel_env = rule( |
| implementation = _kernel_env_impl, |
| doc = """Generates a rule that generates a source-able build environment. |
| |
| A build environment is defined by a single entry build config file |
| that can refer to further build config files. |
| |
| Example: |
| ``` |
| kernel_env( |
| name = "kernel_aarch64_env, |
| build_config = "build.config.gki.aarch64", |
| srcs = glob(["build.config.*"]), |
| ) |
| ``` |
| """, |
| attrs = { |
| "build_config": attr.label( |
| mandatory = True, |
| allow_single_file = True, |
| doc = "label referring to the main build config", |
| ), |
| "srcs": attr.label_list( |
| mandatory = True, |
| allow_files = True, |
| doc = """labels that this build config refers to, including itself. |
| E.g. ["build.config.gki.aarch64", "build.config.gki"]""", |
| ), |
| "setup_env": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel:_setup_env.sh"), |
| doc = "label referring to _setup_env.sh", |
| ), |
| "preserve_env": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:preserve_env.sh"), |
| doc = "label referring to the script capturing the environment", |
| ), |
| "toolchain_version": attr.string( |
| doc = "the toolchain to use for this environment", |
| default = CLANG_VERSION, |
| ), |
| "kconfig_ext": attr.label( |
| allow_single_file = True, |
| doc = "an external Kconfig.ext file sourced by the base kernel", |
| ), |
| "dtstree": attr.label( |
| providers = [_DtsTreeInfo], |
| doc = "Device tree", |
| ), |
| "_tools": attr.label_list(default = _get_tools), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_build_utils_sh": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel:build_utils.sh"), |
| ), |
| "_debug_annotate_scripts": attr.label( |
| default = "//build/kernel/kleaf:debug_annotate_scripts", |
| ), |
| "_config_is_local": attr.label(default = "//build/kernel/kleaf:config_local"), |
| "_config_is_stamp": attr.label(default = "//build/kernel/kleaf:config_stamp"), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| "_linux_x86_libs": attr.label(default = "//prebuilts/kernel-build-tools:linux-x86-libs"), |
| }, |
| ) |
| |
| def _determine_raw_symbollist_path(ctx): |
| """A local action that stores the path to `abi_symbollist.raw` to a file object.""" |
| |
| # Use a local action so we get an absolute path in the execroot that |
| # does not tear down as sandbxes. Then write the absolute path into the |
| # abi_symbollist.raw.abspath. |
| # |
| # In practice, the absolute path looks something like: |
| # /<workspace_root>/out/bazel/output_user_root/<hash>/execroot/__main__/bazel-out/k8-fastbuild/bin/common/kernel_aarch64_raw_kmi_symbol_list/abi_symbollist.raw |
| # |
| # Alternatively, we could use a relative path. However, gen_autoksyms.sh |
| # interprets relative paths as paths relative to $abs_srctree, which |
| # is $(realpath $ROOT_DIR/$KERNEL_DIR). The $abs_srctree is: |
| # - A path within the sandbox for sandbox actions |
| # - /<workspace_root>/$KERNEL_DIR for local actions |
| # Whether KernelConfig is executed in a sandbox may not be consistent with |
| # whether a dependant action is executed in a sandbox. This causes the |
| # interpretation of CONFIG_UNUSED_KSYMS_WHITELIST inconsistent in the |
| # two actions. Hence, we stick with absolute paths. |
| # |
| # NOTE: This may hurt remote caching for developer builds. We may want to |
| # re-visit this when we implement remote caching for developers. |
| abspath = ctx.actions.declare_file("{}/abi_symbollist.raw.abspath".format(ctx.attr.name)) |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| # Record the absolute path so we can use in .config |
| readlink -e {raw_kmi_symbol_list} > {abspath} |
| """.format( |
| abspath = abspath.path, |
| raw_kmi_symbol_list = ctx.file.raw_kmi_symbol_list.path, |
| ) |
| ctx.actions.run_shell( |
| command = command, |
| inputs = ctx.attr._hermetic_tools[HermeticToolsInfo].deps + [ctx.file.raw_kmi_symbol_list], |
| outputs = [abspath], |
| mnemonic = "KernelConfigLocalRawSymbolList", |
| progress_message = "Determining raw symbol list path for trimming {}".format(ctx.label), |
| execution_requirements = { |
| "local": "1", |
| }, |
| ) |
| return abspath |
| |
| def _kernel_config_impl(ctx): |
| inputs = [ |
| s |
| for s in ctx.files.srcs |
| if any([token in s.path for token in [ |
| "Kbuild", |
| "Kconfig", |
| "Makefile", |
| "configs/", |
| "scripts/", |
| ".fragment", |
| ]]) |
| ] |
| |
| config = ctx.outputs.config |
| include_dir = ctx.actions.declare_directory(ctx.attr.name + "_include") |
| |
| scmversion_command = "" |
| if not ctx.attr._config_is_stamp[BuildSettingInfo].value: |
| scmversion_command = """ |
| ${KERNEL_DIR}/scripts/config --file ${OUT_DIR}/.config -d LOCALVERSION_AUTO |
| make -C ${KERNEL_DIR} ${TOOL_ARGS} O=${OUT_DIR} olddefconfig |
| """ |
| |
| lto_config_flag = ctx.attr.lto[BuildSettingInfo].value |
| |
| lto_command = "" |
| if lto_config_flag != "default": |
| # none config |
| lto_config = { |
| "LTO_CLANG": "d", |
| "LTO_NONE": "e", |
| "LTO_CLANG_THIN": "d", |
| "LTO_CLANG_FULL": "d", |
| "THINLTO": "d", |
| } |
| if lto_config_flag == "thin": |
| lto_config.update( |
| LTO_CLANG = "e", |
| LTO_NONE = "d", |
| LTO_CLANG_THIN = "e", |
| THINLTO = "e", |
| ) |
| elif lto_config_flag == "full": |
| lto_config.update( |
| LTO_CLANG = "e", |
| LTO_NONE = "d", |
| LTO_CLANG_FULL = "e", |
| ) |
| |
| lto_command = """ |
| ${{KERNEL_DIR}}/scripts/config --file ${{OUT_DIR}}/.config {configs} |
| make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} olddefconfig |
| """.format(configs = " ".join([ |
| "-%s %s" % (value, key) |
| for key, value in lto_config.items() |
| ])) |
| |
| if ctx.attr.trim_nonlisted_kmi and not ctx.file.raw_kmi_symbol_list: |
| fail("{}: trim_nonlisted_kmi is set but raw_kmi_symbol_list is empty.".format(ctx.label)) |
| |
| trim_kmi_command = "" |
| if ctx.attr.trim_nonlisted_kmi: |
| raw_symbol_list_path_file = _determine_raw_symbollist_path(ctx) |
| trim_kmi_command = """ |
| # Modify .config to trim symbols not listed in KMI |
| ${{KERNEL_DIR}}/scripts/config --file ${{OUT_DIR}}/.config \\ |
| -d UNUSED_SYMBOLS -e TRIM_UNUSED_KSYMS \\ |
| --set-str UNUSED_KSYMS_WHITELIST $(cat {raw_symbol_list_path_file}) |
| make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} olddefconfig |
| """.format( |
| raw_symbol_list_path_file = raw_symbol_list_path_file.path, |
| ) |
| inputs.append(raw_symbol_list_path_file) |
| |
| command = ctx.attr.env[_KernelEnvInfo].setup + """ |
| # Pre-defconfig commands |
| eval ${{PRE_DEFCONFIG_CMDS}} |
| # Actual defconfig |
| make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} ${{DEFCONFIG}} |
| # Post-defconfig commands |
| eval ${{POST_DEFCONFIG_CMDS}} |
| # SCM version configuration |
| {scmversion_command} |
| # LTO configuration |
| {lto_command} |
| # Trim nonlisted symbols |
| {trim_kmi_command} |
| # HACK: run syncconfig to avoid re-triggerring kernel_build |
| make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} syncconfig |
| # Grab outputs |
| rsync -aL ${{OUT_DIR}}/.config {config} |
| rsync -aL ${{OUT_DIR}}/include/ {include_dir}/ |
| """.format( |
| config = config.path, |
| include_dir = include_dir.path, |
| scmversion_command = scmversion_command, |
| lto_command = lto_command, |
| trim_kmi_command = trim_kmi_command, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelConfig", |
| inputs = inputs, |
| outputs = [config, include_dir], |
| tools = ctx.attr.env[_KernelEnvInfo].dependencies, |
| progress_message = "Creating kernel config %s" % ctx.attr.name, |
| command = command, |
| ) |
| |
| setup_deps = ctx.attr.env[_KernelEnvInfo].dependencies + \ |
| [config, include_dir] |
| setup = ctx.attr.env[_KernelEnvInfo].setup + """ |
| # Restore kernel config inputs |
| mkdir -p ${{OUT_DIR}}/include/ |
| rsync -aL {config} ${{OUT_DIR}}/.config |
| rsync -aL {include_dir}/ ${{OUT_DIR}}/include/ |
| find ${{OUT_DIR}}/include -type d -exec chmod +w {{}} \\; |
| """.format(config = config.path, include_dir = include_dir.path) |
| |
| if ctx.attr.trim_nonlisted_kmi: |
| # Ensure the dependent action uses the up-to-date abi_symbollist.raw |
| # at the absolute path specified in abi_symbollist.raw.abspath |
| setup_deps.append(ctx.file.raw_kmi_symbol_list) |
| |
| return [ |
| _KernelEnvInfo( |
| dependencies = setup_deps, |
| setup = setup, |
| ), |
| DefaultInfo(files = depset([config, include_dir])), |
| ] |
| |
| _kernel_config = rule( |
| implementation = _kernel_config_impl, |
| doc = "Defines a kernel config target.", |
| attrs = { |
| "env": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| doc = "environment target that defines the kernel build environment", |
| ), |
| "srcs": attr.label_list(mandatory = True, doc = "kernel sources", allow_files = True), |
| "config": attr.output(mandatory = True, doc = "the .config file"), |
| "lto": attr.label(default = "//build/kernel/kleaf:lto"), |
| "trim_nonlisted_kmi": attr.bool(doc = "If true, modify the config to trim non-listed symbols."), |
| "raw_kmi_symbol_list": attr.label( |
| doc = "Label to abi_symbollist.raw.", |
| allow_single_file = True, |
| ), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_config_is_stamp": attr.label(default = "//build/kernel/kleaf:config_stamp"), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _kmi_symbol_list_impl(ctx): |
| if not ctx.files.srcs: |
| return |
| |
| inputs = [] + ctx.files.srcs |
| inputs += ctx.attr.env[_KernelEnvInfo].dependencies |
| inputs += ctx.files._kernel_abi_scripts |
| |
| outputs = [] |
| out_file = ctx.actions.declare_file("{}/abi_symbollist".format(ctx.attr.name)) |
| report_file = ctx.actions.declare_file("{}/abi_symbollist.report".format(ctx.attr.name)) |
| outputs = [out_file, report_file] |
| |
| command = ctx.attr.env[_KernelEnvInfo].setup + """ |
| mkdir -p {out_dir} |
| {process_symbols} --out-dir={out_dir} --out-file={out_file_base} \ |
| --report-file={report_file_base} --in-dir="${{ROOT_DIR}}/${{KERNEL_DIR}}" \ |
| {srcs} |
| """.format( |
| process_symbols = ctx.file._process_symbols.path, |
| out_dir = out_file.dirname, |
| out_file_base = out_file.basename, |
| report_file_base = report_file.basename, |
| srcs = " ".join(["$(rel_path {} ${{ROOT_DIR}}/${{KERNEL_DIR}})".format(f.path) for f in ctx.files.srcs]), |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KmiSymbolList", |
| inputs = inputs, |
| outputs = outputs, |
| progress_message = "Creating abi_symbollist and report {}".format(ctx.label), |
| command = command, |
| ) |
| |
| return [ |
| DefaultInfo(files = depset(outputs)), |
| OutputGroupInfo(abi_symbollist = depset([out_file])), |
| ] |
| |
| _kmi_symbol_list = rule( |
| implementation = _kmi_symbol_list_impl, |
| doc = "Build abi_symbollist if there are sources, otherwise don't build anything", |
| attrs = { |
| "env": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| doc = "environment target that defines the kernel build environment", |
| ), |
| "srcs": attr.label_list( |
| doc = "`KMI_SYMBOL_LIST` + `ADDTIONAL_KMI_SYMBOL_LISTS`", |
| allow_files = True, |
| ), |
| "_kernel_abi_scripts": attr.label(default = "//build/kernel:kernel-abi-scripts"), |
| "_process_symbols": attr.label(default = "//build/kernel:abi/process_symbols", allow_single_file = True), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _raw_kmi_symbol_list_impl(ctx): |
| if not ctx.file.src: |
| return |
| |
| inputs = [ctx.file.src] |
| inputs += ctx.files._kernel_abi_scripts |
| inputs += ctx.attr.env[_KernelEnvInfo].dependencies |
| |
| out_file = ctx.actions.declare_file("{}/abi_symbollist.raw".format(ctx.attr.name)) |
| |
| command = ctx.attr.env[_KernelEnvInfo].setup + """ |
| mkdir -p {out_dir} |
| cat {src} | {flatten_symbol_list} > {out_file} |
| """.format( |
| out_dir = out_file.dirname, |
| flatten_symbol_list = ctx.file._flatten_symbol_list.path, |
| out_file = out_file.path, |
| src = ctx.file.src.path, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "RawKmiSymbolList", |
| inputs = inputs, |
| outputs = [out_file], |
| progress_message = "Creating abi_symbollist.raw {}".format(ctx.label), |
| command = command, |
| ) |
| |
| return DefaultInfo(files = depset([out_file])) |
| |
| _raw_kmi_symbol_list = rule( |
| implementation = _raw_kmi_symbol_list_impl, |
| doc = "Build `abi_symbollist.raw` if `src` refers to a file, otherwise don't build anything", |
| attrs = { |
| "env": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| doc = "environment target that defines the kernel build environment", |
| ), |
| "src": attr.label( |
| doc = "Label to `abi_symbollist`", |
| allow_single_file = True, |
| ), |
| "_kernel_abi_scripts": attr.label(default = "//build/kernel:kernel-abi-scripts"), |
| "_flatten_symbol_list": attr.label(default = "//build/kernel:abi/flatten_symbol_list", allow_single_file = True), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| _KernelBuildInfo = provider(fields = { |
| "out_dir_kernel_headers_tar": "Archive containing headers in `OUT_DIR`", |
| "outs": "A list of File object corresponding to the `outs` attribute (excluding `module_outs`, `implicit_outs` and `internal_outs`)", |
| "base_kernel_files": "[Default outputs](https://docs.bazel.build/versions/main/skylark/rules.html#default-outputs) of the rule specified by `base_kernel`", |
| "interceptor_output": "`interceptor` log. See [`interceptor`](https://android.googlesource.com/kernel/tools/interceptor/) project.", |
| }) |
| |
| _KernelBuildExtModuleInfo = provider( |
| doc = "A provider that specifies the expectations of a `_kernel_module` (an external module) or a `kernel_modules_install` from its `kernel_build` attribute.", |
| fields = { |
| "modules_staging_archive": "Archive containing staging kernel modules. " + |
| "Does not contain the lib/modules/* suffix.", |
| "module_srcs": "sources for this kernel_build for building external modules", |
| "modules_prepare_setup": "A command that is equivalent to running `make modules_prepare`. Requires env setup.", |
| "modules_prepare_deps": "A list of deps to run `modules_prepare_cmd`.", |
| "collect_unstripped_modules": "Whether an external [`kernel_module`](#kernel_module) building against this [`kernel_build`](#kernel_build) should provide unstripped ones for debugging.", |
| }, |
| ) |
| |
| _KernelBuildUapiInfo = provider( |
| doc = "A provider that specifies the expecation of a `merged_uapi_headers` rule from its `kernel_build` attribute.", |
| fields = { |
| "base_kernel": "the `base_kernel` target, if exists", |
| "kernel_uapi_headers": "the `*_kernel_uapi_headers` target", |
| }, |
| ) |
| |
| _KernelBuildAbiInfo = provider( |
| doc = "A provider that specifies the expectations of a [`kernel_abi`](#kernel_abi) on a `kernel_build`.", |
| fields = { |
| "trim_nonlisted_kmi": "Value of `trim_nonlisted_kmi` in [`kernel_build()`](#kernel_build).", |
| "combined_abi_symbollist": "The **combined** `abi_symbollist` file from the `_kmi_symbol_list` rule, consist of the source `kmi_symbol_list` and `additional_kmi_symbol_lists`.", |
| }, |
| ) |
| |
| _KernelUnstrippedModulesInfo = provider( |
| doc = "A provider that provides unstripped modules", |
| fields = { |
| "base_kernel": "the `base_kernel` target, if exists", |
| "directory": """A [`File`](https://bazel.build/rules/lib/File) that |
| points to a directory containing unstripped modules. |
| |
| For [`kernel_build()`](#kernel_build), this is a directory containing unstripped in-tree modules. |
| - This is `None` if and only if `collect_unstripped_modules = False` |
| - Never `None` if and only if `collect_unstripped_modules = True` |
| - An empty directory if and only if `collect_unstripped_modules = True` and `module_outs` is empty |
| |
| For an external [`kernel_module()`](#kernel_module), this is a directory containing unstripped external modules. |
| - This is `None` if and only if the `kernel_build` argument has `collect_unstripped_modules = False` |
| - Never `None` if and only if the `kernel_build` argument has `collect_unstripped_modules = True` |
| """, |
| }, |
| ) |
| |
| _SrcsInfo = provider(fields = { |
| "srcs": "The srcs attribute of a rule.", |
| }) |
| |
| def _srcs_aspect_impl(target, ctx): |
| return [_SrcsInfo(srcs = getoptattr(ctx.rule.attr, "srcs"))] |
| |
| _srcs_aspect = aspect( |
| implementation = _srcs_aspect_impl, |
| doc = "An aspect that retrieves srcs attribute from a rule.", |
| attr_aspects = ["srcs"], |
| ) |
| |
| def _kernel_build_check_toolchain(ctx): |
| """ |
| Check toolchain_version is the same as base_kernel. |
| """ |
| |
| base_kernel = ctx.attr.base_kernel |
| this_toolchain = ctx.attr.config[_KernelToolchainInfo].toolchain_version |
| base_toolchain = getoptattr(base_kernel[_KernelToolchainInfo], "toolchain_version") |
| base_toolchain_file = getoptattr(base_kernel[_KernelToolchainInfo], "toolchain_version_file") |
| |
| if base_toolchain == None and base_toolchain_file == None: |
| print(("\nWARNING: {this_label}: No check is performed between the toolchain " + |
| "version of the base build ({base_kernel}) and the toolchain version of " + |
| "{this_name} ({this_toolchain}), because the toolchain version of {base_kernel} " + |
| "is unknown.").format( |
| this_label = ctx.label, |
| base_kernel = base_kernel.label, |
| this_name = ctx.label.name, |
| this_toolchain = this_toolchain, |
| )) |
| return |
| |
| if base_toolchain != None and this_toolchain != base_toolchain: |
| fail("""{this_label}: |
| |
| ERROR: `toolchain_version` is "{this_toolchain}" for "{this_label}", but |
| `toolchain_version` is "{base_toolchain}" for "{base_kernel}" (`base_kernel`). |
| They must use the same `toolchain_version`. |
| |
| Fix by setting `toolchain_version` of "{this_label}" |
| to be the one used by "{base_kernel}". |
| If "{base_kernel}" does not set `toolchain_version` explicitly, do not set |
| `toolchain_version` for "{this_label}" either. |
| """.format( |
| this_label = ctx.label, |
| this_toolchain = this_toolchain, |
| base_kernel = base_kernel.label, |
| base_toolchain = base_toolchain, |
| )) |
| |
| if base_toolchain_file != None: |
| out = ctx.actions.declare_file("{}_toolchain_version/toolchain_version_checked".format(ctx.label.name)) |
| base_toolchain = "$(cat {})".format(base_toolchain_file.path) |
| msg = """ERROR: toolchain_version is {this_toolchain} for {this_label}, but |
| toolchain_version is {base_toolchain} for {base_kernel} (base_kernel). |
| They must use the same toolchain_version. |
| |
| Fix by setting toolchain_version of {this_label} to be {base_toolchain}. |
| """.format( |
| this_label = ctx.label, |
| this_toolchain = this_toolchain, |
| base_kernel = base_kernel.label, |
| base_toolchain = base_toolchain, |
| ) |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| # Check toolchain_version against base kernel |
| if ! diff <(cat {base_toolchain_file}) <(echo "{this_toolchain}") > /dev/null; then |
| echo "{msg}" >&2 |
| exit 1 |
| fi |
| touch {out} |
| """.format( |
| base_toolchain_file = base_toolchain_file.path, |
| this_toolchain = this_toolchain, |
| msg = msg, |
| out = out.path, |
| ) |
| |
| _debug_print_scripts(ctx, command, what = "check_toolchain") |
| ctx.actions.run_shell( |
| mnemonic = "KernelBuildCheckToolchain", |
| inputs = [base_toolchain_file] + ctx.attr._hermetic_tools[HermeticToolsInfo].deps, |
| outputs = [out], |
| command = command, |
| progress_message = "Checking toolchain version against base kernel {}".format(ctx.label), |
| ) |
| return out |
| |
| def _kernel_build_dump_toolchain_version(ctx): |
| this_toolchain = ctx.attr.config[_KernelToolchainInfo].toolchain_version |
| out = ctx.actions.declare_file("{}_toolchain_version/{}".format(ctx.attr.name, TOOLCHAIN_VERSION_FILENAME)) |
| ctx.actions.write( |
| output = out, |
| content = this_toolchain + "\n", |
| ) |
| return out |
| |
| def _kmi_symbol_list_strict_mode(ctx, all_output_files, all_module_names_file): |
| """Run for `KMI_SYMBOL_LIST_STRICT_MODE`. |
| """ |
| if not ctx.attr.kmi_symbol_list_strict_mode: |
| return None |
| if not ctx.file.raw_kmi_symbol_list: |
| fail("{}: kmi_symbol_list_strict_mode requires kmi_symbol_list or additional_kmi_symbol_lists.") |
| |
| vmlinux = all_output_files["outs"].get("vmlinux") |
| if not vmlinux: |
| fail("{}: with kmi_symbol_list_strict_mode, outs does not contain vmlinux") |
| module_symvers = all_output_files["internal_outs"].get("Module.symvers") |
| if not module_symvers: |
| fail("{}: with kmi_symbol_list_strict_mode, outs does not contain module_symvers") |
| |
| inputs = [ |
| module_symvers, |
| ctx.file.raw_kmi_symbol_list, |
| all_module_names_file, |
| ] |
| inputs += ctx.files._kernel_abi_scripts |
| inputs += ctx.attr.config[_KernelEnvInfo].dependencies |
| |
| out = ctx.actions.declare_file("{}_kmi_strict_out/kmi_symbol_list_strict_mode_checked".format(ctx.attr.name)) |
| command = ctx.attr.config[_KernelEnvInfo].setup + """ |
| KMI_STRICT_MODE_OBJECTS="{vmlinux_base} $(cat {all_module_names_file} | sed 's/\\.ko$//')" {compare_to_symbol_list} {module_symvers} {raw_kmi_symbol_list} |
| touch {out} |
| """.format( |
| vmlinux_base = vmlinux.basename, # A fancy way of saying "vmlinux" |
| all_module_names_file = all_module_names_file.path, |
| compare_to_symbol_list = ctx.file._compare_to_symbol_list.path, |
| module_symvers = module_symvers.path, |
| raw_kmi_symbol_list = ctx.file.raw_kmi_symbol_list.path, |
| out = out.path, |
| ) |
| _debug_print_scripts(ctx, command, what = "kmi_symbol_list_strict_mode") |
| ctx.actions.run_shell( |
| mnemonic = "KernelBuildKmiSymbolListStrictMode", |
| inputs = inputs, |
| outputs = [out], |
| command = command, |
| progress_message = "Checking for kmi_symbol_list_strict_mode {}".format(ctx.label), |
| ) |
| return out |
| |
| def _kernel_build_impl(ctx): |
| kbuild_mixed_tree = None |
| base_kernel_files = [] |
| check_toolchain_out = None |
| if ctx.attr.base_kernel: |
| check_toolchain_out = _kernel_build_check_toolchain(ctx) |
| |
| # Create a directory for KBUILD_MIXED_TREE. Flatten the directory structure of the files |
| # that ctx.attr.base_kernel provides. declare_directory is sufficient because the directory should |
| # only change when the dependent ctx.attr.base_kernel changes. |
| kbuild_mixed_tree = ctx.actions.declare_directory("{}_kbuild_mixed_tree".format(ctx.label.name)) |
| base_kernel_files = ctx.files.base_kernel |
| kbuild_mixed_tree_command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| # Restore GKI artifacts for mixed build |
| export KBUILD_MIXED_TREE=$(realpath {kbuild_mixed_tree}) |
| rm -rf ${{KBUILD_MIXED_TREE}} |
| mkdir -p ${{KBUILD_MIXED_TREE}} |
| for base_kernel_file in {base_kernel_files}; do |
| ln -s $(readlink -m ${{base_kernel_file}}) ${{KBUILD_MIXED_TREE}} |
| done |
| """.format( |
| base_kernel_files = " ".join([file.path for file in base_kernel_files]), |
| kbuild_mixed_tree = kbuild_mixed_tree.path, |
| ) |
| _debug_print_scripts(ctx, kbuild_mixed_tree_command, what = "kbuild_mixed_tree") |
| ctx.actions.run_shell( |
| mnemonic = "KernelBuildKbuildMixedTree", |
| inputs = base_kernel_files + ctx.attr._hermetic_tools[HermeticToolsInfo].deps, |
| outputs = [kbuild_mixed_tree], |
| progress_message = "Creating KBUILD_MIXED_TREE", |
| command = kbuild_mixed_tree_command, |
| ) |
| |
| ruledir = ctx.actions.declare_directory(ctx.label.name) |
| |
| inputs = [ |
| ctx.file._search_and_cp_output, |
| ctx.file._check_declared_output_list, |
| ] |
| inputs += ctx.files.srcs |
| inputs += ctx.files.deps |
| if check_toolchain_out: |
| inputs.append(check_toolchain_out) |
| if kbuild_mixed_tree: |
| inputs.append(kbuild_mixed_tree) |
| |
| # kernel_build(name="kernel", outs=["out"]) |
| # => _kernel_build(name="kernel", outs=["kernel/out"], internal_outs=["kernel/Module.symvers", ...]) |
| # => all_output_names = ["foo", "Module.symvers", ...] |
| # all_output_files = {"out": {"foo": File(...)}, "internal_outs": {"Module.symvers": File(...)}, ...} |
| all_output_files = {} |
| for attr in ("outs", "module_outs", "implicit_outs", "internal_outs"): |
| all_output_files[attr] = {name: ctx.actions.declare_file("{}/{}".format(ctx.label.name, name)) for name in getattr(ctx.attr, attr)} |
| all_output_names_minus_modules = [] |
| for attr, d in all_output_files.items(): |
| if attr != "module_outs": |
| all_output_names_minus_modules += d.keys() |
| |
| # A file containing all module_outs |
| all_module_names = all_output_files["module_outs"].keys() |
| all_module_names_file = ctx.actions.declare_file("{}_all_module_names/all_module_names.txt".format(ctx.label.name)) |
| ctx.actions.write( |
| output = all_module_names_file, |
| content = "\n".join(all_module_names) + "\n", |
| ) |
| inputs.append(all_module_names_file) |
| |
| all_module_basenames_file = ctx.actions.declare_file("{}_all_module_names/all_module_basenames.txt".format(ctx.label.name)) |
| ctx.actions.write( |
| output = all_module_basenames_file, |
| content = "\n".join([paths.basename(filename) for filename in all_module_names]) + "\n", |
| ) |
| |
| modules_staging_archive = ctx.actions.declare_file( |
| "{name}/modules_staging_dir.tar.gz".format(name = ctx.label.name), |
| ) |
| out_dir_kernel_headers_tar = ctx.actions.declare_file( |
| "{name}/out-dir-kernel-headers.tar.gz".format(name = ctx.label.name), |
| ) |
| interceptor_output = None |
| if ctx.attr.enable_interceptor: |
| interceptor_output = ctx.actions.declare_file("{name}/interceptor_output.bin".format(name = ctx.label.name)) |
| modules_staging_dir = modules_staging_archive.dirname + "/staging" |
| |
| unstripped_dir = None |
| if ctx.attr.collect_unstripped_modules: |
| unstripped_dir = ctx.actions.declare_directory("{name}/unstripped".format(name = ctx.label.name)) |
| |
| # all outputs that |command| generates |
| command_outputs = [ |
| ruledir, |
| modules_staging_archive, |
| out_dir_kernel_headers_tar, |
| ] |
| if interceptor_output: |
| command_outputs.append(interceptor_output) |
| for d in all_output_files.values(): |
| command_outputs += d.values() |
| if unstripped_dir: |
| command_outputs.append(unstripped_dir) |
| |
| command = "" |
| command += ctx.attr.config[_KernelEnvInfo].setup |
| |
| interceptor_command_prefix = "" |
| if interceptor_output: |
| interceptor_command_prefix = "interceptor -r -l {interceptor_output} --".format( |
| interceptor_output = interceptor_output.path, |
| ) |
| |
| if kbuild_mixed_tree: |
| command += """ |
| export KBUILD_MIXED_TREE=$(realpath {kbuild_mixed_tree}) |
| """.format( |
| kbuild_mixed_tree = kbuild_mixed_tree.path, |
| ) |
| |
| grab_intree_modules_cmd = "" |
| if all_module_names: |
| grab_intree_modules_cmd = """ |
| {search_and_cp_output} --srcdir {modules_staging_dir}/lib/modules/*/kernel --dstdir {ruledir} $(cat {all_module_names_file}) |
| """.format( |
| search_and_cp_output = ctx.file._search_and_cp_output.path, |
| modules_staging_dir = modules_staging_dir, |
| ruledir = ruledir.path, |
| all_module_names_file = all_module_names_file.path, |
| ) |
| |
| grab_unstripped_intree_modules_cmd = "" |
| if all_module_names and unstripped_dir: |
| inputs.append(all_module_basenames_file) |
| grab_unstripped_intree_modules_cmd = """ |
| mkdir -p {unstripped_dir} |
| {search_and_cp_output} --srcdir ${{OUT_DIR}} --dstdir {unstripped_dir} $(cat {all_module_basenames_file}) |
| """.format( |
| search_and_cp_output = ctx.file._search_and_cp_output.path, |
| unstripped_dir = unstripped_dir.path, |
| all_module_basenames_file = all_module_basenames_file.path, |
| ) |
| |
| command += """ |
| # Actual kernel build |
| {interceptor_command_prefix} make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} ${{MAKE_GOALS}} |
| # Set variables and create dirs for modules |
| if [ "${{DO_NOT_STRIP_MODULES}}" != "1" ]; then |
| module_strip_flag="INSTALL_MOD_STRIP=1" |
| fi |
| mkdir -p {modules_staging_dir} |
| # Install modules |
| make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} DEPMOD=true O=${{OUT_DIR}} ${{module_strip_flag}} INSTALL_MOD_PATH=$(realpath {modules_staging_dir}) modules_install |
| # Archive headers in OUT_DIR |
| find ${{OUT_DIR}} -name *.h -print0 \ |
| | tar czf {out_dir_kernel_headers_tar} \ |
| --absolute-names \ |
| --dereference \ |
| --transform "s,.*$OUT_DIR,," \ |
| --transform "s,^/,," \ |
| --null -T - |
| # Grab outputs. If unable to find from OUT_DIR, look at KBUILD_MIXED_TREE as well. |
| {search_and_cp_output} --srcdir ${{OUT_DIR}} {kbuild_mixed_tree_arg} {dtstree_arg} --dstdir {ruledir} {all_output_names_minus_modules} |
| # Archive modules_staging_dir |
| tar czf {modules_staging_archive} -C {modules_staging_dir} . |
| # Grab in-tree modules |
| {grab_intree_modules_cmd} |
| # Grab unstripped in-tree modules |
| {grab_unstripped_intree_modules_cmd} |
| # Check if there are remaining *.ko files |
| remaining_ko_files=$({check_declared_output_list} \\ |
| --declared $(cat {all_module_names_file}) \\ |
| --actual $(cd {modules_staging_dir}/lib/modules/*/kernel && find . -type f -name '*.ko' | sed 's:^[.]/::')) |
| if [[ ${{remaining_ko_files}} ]]; then |
| echo "ERROR: The following kernel modules are built but not copied. Add these lines to the module_outs attribute of {label}:" >&2 |
| for ko in ${{remaining_ko_files}}; do |
| echo ' "'"${{ko}}"'",' >&2 |
| done |
| exit 1 |
| fi |
| # Clean up staging directories |
| rm -rf {modules_staging_dir} |
| """.format( |
| check_declared_output_list = ctx.file._check_declared_output_list.path, |
| search_and_cp_output = ctx.file._search_and_cp_output.path, |
| kbuild_mixed_tree_arg = "--srcdir ${KBUILD_MIXED_TREE}" if kbuild_mixed_tree else "", |
| dtstree_arg = "--srcdir ${OUT_DIR}/${dtstree}", |
| ruledir = ruledir.path, |
| all_output_names_minus_modules = " ".join(all_output_names_minus_modules), |
| grab_intree_modules_cmd = grab_intree_modules_cmd, |
| grab_unstripped_intree_modules_cmd = grab_unstripped_intree_modules_cmd, |
| all_module_names_file = all_module_names_file.path, |
| modules_staging_dir = modules_staging_dir, |
| modules_staging_archive = modules_staging_archive.path, |
| out_dir_kernel_headers_tar = out_dir_kernel_headers_tar.path, |
| interceptor_command_prefix = interceptor_command_prefix, |
| label = ctx.label, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelBuild", |
| inputs = inputs, |
| outputs = command_outputs, |
| tools = ctx.attr.config[_KernelEnvInfo].dependencies, |
| progress_message = "Building kernel %s" % ctx.attr.name, |
| command = command, |
| ) |
| |
| toolchain_version_out = _kernel_build_dump_toolchain_version(ctx) |
| kmi_strict_mode_out = _kmi_symbol_list_strict_mode(ctx, all_output_files, all_module_names_file) |
| |
| # Only outs and internal_outs are needed. But for simplicity, copy the full {ruledir} |
| # which includes module_outs and implicit_outs too. |
| env_info_dependencies = [] |
| env_info_dependencies += ctx.attr.config[_KernelEnvInfo].dependencies |
| for d in all_output_files.values(): |
| env_info_dependencies += d.values() |
| env_info_setup = ctx.attr.config[_KernelEnvInfo].setup + """ |
| # Restore kernel build outputs |
| cp -R {ruledir}/* ${{OUT_DIR}} |
| """.format(ruledir = ruledir.path) |
| if kbuild_mixed_tree: |
| env_info_dependencies.append(kbuild_mixed_tree) |
| env_info_setup += """ |
| export KBUILD_MIXED_TREE=$(realpath {kbuild_mixed_tree}) |
| """.format(kbuild_mixed_tree = kbuild_mixed_tree.path) |
| env_info = _KernelEnvInfo( |
| dependencies = env_info_dependencies, |
| setup = env_info_setup, |
| ) |
| |
| module_srcs = _filter_module_srcs(ctx.files.srcs) |
| |
| kernel_build_info = _KernelBuildInfo( |
| out_dir_kernel_headers_tar = out_dir_kernel_headers_tar, |
| outs = all_output_files["outs"].values(), |
| base_kernel_files = base_kernel_files, |
| interceptor_output = interceptor_output, |
| ) |
| |
| kernel_build_module_info = _KernelBuildExtModuleInfo( |
| modules_staging_archive = modules_staging_archive, |
| module_srcs = module_srcs, |
| modules_prepare_setup = ctx.attr.modules_prepare[_KernelEnvInfo].setup, |
| modules_prepare_deps = ctx.attr.modules_prepare[_KernelEnvInfo].dependencies, |
| collect_unstripped_modules = ctx.attr.collect_unstripped_modules, |
| ) |
| |
| kernel_build_uapi_info = _KernelBuildUapiInfo( |
| base_kernel = ctx.attr.base_kernel, |
| kernel_uapi_headers = ctx.attr.kernel_uapi_headers, |
| ) |
| |
| kernel_build_abi_info = _KernelBuildAbiInfo( |
| trim_nonlisted_kmi = ctx.attr.trim_nonlisted_kmi, |
| combined_abi_symbollist = ctx.file.combined_abi_symbollist, |
| ) |
| |
| kernel_unstripped_modules_info = _KernelUnstrippedModulesInfo( |
| base_kernel = ctx.attr.base_kernel, |
| directory = unstripped_dir, |
| ) |
| |
| output_group_kwargs = {} |
| for d in all_output_files.values(): |
| output_group_kwargs.update({name: depset([file]) for name, file in d.items()}) |
| output_group_kwargs["modules_staging_archive"] = depset([modules_staging_archive]) |
| output_group_info = OutputGroupInfo(**output_group_kwargs) |
| |
| default_info_files = all_output_files["outs"].values() + all_output_files["module_outs"].values() |
| default_info_files.append(toolchain_version_out) |
| if kmi_strict_mode_out: |
| default_info_files.append(kmi_strict_mode_out) |
| default_info = DefaultInfo( |
| files = depset(default_info_files), |
| # For kernel_build_test |
| runfiles = ctx.runfiles(files = default_info_files), |
| ) |
| |
| return [ |
| env_info, |
| kernel_build_info, |
| kernel_build_module_info, |
| kernel_build_uapi_info, |
| kernel_build_abi_info, |
| kernel_unstripped_modules_info, |
| output_group_info, |
| default_info, |
| ] |
| |
| _kernel_build = rule( |
| implementation = _kernel_build_impl, |
| doc = "Defines a kernel build target.", |
| attrs = { |
| "config": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| aspects = [_kernel_toolchain_aspect], |
| doc = "the kernel_config target", |
| ), |
| "srcs": attr.label_list(mandatory = True, doc = "kernel sources", allow_files = True), |
| "outs": attr.string_list(), |
| "module_outs": attr.string_list(doc = "output *.ko files"), |
| "internal_outs": attr.string_list(doc = "Like `outs`, but not in dist"), |
| "implicit_outs": attr.string_list(doc = "Like `outs`, but not in dist"), |
| "_check_declared_output_list": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:check_declared_output_list.py"), |
| ), |
| "_search_and_cp_output": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:search_and_cp_output.py"), |
| doc = "label referring to the script to process outputs", |
| ), |
| "deps": attr.label_list( |
| allow_files = True, |
| ), |
| "base_kernel": attr.label( |
| aspects = [_kernel_toolchain_aspect], |
| ), |
| "kmi_symbol_list_strict_mode": attr.bool(), |
| "raw_kmi_symbol_list": attr.label( |
| doc = "Label to abi_symbollist.raw.", |
| allow_single_file = True, |
| ), |
| "collect_unstripped_modules": attr.bool(), |
| "enable_interceptor": attr.bool(), |
| "_kernel_abi_scripts": attr.label(default = "//build/kernel:kernel-abi-scripts"), |
| "_compare_to_symbol_list": attr.label(default = "//build/kernel:abi/compare_to_symbol_list", allow_single_file = True), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| # Though these rules are unrelated to the `_kernel_build` rule, they are added as fake |
| # dependencies so _KernelBuildExtModuleInfo and _KernelBuildUapiInfo works. |
| # There are no real dependencies. Bazel does not build these targets before building the |
| # `_kernel_build` target. |
| "modules_prepare": attr.label(), |
| "kernel_uapi_headers": attr.label(), |
| "trim_nonlisted_kmi": attr.bool(), |
| "combined_abi_symbollist": attr.label(allow_single_file = True, doc = "The **combined** `abi_symbollist` file, consist of `kmi_symbol_list` and `additional_kmi_symbol_lists`."), |
| }, |
| ) |
| |
| def _modules_prepare_impl(ctx): |
| command = ctx.attr.config[_KernelEnvInfo].setup + """ |
| # Prepare for the module build |
| make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}} modules_prepare |
| # Package files |
| tar czf {outdir_tar_gz} -C ${{OUT_DIR}} . |
| """.format(outdir_tar_gz = ctx.outputs.outdir_tar_gz.path) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "ModulesPrepare", |
| inputs = ctx.files.srcs, |
| outputs = [ctx.outputs.outdir_tar_gz], |
| tools = ctx.attr.config[_KernelEnvInfo].dependencies, |
| progress_message = "Preparing for module build %s" % ctx.label, |
| command = command, |
| ) |
| |
| setup = """ |
| # Restore modules_prepare outputs. Assumes env setup. |
| [ -z ${{OUT_DIR}} ] && echo "ERROR: modules_prepare setup run without OUT_DIR set!" >&2 && exit 1 |
| tar xf {outdir_tar_gz} -C ${{OUT_DIR}} |
| """.format(outdir_tar_gz = ctx.outputs.outdir_tar_gz.path) |
| |
| return [_KernelEnvInfo( |
| dependencies = [ctx.outputs.outdir_tar_gz], |
| setup = setup, |
| )] |
| |
| _modules_prepare = rule( |
| implementation = _modules_prepare_impl, |
| attrs = { |
| "config": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| doc = "the kernel_config target", |
| ), |
| "srcs": attr.label_list(mandatory = True, doc = "kernel sources", allow_files = True), |
| "outdir_tar_gz": attr.output( |
| mandatory = True, |
| doc = "the packaged ${OUT_DIR} files", |
| ), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| _KernelModuleInfo = provider(fields = { |
| "kernel_build": "kernel_build attribute of this module", |
| "modules_staging_dws": "`directory_with_structure` containing staging kernel modules. " + |
| "Contains the lib/modules/* suffix.", |
| "kernel_uapi_headers_dws": "`directory_with_structure` containing UAPI headers to use the module.", |
| "files": "The list of output `*.ko` files.", |
| }) |
| |
| def _check_kernel_build(kernel_modules, kernel_build, this_label): |
| """Check that kernel_modules have the same kernel_build as the given one. |
| |
| Args: |
| kernel_modules: the attribute of kernel_module dependencies. Should be |
| an attribute of a list of labels. |
| kernel_build: the attribute of kernel_build. Should be an attribute of |
| a label. |
| this_label: label of the module being checked. |
| """ |
| |
| for kernel_module in kernel_modules: |
| if kernel_module[_KernelModuleInfo].kernel_build.label != \ |
| kernel_build.label: |
| fail(( |
| "{this_label} refers to kernel_build {kernel_build}, but " + |
| "depended kernel_module {dep} refers to kernel_build " + |
| "{dep_kernel_build}. They must refer to the same kernel_build." |
| ).format( |
| this_label = this_label, |
| kernel_build = kernel_build.label, |
| dep = kernel_module.label, |
| dep_kernel_build = kernel_module[_KernelModuleInfo].kernel_build.label, |
| )) |
| |
| def _kernel_module_impl(ctx): |
| _check_kernel_build(ctx.attr.kernel_module_deps, ctx.attr.kernel_build, ctx.label) |
| |
| inputs = [] |
| inputs += ctx.files.srcs |
| inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies |
| inputs += ctx.attr.kernel_build[_KernelBuildExtModuleInfo].modules_prepare_deps |
| inputs += ctx.attr.kernel_build[_KernelBuildExtModuleInfo].module_srcs |
| inputs += ctx.files.makefile |
| inputs += [ |
| ctx.file._search_and_cp_output, |
| ctx.file._check_declared_output_list, |
| ] |
| for kernel_module_dep in ctx.attr.kernel_module_deps: |
| inputs += kernel_module_dep[_KernelEnvInfo].dependencies |
| if ctx.attr._config_is_stamp[BuildSettingInfo].value: |
| inputs.append(ctx.info_file) |
| |
| modules_staging_dws = dws.make(ctx, "{}/staging".format(ctx.attr.name)) |
| kernel_uapi_headers_dws = dws.make(ctx, "{}/kernel-uapi-headers.tar.gz_staging".format(ctx.attr.name)) |
| outdir = modules_staging_dws.directory.dirname |
| |
| unstripped_dir = None |
| if ctx.attr.kernel_build[_KernelBuildExtModuleInfo].collect_unstripped_modules: |
| unstripped_dir = ctx.actions.declare_directory("{name}/unstripped".format(name = ctx.label.name)) |
| |
| # Original `outs` attribute of `kernel_module` macro. |
| original_outs = [] |
| |
| # apply basename to all of original_outs |
| original_outs_base = [] |
| |
| for out in ctx.outputs.outs: |
| # outdir includes target name at the end already. So short_name is the original |
| # token in `outs` of `kernel_module` macro. |
| # e.g. kernel_module(name = "foo", outs = ["bar"]) |
| # => _kernel_module(name = "foo", outs = ["foo/bar"]) |
| # => outdir = ".../foo" |
| # ctx.outputs.outs = [File(".../foo/bar")] |
| # => short_name = "bar" |
| short_name = out.path[len(outdir) + 1:] |
| original_outs.append(short_name) |
| original_outs_base.append(out.basename) |
| |
| all_module_names_file = ctx.actions.declare_file("{}/all_module_names.txt".format(ctx.label.name)) |
| ctx.actions.write( |
| output = all_module_names_file, |
| content = "\n".join(original_outs) + "\n", |
| ) |
| inputs.append(all_module_names_file) |
| |
| module_symvers = ctx.actions.declare_file("{}/Module.symvers".format(ctx.attr.name)) |
| check_no_remaining = ctx.actions.declare_file("{name}/{name}.check_no_remaining".format(name = ctx.attr.name)) |
| command_outputs = [ |
| module_symvers, |
| check_no_remaining, |
| ] |
| command_outputs += dws.files(modules_staging_dws) |
| command_outputs += dws.files(kernel_uapi_headers_dws) |
| if unstripped_dir: |
| command_outputs.append(unstripped_dir) |
| |
| command = "" |
| command += ctx.attr.kernel_build[_KernelEnvInfo].setup |
| command += ctx.attr.kernel_build[_KernelBuildExtModuleInfo].modules_prepare_setup |
| command += """ |
| # create dirs for modules |
| mkdir -p {kernel_uapi_headers_dir}/usr |
| """.format( |
| kernel_uapi_headers_dir = kernel_uapi_headers_dws.directory.path, |
| ) |
| for kernel_module_dep in ctx.attr.kernel_module_deps: |
| command += kernel_module_dep[_KernelEnvInfo].setup |
| |
| grab_unstripped_cmd = "" |
| if unstripped_dir: |
| grab_unstripped_cmd = """ |
| mkdir -p {unstripped_dir} |
| {search_and_cp_output} --srcdir ${{OUT_DIR}}/${{ext_mod_rel}} --dstdir {unstripped_dir} {outs} |
| """.format( |
| search_and_cp_output = ctx.file._search_and_cp_output.path, |
| unstripped_dir = unstripped_dir.path, |
| # Use basenames to flatten the unstripped directory, even though outs may contain items with slash. |
| outs = " ".join(original_outs_base), |
| ) |
| |
| if ctx.attr._config_is_stamp[BuildSettingInfo].value: |
| # {ext_mod}:{scmversion} {ext_mod}:{scmversion} ... |
| scmversion_cmd = _get_stable_status_cmd(ctx, "STABLE_SCMVERSION_EXT_MOD") |
| scmversion_cmd += """ | sed -n 's|.*\\<{ext_mod}:\\(\\S\\+\\).*|\\1|p'""".format(ext_mod = ctx.attr.ext_mod) |
| |
| # workspace_status.py does not set STABLE_SCMVERSION if setlocalversion |
| # should not run on KERNEL_DIR. However, for STABLE_SCMVERSION_EXT_MOD, |
| # we may have a missing item if setlocalversion should not run in |
| # a certain directory. Hence, be lenient about failures. |
| scmversion_cmd += " || true" |
| |
| command += _get_scmversion_cmd( |
| srctree = "${{ROOT_DIR}}/{ext_mod}".format(ext_mod = ctx.attr.ext_mod), |
| scmversion = "$({})".format(scmversion_cmd), |
| ) |
| |
| command += """ |
| # Set variables |
| if [ "${{DO_NOT_STRIP_MODULES}}" != "1" ]; then |
| module_strip_flag="INSTALL_MOD_STRIP=1" |
| fi |
| ext_mod_rel=$(rel_path ${{ROOT_DIR}}/{ext_mod} ${{KERNEL_DIR}}) |
| |
| # Actual kernel module build |
| make -C {ext_mod} ${{TOOL_ARGS}} M=${{ext_mod_rel}} O=${{OUT_DIR}} KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}} |
| # Install into staging directory |
| make -C {ext_mod} ${{TOOL_ARGS}} DEPMOD=true M=${{ext_mod_rel}} \ |
| O=${{OUT_DIR}} KERNEL_SRC=${{ROOT_DIR}}/${{KERNEL_DIR}} \ |
| INSTALL_MOD_PATH=$(realpath {modules_staging_dir}) \ |
| INSTALL_MOD_DIR=extra/{ext_mod} \ |
| KERNEL_UAPI_HEADERS_DIR=$(realpath {kernel_uapi_headers_dir}) \ |
| INSTALL_HDR_PATH=$(realpath {kernel_uapi_headers_dir}/usr) \ |
| ${{module_strip_flag}} modules_install |
| |
| # Check if there are remaining *.ko files |
| remaining_ko_files=$({check_declared_output_list} \\ |
| --declared $(cat {all_module_names_file}) \\ |
| --actual $(cd {modules_staging_dir}/lib/modules/*/extra/{ext_mod} && find . -type f -name '*.ko' | sed 's:^[.]/::')) |
| if [[ ${{remaining_ko_files}} ]]; then |
| echo "ERROR: The following kernel modules are built but not copied. Add these lines to the module_outs attribute of {label}:" >&2 |
| for ko in ${{remaining_ko_files}}; do |
| echo ' "'"${{ko}}"'",' >&2 |
| done |
| exit 1 |
| fi |
| touch {check_no_remaining} |
| |
| # Grab unstripped modules |
| {grab_unstripped_cmd} |
| # Move Module.symvers |
| mv ${{OUT_DIR}}/${{ext_mod_rel}}/Module.symvers {module_symvers} |
| """.format( |
| label = ctx.label, |
| ext_mod = ctx.attr.ext_mod, |
| module_symvers = module_symvers.path, |
| modules_staging_dir = modules_staging_dws.directory.path, |
| outdir = outdir, |
| kernel_uapi_headers_dir = kernel_uapi_headers_dws.directory.path, |
| check_declared_output_list = ctx.file._check_declared_output_list.path, |
| all_module_names_file = all_module_names_file.path, |
| grab_unstripped_cmd = grab_unstripped_cmd, |
| check_no_remaining = check_no_remaining.path, |
| ) |
| |
| command += dws.record(modules_staging_dws) |
| command += dws.record(kernel_uapi_headers_dws) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelModule", |
| inputs = inputs, |
| outputs = command_outputs, |
| command = command, |
| progress_message = "Building external kernel module {}".format(ctx.label), |
| ) |
| |
| # Additional outputs because of the value in outs. This is |
| # [basename(out) for out in outs] - outs |
| additional_declared_outputs = [] |
| for short_name, out in zip(original_outs, ctx.outputs.outs): |
| if "/" in short_name: |
| additional_declared_outputs.append(ctx.actions.declare_file("{name}/{basename}".format( |
| name = ctx.attr.name, |
| basename = out.basename, |
| ))) |
| original_outs_base.append(out.basename) |
| cp_cmd_outputs = ctx.outputs.outs + additional_declared_outputs |
| |
| if cp_cmd_outputs: |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| # Copy files into place |
| {search_and_cp_output} --srcdir {modules_staging_dir}/lib/modules/*/extra/{ext_mod}/ --dstdir {outdir} {outs} |
| """.format( |
| search_and_cp_output = ctx.file._search_and_cp_output.path, |
| modules_staging_dir = modules_staging_dws.directory.path, |
| ext_mod = ctx.attr.ext_mod, |
| outdir = outdir, |
| outs = " ".join(original_outs), |
| ) |
| _debug_print_scripts(ctx, command, what = "cp_outputs") |
| ctx.actions.run_shell( |
| mnemonic = "KernelModuleCpOutputs", |
| inputs = ctx.attr._hermetic_tools[HermeticToolsInfo].deps + [ |
| # We don't need structure_file here because we only care about files in the directory. |
| modules_staging_dws.directory, |
| ctx.file._search_and_cp_output, |
| ], |
| outputs = cp_cmd_outputs, |
| command = command, |
| progress_message = "Copying outputs {}".format(ctx.label), |
| ) |
| |
| setup = """ |
| # Use a new shell to avoid polluting variables |
| ( |
| # Set variables |
| # rel_path requires the existence of ${{ROOT_DIR}}/{ext_mod}, which may not be the case for |
| # _kernel_modules_install. Make that. |
| mkdir -p ${{ROOT_DIR}}/{ext_mod} |
| ext_mod_rel=$(rel_path ${{ROOT_DIR}}/{ext_mod} ${{KERNEL_DIR}}) |
| # Restore Modules.symvers |
| mkdir -p ${{OUT_DIR}}/${{ext_mod_rel}} |
| cp {module_symvers} ${{OUT_DIR}}/${{ext_mod_rel}}/Module.symvers |
| # New shell ends |
| ) |
| """.format( |
| ext_mod = ctx.attr.ext_mod, |
| module_symvers = module_symvers.path, |
| ) |
| |
| # Only declare outputs in the "outs" list. For additional outputs that this rule created, |
| # the label is available, but this rule doesn't explicitly return it in the info. |
| # Also add check_no_remaining in the list of default outputs so that, when |
| # outs is empty, the KernelModule action is still executed, and so |
| # is check_declared_output_list. |
| return [ |
| DefaultInfo( |
| files = depset(ctx.outputs.outs + [check_no_remaining]), |
| # For kernel_module_test |
| runfiles = ctx.runfiles(files = ctx.outputs.outs), |
| ), |
| _KernelEnvInfo( |
| dependencies = [module_symvers], |
| setup = setup, |
| ), |
| _KernelModuleInfo( |
| kernel_build = ctx.attr.kernel_build, |
| modules_staging_dws = modules_staging_dws, |
| kernel_uapi_headers_dws = kernel_uapi_headers_dws, |
| files = ctx.outputs.outs, |
| ), |
| _KernelUnstrippedModulesInfo( |
| directory = unstripped_dir, |
| ), |
| ] |
| |
| _kernel_module = rule( |
| implementation = _kernel_module_impl, |
| doc = """ |
| """, |
| attrs = { |
| "srcs": attr.label_list( |
| mandatory = True, |
| allow_files = True, |
| ), |
| "makefile": attr.label_list( |
| allow_files = True, |
| ), |
| "kernel_build": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo, _KernelBuildExtModuleInfo], |
| ), |
| "kernel_module_deps": attr.label_list( |
| providers = [_KernelEnvInfo, _KernelModuleInfo], |
| ), |
| "ext_mod": attr.string(mandatory = True), |
| # Not output_list because it is not a list of labels. The list of |
| # output labels are inferred from name and outs. |
| "outs": attr.output_list(), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_search_and_cp_output": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:search_and_cp_output.py"), |
| doc = "Label referring to the script to process outputs", |
| ), |
| "_check_declared_output_list": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:check_declared_output_list.py"), |
| ), |
| "_config_is_stamp": attr.label(default = "//build/kernel/kleaf:config_stamp"), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def kernel_module( |
| name, |
| kernel_build, |
| outs = None, |
| srcs = None, |
| kernel_module_deps = None, |
| **kwargs): |
| """Generates a rule that builds an external kernel module. |
| |
| Example: |
| ``` |
| kernel_module( |
| name = "nfc", |
| srcs = glob([ |
| "**/*.c", |
| "**/*.h", |
| |
| # If there are Kbuild files, add them |
| "**/Kbuild", |
| # If there are additional makefiles in subdirectories, add them |
| "**/Makefile", |
| ]), |
| outs = ["nfc.ko"], |
| kernel_build = "//common:kernel_aarch64", |
| ) |
| ``` |
| |
| Args: |
| name: Name of this kernel module. |
| srcs: Source files to build this kernel module. If unspecified or value |
| is `None`, it is by default the list in the above example: |
| ``` |
| glob([ |
| "**/*.c", |
| "**/*.h", |
| "**/Kbuild", |
| "**/Makefile", |
| ]) |
| ``` |
| kernel_build: Label referring to the kernel_build module. |
| kernel_module_deps: A list of other kernel_module dependencies. |
| |
| Before building this target, `Modules.symvers` from the targets in |
| `kernel_module_deps` are restored, so this target can be built against |
| them. |
| outs: The expected output files. If unspecified or value is `None`, it |
| is `["{name}.ko"]` by default. |
| |
| For each token `out`, the build rule automatically finds a |
| file named `out` in the legacy kernel modules staging |
| directory. The file is copied to the output directory of |
| this package, with the label `name/out`. |
| |
| - If `out` doesn't contain a slash, subdirectories are searched. |
| |
| Example: |
| ``` |
| kernel_module(name = "nfc", outs = ["nfc.ko"]) |
| ``` |
| |
| The build system copies |
| ``` |
| <legacy modules staging dir>/lib/modules/*/extra/<some subdir>/nfc.ko |
| ``` |
| to |
| ``` |
| <package output dir>/nfc.ko |
| ``` |
| |
| `nfc/nfc.ko` is the label to the file. |
| |
| - If `out` contains slashes, its value is used. The file is |
| also copied to the top of package output directory. |
| |
| For example: |
| ``` |
| kernel_module(name = "nfc", outs = ["foo/nfc.ko"]) |
| ``` |
| |
| The build system copies |
| ``` |
| <legacy modules staging dir>/lib/modules/*/extra/foo/nfc.ko |
| ``` |
| to |
| ``` |
| foo/nfc.ko |
| ``` |
| |
| `nfc/foo/nfc.ko` is the label to the file. |
| |
| The file is also copied to `<package output dir>/nfc.ko`. |
| |
| `nfc/nfc.ko` is the label to the file. |
| |
| See `search_and_cp_output.py` for details. |
| kwargs: Additional attributes to the internal rule, e.g. |
| [`visibility`](https://docs.bazel.build/versions/main/visibility.html). |
| See complete list |
| [here](https://docs.bazel.build/versions/main/be/common-definitions.html#common-attributes). |
| """ |
| kwargs.update( |
| # This should be the exact list of arguments of kernel_module. |
| # Default arguments of _kernel_module go into _kernel_module_set_defaults. |
| name = name, |
| srcs = srcs, |
| kernel_build = kernel_build, |
| kernel_module_deps = kernel_module_deps, |
| outs = outs, |
| ) |
| kwargs = _kernel_module_set_defaults(kwargs) |
| |
| main_kwargs = dict(kwargs) |
| main_kwargs["name"] = name |
| main_kwargs["outs"] = ["{name}/{out}".format(name = name, out = out) for out in main_kwargs["outs"]] |
| _kernel_module(**main_kwargs) |
| |
| kernel_module_test( |
| name = name + "_test", |
| modules = [name], |
| ) |
| |
| # Define external module for sibling kernel_build's. |
| # It may be possible to optimize this to alias some of them with the same |
| # kernel_build, but we don't have a way to get this information in |
| # the load phase right now. |
| for sibling_name in _sibling_names: |
| sibling_kwargs = dict(kwargs) |
| sibling_target_name = name + "_" + sibling_name |
| sibling_kwargs["name"] = sibling_target_name |
| sibling_kwargs["outs"] = ["{sibling_target_name}/{out}".format(sibling_target_name = sibling_target_name, out = out) for out in outs] |
| |
| # This assumes the target is a kernel_build_abi with define_abi_targets |
| # etc., which may not be the case. See below for adding "manual" tag. |
| # TODO(b/231647455): clean up dependencies on implementation details. |
| sibling_kwargs["kernel_build"] = sibling_kwargs["kernel_build"] + "_" + sibling_name |
| if sibling_kwargs.get("kernel_module_deps") != None: |
| sibling_kwargs["kernel_module_deps"] = [dep + "_" + sibling_name for dep in sibling_kwargs["kernel_module_deps"]] |
| |
| # We don't know if {kernel_build}_{sibling_name} exists or not, so |
| # add "manual" tag to prevent it from being built by default. |
| sibling_kwargs["tags"] = sibling_kwargs.get("tags", []) + ["manual"] |
| |
| _kernel_module(**sibling_kwargs) |
| |
| def _kernel_module_set_defaults(kwargs): |
| """ |
| Set default values for `_kernel_module` that can't be specified in |
| `attr.*(default=...)` in rule(). |
| """ |
| if kwargs.get("makefile") == None: |
| kwargs["makefile"] = native.glob(["Makefile"]) |
| |
| if kwargs.get("ext_mod") == None: |
| kwargs["ext_mod"] = native.package_name() |
| |
| if kwargs.get("outs") == None: |
| kwargs["outs"] = ["{}.ko".format(kwargs["name"])] |
| |
| if kwargs.get("srcs") == None: |
| kwargs["srcs"] = native.glob([ |
| "**/*.c", |
| "**/*.h", |
| "**/Kbuild", |
| "**/Makefile", |
| ]) |
| |
| return kwargs |
| |
| def _kernel_modules_install_impl(ctx): |
| _check_kernel_build(ctx.attr.kernel_modules, ctx.attr.kernel_build, ctx.label) |
| |
| # A list of declared files for outputs of kernel_module rules |
| external_modules = [] |
| |
| inputs = [] |
| inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies |
| inputs += ctx.attr.kernel_build[_KernelBuildExtModuleInfo].modules_prepare_deps |
| inputs += ctx.attr.kernel_build[_KernelBuildExtModuleInfo].module_srcs |
| inputs += [ |
| ctx.file._search_and_cp_output, |
| ctx.file._check_duplicated_files_in_archives, |
| ctx.attr.kernel_build[_KernelBuildExtModuleInfo].modules_staging_archive, |
| ] |
| for kernel_module in ctx.attr.kernel_modules: |
| inputs += dws.files(kernel_module[_KernelModuleInfo].modules_staging_dws) |
| |
| for module_file in kernel_module[_KernelModuleInfo].files: |
| declared_file = ctx.actions.declare_file("{}/{}".format(ctx.label.name, module_file.basename)) |
| external_modules.append(declared_file) |
| |
| modules_staging_dws = dws.make(ctx, "{}/staging".format(ctx.label.name)) |
| |
| command = "" |
| command += ctx.attr.kernel_build[_KernelEnvInfo].setup |
| command += ctx.attr.kernel_build[_KernelBuildExtModuleInfo].modules_prepare_setup |
| command += """ |
| # create dirs for modules |
| mkdir -p {modules_staging_dir} |
| # Restore modules_staging_dir from kernel_build |
| tar xf {kernel_build_modules_staging_archive} -C {modules_staging_dir} |
| """.format( |
| modules_staging_dir = modules_staging_dws.directory.path, |
| kernel_build_modules_staging_archive = |
| ctx.attr.kernel_build[_KernelBuildExtModuleInfo].modules_staging_archive.path, |
| ) |
| for kernel_module in ctx.attr.kernel_modules: |
| # Allow directories to be written because we are merging multiple directories into one. |
| # However, don't allow files to be written because we don't expect modules to produce |
| # conflicting files. check_duplicated_files_in_archives further enforces this. |
| command += dws.restore( |
| kernel_module[_KernelModuleInfo].modules_staging_dws, |
| dst = modules_staging_dws.directory.path, |
| options = "-aL --chmod=D+w", |
| ) |
| |
| # TODO(b/194347374): maybe run depmod.sh with CONFIG_SHELL? |
| command += """ |
| # Check if there are duplicated files in modules_staging_archive of |
| # depended kernel_build and kernel_module's |
| {check_duplicated_files_in_archives} {modules_staging_archives} |
| # Set variables |
| if [[ ! -f ${{OUT_DIR}}/include/config/kernel.release ]]; then |
| echo "ERROR: No ${{OUT_DIR}}/include/config/kernel.release" >&2 |
| exit 1 |
| fi |
| kernelrelease=$(cat ${{OUT_DIR}}/include/config/kernel.release 2> /dev/null) |
| mixed_build_prefix= |
| if [[ ${{KBUILD_MIXED_TREE}} ]]; then |
| mixed_build_prefix=${{KBUILD_MIXED_TREE}}/ |
| fi |
| real_modules_staging_dir=$(realpath {modules_staging_dir}) |
| # Run depmod |
| ( |
| cd ${{OUT_DIR}} # for System.map when mixed_build_prefix is not set |
| INSTALL_MOD_PATH=${{real_modules_staging_dir}} ${{ROOT_DIR}}/${{KERNEL_DIR}}/scripts/depmod.sh depmod ${{kernelrelease}} ${{mixed_build_prefix}} |
| ) |
| # Remove symlinks that are dead outside of the sandbox |
| ( |
| symlink="$(ls {modules_staging_dir}/lib/modules/*/source)" |
| if [[ -n "$symlink" ]] && [[ -L "$symlink" ]]; then rm "$symlink"; fi |
| symlink="$(ls {modules_staging_dir}/lib/modules/*/build)" |
| if [[ -n "$symlink" ]] && [[ -L "$symlink" ]]; then rm "$symlink"; fi |
| ) |
| """.format( |
| modules_staging_archives = " ".join( |
| [ctx.attr.kernel_build[_KernelBuildExtModuleInfo].modules_staging_archive.path] + |
| [kernel_module[_KernelModuleInfo].modules_staging_dws.directory.path for kernel_module in ctx.attr.kernel_modules], |
| ), |
| modules_staging_dir = modules_staging_dws.directory.path, |
| check_duplicated_files_in_archives = ctx.file._check_duplicated_files_in_archives.path, |
| ) |
| |
| if external_modules: |
| external_module_dir = external_modules[0].dirname |
| command += """ |
| # Move external modules to declared output location |
| {search_and_cp_output} --srcdir {modules_staging_dir}/lib/modules/*/extra --dstdir {outdir} {filenames} |
| """.format( |
| modules_staging_dir = modules_staging_dws.directory.path, |
| outdir = external_module_dir, |
| filenames = " ".join([declared_file.basename for declared_file in external_modules]), |
| search_and_cp_output = ctx.file._search_and_cp_output.path, |
| ) |
| |
| command += dws.record(modules_staging_dws) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelModulesInstall", |
| inputs = inputs, |
| outputs = external_modules + dws.files(modules_staging_dws), |
| command = command, |
| progress_message = "Running depmod {}".format(ctx.label), |
| ) |
| |
| return [ |
| DefaultInfo(files = depset(external_modules)), |
| _KernelModuleInfo( |
| kernel_build = ctx.attr.kernel_build, |
| modules_staging_dws = modules_staging_dws, |
| ), |
| ] |
| |
| kernel_modules_install = rule( |
| implementation = _kernel_modules_install_impl, |
| doc = """Generates a rule that runs depmod in the module installation directory. |
| |
| When including this rule to the `data` attribute of a `copy_to_dist_dir` rule, |
| all external kernel modules specified in `kernel_modules` are included in |
| distribution. This excludes `module_outs` in `kernel_build` to avoid conflicts. |
| |
| Example: |
| ``` |
| kernel_modules_install( |
| name = "foo_modules_install", |
| kernel_build = ":foo", # A kernel_build rule |
| kernel_modules = [ # kernel_module rules |
| "//path/to/nfc:nfc_module", |
| ], |
| ) |
| kernel_build( |
| name = "foo", |
| outs = ["vmlinux"], |
| module_outs = ["core_module.ko"], |
| ) |
| copy_to_dist_dir( |
| name = "foo_dist", |
| data = [ |
| ":foo", # Includes core_module.ko and vmlinux |
| ":foo_modules_install", # Includes nfc_module |
| ], |
| ) |
| ``` |
| In `foo_dist`, specifying `foo_modules_install` in `data` won't include |
| `core_module.ko`, because it is already included in `foo` in `data`. |
| """, |
| attrs = { |
| "kernel_modules": attr.label_list( |
| providers = [_KernelEnvInfo, _KernelModuleInfo], |
| doc = "A list of labels referring to `kernel_module`s to install. Must have the same `kernel_build` as this rule.", |
| ), |
| "kernel_build": attr.label( |
| providers = [_KernelEnvInfo, _KernelBuildExtModuleInfo], |
| doc = "Label referring to the `kernel_build` module.", |
| ), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| "_check_duplicated_files_in_archives": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:check_duplicated_files_in_archives.py"), |
| doc = "Label referring to the script to process outputs", |
| ), |
| "_search_and_cp_output": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:search_and_cp_output.py"), |
| doc = "Label referring to the script to process outputs", |
| ), |
| }, |
| ) |
| |
| def _kernel_uapi_headers_impl(ctx): |
| out_file = ctx.actions.declare_file("{}/kernel-uapi-headers.tar.gz".format(ctx.label.name)) |
| command = ctx.attr.config[_KernelEnvInfo].setup + """ |
| # Create staging directory |
| mkdir -p {kernel_uapi_headers_dir}/usr |
| # Actual headers_install |
| make -C ${{KERNEL_DIR}} ${{TOOL_ARGS}} O=${{OUT_DIR}} INSTALL_HDR_PATH=$(realpath {kernel_uapi_headers_dir}/usr) headers_install |
| # Create archive |
| tar czf {out_file} --directory={kernel_uapi_headers_dir} usr/ |
| # Delete kernel_uapi_headers_dir because it is not declared |
| rm -rf {kernel_uapi_headers_dir} |
| """.format( |
| out_file = out_file.path, |
| kernel_uapi_headers_dir = out_file.path + "_staging", |
| ) |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelUapiHeaders", |
| inputs = ctx.files.srcs + ctx.attr.config[_KernelEnvInfo].dependencies, |
| outputs = [out_file], |
| progress_message = "Building UAPI kernel headers %s" % ctx.attr.name, |
| command = command, |
| ) |
| |
| return [ |
| DefaultInfo(files = depset([out_file])), |
| ] |
| |
| _kernel_uapi_headers = rule( |
| implementation = _kernel_uapi_headers_impl, |
| doc = """Build kernel-uapi-headers.tar.gz""", |
| attrs = { |
| "srcs": attr.label_list(allow_files = True), |
| "config": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| doc = "the kernel_config target", |
| ), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _merged_kernel_uapi_headers_impl(ctx): |
| kernel_build = ctx.attr.kernel_build |
| base_kernel = kernel_build[_KernelBuildUapiInfo].base_kernel |
| |
| # srcs and dws_srcs are the list of sources to merge. |
| # Early elements = higher priority. srcs has higher priority than dws_srcs. |
| srcs = [] |
| if base_kernel: |
| srcs += base_kernel[_KernelBuildUapiInfo].kernel_uapi_headers.files.to_list() |
| srcs += kernel_build[_KernelBuildUapiInfo].kernel_uapi_headers.files.to_list() |
| dws_srcs = [kernel_module[_KernelModuleInfo].kernel_uapi_headers_dws for kernel_module in ctx.attr.kernel_modules] |
| |
| inputs = srcs + ctx.attr._hermetic_tools[HermeticToolsInfo].deps |
| for dws_src in dws_srcs: |
| inputs += dws.files(dws_src) |
| |
| out_file = ctx.actions.declare_file("{}/kernel-uapi-headers.tar.gz".format(ctx.attr.name)) |
| intermediates_dir = utils.intermediates_dir(ctx) |
| |
| command = "" |
| command += ctx.attr._hermetic_tools[HermeticToolsInfo].setup |
| command += """ |
| mkdir -p {intermediates_dir} |
| """.format( |
| intermediates_dir = intermediates_dir, |
| ) |
| |
| # Extract the source tarballs in low to high priority order. |
| for dws_src in reversed(dws_srcs): |
| # Copy the directory over, overwriting existing files. Add write permission |
| # targets with higher priority can overwrite existing files. |
| command += dws.restore( |
| dws_src, |
| dst = intermediates_dir, |
| options = "-aL --chmod=+w", |
| ) |
| |
| for src in reversed(srcs): |
| command += """ |
| tar xf {src} -C {intermediates_dir} |
| """.format( |
| src = src.path, |
| intermediates_dir = intermediates_dir, |
| ) |
| |
| command += """ |
| tar czf {out_file} -C {intermediates_dir} usr/ |
| rm -rf {intermediates_dir} |
| """.format( |
| out_file = out_file.path, |
| intermediates_dir = intermediates_dir, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| inputs = inputs, |
| outputs = [out_file], |
| progress_message = "Merging kernel-uapi-headers.tar.gz {}".format(ctx.label), |
| command = command, |
| mnemonic = "MergedKernelUapiHeaders", |
| ) |
| return DefaultInfo(files = depset([out_file])) |
| |
| merged_kernel_uapi_headers = rule( |
| implementation = _merged_kernel_uapi_headers_impl, |
| doc = """Merge `kernel-uapi-headers.tar.gz`. |
| |
| On certain devices, kernel modules install additional UAPI headers. Use this |
| rule to add these module UAPI headers to the final `kernel-uapi-headers.tar.gz`. |
| |
| If there are conflicts of file names in the source tarballs, files higher in |
| the list have higher priority: |
| 1. UAPI headers from the `base_kernel` of the `kernel_build` (ususally the GKI build) |
| 2. UAPI headers from the `kernel_build` (usually the device build) |
| 3. UAPI headers from ``kernel_modules`. Order among the modules are undetermined. |
| """, |
| attrs = { |
| "kernel_build": attr.label( |
| doc = "The `kernel_build`", |
| mandatory = True, |
| providers = [_KernelBuildUapiInfo], |
| ), |
| "kernel_modules": attr.label_list( |
| doc = """A list of external `kernel_module`s to merge `kernel-uapi-headers.tar.gz`""", |
| providers = [_KernelModuleInfo], |
| ), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _kernel_headers_impl(ctx): |
| inputs = [] |
| inputs += ctx.files.srcs |
| inputs += ctx.attr.env[_KernelEnvInfo].dependencies |
| inputs += [ |
| ctx.attr.kernel_build[_KernelBuildInfo].out_dir_kernel_headers_tar, |
| ] |
| out_file = ctx.actions.declare_file("{}/kernel-headers.tar.gz".format(ctx.label.name)) |
| command = ctx.attr.env[_KernelEnvInfo].setup + """ |
| # Restore headers in ${{OUT_DIR}} |
| mkdir -p ${{OUT_DIR}} |
| tar xf {out_dir_kernel_headers_tar} -C ${{OUT_DIR}} |
| # Create archive |
| ( |
| real_out_file=$(realpath {out_file}) |
| cd ${{ROOT_DIR}}/${{KERNEL_DIR}} |
| find arch include ${{OUT_DIR}} -name *.h -print0 \ |
| | tar czf ${{real_out_file}} \ |
| --absolute-names \ |
| --dereference \ |
| --transform "s,.*$OUT_DIR,," \ |
| --transform "s,^,kernel-headers/," \ |
| --null -T - |
| ) |
| """.format( |
| out_file = out_file.path, |
| out_dir_kernel_headers_tar = ctx.attr.kernel_build[_KernelBuildInfo].out_dir_kernel_headers_tar.path, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "KernelHeaders", |
| inputs = inputs, |
| outputs = [out_file], |
| progress_message = "Building kernel headers %s" % ctx.attr.name, |
| command = command, |
| ) |
| |
| return [ |
| DefaultInfo(files = depset([out_file])), |
| ] |
| |
| _kernel_headers = rule( |
| implementation = _kernel_headers_impl, |
| doc = "Build kernel-headers.tar.gz", |
| attrs = { |
| "srcs": attr.label_list(allow_files = True), |
| "kernel_build": attr.label( |
| mandatory = True, |
| providers = [_KernelBuildInfo], # for out_dir_kernel_headers_tar only |
| ), |
| "env": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| ), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _vmlinux_btf_impl(ctx): |
| inputs = [ |
| ctx.file.vmlinux, |
| ] |
| inputs += ctx.attr.env[_KernelEnvInfo].dependencies |
| out_file = ctx.actions.declare_file("{}/vmlinux.btf".format(ctx.label.name)) |
| out_dir = out_file.dirname |
| command = ctx.attr.env[_KernelEnvInfo].setup + """ |
| mkdir -p {out_dir} |
| cp -Lp {vmlinux} {vmlinux_btf} |
| pahole -J {vmlinux_btf} |
| llvm-strip --strip-debug {vmlinux_btf} |
| """.format( |
| vmlinux = ctx.file.vmlinux.path, |
| vmlinux_btf = out_file.path, |
| out_dir = out_dir, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "VmlinuxBtf", |
| inputs = inputs, |
| outputs = [out_file], |
| progress_message = "Building vmlinux.btf {}".format(ctx.label), |
| command = command, |
| ) |
| return DefaultInfo(files = depset([out_file])) |
| |
| _vmlinux_btf = rule( |
| implementation = _vmlinux_btf_impl, |
| doc = "Build vmlinux.btf", |
| attrs = { |
| "vmlinux": attr.label( |
| mandatory = True, |
| allow_single_file = True, |
| ), |
| "env": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo], |
| ), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _build_modules_image_impl_common( |
| ctx, |
| what, |
| outputs, |
| build_command, |
| modules_staging_dir, |
| implicit_outputs = None, |
| additional_inputs = None, |
| mnemonic = None): |
| """Command implementation for building images that directly contain modules. |
| |
| Args: |
| ctx: ctx |
| what: what is being built, for logging |
| outputs: list of `ctx.actions.declare_file` |
| build_command: the command to build `outputs` and `implicit_outputs` |
| modules_staging_dir: a staging directory for module installation |
| implicit_outputs: like `outputs`, but not installed to `DIST_DIR` (not returned in |
| `DefaultInfo`) |
| """ |
| kernel_build = ctx.attr.kernel_modules_install[_KernelModuleInfo].kernel_build |
| kernel_build_outs = kernel_build[_KernelBuildInfo].outs + kernel_build[_KernelBuildInfo].base_kernel_files |
| system_map = find_file( |
| name = "System.map", |
| files = kernel_build_outs, |
| required = True, |
| what = "{}: outs of dependent kernel_build {}".format(ctx.label, kernel_build), |
| ) |
| modules_install_staging_dws = ctx.attr.kernel_modules_install[_KernelModuleInfo].modules_staging_dws |
| |
| inputs = [] |
| if additional_inputs != None: |
| inputs += additional_inputs |
| inputs += [ |
| system_map, |
| ] |
| inputs += dws.files(modules_install_staging_dws) |
| inputs += ctx.files.deps |
| inputs += kernel_build[_KernelEnvInfo].dependencies |
| |
| command_outputs = [] |
| command_outputs += outputs |
| if implicit_outputs != None: |
| command_outputs += implicit_outputs |
| |
| command = "" |
| command += kernel_build[_KernelEnvInfo].setup |
| |
| for attr_name in ( |
| "modules_list", |
| "modules_blocklist", |
| "modules_options", |
| "vendor_dlkm_modules_list", |
| "vendor_dlkm_modules_blocklist", |
| "vendor_dlkm_props", |
| ): |
| # Checks if attr_name is a valid attribute name in the current rule. |
| # If not, do not touch its value. |
| if not hasattr(ctx.file, attr_name): |
| continue |
| |
| # If it is a valid attribute name, set environment variable to the path if the argument is |
| # supplied, otherwise set environment variable to empty. |
| file = getattr(ctx.file, attr_name) |
| path = "" |
| if file != None: |
| path = file.path |
| inputs.append(file) |
| command += """ |
| {name}={path} |
| """.format( |
| name = attr_name.upper(), |
| path = path, |
| ) |
| |
| # Allow writing to files because create_modules_staging wants to overwrite modules.order. |
| command += dws.restore( |
| modules_install_staging_dws, |
| dst = modules_staging_dir, |
| options = "-aL --chmod=F+w", |
| ) |
| |
| command += """ |
| # Restore System.map to DIST_DIR for run_depmod in create_modules_staging |
| mkdir -p ${{DIST_DIR}} |
| cp {system_map} ${{DIST_DIR}}/System.map |
| |
| {build_command} |
| """.format( |
| system_map = system_map.path, |
| build_command = build_command, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = mnemonic, |
| inputs = inputs, |
| outputs = command_outputs, |
| progress_message = "Building {} {}".format(what, ctx.label), |
| command = command, |
| ) |
| return DefaultInfo(files = depset(outputs)) |
| |
| def _build_modules_image_attrs_common(additional = None): |
| """Common attrs for rules that builds images that directly contain modules.""" |
| ret = { |
| "kernel_modules_install": attr.label( |
| mandatory = True, |
| providers = [_KernelModuleInfo], |
| ), |
| "deps": attr.label_list( |
| allow_files = True, |
| ), |
| "_debug_print_scripts": attr.label( |
| default = "//build/kernel/kleaf:debug_print_scripts", |
| ), |
| } |
| if additional != None: |
| ret.update(additional) |
| return ret |
| |
| _InitramfsInfo = provider(fields = { |
| "initramfs_img": "Output image", |
| "initramfs_staging_archive": "Archive of initramfs staging directory", |
| }) |
| |
| def _initramfs_impl(ctx): |
| initramfs_img = ctx.actions.declare_file("{}/initramfs.img".format(ctx.label.name)) |
| modules_load = ctx.actions.declare_file("{}/modules.load".format(ctx.label.name)) |
| vendor_boot_modules_load = ctx.outputs.vendor_boot_modules_load |
| initramfs_staging_archive = ctx.actions.declare_file("{}/initramfs_staging_archive.tar.gz".format(ctx.label.name)) |
| |
| outputs = [ |
| initramfs_img, |
| modules_load, |
| vendor_boot_modules_load, |
| ] |
| |
| modules_staging_dir = initramfs_img.dirname + "/staging" |
| initramfs_staging_dir = modules_staging_dir + "/initramfs_staging" |
| |
| command = """ |
| mkdir -p {initramfs_staging_dir} |
| # Build initramfs |
| create_modules_staging "${{MODULES_LIST}}" {modules_staging_dir} \ |
| {initramfs_staging_dir} "${{MODULES_BLOCKLIST}}" "-e" |
| modules_root_dir=$(echo {initramfs_staging_dir}/lib/modules/*) |
| cp ${{modules_root_dir}}/modules.load {modules_load} |
| cp ${{modules_root_dir}}/modules.load {vendor_boot_modules_load} |
| echo "${{MODULES_OPTIONS}}" > ${{modules_root_dir}}/modules.options |
| mkbootfs "{initramfs_staging_dir}" >"{modules_staging_dir}/initramfs.cpio" |
| ${{RAMDISK_COMPRESS}} "{modules_staging_dir}/initramfs.cpio" >"{initramfs_img}" |
| # Archive initramfs_staging_dir |
| tar czf {initramfs_staging_archive} -C {initramfs_staging_dir} . |
| # Remove staging directories |
| rm -rf {initramfs_staging_dir} |
| """.format( |
| modules_staging_dir = modules_staging_dir, |
| initramfs_staging_dir = initramfs_staging_dir, |
| modules_load = modules_load.path, |
| vendor_boot_modules_load = vendor_boot_modules_load.path, |
| initramfs_img = initramfs_img.path, |
| initramfs_staging_archive = initramfs_staging_archive.path, |
| ) |
| |
| default_info = _build_modules_image_impl_common( |
| ctx = ctx, |
| what = "initramfs", |
| outputs = outputs, |
| build_command = command, |
| modules_staging_dir = modules_staging_dir, |
| implicit_outputs = [ |
| initramfs_staging_archive, |
| ], |
| mnemonic = "Initramfs", |
| ) |
| return [ |
| default_info, |
| _InitramfsInfo( |
| initramfs_img = initramfs_img, |
| initramfs_staging_archive = initramfs_staging_archive, |
| ), |
| ] |
| |
| _initramfs = rule( |
| implementation = _initramfs_impl, |
| doc = """Build initramfs. |
| |
| When included in a `copy_to_dist_dir` rule, this rule copies the following to `DIST_DIR`: |
| - `initramfs.img` |
| - `modules.load` |
| - `vendor_boot.modules.load` |
| |
| An additional label, `{name}/vendor_boot.modules.load`, is declared to point to the |
| corresponding files. |
| """, |
| attrs = _build_modules_image_attrs_common({ |
| "vendor_boot_modules_load": attr.output(), |
| "modules_list": attr.label(allow_single_file = True), |
| "modules_blocklist": attr.label(allow_single_file = True), |
| "modules_options": attr.label(allow_single_file = True), |
| }), |
| ) |
| |
| def _system_dlkm_image_impl(ctx): |
| system_dlkm_img = ctx.actions.declare_file("{}/system_dlkm.img".format(ctx.label.name)) |
| system_dlkm_staging_archive = ctx.actions.declare_file("{}/system_dlkm_staging_archive.tar.gz".format(ctx.label.name)) |
| |
| modules_staging_dir = system_dlkm_img.dirname + "/staging" |
| system_dlkm_staging_dir = modules_staging_dir + "/system_dlkm_staging" |
| |
| command = """ |
| mkdir -p {system_dlkm_staging_dir} |
| # Build system_dlkm.img |
| create_modules_staging "${{MODULES_LIST}}" {modules_staging_dir} \ |
| {system_dlkm_staging_dir} "${{MODULES_BLOCKLIST}}" "-e" |
| modules_root_dir=$(ls {system_dlkm_staging_dir}/lib/modules/*) |
| # Re-sign the stripped modules using kernel build time key |
| for module in $(find {system_dlkm_staging_dir} -type f -name '*.ko'); do |
| "${{OUT_DIR}}"/scripts/sign-file sha1 \ |
| "${{OUT_DIR}}"/certs/signing_key.pem \ |
| "${{OUT_DIR}}"/certs/signing_key.x509 "${{module}}" |
| done |
| # Build system_dlkm.img with signed GKI modules |
| mkfs.erofs -zlz4hc "{system_dlkm_img}" "{system_dlkm_staging_dir}" |
| # No need to sign the image as modules are signed; add hash footer |
| avbtool add_hashtree_footer \ |
| --partition_name system_dlkm \ |
| --image "{system_dlkm_img}" |
| # Archive system_dlkm_staging_dir |
| tar czf {system_dlkm_staging_archive} -C {system_dlkm_staging_dir} . |
| # Remove staging directories |
| rm -rf {system_dlkm_staging_dir} |
| """.format( |
| modules_staging_dir = modules_staging_dir, |
| system_dlkm_staging_dir = system_dlkm_staging_dir, |
| system_dlkm_img = system_dlkm_img.path, |
| system_dlkm_staging_archive = system_dlkm_staging_archive.path, |
| ) |
| |
| default_info = _build_modules_image_impl_common( |
| ctx = ctx, |
| what = "system_dlkm", |
| outputs = [system_dlkm_img, system_dlkm_staging_archive], |
| build_command = command, |
| modules_staging_dir = modules_staging_dir, |
| mnemonic = "SystemDlkmImage", |
| ) |
| return [default_info] |
| |
| _system_dlkm_image = rule( |
| implementation = _system_dlkm_image_impl, |
| doc = """Build system_dlkm.img an erofs image with GKI modules. |
| |
| When included in a `copy_to_dist_dir` rule, this rule copies the `system_dlkm.img` to `DIST_DIR`. |
| |
| """, |
| attrs = _build_modules_image_attrs_common({ |
| "modules_list": attr.label(allow_single_file = True), |
| "modules_blocklist": attr.label(allow_single_file = True), |
| }), |
| ) |
| |
| def _vendor_dlkm_image_impl(ctx): |
| vendor_dlkm_img = ctx.actions.declare_file("{}/vendor_dlkm.img".format(ctx.label.name)) |
| vendor_dlkm_modules_load = ctx.actions.declare_file("{}/vendor_dlkm.modules.load".format(ctx.label.name)) |
| vendor_dlkm_modules_blocklist = ctx.actions.declare_file("{}/vendor_dlkm.modules.blocklist".format(ctx.label.name)) |
| modules_staging_dir = vendor_dlkm_img.dirname + "/staging" |
| vendor_dlkm_staging_dir = modules_staging_dir + "/vendor_dlkm_staging" |
| command = """ |
| # Restore vendor_boot.modules.load |
| cp {vendor_boot_modules_load} ${{DIST_DIR}}/vendor_boot.modules.load |
| # Build vendor_dlkm |
| mkdir -p {vendor_dlkm_staging_dir} |
| ( |
| MODULES_STAGING_DIR={modules_staging_dir} |
| VENDOR_DLKM_STAGING_DIR={vendor_dlkm_staging_dir} |
| build_vendor_dlkm |
| ) |
| # Move output files into place |
| mv "${{DIST_DIR}}/vendor_dlkm.img" {vendor_dlkm_img} |
| mv "${{DIST_DIR}}/vendor_dlkm.modules.load" {vendor_dlkm_modules_load} |
| if [[ -f "${{DIST_DIR}}/vendor_dlkm.modules.blocklist" ]]; then |
| mv "${{DIST_DIR}}/vendor_dlkm.modules.blocklist" {vendor_dlkm_modules_blocklist} |
| else |
| : > {vendor_dlkm_modules_blocklist} |
| fi |
| # Remove staging directories |
| rm -rf {vendor_dlkm_staging_dir} |
| """.format( |
| vendor_boot_modules_load = ctx.file.vendor_boot_modules_load.path, |
| modules_staging_dir = modules_staging_dir, |
| vendor_dlkm_staging_dir = vendor_dlkm_staging_dir, |
| vendor_dlkm_img = vendor_dlkm_img.path, |
| vendor_dlkm_modules_load = vendor_dlkm_modules_load.path, |
| vendor_dlkm_modules_blocklist = vendor_dlkm_modules_blocklist.path, |
| ) |
| |
| return _build_modules_image_impl_common( |
| ctx = ctx, |
| what = "vendor_dlkm", |
| outputs = [vendor_dlkm_img, vendor_dlkm_modules_load, vendor_dlkm_modules_blocklist], |
| build_command = command, |
| modules_staging_dir = modules_staging_dir, |
| additional_inputs = [ctx.file.vendor_boot_modules_load], |
| mnemonic = "VendorDlkmImage", |
| ) |
| |
| _vendor_dlkm_image = rule( |
| implementation = _vendor_dlkm_image_impl, |
| doc = """Build vendor_dlkm image. |
| |
| Execute `build_vendor_dlkm` in `build_utils.sh`. |
| |
| When included in a `copy_to_dist_dir` rule, this rule copies a `vendor_dlkm.img` to `DIST_DIR`. |
| """, |
| attrs = _build_modules_image_attrs_common({ |
| "vendor_boot_modules_load": attr.label( |
| allow_single_file = True, |
| doc = """File to `vendor_boot.modules.load`. |
| |
| Modules listed in this file is stripped away from the `vendor_dlkm` image.""", |
| ), |
| "vendor_dlkm_modules_list": attr.label(allow_single_file = True), |
| "vendor_dlkm_modules_blocklist": attr.label(allow_single_file = True), |
| "vendor_dlkm_props": attr.label(allow_single_file = True), |
| }), |
| ) |
| |
| def _boot_images_impl(ctx): |
| initramfs_staging_archive = ctx.attr.initramfs[_InitramfsInfo].initramfs_staging_archive |
| outdir = ctx.actions.declare_directory(ctx.label.name) |
| modules_staging_dir = outdir.path + "/staging" |
| initramfs_staging_dir = modules_staging_dir + "/initramfs_staging" |
| mkbootimg_staging_dir = modules_staging_dir + "/mkbootimg_staging" |
| |
| outs = [] |
| for out in ctx.outputs.outs: |
| outs.append(out.short_path[len(outdir.short_path) + 1:]) |
| |
| kernel_build_outs = ctx.attr.kernel_build[_KernelBuildInfo].outs + ctx.attr.kernel_build[_KernelBuildInfo].base_kernel_files |
| |
| inputs = [ |
| ctx.attr.initramfs[_InitramfsInfo].initramfs_img, |
| initramfs_staging_archive, |
| ctx.file.mkbootimg, |
| ctx.file._search_and_cp_output, |
| ] |
| inputs += ctx.files.deps |
| inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies |
| inputs += kernel_build_outs |
| inputs += ctx.files.vendor_ramdisk_binaries |
| |
| command = "" |
| command += ctx.attr.kernel_build[_KernelEnvInfo].setup |
| |
| vendor_boot_flag_cmd = "" |
| if not ctx.attr.vendor_boot_name: |
| vendor_boot_flag_cmd = """ |
| SKIP_VENDOR_BOOT=1 |
| BUILD_VENDOR_KERNEL_BOOT= |
| """ |
| elif ctx.attr.vendor_boot_name == "vendor_boot": |
| vendor_boot_flag_cmd = """ |
| SKIP_VENDOR_BOOT= |
| BUILD_VENDOR_KERNEL_BOOT= |
| """ |
| elif ctx.attr.vendor_boot_name == "vendor_kernel_boot": |
| vendor_boot_flag_cmd = """ |
| SKIP_VENDOR_BOOT= |
| BUILD_VENDOR_KERNEL_BOOT=1 |
| """ |
| else: |
| fail("{}: unknown vendor_boot_name {}".format(ctx.label, ctx.attr.vendor_boot_name)) |
| |
| if ctx.files.vendor_ramdisk_binaries: |
| # build_utils.sh uses singular VENDOR_RAMDISK_BINARY |
| command += """ |
| VENDOR_RAMDISK_BINARY="{vendor_ramdisk_binaries}" |
| """.format( |
| vendor_ramdisk_binaries = " ".join([file.path for file in ctx.files.vendor_ramdisk_binaries]), |
| ) |
| |
| command += """ |
| # Create and restore initramfs_staging_dir |
| mkdir -p {initramfs_staging_dir} |
| tar xf {initramfs_staging_archive} -C {initramfs_staging_dir} |
| # Create and restore DIST_DIR. |
| # We don't need all of *_for_dist. Copying all declared outputs of kernel_build is |
| # sufficient. |
| mkdir -p ${{DIST_DIR}} |
| cp {kernel_build_outs} ${{DIST_DIR}} |
| cp {initramfs_img} ${{DIST_DIR}}/initramfs.img |
| # Build boot images |
| ( |
| {vendor_boot_flag_cmd} |
| INITRAMFS_STAGING_DIR={initramfs_staging_dir} |
| MKBOOTIMG_STAGING_DIR=$(realpath {mkbootimg_staging_dir}) |
| build_boot_images |
| ) |
| {search_and_cp_output} --srcdir ${{DIST_DIR}} --dstdir {outdir} {outs} |
| # Remove staging directories |
| rm -rf {modules_staging_dir} |
| """.format( |
| initramfs_staging_dir = initramfs_staging_dir, |
| mkbootimg_staging_dir = mkbootimg_staging_dir, |
| search_and_cp_output = ctx.file._search_and_cp_output.path, |
| outdir = outdir.path, |
| outs = " ".join(outs), |
| modules_staging_dir = modules_staging_dir, |
| initramfs_staging_archive = initramfs_staging_archive.path, |
| initramfs_img = ctx.attr.initramfs[_InitramfsInfo].initramfs_img.path, |
| kernel_build_outs = " ".join([out.path for out in kernel_build_outs]), |
| vendor_boot_flag_cmd = vendor_boot_flag_cmd, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "BootImages", |
| inputs = inputs, |
| outputs = ctx.outputs.outs + [outdir], |
| progress_message = "Building boot images {}".format(ctx.label), |
| command = command, |
| ) |
| |
| _boot_images = rule( |
| implementation = _boot_images_impl, |
| doc = """Build boot images, including `boot.img`, `vendor_boot.img`, etc. |
| |
| Execute `build_boot_images` in `build_utils.sh`.""", |
| attrs = { |
| "kernel_build": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo, _KernelBuildInfo], |
| ), |
| "initramfs": attr.label( |
| providers = [_InitramfsInfo], |
| ), |
| "deps": attr.label_list( |
| allow_files = True, |
| ), |
| "outs": attr.output_list(), |
| "mkbootimg": attr.label( |
| allow_single_file = True, |
| default = "//tools/mkbootimg:mkbootimg.py", |
| ), |
| "vendor_boot_name": attr.string(doc = """ |
| * If `"vendor_boot"`, build `vendor_boot.img` |
| * If `"vendor_kernel_boot"`, build `vendor_kernel_boot.img` |
| * If `None`, skip `vendor_boot`. |
| """, values = ["vendor_boot", "vendor_kernel_boot"]), |
| "vendor_ramdisk_binaries": attr.label_list(allow_files = True), |
| "_debug_print_scripts": attr.label( |
| default = "//build/kernel/kleaf:debug_print_scripts", |
| ), |
| "_search_and_cp_output": attr.label( |
| allow_single_file = True, |
| default = Label("//build/kernel/kleaf:search_and_cp_output.py"), |
| ), |
| }, |
| ) |
| |
| def _dtbo_impl(ctx): |
| output = ctx.actions.declare_file("{}/dtbo.img".format(ctx.label.name)) |
| inputs = [] |
| inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies |
| inputs += ctx.files.srcs |
| command = "" |
| command += ctx.attr.kernel_build[_KernelEnvInfo].setup |
| |
| command += """ |
| # make dtbo |
| mkdtimg create {output} ${{MKDTIMG_FLAGS}} {srcs} |
| """.format( |
| output = output.path, |
| srcs = " ".join([f.path for f in ctx.files.srcs]), |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| mnemonic = "Dtbo", |
| inputs = inputs, |
| outputs = [output], |
| progress_message = "Building dtbo {}".format(ctx.label), |
| command = command, |
| ) |
| return DefaultInfo(files = depset([output])) |
| |
| _dtbo = rule( |
| implementation = _dtbo_impl, |
| doc = "Build dtbo.", |
| attrs = { |
| "kernel_build": attr.label( |
| mandatory = True, |
| providers = [_KernelEnvInfo, _KernelBuildInfo], |
| ), |
| "srcs": attr.label_list( |
| allow_files = True, |
| ), |
| "_debug_print_scripts": attr.label( |
| default = "//build/kernel/kleaf:debug_print_scripts", |
| ), |
| }, |
| ) |
| |
| def kernel_images( |
| name, |
| kernel_modules_install, |
| kernel_build = None, |
| build_initramfs = None, |
| build_vendor_dlkm = None, |
| build_boot = None, |
| build_vendor_boot = None, |
| build_vendor_kernel_boot = None, |
| build_system_dlkm = None, |
| build_dtbo = None, |
| dtbo_srcs = None, |
| mkbootimg = None, |
| deps = None, |
| boot_image_outs = None, |
| modules_list = None, |
| modules_blocklist = None, |
| modules_options = None, |
| vendor_ramdisk_binaries = None, |
| vendor_dlkm_modules_list = None, |
| vendor_dlkm_modules_blocklist = None, |
| vendor_dlkm_props = None): |
| """Build multiple kernel images. |
| |
| Args: |
| name: name of this rule, e.g. `kernel_images`, |
| kernel_modules_install: A `kernel_modules_install` rule. |
| |
| The main kernel build is inferred from the `kernel_build` attribute of the |
| specified `kernel_modules_install` rule. The main kernel build must contain |
| `System.map` in `outs` (which is included if you use `aarch64_outs` or |
| `x86_64_outs` from `common_kernels.bzl`). |
| kernel_build: A `kernel_build` rule. Must specify if `build_boot`. |
| mkbootimg: Path to the mkbootimg.py script which builds boot.img. |
| Keep in sync with `MKBOOTIMG_PATH`. Only used if `build_boot`. If `None`, |
| default to `//tools/mkbootimg:mkbootimg.py`. |
| deps: Additional dependencies to build images. |
| |
| This must include the following: |
| - For `initramfs`: |
| - The file specified by `MODULES_LIST` |
| - The file specified by `MODULES_BLOCKLIST`, if `MODULES_BLOCKLIST` is set |
| - For `vendor_dlkm` image: |
| - The file specified by `VENDOR_DLKM_MODULES_LIST` |
| - The file specified by `VENDOR_DLKM_MODULES_BLOCKLIST`, if set |
| - The file specified by `VENDOR_DLKM_PROPS`, if set |
| - The file specified by `selinux_fc` in `VENDOR_DLKM_PROPS`, if set |
| |
| boot_image_outs: A list of output files that will be installed to `DIST_DIR` when |
| `build_boot_images` in `build/kernel/build_utils.sh` is executed. |
| |
| You may leave out `vendor_boot.img` from the list. It is automatically added when |
| `build_vendor_boot = True`. |
| |
| If `build_boot` is equal to `False`, the default is empty. |
| |
| If `build_boot` is equal to `True`, the default list assumes the following: |
| - `BOOT_IMAGE_FILENAME` is not set (which takes default value `boot.img`), or is set to |
| `"boot.img"` |
| - `vendor_boot.img` if `build_vendor_boot` |
| - `RAMDISK_EXT=lz4`. If the build configuration has a different value, replace |
| `ramdisk.lz4` with `ramdisk.{RAMDISK_EXT}` accordingly. |
| - `BOOT_IMAGE_HEADER_VERSION >= 4`, which creates `vendor-bootconfig.img` to contain |
| `VENDOR_BOOTCONFIG` |
| - The list contains `dtb.img` |
| build_initramfs: Whether to build initramfs. Keep in sync with `BUILD_INITRAMFS`. |
| build_system_dlkm: Whether to build system_dlkm.img an erofs image with GKI modules. |
| build_vendor_dlkm: Whether to build `vendor_dlkm` image. It must be set if |
| `vendor_dlkm_modules_list` is set. |
| |
| Note: at the time of writing (Jan 2022), unlike `build.sh`, |
| `vendor_dlkm.modules.blocklist` is **always** created |
| regardless of the value of `VENDOR_DLKM_MODULES_BLOCKLIST`. |
| If `build_vendor_dlkm()` in `build_utils.sh` does not generate |
| `vendor_dlkm.modules.blocklist`, an empty file is created. |
| build_boot: Whether to build boot image. It must be set if either `BUILD_BOOT_IMG` |
| or `BUILD_VENDOR_BOOT_IMG` is set. |
| |
| This depends on `initramfs` and `kernel_build`. Hence, if this is set to `True`, |
| `build_initramfs` is implicitly true, and `kernel_build` must be set. |
| build_vendor_boot: Whether to build `vendor_boot.img`. It must be set if either |
| `BUILD_BOOT_IMG` or `BUILD_VENDOR_BOOT_IMG` is set, and `SKIP_VENDOR_BOOT` is not set, |
| and `BUILD_VENDOR_KERNEL_BOOT` is not set. |
| |
| At most **one** of `build_vendor_boot` and `build_vendor_kernel_boot` may be set to |
| `True`. |
| |
| If `True`, requires `build_boot = True`. |
| |
| If `True`, adds `vendor_boot.img` to `boot_image_outs` if not already in the list. |
| |
| build_vendor_kernel_boot: Whether to build `vendor_kernel_boot.img`. It must be set if either |
| `BUILD_BOOT_IMG` or `BUILD_VENDOR_BOOT_IMG` is set, and `SKIP_VENDOR_BOOT` is not set, |
| and `BUILD_VENDOR_KERNEL_BOOT` is set. |
| |
| At most **one** of `build_vendor_boot` and `build_vendor_kernel_boot` may be set to |
| `True`. |
| |
| If `True`, requires `build_boot = True`. |
| |
| If `True`, adds `vendor_kernel_boot.img` to `boot_image_outs` if not already in the list. |
| build_dtbo: Whether to build dtbo image. Keep this in sync with `BUILD_DTBO_IMG`. |
| |
| If `dtbo_srcs` is non-empty, `build_dtbo` is `True` by default. Otherwise it is `False` |
| by default. |
| dtbo_srcs: list of `*.dtbo` files used to package the `dtbo.img`. Keep this in sync |
| with `MKDTIMG_DTBOS`; see example below. |
| |
| If `dtbo_srcs` is non-empty, `build_dtbo` must not be explicitly set to `False`. |
| |
| Example: |
| ``` |
| kernel_build( |
| name = "tuna_kernel", |
| outs = [ |
| "path/to/foo.dtbo", |
| "path/to/bar.dtbo", |
| ], |
| ) |
| kernel_images( |
| name = "tuna_images", |
| kernel_build = ":tuna_kernel", |
| dtbo_srcs = [ |
| ":tuna_kernel/path/to/foo.dtbo", |
| ":tuna_kernel/path/to/bar.dtbo", |
| ] |
| ) |
| ``` |
| modules_list: A file containing list of modules to use for `vendor_boot.modules.load`. |
| |
| This corresponds to `MODULES_LIST` in `build.config` for `build.sh`. |
| modules_blocklist: A file containing a list of modules which are |
| blocked from being loaded. |
| |
| This file is copied directly to staging directory, and should be in the format: |
| ``` |
| blocklist module_name |
| ``` |
| |
| This corresponds to `MODULES_BLOCKLIST` in `build.config` for `build.sh`. |
| modules_options: A `/lib/modules/modules.options` file is created on the ramdisk containing |
| the contents of this variable. |
| |
| Lines should be of the form: |
| ``` |
| options <modulename> <param1>=<val> <param2>=<val> ... |
| ``` |
| |
| This corresponds to `MODULES_OPTIONS` in `build.config` for `build.sh`. |
| vendor_dlkm_modules_list: location of an optional file |
| containing the list of kernel modules which shall be copied into a |
| `vendor_dlkm` partition image. Any modules passed into `MODULES_LIST` which |
| become part of the `vendor_boot.modules.load` will be trimmed from the |
| `vendor_dlkm.modules.load`. |
| |
| This corresponds to `VENDOR_DLKM_MODULES_LIST` in `build.config` for `build.sh`. |
| vendor_dlkm_modules_blocklist: location of an optional file containing a list of modules |
| which are blocked from being loaded. |
| |
| This file is copied directly to the staging directory and should be in the format: |
| ``` |
| blocklist module_name |
| ``` |
| |
| This corresponds to `VENDOR_DLKM_MODULES_BLOCKLIST` in `build.config` for `build.sh`. |
| vendor_dlkm_props: location of a text file containing |
| the properties to be used for creation of a `vendor_dlkm` image |
| (filesystem, partition size, etc). If this is not set (and |
| `build_vendor_dlkm` is), a default set of properties will be used |
| which assumes an ext4 filesystem and a dynamic partition. |
| |
| This corresponds to `VENDOR_DLKM_PROPS` in `build.config` for `build.sh`. |
| vendor_ramdisk_binaries: List of vendor ramdisk binaries |
| which includes the device-specific components of ramdisk like the fstab |
| file and the device-specific rc files. If specifying multiple vendor ramdisks |
| and identical file paths exist in the ramdisks, the file from last ramdisk is used. |
| |
| Note: **order matters**. To prevent buildifier from sorting the list, add the following: |
| ``` |
| # do not sort |
| ``` |
| |
| This corresponds to `VENDOR_RAMDISK_BINARY` in `build.config` for `build.sh`. |
| """ |
| all_rules = [] |
| |
| if build_vendor_boot and build_vendor_kernel_boot: |
| fail("{}: build_vendor_boot and build_vendor_kernel_boot must not be set simultaneously.".format(name)) |
| |
| if build_vendor_boot and not build_boot: |
| fail("{}: build_vendor_boot = True requires build_boot = True.".format(name)) |
| |
| if build_vendor_kernel_boot and not build_boot: |
| fail("{}: build_vendor_kernel_boot = True requires build_boot = True.".format(name)) |
| |
| if build_boot: |
| if build_initramfs == None: |
| build_initramfs = True |
| if not build_initramfs: |
| fail("{}: Must set build_initramfs to True if build_boot".format(name)) |
| if kernel_build == None: |
| fail("{}: Must set kernel_build if build_boot".format(name)) |
| |
| # Set default value for boot_image_outs according to build_boot |
| if boot_image_outs == None: |
| if not build_boot: |
| boot_image_outs = [] |
| else: |
| boot_image_outs = [ |
| "boot.img", |
| "dtb.img", |
| "ramdisk.lz4", |
| "vendor-bootconfig.img", |
| ] |
| |
| boot_image_outs = list(boot_image_outs) |
| if build_vendor_boot and "vendor_boot.img" not in boot_image_outs: |
| boot_image_outs.append("vendor_boot.img") |
| |
| if build_vendor_kernel_boot and "vendor_kernel_boot.img" not in boot_image_outs: |
| boot_image_outs.append("vendor_kernel_boot.img") |
| |
| if build_initramfs: |
| _initramfs( |
| name = "{}_initramfs".format(name), |
| kernel_modules_install = kernel_modules_install, |
| deps = deps, |
| vendor_boot_modules_load = "{}_initramfs/vendor_boot.modules.load".format(name), |
| modules_list = modules_list, |
| modules_blocklist = modules_blocklist, |
| modules_options = modules_options, |
| ) |
| all_rules.append(":{}_initramfs".format(name)) |
| |
| if build_system_dlkm: |
| _system_dlkm_image( |
| name = "{}_system_dlkm_image".format(name), |
| kernel_modules_install = kernel_modules_install, |
| deps = deps, |
| modules_list = modules_list, |
| modules_blocklist = modules_blocklist, |
| ) |
| all_rules.append(":{}_system_dlkm_image".format(name)) |
| |
| if build_vendor_dlkm: |
| _vendor_dlkm_image( |
| name = "{}_vendor_dlkm_image".format(name), |
| kernel_modules_install = kernel_modules_install, |
| vendor_boot_modules_load = "{}_initramfs/vendor_boot.modules.load".format(name), |
| deps = deps, |
| vendor_dlkm_modules_list = vendor_dlkm_modules_list, |
| vendor_dlkm_modules_blocklist = vendor_dlkm_modules_blocklist, |
| vendor_dlkm_props = vendor_dlkm_props, |
| ) |
| all_rules.append(":{}_vendor_dlkm_image".format(name)) |
| |
| if build_boot: |
| if build_vendor_kernel_boot: |
| vendor_boot_name = "vendor_kernel_boot" |
| elif build_vendor_boot: |
| vendor_boot_name = "vendor_boot" |
| else: |
| vendor_boot_name = None |
| _boot_images( |
| name = "{}_boot_images".format(name), |
| kernel_build = kernel_build, |
| outs = ["{}_boot_images/{}".format(name, out) for out in boot_image_outs], |
| deps = deps, |
| initramfs = ":{}_initramfs".format(name), |
| mkbootimg = mkbootimg, |
| vendor_boot_name = vendor_boot_name, |
| vendor_ramdisk_binaries = vendor_ramdisk_binaries, |
| ) |
| all_rules.append(":{}_boot_images".format(name)) |
| |
| if build_dtbo == None: |
| build_dtbo = bool(dtbo_srcs) |
| |
| if dtbo_srcs: |
| if not build_dtbo: |
| fail("{}: build_dtbo must be True if dtbo_srcs is non-empty.") |
| |
| if build_dtbo: |
| _dtbo( |
| name = "{}_dtbo".format(name), |
| srcs = dtbo_srcs, |
| kernel_build = kernel_build, |
| ) |
| all_rules.append(":{}_dtbo".format(name)) |
| |
| native.filegroup( |
| name = name, |
| srcs = all_rules, |
| ) |
| |
| def _kernel_filegroup_impl(ctx): |
| all_deps = ctx.files.srcs + ctx.files.deps |
| |
| # TODO(b/219112010): implement _KernelEnvInfo for the modules_prepare target |
| modules_prepare_out_dir_tar_gz = find_file("modules_prepare_outdir.tar.gz", all_deps, what = ctx.label) |
| modules_prepare_setup = """ |
| # Restore modules_prepare outputs. Assumes env setup. |
| [ -z ${{OUT_DIR}} ] && echo "ERROR: modules_prepare setup run without OUT_DIR set!" >&2 && exit 1 |
| tar xf {outdir_tar_gz} -C ${{OUT_DIR}} |
| """.format(outdir_tar_gz = modules_prepare_out_dir_tar_gz) |
| modules_prepare_deps = [modules_prepare_out_dir_tar_gz] |
| |
| kernel_module_dev_info = _KernelBuildExtModuleInfo( |
| modules_staging_archive = find_file("modules_staging_dir.tar.gz", all_deps, what = ctx.label), |
| modules_prepare_setup = modules_prepare_setup, |
| modules_prepare_deps = modules_prepare_deps, |
| # TODO(b/211515836): module_srcs might also be downloaded |
| module_srcs = _filter_module_srcs(ctx.files.kernel_srcs), |
| collect_unstripped_modules = ctx.attr.collect_unstripped_modules, |
| ) |
| uapi_info = _KernelBuildUapiInfo( |
| kernel_uapi_headers = ctx.attr.kernel_uapi_headers, |
| ) |
| |
| unstripped_modules_info = None |
| for target in ctx.attr.srcs: |
| if _KernelUnstrippedModulesInfo in target: |
| unstripped_modules_info = target[_KernelUnstrippedModulesInfo] |
| break |
| if unstripped_modules_info == None: |
| # Reverse of kernel_unstripped_modules_archive |
| unstripped_modules_archive = find_file("unstripped_modules.tar.gz", all_deps, what = ctx.label, required = True) |
| unstripped_dir = ctx.actions.declare_directory("{}/unstripped".format(ctx.label.name)) |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| tar xf {unstripped_modules_archive} -C $(dirname {unstripped_dir}) $(basename {unstripped_dir}) |
| """ |
| _debug_print_scripts(ctx, command, what = "unstripped_modules_archive") |
| ctx.actions.run_shell( |
| command = command, |
| inputs = ctx.attr._hermetic_tools[HermeticToolsInfo].deps + [ |
| unstripped_modules_archive, |
| ], |
| outputs = [unstripped_dir], |
| progress_message = "Extracting unstripped_modules_archive {}".format(ctx.label), |
| mnemonic = "KernelFilegroupUnstrippedModulesArchive", |
| ) |
| unstripped_modules_info = _KernelUnstrippedModulesInfo(directory = unstripped_dir) |
| |
| return [ |
| DefaultInfo(files = depset(ctx.files.srcs)), |
| kernel_module_dev_info, |
| # TODO(b/219112010): implement _KernelEnvInfo for _kernel_build |
| uapi_info, |
| unstripped_modules_info, |
| ] |
| |
| kernel_filegroup = rule( |
| implementation = _kernel_filegroup_impl, |
| doc = """Specify a list of kernel prebuilts. |
| |
| This is similar to [`filegroup`](https://docs.bazel.build/versions/main/be/general.html#filegroup) |
| that gives a convenient name to a collection of targets, which can be referenced from other rules. |
| |
| It can be used in the `base_kernel` attribute of a [`kernel_build`](#kernel_build). |
| """, |
| attrs = { |
| "srcs": attr.label_list( |
| allow_files = True, |
| doc = """The list of labels that are members of this file group. |
| |
| This usually contains a list of prebuilts, e.g. `vmlinux`, `Image.lz4`, `kernel-headers.tar.gz`, |
| etc. |
| |
| Not to be confused with [`kernel_srcs`](#kernel_filegroup-kernel_srcs).""", |
| ), |
| "deps": attr.label_list( |
| allow_files = True, |
| doc = """A list of additional labels that participates in implementing the providers. |
| |
| This usually contains a list of prebuilts. |
| |
| Unlike srcs, these labels are NOT added to the [`DefaultInfo`](https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html)""", |
| ), |
| "kernel_srcs": attr.label_list( |
| allow_files = True, |
| doc = """A list of files that would have been listed as `srcs` if this rule were a [`kernel_build`](#kernel_build). |
| |
| This is usually a `glob()` of source files. |
| |
| Not to be confused with [`srcs`](#kernel_filegroup-srcs). |
| """, |
| ), |
| "kernel_uapi_headers": attr.label( |
| allow_files = True, |
| doc = """The label pointing to `kernel-uapi-headers.tar.gz`. |
| |
| This attribute should be set to the `kernel-uapi-headers.tar.gz` artifact built by the |
| [`kernel_build`](#kernel_build) macro if the `kernel_filegroup` rule were a `kernel_build`. |
| |
| Setting this attribute allows [`merged_kernel_uapi_headers`](#merged_kernel_uapi_headers) to |
| work properly when this `kernel_filegroup` is set to the `base_kernel`. |
| |
| For example: |
| ``` |
| kernel_filegroup( |
| name = "kernel_aarch64_prebuilts", |
| srcs = [ |
| "vmlinux", |
| # ... |
| ], |
| kernel_uapi_headers = "kernel-uapi-headers.tar.gz", |
| ) |
| |
| kernel_build( |
| name = "tuna", |
| base_kernel = ":kernel_aarch64_prebuilts", |
| # ... |
| ) |
| |
| merged_kernel_uapi_headers( |
| name = "tuna_merged_kernel_uapi_headers", |
| kernel_build = "tuna", |
| # ... |
| ) |
| ``` |
| """, |
| ), |
| "collect_unstripped_modules": attr.bool( |
| default = True, |
| doc = """See [`kernel_build.collect_unstripped_modules`](#kernel_build-collect_unstripped_modules). |
| |
| Unlike `kernel_build`, this has default value `True` because |
| [`kernel_build_abi`](#kernel_build_abi) sets |
| [`define_abi_targets`](#kernel_build_abi-define_abi_targets) to `True` by |
| default, which in turn sets `collect_unstripped_modules` to `True` by default. |
| """, |
| ), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| }, |
| ) |
| |
| def _kernel_compile_commands_impl(ctx): |
| interceptor_output = ctx.attr.kernel_build[_KernelBuildInfo].interceptor_output |
| if not interceptor_output: |
| fail("{}: kernel_build {} does not have enable_interceptor = True.".format(ctx.label, ctx.attr.kernel_build.label)) |
| compile_commands = ctx.actions.declare_file(ctx.attr.name + "/compile_commands.json") |
| inputs = [interceptor_output] |
| inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies |
| command = ctx.attr.kernel_build[_KernelEnvInfo].setup |
| command += """ |
| # Generate compile_commands.json |
| interceptor_analysis -l {interceptor_output} -o {compile_commands} -t compdb_commands --relative |
| """.format( |
| interceptor_output = interceptor_output.path, |
| compile_commands = compile_commands.path, |
| ) |
| ctx.actions.run_shell( |
| mnemonic = "KernelCompileCommands", |
| inputs = inputs, |
| outputs = [compile_commands], |
| command = command, |
| progress_message = "Building compile_commands.json {}".format(ctx.label), |
| ) |
| return DefaultInfo(files = depset([compile_commands])) |
| |
| kernel_compile_commands = rule( |
| implementation = _kernel_compile_commands_impl, |
| doc = """ |
| Generate `compile_commands.json` from a `kernel_build`. |
| """, |
| attrs = { |
| "kernel_build": attr.label( |
| mandatory = True, |
| doc = "The `kernel_build` rule to extract from.", |
| providers = [_KernelEnvInfo, _KernelBuildInfo], |
| ), |
| }, |
| ) |
| |
| def _kernel_kythe_impl(ctx): |
| compile_commands = ctx.file.compile_commands |
| all_kzip = ctx.actions.declare_file(ctx.attr.name + "/all.kzip") |
| runextractor_error = ctx.actions.declare_file(ctx.attr.name + "/runextractor_error.log") |
| intermediates_dir = utils.intermediates_dir(ctx) |
| kzip_dir = intermediates_dir + "/kzip" |
| extracted_kzip_dir = intermediates_dir + "/extracted" |
| transitive_inputs = [src.files for src in ctx.attr.kernel_build[_SrcsInfo].srcs] |
| inputs = [compile_commands] |
| inputs += ctx.attr.kernel_build[_KernelEnvInfo].dependencies |
| command = ctx.attr.kernel_build[_KernelEnvInfo].setup |
| command += """ |
| # Copy compile_commands.json to root |
| cp {compile_commands} ${{ROOT_DIR}} |
| # Prepare directories |
| mkdir -p {kzip_dir} {extracted_kzip_dir} ${{OUT_DIR}} |
| # Define env variables |
| export KYTHE_ROOT_DIRECTORY=${{ROOT_DIR}} |
| export KYTHE_OUTPUT_DIRECTORY={kzip_dir} |
| export KYTHE_CORPUS="{corpus}" |
| # Generate kzips |
| runextractor compdb -extractor $(which cxx_extractor) 2> {runextractor_error} || true |
| |
| # Package it all into a single .kzip, ignoring duplicates. |
| for zip in $(find {kzip_dir} -name '*.kzip'); do |
| unzip -qn "${{zip}}" -d {extracted_kzip_dir} |
| done |
| soong_zip -C {extracted_kzip_dir} -D {extracted_kzip_dir} -o {all_kzip} |
| # Clean up directories |
| rm -rf {kzip_dir} |
| rm -rf {extracted_kzip_dir} |
| """.format( |
| compile_commands = compile_commands.path, |
| kzip_dir = kzip_dir, |
| extracted_kzip_dir = extracted_kzip_dir, |
| corpus = ctx.attr.corpus, |
| all_kzip = all_kzip.path, |
| runextractor_error = runextractor_error.path, |
| ) |
| ctx.actions.run_shell( |
| mnemonic = "KernelKythe", |
| inputs = depset(inputs, transitive = transitive_inputs), |
| outputs = [all_kzip, runextractor_error], |
| command = command, |
| progress_message = "Building Kythe source code index (kzip) {}".format(ctx.label), |
| ) |
| |
| return DefaultInfo(files = depset([ |
| all_kzip, |
| runextractor_error, |
| ])) |
| |
| kernel_kythe = rule( |
| implementation = _kernel_kythe_impl, |
| doc = """ |
| Extract Kythe source code index (kzip file) from a `kernel_build`. |
| """, |
| attrs = { |
| "kernel_build": attr.label( |
| mandatory = True, |
| doc = "The `kernel_build` target to extract from.", |
| providers = [_KernelEnvInfo, _KernelBuildInfo], |
| aspects = [_srcs_aspect], |
| ), |
| "compile_commands": attr.label( |
| mandatory = True, |
| allow_single_file = True, |
| doc = "The `compile_commands.json`, or a `kernel_compile_commands` target.", |
| ), |
| "corpus": attr.string( |
| default = "android.googlesource.com/kernel/superproject", |
| doc = "The value of `KYTHE_CORPUS`. See [kythe.io/examples](https://kythe.io/examples).", |
| ), |
| }, |
| ) |
| |
| def _kernel_extracted_symbols_impl(ctx): |
| if ctx.attr.kernel_build_notrim[_KernelBuildAbiInfo].trim_nonlisted_kmi: |
| fail("{}: Requires `kernel_build` {} to have `trim_nonlisted_kmi = False`.".format( |
| ctx.label, |
| ctx.attr.kernel_build_notrim.label, |
| )) |
| |
| if ctx.attr.kmi_symbol_list_add_only and not ctx.file.src: |
| fail("{}: kmi_symbol_list_add_only requires kmi_symbol_list.".format(ctx.label)) |
| |
| out = ctx.actions.declare_file("{}/extracted_symbols".format(ctx.attr.name)) |
| intermediates_dir = utils.intermediates_dir(ctx) |
| |
| vmlinux = find_file(name = "vmlinux", files = ctx.files.kernel_build_notrim, what = "{}: kernel_build_notrim".format(ctx.attr.name), required = True) |
| in_tree_modules = find_files(suffix = ".ko", files = ctx.files.kernel_build_notrim, what = "{}: kernel_build_notrim".format(ctx.attr.name)) |
| srcs = [vmlinux] + in_tree_modules |
| for kernel_module in ctx.attr.kernel_modules: # external modules |
| srcs += kernel_module[_KernelModuleInfo].files |
| |
| inputs = [ctx.file._extract_symbols] |
| inputs += srcs |
| inputs += ctx.attr.kernel_build_notrim[_KernelEnvInfo].dependencies |
| |
| cp_src_cmd = "" |
| flags = ["--symbol-list", out.path] |
| if not ctx.attr.module_grouping: |
| flags.append("--skip-module-grouping") |
| if ctx.attr.kmi_symbol_list_add_only: |
| flags.append("--additions-only") |
| inputs.append(ctx.file.src) |
| |
| # Follow symlinks because we are in the execroot. |
| # Do not preserve permissions because we are overwriting the file immediately. |
| cp_src_cmd = "cp -L {src} {out}".format( |
| src = ctx.file.src.path, |
| out = out.path, |
| ) |
| # Always consider symbols exported by modules |
| flags.append("--include-module-exports") |
| |
| command = ctx.attr.kernel_build_notrim[_KernelEnvInfo].setup |
| command += """ |
| mkdir -p {intermediates_dir} |
| cp -pl {srcs} {intermediates_dir} |
| {cp_src_cmd} |
| {extract_symbols} {flags} {intermediates_dir} |
| rm -rf {intermediates_dir} |
| """.format( |
| srcs = " ".join([file.path for file in srcs]), |
| intermediates_dir = intermediates_dir, |
| extract_symbols = ctx.file._extract_symbols.path, |
| flags = " ".join(flags), |
| cp_src_cmd = cp_src_cmd, |
| ) |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| inputs = inputs, |
| outputs = [out], |
| command = command, |
| progress_message = "Extracting symbols {}".format(ctx.label), |
| mnemonic = "KernelExtractedSymbols", |
| ) |
| |
| return DefaultInfo(files = depset([out])) |
| |
| _kernel_extracted_symbols = rule( |
| implementation = _kernel_extracted_symbols_impl, |
| attrs = { |
| # We can't use kernel_filegroup + hermetic_tools here because |
| # - extract_symbols depends on the clang toolchain, which requires us to |
| # know the toolchain_version ahead of time. |
| # - We also don't have the necessity to extract symbols from prebuilts. |
| "kernel_build_notrim": attr.label(providers = [_KernelEnvInfo, _KernelBuildAbiInfo]), |
| "kernel_modules": attr.label_list(providers = [_KernelModuleInfo]), |
| "module_grouping": attr.bool(default = True), |
| "src": attr.label(doc = "Source `abi_gki_*` file. Used when `kmi_symbol_list_add_only`.", allow_single_file = True), |
| "kmi_symbol_list_add_only": attr.bool(), |
| "_extract_symbols": attr.label(default = "//build/kernel:abi/extract_symbols", allow_single_file = True), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _kernel_abi_dump_impl(ctx): |
| full_abi_out_file = _kernel_abi_dump_full(ctx) |
| abi_out_file = _kernel_abi_dump_filtered(ctx, full_abi_out_file) |
| return [ |
| DefaultInfo(files = depset([full_abi_out_file, abi_out_file])), |
| OutputGroupInfo(abi_out_file = depset([abi_out_file])), |
| ] |
| |
| def _kernel_abi_dump_epilog_cmd(path, append_version): |
| ret = "" |
| if append_version: |
| ret += """ |
| # Append debug information to abi file |
| echo " |
| <!-- |
| libabigail: $(abidw --version) |
| -->" >> {path} |
| """.format(path = path) |
| return ret |
| |
| def _kernel_abi_dump_full(ctx): |
| abi_linux_tree = utils.intermediates_dir(ctx) + "/abi_linux_tree" |
| full_abi_out_file = ctx.actions.declare_file("{}/abi-full.xml".format(ctx.attr.name)) |
| vmlinux = find_file(name = "vmlinux", files = ctx.files.kernel_build, what = "{}: kernel_build".format(ctx.attr.name), required = True) |
| |
| unstripped_dir_provider_targets = [ctx.attr.kernel_build] + ctx.attr.kernel_modules |
| unstripped_dir_providers = [target[_KernelUnstrippedModulesInfo] for target in unstripped_dir_provider_targets] |
| for prov, target in zip(unstripped_dir_providers, unstripped_dir_provider_targets): |
| if not prov.directory: |
| fail("{}: Requires dep {} to set collect_unstripped_modules = True".format(ctx.label, target.label)) |
| unstripped_dirs = [prov.directory for prov in unstripped_dir_providers] |
| |
| inputs = [vmlinux, ctx.file._dump_abi] |
| inputs += ctx.files._dump_abi_scripts |
| inputs += unstripped_dirs |
| |
| inputs += ctx.attr._hermetic_tools[HermeticToolsInfo].deps |
| |
| # Directories could be empty, so use a find + cp |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| mkdir -p {abi_linux_tree} |
| find {unstripped_dirs} -type f -name '*.ko' -exec cp -pl -t {abi_linux_tree} {{}} + |
| cp -pl {vmlinux} {abi_linux_tree} |
| {dump_abi} --linux-tree {abi_linux_tree} --out-file {full_abi_out_file} |
| {epilog} |
| rm -rf {abi_linux_tree} |
| """.format( |
| abi_linux_tree = abi_linux_tree, |
| unstripped_dirs = " ".join([unstripped_dir.path for unstripped_dir in unstripped_dirs]), |
| dump_abi = ctx.file._dump_abi.path, |
| vmlinux = vmlinux.path, |
| full_abi_out_file = full_abi_out_file.path, |
| epilog = _kernel_abi_dump_epilog_cmd(full_abi_out_file.path, True), |
| ) |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| inputs = inputs, |
| outputs = [full_abi_out_file], |
| command = command, |
| mnemonic = "AbiDumpFull", |
| progress_message = "Extracting ABI {}".format(ctx.label), |
| ) |
| return full_abi_out_file |
| |
| def _kernel_abi_dump_filtered(ctx, full_abi_out_file): |
| abi_out_file = ctx.actions.declare_file("{}/abi.xml".format(ctx.attr.name)) |
| inputs = [full_abi_out_file] |
| |
| inputs += ctx.attr._hermetic_tools[HermeticToolsInfo].deps |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup |
| combined_abi_symbollist = ctx.attr.kernel_build[_KernelBuildAbiInfo].combined_abi_symbollist |
| if combined_abi_symbollist: |
| inputs += [ |
| ctx.file._filter_abi, |
| combined_abi_symbollist, |
| ] |
| |
| command += """ |
| {filter_abi} --in-file {full_abi_out_file} --out-file {abi_out_file} --kmi-symbol-list {abi_symbollist} |
| {epilog} |
| """.format( |
| abi_out_file = abi_out_file.path, |
| full_abi_out_file = full_abi_out_file.path, |
| filter_abi = ctx.file._filter_abi.path, |
| abi_symbollist = combined_abi_symbollist.path, |
| epilog = _kernel_abi_dump_epilog_cmd(abi_out_file.path, False), |
| ) |
| else: |
| command += """ |
| cp -p {full_abi_out_file} {abi_out_file} |
| """.format( |
| abi_out_file = abi_out_file.path, |
| full_abi_out_file = full_abi_out_file.path, |
| ) |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| inputs = inputs, |
| outputs = [abi_out_file], |
| command = command, |
| mnemonic = "AbiDumpFiltered", |
| progress_message = "Filtering ABI dump {}".format(ctx.label), |
| ) |
| return abi_out_file |
| |
| _kernel_abi_dump = rule( |
| implementation = _kernel_abi_dump_impl, |
| doc = "Extracts the ABI.", |
| attrs = { |
| "kernel_build": attr.label(providers = [_KernelEnvInfo, _KernelBuildAbiInfo, _KernelUnstrippedModulesInfo]), |
| "kernel_modules": attr.label_list(providers = [_KernelUnstrippedModulesInfo]), |
| "_dump_abi_scripts": attr.label(default = "//build/kernel:dump-abi-scripts"), |
| "_dump_abi": attr.label(default = "//build/kernel:abi/dump_abi", allow_single_file = True), |
| "_filter_abi": attr.label(default = "//build/kernel:abi/filter_abi", allow_single_file = True), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |
| |
| def _kernel_abi_prop_impl(ctx): |
| content = [] |
| if ctx.file.kmi_definition: |
| content.append("KMI_DEFINITION={}".format(ctx.file.kmi_definition.basename)) |
| content.append("KMI_MONITORED=1") |
| |
| if ctx.attr.kmi_enforced: |
| content.append("KMI_ENFORCED=1") |
| |
| combined_abi_symbollist = ctx.attr.kernel_build[_KernelBuildAbiInfo].combined_abi_symbollist |
| if combined_abi_symbollist: |
| content.append("KMI_SYMBOL_LIST={}".format(combined_abi_symbollist.basename)) |
| |
| # This just appends `KERNEL_BINARY=vmlinux`, but find_file additionally ensures that |
| # we are building vmlinux. |
| vmlinux = find_file(name = "vmlinux", files = ctx.files.kernel_build, what = "{}: kernel_build".format(ctx.attr.name), required = True) |
| content.append("KERNEL_BINARY={}".format(vmlinux.basename)) |
| |
| if ctx.file.modules_archive: |
| content.append("MODULES_ARCHIVE={}".format(ctx.file.modules_archive.basename)) |
| |
| out = ctx.actions.declare_file("{}/abi.prop".format(ctx.attr.name)) |
| ctx.actions.write( |
| output = out, |
| content = "\n".join(content) + "\n", |
| ) |
| return DefaultInfo(files = depset([out])) |
| |
| _kernel_abi_prop = rule( |
| implementation = _kernel_abi_prop_impl, |
| doc = "Create `abi.prop`", |
| attrs = { |
| "kernel_build": attr.label(providers = [_KernelBuildAbiInfo]), |
| "modules_archive": attr.label(allow_single_file = True), |
| "kmi_definition": attr.label(allow_single_file = True), |
| "kmi_enforced": attr.bool(), |
| }, |
| ) |
| |
| def kernel_build_abi( |
| name, |
| define_abi_targets = None, |
| # for kernel_abi |
| kernel_modules = None, |
| module_grouping = None, |
| abi_definition = None, |
| kmi_enforced = None, |
| unstripped_modules_archive = None, |
| kmi_symbol_list_add_only = None, |
| # for kernel_build |
| **kwargs): |
| """Declare multiple targets to support ABI monitoring. |
| |
| This macro is meant to be used in place of the [`kernel_build`](#kernel_build) |
| marco. All arguments in `kwargs` are passed to `kernel_build` directly. |
| |
| For example, you may have the following declaration. (For actual definition |
| of `kernel_aarch64`, see |
| [`define_common_kernels()`](#define_common_kernels). |
| |
| ``` |
| kernel_build_abi(name = "kernel_aarch64", **kwargs) |
| _dist_targets = ["kernel_aarch64", ...] |
| copy_to_dist_dir(name = "kernel_aarch64_dist", data = _dist_targets) |
| kernel_build_abi_dist( |
| name = "kernel_aarch64_abi_dist", |
| kernel_build_abi = "kernel_aarch64", |
| data = _dist_targets, |
| ) |
| ``` |
| |
| The `kernel_build_abi` invocation is equivalent to the following: |
| |
| ``` |
| kernel_build(name = "kernel_aarch64", **kwargs) |
| # if define_abi_targets, also define some other targets |
| ``` |
| |
| See [`kernel_build`](#kernel_build) for the targets defined. |
| |
| In addition, the following targets are defined: |
| - `kernel_aarch64_abi_dump` |
| - Building this target extracts the ABI. |
| - Include this target in a [`kernel_build_abi_dist`](#kernel_build_abi_dist) |
| target to copy ABI dump to `--dist-dir`. |
| - `kernel_aarch64_abi` |
| - A filegroup that contains `kernel_aarch64_abi_dump`. It also contains other targets |
| if `define_abi_targets = True`; see below. |
| |
| In addition, the following targets are defined if `define_abi_targets = True`: |
| - `kernel_aarch64_abi_update_symbol_list` |
| - Running this target updates `kmi_symbol_list`. |
| - `kernel_aarch64_abi_update` |
| - Running this target updates `abi_definition`. |
| - `kernel_aarch64_abi_dump` |
| - Building this target extracts the ABI. |
| - Include this target in a [`kernel_build_abi_dist`](#kernel_build_abi_dist) |
| target to copy ABI dump to `--dist-dir`. |
| |
| See build/kernel/kleaf/abi.md for a conversion chart from `build_abi.sh` |
| commands to Bazel commands. |
| |
| Args: |
| name: Name of the main `kernel_build`. |
| define_abi_targets: Whether the `<name>_abi` target contains other |
| files to support ABI monitoring. If `None`, defaults to `True`. |
| |
| If `False`, this macro is equivalent to just calling |
| ``` |
| kernel_build(name = name, **kwargs) |
| filegroup(name = name + "_abi", data = [name, abi_dump_target]) |
| ``` |
| |
| If `True`, implies `collect_unstripped_modules = True`. See |
| [`kernel_build.collect_unstripped_modules`](#kernel_build-collect_unstripped_modules). |
| kernel_modules: A list of external [`kernel_module()`](#kernel_module)s |
| to extract symbols from. |
| module_grouping: If unspecified or `None`, it is `True` by default. |
| If `True`, then the symbol list will group symbols based |
| on the kernel modules that reference the symbol. Otherwise the symbol |
| list will simply be a sorted list of symbols used by all the kernel |
| modules. |
| abi_definition: Location of the ABI definition. |
| kmi_enforced: This is an indicative option to signal that KMI is enforced. |
| If set to `True`, KMI checking tools respects it and |
| reacts to it by failing if KMI differences are detected. |
| unstripped_modules_archive: A [`kernel_unstripped_modules_archive`](#kernel_unstripped_modules_archive) |
| which name is specified in `abi.prop`. |
| kmi_symbol_list_add_only: If unspecified or `None`, it is `False` by |
| default. If `True`, |
| then any symbols in the symbol list that would have been |
| removed are preserved (at the end of the file). Symbol list update will |
| fail if there is no pre-existing symbol list file to read from. This |
| property is intended to prevent unintentional shrinkage of a stable ABI. |
| |
| This should be set to `True` if `KMI_SYMBOL_LIST_ADD_ONLY=1`. |
| kwargs: See [`kernel_build.kwargs`](#kernel_build-kwargs) |
| """ |
| |
| if define_abi_targets == None: |
| define_abi_targets = True |
| |
| kwargs = dict(kwargs) |
| if define_abi_targets and kwargs.get("collect_unstripped_modules") == None: |
| kwargs["collect_unstripped_modules"] = True |
| |
| _kernel_build_abi_define_other_targets( |
| name = name, |
| define_abi_targets = define_abi_targets, |
| kernel_modules = kernel_modules, |
| module_grouping = module_grouping, |
| kmi_symbol_list_add_only = kmi_symbol_list_add_only, |
| abi_definition = abi_definition, |
| kmi_enforced = kmi_enforced, |
| unstripped_modules_archive = unstripped_modules_archive, |
| kernel_build_kwargs = kwargs, |
| ) |
| |
| kernel_build(name = name, **kwargs) |
| |
| def _kernel_build_abi_define_other_targets( |
| name, |
| define_abi_targets, |
| kernel_modules, |
| module_grouping, |
| kmi_symbol_list_add_only, |
| abi_definition, |
| kmi_enforced, |
| unstripped_modules_archive, |
| kernel_build_kwargs): |
| """Helper to `kernel_build_abi`. |
| |
| Defines targets other than the main `kernel_build()`. |
| |
| Defines: |
| * `{name}_with_vmlinux` |
| * `{name}_notrim` (if `define_abi_targets`) |
| * `{name}_abi_diff_executable` |
| * `{name}_abi` |
| """ |
| new_outs, outs_changed = _kernel_build_outs_add_vmlinux(name, kernel_build_kwargs.get("outs")) |
| |
| # with_vmlinux: outs += [vmlinux] |
| if outs_changed or kernel_build_kwargs.get("base_kernel"): |
| with_vmlinux_kwargs = dict(kernel_build_kwargs) |
| with_vmlinux_kwargs["outs"] = _transform_kernel_build_outs(name + "_with_vmlinux", "outs", new_outs) |
| with_vmlinux_kwargs.pop("base_kernel", default = None) |
| kernel_build(name = name + "_with_vmlinux", **with_vmlinux_kwargs) |
| else: |
| native.alias(name = name + "_with_vmlinux", actual = name) |
| |
| _kernel_abi_dump( |
| name = name + "_abi_dump", |
| kernel_build = name + "_with_vmlinux", |
| kernel_modules = [module + "_with_vmlinux" for module in kernel_modules] if kernel_modules else kernel_modules, |
| ) |
| |
| if not define_abi_targets: |
| _kernel_build_abi_not_define_abi_targets( |
| name = name, |
| abi_dump_target = name + "_abi_dump", |
| ) |
| else: |
| _kernel_build_abi_define_abi_targets( |
| name = name, |
| kernel_modules = kernel_modules, |
| module_grouping = module_grouping, |
| kmi_symbol_list_add_only = kmi_symbol_list_add_only, |
| abi_definition = abi_definition, |
| kmi_enforced = kmi_enforced, |
| unstripped_modules_archive = unstripped_modules_archive, |
| outs_changed = outs_changed, |
| new_outs = new_outs, |
| abi_dump_target = name + "_abi_dump", |
| kernel_build_with_vmlinux_target = name + "_with_vmlinux", |
| kernel_build_kwargs = kernel_build_kwargs, |
| ) |
| |
| def _kernel_build_abi_not_define_abi_targets( |
| name, |
| abi_dump_target): |
| """Helper to `_kernel_build_abi_define_other_targets` when `define_abi_targets = False.` |
| |
| Defines `{name}_abi` filegroup that only contains the ABI dump, provided |
| in `abi_dump_target`. |
| |
| Defines: |
| * `{name}_abi_diff_executable` |
| * `{name}_abi` |
| """ |
| native.filegroup( |
| name = name + "_abi", |
| srcs = [abi_dump_target], |
| ) |
| |
| # For kernel_build_abi_dist to use when define_abi_targets is not set. |
| exec( |
| name = name + "_abi_diff_executable", |
| script = "", |
| ) |
| |
| def _kernel_build_abi_define_abi_targets( |
| name, |
| kernel_modules, |
| module_grouping, |
| kmi_symbol_list_add_only, |
| abi_definition, |
| kmi_enforced, |
| unstripped_modules_archive, |
| outs_changed, |
| new_outs, |
| abi_dump_target, |
| kernel_build_with_vmlinux_target, |
| kernel_build_kwargs): |
| """Helper to `_kernel_build_abi_define_other_targets` when `define_abi_targets = True.` |
| |
| Define targets to extract symbol list, extract ABI, update them, etc. |
| |
| Defines: |
| * `{name}_notrim` |
| * `{name}_abi_diff_executable` |
| * `{name}_abi` |
| """ |
| |
| default_outputs = [abi_dump_target] |
| |
| # notrim: outs += [vmlinux], trim_nonlisted_kmi = False |
| if kernel_build_kwargs.get("trim_nonlisted_kmi") or outs_changed or kernel_build_kwargs.get("base_kernel"): |
| notrim_kwargs = dict(kernel_build_kwargs) |
| notrim_kwargs["outs"] = _transform_kernel_build_outs(name + "_notrim", "outs", new_outs) |
| notrim_kwargs["trim_nonlisted_kmi"] = False |
| notrim_kwargs["kmi_symbol_list_strict_mode"] = False |
| notrim_kwargs.pop("base_kernel", default = None) |
| kernel_build(name = name + "_notrim", **notrim_kwargs) |
| else: |
| native.alias(name = name + "_notrim", actual = name) |
| |
| # extract_symbols ... |
| _kernel_extracted_symbols( |
| name = name + "_abi_extracted_symbols", |
| kernel_build_notrim = name + "_notrim", |
| kernel_modules = [module + "_notrim" for module in kernel_modules] if kernel_modules else kernel_modules, |
| module_grouping = module_grouping, |
| src = kernel_build_kwargs.get("kmi_symbol_list"), |
| kmi_symbol_list_add_only = kmi_symbol_list_add_only, |
| ) |
| update_source_file( |
| name = name + "_abi_update_symbol_list", |
| src = name + "_abi_extracted_symbols", |
| dst = kernel_build_kwargs.get("kmi_symbol_list"), |
| ) |
| |
| default_outputs += _kernel_build_abi_define_abi_definition_targets( |
| name = name, |
| abi_definition = abi_definition, |
| kmi_enforced = kmi_enforced, |
| kmi_symbol_list = kernel_build_kwargs.get("kmi_symbol_list"), |
| ) |
| |
| _kernel_abi_prop( |
| name = name + "_abi_prop", |
| kmi_definition = name + "_abi_out_file" if abi_definition else None, |
| kmi_enforced = kmi_enforced, |
| kernel_build = kernel_build_with_vmlinux_target, |
| modules_archive = unstripped_modules_archive, |
| ) |
| default_outputs.append(name + "_abi_prop") |
| |
| native.filegroup( |
| name = name + "_abi", |
| srcs = default_outputs, |
| ) |
| |
| def _kernel_build_abi_define_abi_definition_targets( |
| name, |
| abi_definition, |
| kmi_enforced, |
| kmi_symbol_list): |
| """Helper to `_kernel_build_abi_define_abi_targets`. |
| |
| Defines targets to extract ABI, update ABI, compare ABI, etc. etc. |
| |
| Defines `{name}_abi_diff_executable`. |
| """ |
| if not abi_definition: |
| # For kernel_build_abi_dist to use when abi_definition is empty. |
| exec( |
| name = name + "_abi_diff_executable", |
| script = "", |
| ) |
| return [] |
| |
| default_outputs = [] |
| |
| native.filegroup( |
| name = name + "_abi_out_file", |
| srcs = [name + "_abi_dump"], |
| output_group = "abi_out_file", |
| ) |
| |
| _kernel_abi_diff( |
| name = name + "_abi_diff", |
| baseline = abi_definition, |
| new = name + "_abi_out_file", |
| kmi_enforced = kmi_enforced, |
| ) |
| default_outputs.append(name + "_abi_diff") |
| |
| # The default outputs of _abi_diff does not contain the executable, |
| # but the reports. Use this filegroup to select the executable |
| # so rootpath in _abi_update works. |
| native.filegroup( |
| name = name + "_abi_diff_executable", |
| srcs = [name + "_abi_diff"], |
| output_group = "executable", |
| ) |
| |
| update_source_file( |
| name = name + "_abi_update_definition", |
| src = name + "_abi_out_file", |
| dst = abi_definition, |
| ) |
| |
| exec( |
| name = name + "_abi_nodiff_update", |
| data = [ |
| name + "_abi_extracted_symbols", |
| name + "_abi_update_definition", |
| kmi_symbol_list, |
| ], |
| script = """ |
| # Ensure that symbol list is updated |
| if ! diff -q $(rootpath {src_symbol_list}) $(rootpath {dst_symbol_list}); then |
| echo "ERROR: symbol list must be updated before updating ABI definition. To update, execute 'tools/bazel run //{package}:{update_symbol_list_label}'." >&2 |
| exit 1 |
| fi |
| # Update abi_definition |
| $(rootpath {update_definition}) |
| """.format( |
| src_symbol_list = name + "_abi_extracted_symbols", |
| dst_symbol_list = kmi_symbol_list, |
| package = native.package_name(), |
| update_symbol_list_label = name + "_abi_update_symbol_list", |
| update_definition = name + "_abi_update_definition", |
| ), |
| ) |
| |
| exec( |
| name = name + "_abi_update", |
| data = [ |
| name + "_abi_diff_executable", |
| name + "_abi_nodiff_update", |
| ], |
| script = """ |
| # Update abi_definition |
| $(rootpath {nodiff_update}) |
| # Check return code of diff_abi and kmi_enforced |
| $(rootpath {diff}) |
| """.format( |
| diff = name + "_abi_diff_executable", |
| nodiff_update = name + "_abi_nodiff_update", |
| ), |
| ) |
| |
| return default_outputs |
| |
| def kernel_build_abi_dist( |
| name, |
| kernel_build_abi, |
| **kwargs): |
| """A wrapper over `copy_to_dist_dir` for [`kernel_build_abi`](#kernel_build_abi). |
| |
| After copying all files to dist dir, return the exit code from `diff_abi`. |
| |
| Args: |
| name: name of the dist target |
| kernel_build_abi: name of the [`kernel_build_abi`](#kernel_build_abi) |
| invocation. |
| """ |
| |
| # TODO(b/231647455): Clean up hard-coded name "_abi" and "_abi_diff_executable". |
| |
| if kwargs.get("data") == None: |
| kwargs["data"] = [] |
| |
| # Use explicit + to prevent modifying the original list. |
| kwargs["data"] = kwargs["data"] + [kernel_build_abi + "_abi"] |
| |
| copy_to_dist_dir( |
| name = name + "_copy_to_dist_dir", |
| **kwargs |
| ) |
| |
| exec( |
| name = name, |
| data = [ |
| name + "_copy_to_dist_dir", |
| kernel_build_abi + "_abi_diff_executable", |
| ], |
| script = """ |
| # Copy to dist dir |
| $(rootpath {copy_to_dist_dir}) $@ |
| # Check return code of diff_abi and kmi_enforced |
| $(rootpath {diff}) |
| """.format( |
| copy_to_dist_dir = name + "_copy_to_dist_dir", |
| diff = kernel_build_abi + "_abi_diff_executable", |
| ), |
| ) |
| |
| def _kernel_abi_diff_impl(ctx): |
| inputs = [ |
| ctx.file._diff_abi, |
| ctx.file.baseline, |
| ctx.file.new, |
| ] |
| inputs += ctx.attr._hermetic_tools[HermeticToolsInfo].deps |
| inputs += ctx.files._diff_abi_scripts |
| |
| output_dir = ctx.actions.declare_directory("{}/abi_diff".format(ctx.attr.name)) |
| error_msg_file = ctx.actions.declare_file("{}/error_msg_file".format(ctx.attr.name)) |
| exit_code_file = ctx.actions.declare_file("{}/exit_code_file".format(ctx.attr.name)) |
| default_outputs = [output_dir] |
| |
| command_outputs = default_outputs + [ |
| error_msg_file, |
| exit_code_file, |
| ] |
| |
| command = ctx.attr._hermetic_tools[HermeticToolsInfo].setup + """ |
| set +e |
| {diff_abi} --baseline {baseline} \\ |
| --new {new} \\ |
| --report {output_dir}/abi.report \\ |
| --abi-tool delegated > {error_msg_file} 2>&1 |
| rc=$? |
| set -e |
| echo $rc > {exit_code_file} |
| if [[ $rc == 0 ]]; then |
| echo "INFO: $(cat {error_msg_file})" |
| else |
| echo "ERROR: $(cat {error_msg_file})" >&2 |
| echo "INFO: exit code is not checked. 'tools/bazel run {label}' to check the exit code." >&2 |
| fi |
| """.format( |
| diff_abi = ctx.file._diff_abi.path, |
| baseline = ctx.file.baseline.path, |
| new = ctx.file.new.path, |
| output_dir = output_dir.path, |
| exit_code_file = exit_code_file.path, |
| error_msg_file = error_msg_file.path, |
| label = ctx.label, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| inputs = inputs, |
| outputs = command_outputs, |
| command = command, |
| mnemonic = "KernelDiffAbi", |
| progress_message = "Comparing ABI {}".format(ctx.label), |
| ) |
| |
| script = ctx.actions.declare_file("{}/print_results.sh".format(ctx.attr.name)) |
| script_content = """#!/bin/bash -e |
| rc=$(cat {exit_code_file}) |
| if [[ $rc == 0 ]]; then |
| echo "INFO: $(cat {error_msg_file})" |
| else |
| echo "ERROR: $(cat {error_msg_file})" >&2 |
| fi |
| """.format( |
| exit_code_file = exit_code_file.short_path, |
| error_msg_file = error_msg_file.short_path, |
| ) |
| if ctx.attr.kmi_enforced: |
| script_content += """ |
| exit $rc |
| """ |
| ctx.actions.write(script, script_content, is_executable = True) |
| |
| return [ |
| DefaultInfo( |
| files = depset(default_outputs), |
| executable = script, |
| runfiles = ctx.runfiles(files = command_outputs), |
| ), |
| OutputGroupInfo(executable = depset([script])), |
| ] |
| |
| _kernel_abi_diff = rule( |
| implementation = _kernel_abi_diff_impl, |
| doc = "Run `diff_abi`", |
| attrs = { |
| "baseline": attr.label(allow_single_file = True), |
| "new": attr.label(allow_single_file = True), |
| "kmi_enforced": attr.bool(), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_diff_abi_scripts": attr.label(default = "//build/kernel:diff-abi-scripts"), |
| "_diff_abi": attr.label(default = "//build/kernel:abi/diff_abi", allow_single_file = True), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| executable = True, |
| ) |
| |
| def _kernel_unstripped_modules_archive_impl(ctx): |
| kernel_build = ctx.attr.kernel_build |
| base_kernel = kernel_build[_KernelUnstrippedModulesInfo].base_kernel if kernel_build else None |
| |
| # Early elements = higher priority. In-tree modules from base_kernel has highest priority, |
| # then in-tree modules of the device kernel_build, then external modules (in an undetermined |
| # order). |
| # TODO(b/228557644): kernel module names should not collide. Detect collsions. |
| srcs = [] |
| for kernel_build_object in (base_kernel, kernel_build): |
| if not kernel_build_object: |
| continue |
| directory = kernel_build_object[_KernelUnstrippedModulesInfo].directory |
| if not directory: |
| fail("{} does not have collect_unstripped_modules = True.".format(kernel_build_object.label)) |
| srcs.append(directory) |
| for kernel_module in ctx.attr.kernel_modules: |
| srcs.append(kernel_module[_KernelUnstrippedModulesInfo].directory) |
| |
| inputs = ctx.attr._hermetic_tools[HermeticToolsInfo].deps + srcs |
| |
| out_file = ctx.actions.declare_file("{}/unstripped_modules.tar.gz".format(ctx.attr.name)) |
| unstripped_dir = ctx.genfiles_dir.path + "/unstripped" |
| |
| command = "" |
| command += ctx.attr._hermetic_tools[HermeticToolsInfo].setup |
| command += """ |
| mkdir -p {unstripped_dir} |
| """.format(unstripped_dir = unstripped_dir) |
| |
| # Copy the source ko files in low to high priority order. |
| for src in reversed(srcs): |
| # src could be empty, so use find + cp |
| command += """ |
| find {src} -name '*.ko' -exec cp -l -t {unstripped_dir} {{}} + |
| """.format( |
| src = src.path, |
| unstripped_dir = unstripped_dir, |
| ) |
| |
| command += """ |
| tar -czhf {out_file} -C $(dirname {unstripped_dir}) $(basename {unstripped_dir}) |
| """.format( |
| out_file = out_file.path, |
| unstripped_dir = unstripped_dir, |
| ) |
| |
| _debug_print_scripts(ctx, command) |
| ctx.actions.run_shell( |
| inputs = inputs, |
| outputs = [out_file], |
| progress_message = "Compressing unstripped modules {}".format(ctx.label), |
| command = command, |
| mnemonic = "KernelUnstrippedModulesArchive", |
| ) |
| return DefaultInfo(files = depset([out_file])) |
| |
| kernel_unstripped_modules_archive = rule( |
| implementation = _kernel_unstripped_modules_archive_impl, |
| doc = """Compress the unstripped modules into a tarball. |
| |
| This is the equivalent of `COMPRESS_UNSTRIPPED_MODULES=1` in `build.sh`. |
| |
| Add this target to a `copy_to_dist_dir` rule to copy it to the distribution |
| directory, or `DIST_DIR`. |
| """, |
| attrs = { |
| "kernel_build": attr.label( |
| doc = """A [`kernel_build`](#kernel_build) to retrieve unstripped in-tree modules from. |
| |
| It requires `collect_unstripped_modules = True`. If the `kernel_build` has a `base_kernel`, the rule |
| also retrieves unstripped in-tree modules from the `base_kernel`, and requires the |
| `base_kernel` has `collect_unstripped_modules = True`. |
| """, |
| providers = [_KernelUnstrippedModulesInfo], |
| ), |
| "kernel_modules": attr.label_list( |
| doc = """A list of external [`kernel_module`](#kernel_module)s to retrieve unstripped external modules from. |
| |
| It requires that the base `kernel_build` has `collect_unstripped_modules = True`. |
| """, |
| providers = [_KernelUnstrippedModulesInfo], |
| ), |
| "_hermetic_tools": attr.label(default = "//build/kernel:hermetic-tools", providers = [HermeticToolsInfo]), |
| "_debug_print_scripts": attr.label(default = "//build/kernel/kleaf:debug_print_scripts"), |
| }, |
| ) |