1""" 2Copyright (C) 2021 The Android Open Source Project 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15""" 16 17load(":apex_key.bzl", "ApexKeyInfo") 18load(":prebuilt_file.bzl", "PrebuiltFileInfo") 19load(":sh_binary.bzl", "ShBinaryInfo") 20load("//build/bazel/rules/cc:stripped_cc_common.bzl", "StrippedCcBinaryInfo") 21load("//build/bazel/rules/android:android_app_certificate.bzl", "AndroidAppCertificateInfo") 22load("//build/bazel/rules/apex:transition.bzl", "apex_transition", "shared_lib_transition_32", "shared_lib_transition_64") 23load("//build/bazel/rules/apex:cc.bzl", "ApexCcInfo", "apex_cc_aspect") 24 25DIR_LIB = "lib" 26DIR_LIB64 = "lib64" 27 28ApexInfo = provider( 29 "ApexInfo has no field currently and is used by apex rule dependents to ensure an attribute is a target of apex rule.", 30 fields = {}, 31) 32 33# Prepare the input files info for bazel_apexer_wrapper to generate APEX filesystem image. 34def _prepare_apexer_wrapper_inputs(ctx): 35 # dictionary to return in the format: 36 # apex_manifest[(image_file_dirname, image_file_basename)] = bazel_output_file 37 apex_manifest = {} 38 39 x86_constraint = ctx.attr._x86_constraint[platform_common.ConstraintValueInfo] 40 x86_64_constraint = ctx.attr._x86_64_constraint[platform_common.ConstraintValueInfo] 41 arm_constraint = ctx.attr._arm_constraint[platform_common.ConstraintValueInfo] 42 arm64_constraint = ctx.attr._arm64_constraint[platform_common.ConstraintValueInfo] 43 44 if ctx.target_platform_has_constraint(x86_constraint): 45 _add_libs_32_target(ctx, "x86", apex_manifest) 46 elif ctx.target_platform_has_constraint(x86_64_constraint): 47 _add_libs_64_target(ctx, "x86", "x86_64", apex_manifest) 48 elif ctx.target_platform_has_constraint(arm_constraint): 49 _add_libs_32_target(ctx, "arm", apex_manifest) 50 elif ctx.target_platform_has_constraint(arm64_constraint): 51 _add_libs_64_target(ctx, "arm", "arm64", apex_manifest) 52 53 # Handle prebuilts 54 for dep in ctx.attr.prebuilts: 55 prebuilt_file_info = dep[PrebuiltFileInfo] 56 if prebuilt_file_info.filename: 57 filename = prebuilt_file_info.filename 58 else: 59 filename = dep.label.name 60 apex_manifest[(prebuilt_file_info.dir, filename)] = prebuilt_file_info.src 61 62 # Handle binaries 63 for dep in ctx.attr.binaries: 64 if ShBinaryInfo in dep: 65 # sh_binary requires special handling on directory/filename construction. 66 sh_binary_info = dep[ShBinaryInfo] 67 default_info = dep[DefaultInfo] 68 if sh_binary_info != None: 69 directory = "bin" 70 if sh_binary_info.sub_dir != None and sh_binary_info.sub_dir != "": 71 directory = "/".join([directory, sh_binary_info.sub_dir]) 72 73 if sh_binary_info.filename != None and sh_binary_info.filename != "": 74 filename = sh_binary_info.filename 75 else: 76 filename = dep.label.name 77 78 apex_manifest[(directory, filename)] = default_info.files_to_run.executable 79 elif CcInfo in dep: 80 # cc_binary just takes the final executable from the runfiles. 81 apex_manifest[("bin", dep.label.name)] = dep[DefaultInfo].files_to_run.executable 82 83 apex_content_inputs = [] 84 85 bazel_apexer_wrapper_manifest = ctx.actions.declare_file("%s_bazel_apexer_wrapper_manifest" % ctx.attr.name) 86 file_lines = [] 87 88 # Store the apex file target directory, file name and the path in the source tree in a file. 89 # This file will be read by the bazel_apexer_wrapper to create the apex input directory. 90 # Here is an example: 91 # {etc/tz,tz_version,system/timezone/output_data/version/tz_version} 92 for (apex_dirname, apex_basename), bazel_input_file in apex_manifest.items(): 93 apex_content_inputs.append(bazel_input_file) 94 file_lines += [",".join([apex_dirname, apex_basename, bazel_input_file.path])] 95 96 ctx.actions.write(bazel_apexer_wrapper_manifest, "\n".join(file_lines)) 97 98 return apex_content_inputs, bazel_apexer_wrapper_manifest 99 100def _add_libs_32_target(ctx, key, apex_manifest): 101 if len(ctx.split_attr.native_shared_libs_32.keys()) > 0: 102 _add_lib_file(DIR_LIB, ctx.split_attr.native_shared_libs_32[key], apex_manifest) 103 104def _add_libs_64_target(ctx, key_32, key_64, apex_manifest): 105 _add_libs_32_target(ctx, key_32, apex_manifest) 106 if len(ctx.split_attr.native_shared_libs_64.keys()) > 0: 107 _add_lib_file(DIR_LIB64, ctx.split_attr.native_shared_libs_64[key_64], apex_manifest) 108 109def _add_lib_file(dir, libs, apex_manifest): 110 for dep in libs: 111 apex_cc_info = dep[ApexCcInfo] 112 for lib_file in apex_cc_info.transitive_shared_libs.to_list(): 113 apex_manifest[(dir, lib_file.basename)] = lib_file 114 115# conv_apex_manifest - Convert the JSON APEX manifest to protobuf, which is needed by apexer. 116def _convert_apex_manifest_json_to_pb(ctx, apex_toolchain): 117 apex_manifest_json = ctx.file.manifest 118 apex_manifest_pb = ctx.actions.declare_file("apex_manifest.pb") 119 120 ctx.actions.run( 121 outputs = [apex_manifest_pb], 122 inputs = [ctx.file.manifest], 123 executable = apex_toolchain.conv_apex_manifest, 124 arguments = [ 125 "proto", 126 apex_manifest_json.path, 127 "-o", 128 apex_manifest_pb.path, 129 ], 130 mnemonic = "ConvApexManifest", 131 ) 132 133 return apex_manifest_pb 134 135# apexer - generate the APEX file. 136def _run_apexer(ctx, apex_toolchain, apex_content_inputs, bazel_apexer_wrapper_manifest, apex_manifest_pb): 137 # Inputs 138 file_contexts = ctx.file.file_contexts 139 apex_key_info = ctx.attr.key[ApexKeyInfo] 140 privkey = apex_key_info.private_key 141 pubkey = apex_key_info.public_key 142 android_jar = apex_toolchain.android_jar 143 android_manifest = ctx.file.android_manifest 144 145 # Outputs 146 apex_output_file = ctx.actions.declare_file(ctx.attr.name + ".apex.unsigned") 147 148 # Arguments 149 args = ctx.actions.args() 150 args.add_all(["--manifest", apex_manifest_pb.path]) 151 args.add_all(["--file_contexts", file_contexts.path]) 152 args.add_all(["--key", privkey.path]) 153 args.add_all(["--pubkey", pubkey.path]) 154 min_sdk_version = ctx.attr.min_sdk_version 155 156 # TODO(b/215339575): This is a super rudimentary way to convert "current" to a numerical number. 157 # Generalize this to API level handling logic in a separate Starlark utility, preferably using 158 # API level maps dumped from api_levels.go 159 if min_sdk_version == "current": 160 min_sdk_version = "10000" 161 args.add_all(["--min_sdk_version", min_sdk_version]) 162 args.add_all(["--bazel_apexer_wrapper_manifest", bazel_apexer_wrapper_manifest]) 163 args.add_all(["--apexer_path", apex_toolchain.apexer]) 164 165 # apexer needs the list of directories containing all auxilliary tools invoked during 166 # the creation of an apex 167 avbtool_files = apex_toolchain.avbtool[DefaultInfo].files_to_run 168 e2fsdroid_files = apex_toolchain.e2fsdroid[DefaultInfo].files_to_run 169 mke2fs_files = apex_toolchain.mke2fs[DefaultInfo].files_to_run 170 resize2fs_files = apex_toolchain.resize2fs[DefaultInfo].files_to_run 171 apexer_tool_paths = [ 172 # These are built by make_injection 173 apex_toolchain.apexer.dirname, 174 175 # These are real Bazel targets 176 apex_toolchain.aapt2.dirname, 177 avbtool_files.executable.dirname, 178 e2fsdroid_files.executable.dirname, 179 mke2fs_files.executable.dirname, 180 resize2fs_files.executable.dirname, 181 ] 182 183 args.add_all(["--apexer_tool_path", ":".join(apexer_tool_paths)]) 184 args.add_all(["--apex_output_file", apex_output_file]) 185 186 if android_manifest != None: 187 args.add_all(["--android_manifest", android_manifest.path]) 188 189 inputs = apex_content_inputs + [ 190 bazel_apexer_wrapper_manifest, 191 apex_manifest_pb, 192 file_contexts, 193 privkey, 194 pubkey, 195 android_jar, 196 ] 197 198 tools = [ 199 avbtool_files, 200 e2fsdroid_files, 201 mke2fs_files, 202 resize2fs_files, 203 apex_toolchain.aapt2, 204 205 apex_toolchain.apexer, 206 apex_toolchain.sefcontext_compile, 207 ] 208 209 if android_manifest != None: 210 inputs.append(android_manifest) 211 212 ctx.actions.run( 213 inputs = inputs, 214 tools = tools, 215 outputs = [apex_output_file], 216 executable = ctx.executable._bazel_apexer_wrapper, 217 arguments = [args], 218 mnemonic = "BazelApexerWrapper", 219 ) 220 221 return apex_output_file 222 223# Sign a file with signapk. 224def _run_signapk(ctx, unsigned_file, signed_file, private_key, public_key, mnemonic): 225 # Inputs 226 inputs = [ 227 unsigned_file, 228 private_key, 229 public_key, 230 ctx.executable._signapk, 231 ] 232 233 # Outputs 234 outputs = [signed_file] 235 236 # Arguments 237 args = ctx.actions.args() 238 args.add_all(["-a", 4096]) 239 args.add_all(["--align-file-size"]) 240 args.add_all([public_key, private_key]) 241 args.add_all([unsigned_file, signed_file]) 242 243 ctx.actions.run( 244 inputs = inputs, 245 outputs = outputs, 246 executable = ctx.executable._signapk, 247 arguments = [args], 248 mnemonic = mnemonic, 249 ) 250 251 return signed_file 252 253# Compress a file with apex_compression_tool. 254def _run_apex_compression_tool(ctx, apex_toolchain, input_file, output_file_name): 255 # Inputs 256 inputs = [ 257 input_file, 258 ] 259 260 avbtool_files = apex_toolchain.avbtool[DefaultInfo].files_to_run 261 tools = [ 262 avbtool_files, 263 apex_toolchain.apex_compression_tool, 264 apex_toolchain.soong_zip, 265 ] 266 267 # Outputs 268 compressed_file = ctx.actions.declare_file(output_file_name) 269 outputs = [compressed_file] 270 271 # Arguments 272 args = ctx.actions.args() 273 args.add_all(["compress"]) 274 tool_dirs = [apex_toolchain.soong_zip.dirname, avbtool_files.executable.dirname] 275 args.add_all(["--apex_compression_tool", ":".join(tool_dirs)]) 276 args.add_all(["--input", input_file]) 277 args.add_all(["--output", compressed_file]) 278 279 ctx.actions.run( 280 inputs = inputs, 281 tools = tools, 282 outputs = outputs, 283 executable = apex_toolchain.apex_compression_tool, 284 arguments = [args], 285 mnemonic = "BazelApexCompressing", 286 ) 287 return compressed_file 288 289# See the APEX section in the README on how to use this rule. 290def _apex_rule_impl(ctx): 291 apex_toolchain = ctx.toolchains["//build/bazel/rules/apex:apex_toolchain_type"].toolchain_info 292 293 apex_content_inputs, bazel_apexer_wrapper_manifest = _prepare_apexer_wrapper_inputs(ctx) 294 apex_manifest_pb = _convert_apex_manifest_json_to_pb(ctx, apex_toolchain) 295 296 unsigned_apex_output_file = _run_apexer(ctx, apex_toolchain, apex_content_inputs, bazel_apexer_wrapper_manifest, apex_manifest_pb) 297 298 apex_cert_info = ctx.attr.certificate[AndroidAppCertificateInfo] 299 private_key = apex_cert_info.pk8 300 public_key = apex_cert_info.pem 301 302 signed_apex = ctx.outputs.apex_output 303 _run_signapk(ctx, unsigned_apex_output_file, signed_apex, private_key, public_key, "BazelApexSigning") 304 output_file = signed_apex 305 306 if ctx.attr.compressible: 307 compressed_apex_output_file = _run_apex_compression_tool(ctx, apex_toolchain, signed_apex, ctx.attr.name + ".capex.unsigned") 308 signed_capex = ctx.outputs.capex_output 309 _run_signapk(ctx, compressed_apex_output_file, signed_capex, private_key, public_key, "BazelCompressedApexSigning") 310 311 files_to_build = depset([output_file]) 312 return [DefaultInfo(files = files_to_build), ApexInfo()] 313 314_apex = rule( 315 implementation = _apex_rule_impl, 316 attrs = { 317 "manifest": attr.label(allow_single_file = [".json"]), 318 "android_manifest": attr.label(allow_single_file = [".xml"]), 319 "file_contexts": attr.label(allow_single_file = True, mandatory = True), 320 "key": attr.label(providers = [ApexKeyInfo]), 321 "certificate": attr.label(providers = [AndroidAppCertificateInfo]), 322 "min_sdk_version": attr.string(default = "current"), 323 "updatable": attr.bool(default = True), 324 "installable": attr.bool(default = True), 325 "compressible": attr.bool(default = False), 326 "native_shared_libs_32": attr.label_list( 327 providers = [ApexCcInfo], 328 aspects = [apex_cc_aspect], 329 cfg = shared_lib_transition_32, 330 doc = "The libs compiled for 32-bit", 331 ), 332 "native_shared_libs_64": attr.label_list( 333 providers = [ApexCcInfo], 334 aspects = [apex_cc_aspect], 335 cfg = shared_lib_transition_64, 336 doc = "The libs compiled for 64-bit", 337 ), 338 "binaries": attr.label_list( 339 providers = [ 340 # The dependency must produce _all_ of the providers in _one_ of these lists. 341 [ShBinaryInfo], # sh_binary 342 [StrippedCcBinaryInfo, CcInfo], # cc_binary (stripped) 343 ], 344 cfg = apex_transition, 345 ), 346 "prebuilts": attr.label_list(providers = [PrebuiltFileInfo], cfg = apex_transition), 347 "apex_output": attr.output(doc = "signed .apex output"), 348 "capex_output": attr.output(doc = "signed .capex output"), 349 350 # Required to use apex_transition. This is an acknowledgement to the risks of memory bloat when using transitions. 351 "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"), 352 "_bazel_apexer_wrapper": attr.label( 353 cfg = "host", 354 doc = "The apexer wrapper to avoid the problem where symlinks are created inside apex image.", 355 executable = True, 356 default = "//build/bazel/rules/apex:bazel_apexer_wrapper", 357 ), 358 "_signapk": attr.label( 359 cfg = "host", 360 doc = "The signapk tool.", 361 executable = True, 362 default = "//build/make/tools/signapk", 363 ), 364 "_x86_constraint": attr.label( 365 default = Label("//build/bazel/platforms/arch:x86"), 366 ), 367 "_x86_64_constraint": attr.label( 368 default = Label("//build/bazel/platforms/arch:x86_64"), 369 ), 370 "_arm_constraint": attr.label( 371 default = Label("//build/bazel/platforms/arch:arm"), 372 ), 373 "_arm64_constraint": attr.label( 374 default = Label("//build/bazel/platforms/arch:arm64"), 375 ), 376 }, 377 toolchains = ["//build/bazel/rules/apex:apex_toolchain_type"], 378 fragments = ["platform"], 379) 380 381def apex( 382 name, 383 manifest = "apex_manifest.json", 384 android_manifest = None, 385 file_contexts = None, 386 key = None, 387 certificate = None, 388 min_sdk_version = None, 389 updatable = True, 390 installable = True, 391 compressible = False, 392 native_shared_libs_32 = [], 393 native_shared_libs_64 = [], 394 binaries = [], 395 prebuilts = [], 396 **kwargs): 397 "Bazel macro to correspond with the APEX bundle Soong module." 398 399 # If file_contexts is not specified, then use the default from //system/sepolicy/apex. 400 # https://cs.android.com/android/platform/superproject/+/master:build/soong/apex/builder.go;l=259-263;drc=b02043b84d86fe1007afef1ff012a2155172215c 401 if file_contexts == None: 402 file_contexts = "//system/sepolicy/apex:" + name + "-file_contexts" 403 404 apex_output = name + ".apex" 405 capex_output = None 406 if compressible: 407 capex_output = name + ".capex" 408 409 _apex( 410 name = name, 411 manifest = manifest, 412 android_manifest = android_manifest, 413 file_contexts = file_contexts, 414 key = key, 415 certificate = certificate, 416 min_sdk_version = min_sdk_version, 417 updatable = updatable, 418 installable = installable, 419 compressible = compressible, 420 native_shared_libs_32 = native_shared_libs_32, 421 native_shared_libs_64 = native_shared_libs_64, 422 binaries = binaries, 423 prebuilts = prebuilts, 424 425 # Enables predeclared output builds from command line directly, e.g. 426 # 427 # $ bazel build //path/to/module:com.android.module.apex 428 # $ bazel build //path/to/module:com.android.module.capex 429 apex_output = apex_output, 430 capex_output = capex_output, 431 **kwargs 432 ) 433