xref: /aosp_15_r20/build/bazel/rules/partitions/partition.bzl (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1# Copyright (C) 2022 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""This file defines the rule that builds android partitions."""
16
17load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
18load("//build/bazel/rules:build_fingerprint.bzl", "BuildFingerprintInfo")
19
20_IMAGE_TYPES = [
21    "system",
22    "system_other",
23    "userdata",
24    "cache",
25    "vendor",
26    "product",
27    "system_ext",
28    "odm",
29    "vendor_dlkm",
30    "system_dlkm",
31    "oem",
32]
33
34def _get_python3(ctx):
35    python_interpreter = ctx.toolchains["@bazel_tools//tools/python:toolchain_type"].py3_runtime.interpreter
36    if python_interpreter.basename == "python3":
37        return python_interpreter
38
39    renamed = ctx.actions.declare_file(ctx.attr.name + "/python3")
40    ctx.actions.symlink(
41        output = renamed,
42        target_file = python_interpreter,
43        is_executable = True,
44    )
45    return renamed
46
47def _partition_impl(ctx):
48    if ctx.attr.type != "system":
49        fail("currently only system images are supported")
50
51    toolchain = ctx.toolchains[":partition_toolchain_type"].toolchain_info
52    python_interpreter = _get_python3(ctx)
53
54    du = ctx.actions.declare_file(ctx.attr.name + "/du")
55    ctx.actions.symlink(
56        output = du,
57        target_file = toolchain.toybox[DefaultInfo].files_to_run.executable,
58        is_executable = True,
59    )
60    find = ctx.actions.declare_file(ctx.attr.name + "/find")
61    ctx.actions.symlink(
62        output = find,
63        target_file = toolchain.toybox[DefaultInfo].files_to_run.executable,
64        is_executable = True,
65    )
66
67    # build_image requires that the output file be named specifically <type>.img, so
68    # put all the outputs under a name-qualified folder.
69    output_image = ctx.actions.declare_file(ctx.attr.name + "/" + ctx.attr.type + ".img")
70
71    # TODO(b/297269187) Fill this out with the contents of ctx.attr.deps
72    files = {}
73
74    staging_dir_builder_options = {
75        "file_mapping": {k: v.path for k, v in files.items()},
76    }
77
78    extra_inputs = []
79    if ctx.attr.base_staging_dir:
80        staging_dir_builder_options["base_staging_dir"] = ctx.file.base_staging_dir.path
81        extra_inputs.append(ctx.file.base_staging_dir)
82        bbipi = ctx.attr._build_broken_incorrect_partition_images[BuildSettingInfo].value
83        if ctx.attr.base_staging_dir_file_list and not bbipi:
84            staging_dir_builder_options["base_staging_dir_file_list"] = ctx.file.base_staging_dir_file_list.path
85            extra_inputs.append(ctx.file.base_staging_dir_file_list)
86
87    if "{BUILD_NUMBER}" in ctx.attr.image_properties:
88        fail("Can't have {BUILD_NUMBER} in image_properties")
89    for line in ctx.attr.image_properties.splitlines():
90        if line.startswith("avb_"):
91            fail("avb properties should be managed by their bespoke attributes: " + line)
92
93    image_info_contents = ctx.attr.image_properties + "\n\n"
94    image_info_contents += "ext_mkuserimg=mkuserimg_mke2fs\n"
95    if ctx.attr.root_dir:
96        extra_inputs.append(ctx.file.root_dir)
97        image_info_contents += "root_dir=" + ctx.file.root_dir.path + "\n"
98    if ctx.attr.selinux_file_contexts:
99        extra_inputs.append(ctx.file.selinux_file_contexts)
100        image_info_contents += ctx.attr.type + "_selinux_fc=" + ctx.file.selinux_file_contexts.path + "\n"
101
102    if not ctx.attr.avb_enable:
103        if ctx.attr.avb_add_hashtree_footer_args:
104            fail("Must specify avb_enable = True to use avb_add_hashtree_footer_args")
105        if ctx.attr.avb_key:
106            fail("Must specify avb_enable = True to use avb_key")
107        if ctx.attr.avb_algorithm:
108            fail("Must specify avb_enable = True to use avb_key")
109        if ctx.attr.avb_rollback_index >= 0:
110            fail("Must specify avb_enable = True to use avb_rollback_index")
111        if ctx.attr.avb_rollback_index_location >= 0:
112            fail("Must specify avb_enable = True to use avb_rollback_index_location")
113    else:
114        image_info_contents += "avb_avbtool=avbtool\n"
115        image_info_contents += "avb_" + ctx.attr.type + "_hashtree_enable=true" + "\n"
116        footer_args = ctx.attr.avb_add_hashtree_footer_args
117        if footer_args:
118            footer_args += " "
119        footer_args += "--prop com.android.build.system.os_version:" + ctx.attr._platform_version_last_stable[BuildSettingInfo].value
120        footer_args += " --prop com.android.build.system.fingerprint:" + ctx.attr._build_fingerprint[BuildFingerprintInfo].fingerprint_placeholder_build_number
121        footer_args += " --prop com.android.build.system.security_patch:" + ctx.attr._platform_security_patch[BuildSettingInfo].value
122        if not ctx.attr.type.startswith("vbmeta_") and ctx.attr.avb_rollback_index >= 0:
123            footer_args += " --rollback_index " + str(ctx.attr.avb_rollback_index)
124        image_info_contents += "avb_" + ctx.attr.type + "_add_hashtree_footer_args=" + footer_args + "\n"
125        if ctx.attr.avb_key:
126            image_info_contents += "avb_" + ctx.attr.type + "_key_path=" + ctx.file.avb_key.path + "\n"
127            extra_inputs.append(ctx.file.avb_key)
128            image_info_contents += "avb_" + ctx.attr.type + "_algorithm=" + ctx.attr.avb_algorithm + "\n"
129            if ctx.attr.avb_rollback_index_location >= 0:
130                image_info_contents += "avb_" + ctx.attr.type + "_rollback_index_location=" + str(ctx.attr.avb_rollback_index_location) + "\n"
131
132    image_info_without_build_number = ctx.actions.declare_file(ctx.attr.name + "/image_info_without_build_number.txt")
133    ctx.actions.write(image_info_without_build_number, image_info_contents)
134    image_info = ctx.actions.declare_file(ctx.attr.name + "/image_info.txt")
135    ctx.actions.run(
136        inputs = [
137            ctx.version_file,
138            image_info_without_build_number,
139        ],
140        outputs = [image_info],
141        executable = ctx.executable._status_file_reader,
142        arguments = [
143            "replace",
144            ctx.version_file.path,
145            image_info_without_build_number.path,
146            image_info.path,
147            "--var",
148            "BUILD_NUMBER",
149        ],
150    )
151
152    staging_dir_builder_options_file = ctx.actions.declare_file(ctx.attr.name + "/staging_dir_builder_options.json")
153    ctx.actions.write(staging_dir_builder_options_file, json.encode(staging_dir_builder_options))
154
155    build_image_files = toolchain.build_image[DefaultInfo].files_to_run
156
157    # These are tools that are run from build_image or another tool that build_image runs.
158    # They are all expected to be available in the PATH.
159    extra_tools = [
160        toolchain.avbtool[DefaultInfo].files_to_run,
161        toolchain.e2fsdroid[DefaultInfo].files_to_run,
162        toolchain.fec[DefaultInfo].files_to_run,
163        toolchain.mke2fs[DefaultInfo].files_to_run,
164        toolchain.mkfs_erofs[DefaultInfo].files_to_run,
165        toolchain.mkuserimg_mke2fs[DefaultInfo].files_to_run,
166        toolchain.simg2img[DefaultInfo].files_to_run,
167        toolchain.tune2fs[DefaultInfo].files_to_run,
168    ]
169
170    ctx.actions.run(
171        inputs = [
172            image_info,
173            staging_dir_builder_options_file,
174            toolchain.openssl,
175        ] + files.values() + extra_inputs,
176        tools = extra_tools + [
177            build_image_files,
178            du,
179            find,
180            python_interpreter,
181            toolchain.toybox[DefaultInfo].files_to_run,
182        ],
183        outputs = [output_image],
184        executable = ctx.executable._staging_dir_builder,
185        arguments = [
186            staging_dir_builder_options_file.path,
187            build_image_files.executable.path,
188            "STAGING_DIR_PLACEHOLDER",
189            image_info.path,
190            output_image.path,
191            "STAGING_DIR_PLACEHOLDER",
192        ],
193        mnemonic = "BuildPartition",
194        env = {
195            # The dict + .keys() is to dedup the path elements, as some tools are in the same folder
196            "PATH": ":".join(({t.executable.dirname: True for t in extra_tools} | {
197                python_interpreter.dirname: True,
198            } | {
199                du.dirname: True,
200            } | {
201                find.dirname: True,
202            } | {
203                toolchain.openssl.dirname: True,
204            }).keys()),
205        },
206    )
207
208    return DefaultInfo(files = depset([output_image]))
209
210_partition = rule(
211    implementation = _partition_impl,
212    attrs = {
213        "type": attr.string(
214            mandatory = True,
215            values = _IMAGE_TYPES,
216        ),
217        "image_properties": attr.string(
218            doc = "The image property dictionary in key=value format. TODO: consider replacing this with explicit bazel properties for each property in this file.",
219        ),
220        "avb_enable": attr.bool(),
221        "avb_add_hashtree_footer_args": attr.string(),
222        "avb_key": attr.label(allow_single_file = True),
223        "avb_algorithm": attr.string(),
224        "avb_rollback_index": attr.int(default = -1),
225        "avb_rollback_index_location": attr.int(default = -1),
226        "base_staging_dir": attr.label(
227            allow_single_file = True,
228            doc = "A staging dir that the deps will be added to. This is intended to be used to import a make-built staging directory when building the partition with bazel.",
229        ),
230        "base_staging_dir_file_list": attr.label(
231            allow_single_file = True,
232            doc = "A file list that will be used to filter the base_staging_dir.",
233        ),
234        "deps": attr.label_list(),
235        "root_dir": attr.label(
236            allow_single_file = True,
237            doc = "A folder to add as the root_dir property in the property file",
238        ),
239        "selinux_file_contexts": attr.label(
240            allow_single_file = True,
241            doc = "The file specifying the selinux rules for all the files in this partition.",
242        ),
243        "_build_broken_incorrect_partition_images": attr.label(
244            default = "//build/bazel/product_config:build_broken_incorrect_partition_images",
245        ),
246        "_build_fingerprint": attr.label(
247            default = "//build/bazel/rules:build_fingerprint",
248        ),
249        "_platform_version_last_stable": attr.label(
250            default = "//build/bazel/product_config:platform_version_last_stable",
251        ),
252        "_platform_security_patch": attr.label(
253            default = "//build/bazel/product_config:platform_security_patch",
254        ),
255        "_staging_dir_builder": attr.label(
256            cfg = "exec",
257            doc = "The tool used to build a staging directory, because if bazel were to build it it would be entirely symlinks.",
258            executable = True,
259            default = "//build/bazel/rules:staging_dir_builder",
260        ),
261        "_status_file_reader": attr.label(
262            cfg = "exec",
263            executable = True,
264            default = "//build/bazel/rules:status_file_reader",
265        ),
266    },
267    toolchains = [
268        ":partition_toolchain_type",
269        "@bazel_tools//tools/python:toolchain_type",
270    ],
271)
272
273def partition(target_compatible_with = [], **kwargs):
274    target_compatible_with = select({
275        "//build/bazel_common_rules/platforms/os:android": [],
276        "//conditions:default": ["@platforms//:incompatible"],
277    }) + target_compatible_with
278    _partition(
279        target_compatible_with = target_compatible_with,
280        **kwargs
281    )
282