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