xref: /aosp_15_r20/external/bazelbuild-rules_android/mobile_install/resources.bzl (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1# Copyright 2018 The Bazel Authors. All rights reserved.
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"""Methods to process Android resources."""
15
16load(":constants.bzl", "constants")
17load(":utils.bzl", "utils")
18
19# Android resource types, see https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/tools/aapt2/Resource.h
20res_types = [
21    "anim",
22    "animator",
23    "array",
24    "attr",
25    "^attr-private",
26    "bool",
27    "color",
28    "configVarying",  #  Not really a type, but it shows up in some CTS tests
29    "dimen",
30    "drawable",
31    "font",
32    "fraction",
33    "id",
34    "integer",
35    "interpolator",
36    "layout",
37    "macro",
38    "menu",
39    "mipmap",
40    "navigation",
41    "plurals",
42    "raw",
43    "string",
44    "style",
45    "styleable",
46    "transition",
47    "xml",
48]
49
50def get_assets_dir(asset, base_dir):
51    """Build the full assets directory sanitizing the input first."""
52    if base_dir == "":
53        # some targets specify assets files and set assets_dirs = ""
54        return asset.dirname
55    asset_path = asset.path.rstrip("/")
56    base_dir = base_dir.rstrip("/")
57    if asset_path.endswith("/" + base_dir):
58        return asset_path
59    return "%s/%s" % (asset_path.rpartition("/%s/" % base_dir)[0], base_dir)
60
61def compile_resources(ctx, lib_strategy = True):
62    """Compiles android resources using aapt2
63
64    Args:
65      ctx: The context.
66      lib_strategy: If to use library strategy or bucket. Default is lib strategy.
67
68    Returns:
69      A list of compiled android resource archives (.flata) files, otherwise None
70      if data is None or empty.
71    """
72    res_dir_file_map = partition_by_res_dirs(ctx.rule.files.resource_files)
73    if lib_strategy:
74        return _compile_library_resouces(ctx, res_dir_file_map)
75    res_dir_buckets_map = _bucketize_resources(ctx, res_dir_file_map)
76    return _compile_bucketized_resources(ctx, res_dir_buckets_map)
77
78def partition_by_res_dirs(res_files):
79    """Partitions the resources by res directories.
80
81    Args:
82      res_files: A list of resource artifacts files.
83
84    Returns:
85      A map of "res" directories to files corresponding to the directory.
86    """
87    if not res_files:
88        return None
89
90    # Given the fact that res directories can be named anything and
91    # its not possible to distinguish between directories and regular files
92    # during analysis time, we use file extensions as an heuristic to group
93    # resource files. All Android resource files have the following form
94    # res-dir/type-dir/res_file. When we see a regular file (by looking at
95    # the extesion) we use the directory two levels up as the grouping dir.
96    # Most of the resource directories will contain at least one file with
97    # and extension, so this heuristic will generally result in good groupings.
98    res_non_values_file_map = {}
99    res_value_file_map = {}
100    res_dir_map = {}
101    for res_file in res_files:
102        if res_file.is_directory:
103            res_dir_map.setdefault(res_file.path, []).append(res_file)
104        else:
105            path_segments = res_file.dirname.rsplit("/", 1)
106            root_dir = path_segments[0]
107            if path_segments[1].startswith("values"):
108                res_value_file_map.setdefault(root_dir, []).append(res_file)
109            else:
110                res_non_values_file_map.setdefault(root_dir, []).append(res_file)
111    return {
112        "values": res_value_file_map,
113        "non-values": res_non_values_file_map,
114        "res_dir": res_dir_map,
115    }
116
117def _bucketize_resources(ctx, data):
118    """Bucketizes resources by type.
119
120    Args:
121      ctx: The context.
122      data: A map of "res" directories to files corresponding to the directory.
123
124    Returns:
125      A map of "res" directories to "res" buckets, None when there no resource
126      files to compile.
127    """
128    if not data:
129        return None
130
131    # Create backing files for the resource sharder.
132    res_dir_buckets_map = {}
133    for i, res_dir in enumerate(data.keys()):
134        res_buckets = []
135        typed_outputs = []
136
137        for r_type in res_types:
138            for idx in range(ctx.attr._mi_res_shards):
139                res_bucket = utils.isolated_declare_file(
140                    ctx,
141                    ctx.label.name + "_mi/resources/buckets/%d/%s_%s.zip" % (i, r_type, idx),
142                )
143                res_buckets.append(res_bucket)
144                typed_outputs.append(r_type + ":" + res_bucket.path)
145
146        args = ctx.actions.args()
147        args.use_param_file(param_file_arg = "-flagfile=%s")
148        args.set_param_file_format("multiline")
149        args.add_joined("-typed_outputs", typed_outputs, join_with = ",")
150        if data[res_dir]:
151            args.add_joined("-res_paths", data[res_dir], join_with = ",")
152
153        ctx.actions.run(
154            executable = ctx.executable._android_kit,
155            arguments = ["bucketize", args],
156            inputs = data[res_dir],
157            outputs = res_buckets,
158            mnemonic = "BucketizeRes",
159            progress_message = "MI Bucketize resources for %s" % res_dir,
160        )
161        res_dir_buckets_map[res_dir] = res_buckets
162    return res_dir_buckets_map
163
164def _compile_bucketized_resources(ctx, data):
165    """Compiles android resources using aapt2
166
167    Args:
168      ctx: The context.
169      data: A map of res directories to resource buckets.
170
171    Returns:
172      A list of compiled android resource archives (.flata) files, otherwise None
173      if data is None or empty.
174    """
175    if not data:
176        return constants.EMPTY_LIST
177
178    # TODO(mauriciogg): use no-crunch. We are using crunch to process 9-patch
179    # pngs should be disabled in general either by having a more granular flag
180    # in aapt2 or bucketizing 9patch pngs. See (b/70578281)
181    compiled_res_buckets = []
182    for res_dir, res_buckets in data.items():
183        for res_bucket in res_buckets:
184            # Note that extension matters for aapt2.
185            out = utils.isolated_declare_file(
186                ctx,
187                res_bucket.basename + ".flata",
188                sibling = res_bucket,
189            )
190            ctx.actions.run(
191                executable = ctx.executable._android_kit,
192                arguments = [
193                    "compile",
194                    "--aapt2=" + utils.first(ctx.attr._aapt2.files).path,
195                    "--in=" + res_bucket.path,
196                    "--out=" + out.path,
197                ],
198                inputs = [res_bucket] + ctx.attr._aapt2.files.to_list(),
199                outputs = [out],
200                mnemonic = "CompileRes",
201                progress_message = "MI Compiling resources for %s" % res_dir,
202            )
203            compiled_res_buckets.append(out)
204
205    return compiled_res_buckets
206
207def _compile_library_resouces(ctx, data):
208    """Compiles android resources using aapt2
209
210    Args:
211      ctx: The context.
212      data: A map of res directories to resource buckets.
213
214    Returns:
215      A list of compiled android resource archives (.flata) files, otherwise None
216      if data is None or empty.
217    """
218    if not data:
219        return constants.EMPTY_LIST
220
221    # TODO(mauriciogg): use no-crunch. We are using crunch to process 9-patch
222    # pngs should be disabled in general either by having a more granular flag
223    # in aapt2 or bucketizing 9patch pngs. See (b/70578281)
224    compiled_res_dirs = []
225    for res_type in data.keys():
226        for res_dir in data[res_type].keys():
227            # Note that extension matters for aapt2.
228            out = utils.isolated_declare_file(
229                ctx,
230                ctx.label.name + "_mi/resources/%s_%s.flata" % (res_dir.replace("/", "_"), res_type),
231            )
232            compiled_res_dirs.append(out)
233
234            args = ctx.actions.args()
235            args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True)
236            args.set_param_file_format("multiline")
237            args.add("-aapt2", ctx.file._aapt2)
238            args.add("-in", res_dir)
239            args.add("-out", out)
240            ctx.actions.run(
241                executable = ctx.executable._android_kit,
242                arguments = ["compile", args],
243                inputs = data[res_type][res_dir] + ctx.attr._aapt2.files.to_list(),
244                outputs = [out],
245                mnemonic = "CompileRes",
246                progress_message = "MI Compiling resources for %s" % res_dir,
247            )
248    return compiled_res_dirs
249
250def link_resources(
251        ctx,
252        manifest,
253        java_package,
254        android_jar,
255        resource_archives,
256        assets,
257        assets_dirs):
258    """Links android resources using aapt2
259
260    Args:
261      ctx: The context.
262      manifest: The AndroidManifest.xml file
263      java_package: The package to use to generate R.java
264      android_jar: The android jar
265      resource_archives: List of intermediate compiled android resource files.
266      assets: The list of assets.
267      assets_dirs: The list of directories for the assets.
268
269    Returns:
270      The resource apk and the R java file generated by aapt2.
271    """
272    if not resource_archives:
273        return None
274
275    resource_apk = utils.isolated_declare_file(ctx, ctx.label.name + "_mi/resources/resource.apk")
276    rjava_zip = utils.isolated_declare_file(ctx, "R.zip", sibling = resource_apk)
277
278    args = ctx.actions.args()
279    args.use_param_file(param_file_arg = "-flagfile=%s", use_always = True)
280    args.add("-aapt2", ctx.executable._aapt2)
281    args.add("-sdk_jar", android_jar)
282    args.add("-manifest", manifest)
283    args.add("-pkg", java_package)
284    args.add("-src_jar", rjava_zip)
285    args.add("-out", resource_apk)
286    args.add_joined("-res_dirs", resource_archives, join_with = ",")
287    args.add_joined("-asset_dirs", assets_dirs, join_with = ",")
288
289    ctx.actions.run(
290        executable = ctx.executable._android_kit,
291        arguments = ["link", args],
292        inputs = depset(
293            [manifest, android_jar, ctx.executable._aapt2] + resource_archives,
294            transitive = [assets],
295        ),
296        outputs = [resource_apk, rjava_zip],
297        mnemonic = "LinkRes",
298        progress_message = "MI Linking resources for %s" % ctx.label,
299    )
300    return resource_apk, rjava_zip
301
302def liteparse(ctx):
303    """Creates an R.pb which contains the resource ids gotten from a light parse.
304
305    Args:
306      ctx: The context.
307
308    Returns:
309      The resource pb file object.
310    """
311    if not hasattr(ctx.rule.files, "resource_files"):
312        return None
313
314    r_pb = utils.isolated_declare_file(ctx, ctx.label.name + "_mi/resources/R.pb")
315
316    args = ctx.actions.args()
317    args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True)
318    args.set_param_file_format("multiline")
319    args.add_joined("--res_files", ctx.rule.files.resource_files, join_with = ",")
320    args.add("--out", r_pb)
321
322    ctx.actions.run(
323        executable = ctx.executable._android_kit,
324        arguments = ["liteparse", args],
325        inputs = ctx.rule.files.resource_files,
326        outputs = [r_pb],
327        mnemonic = "ResLiteParse",
328        progress_message = "MI Lite parse Android Resources %s" % ctx.label,
329    )
330    return r_pb
331
332def compiletime_r_srcjar(ctx, output_srcjar, r_pbs, package):
333    """Create R.srcjar from the given R.pb files in the transitive closure.
334
335    Args:
336      ctx: The context.
337      output_srcjar: The output R source jar artifact.
338      r_pbs: Transitive  set of resource pbs.
339      package: The package name of the compile-time R.java.
340    """
341    args = ctx.actions.args()
342    args.use_param_file(param_file_arg = "--flagfile=%s", use_always = True)
343    args.set_param_file_format("multiline")
344    args.add("-rJavaOutput", output_srcjar)
345    args.add("-packageForR", package)
346    args.add_joined("-resourcePbs", r_pbs, join_with = ",")
347
348    ctx.actions.run(
349        executable = ctx.executable._android_kit,
350        arguments = ["rstub", args],
351        inputs = r_pbs,
352        outputs = [output_srcjar],
353        mnemonic = "CompileTimeRSrcjar",
354        progress_message = "MI Make compile-time R.srcjar %s" % ctx.label,
355    )
356