xref: /aosp_15_r20/external/bazelbuild-rules_android/rules/busybox.bzl (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1# Copyright 2019 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
15"""Bazel ResourcesBusyBox Commands."""
16
17load(":java.bzl", _java = "java")
18
19_ANDROID_RESOURCES_STRICT_DEPS = "android_resources_strict_deps"
20
21def _sanitize_assets_dir(assets_dir):
22    sanitized_assets_dir = "/".join(
23        [
24            part
25            for part in assets_dir.split("/")
26            if part != "" and part != "."
27        ],
28    )
29
30    return "/" + sanitized_assets_dir if assets_dir.startswith("/") else sanitized_assets_dir
31
32def _get_unique_assets_dirs(assets, assets_dir):
33    """Find the unique assets directories, partitioned by assets_dir.
34
35    Args:
36      assets: A list of Files. List of asset files to process.
37      assets_dir: String. String giving the path to the files in assets.
38
39    Returns:
40      A list of short_paths representing unique asset dirs.
41    """
42    if not assets:
43        return []
44
45    dirs = dict()
46
47    assets_dir = _sanitize_assets_dir(assets_dir)
48    if assets_dir:
49        partition_by = "/%s/" % assets_dir.strip("/")
50        for f in assets:
51            if f.is_directory and f.path.endswith(partition_by[:-1]):
52                # If f is a directory, check if its path ends with the assets_dir.
53                dirs[f.path] = True
54            elif f.is_directory and "_aar/unzipped" in f.path:
55                # Assets from an aar_import rule are extracted in a
56                # "assets" subdirectory of the given path
57                dirs["%s/assets" % f.path] = True
58            else:
59                # Partition to remove subdirectories beneath assets_dir
60                # Also removes the trailing /
61                dirs["".join(f.path.rpartition(partition_by)[:2])[:-1]] = True
62    else:
63        # Use the dirname of the generating target if no assets_dir.
64        for f in assets:
65            if f.is_source:
66                dirs[f.owner.package] = True
67            else:
68                # Prepend the root path for generated files.
69                dirs[f.root.path + "/" + f.owner.package] = True
70    return dirs.keys()
71
72def _get_unique_res_dirs(resource_files):
73    """Find the unique res dirs.
74
75    Args:
76      resource_files: A list of Files. A list of resource_files.
77
78    Returns:
79      A list of short_paths representing unique res dirs from the given resource files.
80    """
81    dirs = dict()
82    for f in resource_files:
83        if f.is_directory:
84            dirs[f.path] = True
85        else:
86            dirs[f.dirname.rpartition("/" + f.dirname.split("/")[-1])[0]] = True
87    return dirs.keys()
88
89def _make_serialized_resources_flag(
90        assets = [],
91        assets_dir = None,
92        resource_files = [],
93        label = "",
94        symbols = None):
95    return ";".join(
96        [
97            "#".join(_get_unique_res_dirs(resource_files)),
98            "#".join(_get_unique_assets_dirs(assets, assets_dir)),
99            label,
100            symbols.path if symbols else "",
101        ],
102    ).rstrip(":")
103
104def _make_resources_flag(
105        assets = [],
106        assets_dir = None,
107        resource_files = [],
108        manifest = None,
109        r_txt = None,
110        symbols = None):
111    return ":".join(
112        [
113            "#".join(_get_unique_res_dirs(resource_files)),
114            "#".join(_get_unique_assets_dirs(assets, assets_dir)),
115            manifest.path if manifest else "",
116            r_txt.path if r_txt else "",
117            symbols.path if symbols else "",
118        ],
119    )
120
121def _path(f):
122    return f.path
123
124def _make_package_resources_flags(resources_node):
125    if not (resources_node.manifest and resources_node.r_txt and resources_node.compiled_resources):
126        return None
127    flag = _make_resources_flag(
128        resource_files = resources_node.resource_files.to_list(),
129        assets = resources_node.assets.to_list(),
130        assets_dir = resources_node.assets_dir,
131        manifest = resources_node.manifest,
132        r_txt = resources_node.r_txt,
133        symbols = resources_node.compiled_resources,
134    )
135    return flag
136
137def _make_package_assets_flags(resources_node):
138    assets = resources_node.assets.to_list()
139    if not assets:
140        return None
141    return _make_serialized_resources_flag(
142        assets = assets,
143        assets_dir = resources_node.assets_dir,
144        label = str(resources_node.label),
145        symbols = resources_node.compiled_assets,
146    )
147
148def _extract_filters(
149        raw_list):
150    """Extract densities and resource_configuration filters from raw string lists.
151
152    In BUILD files, string lists can be represented as a list of strings, a single comma-separated
153    string, or a combination of both. This method outputs a single list of individual string values,
154    which can then be passed directly to resource processing actions. Empty strings are removed and
155    the final list is sorted.
156
157    Args:
158      raw_list: List of strings. The raw densities or resource configuration filters.
159
160    Returns:
161      List of strings extracted from the raw list.
162    """
163    out_filters = []
164    for item in raw_list:
165        if "," in item:
166            item_list = item.split(",")
167            for entry in item_list:
168                stripped_entry = entry.strip()
169                if stripped_entry:
170                    out_filters.append(stripped_entry)
171        elif item:
172            out_filters.append(item)
173    return sorted(out_filters)
174
175def _package(
176        ctx,
177        out_r_src_jar = None,
178        out_r_txt = None,
179        out_symbols = None,
180        out_manifest = None,
181        out_proguard_cfg = None,
182        out_main_dex_proguard_cfg = None,
183        out_resource_files_zip = None,
184        out_file = None,
185        package_type = None,
186        java_package = None,
187        manifest = None,
188        assets = [],
189        assets_dir = None,
190        resource_files = [],
191        resource_configs = None,
192        densities = [],
193        application_id = None,
194        package_id = None,
195        direct_resources_nodes = [],
196        transitive_resources_nodes = [],
197        transitive_manifests = [],
198        transitive_assets = [],
199        transitive_compiled_assets = [],
200        transitive_resource_files = [],
201        transitive_compiled_resources = [],
202        transitive_r_txts = [],
203        additional_apks_to_link_against = [],
204        resource_apks = depset(),
205        nocompress_extensions = [],
206        proto_format = False,
207        shrink_resource_cycles = False,
208        version_name = None,
209        version_code = None,
210        android_jar = None,
211        aapt = None,
212        busybox = None,
213        host_javabase = None,
214        should_throw_on_conflict = True,  # TODO: read this from allowlist at caller
215        debug = True):  # TODO: we will set this to false in prod builds
216    """Packages the compiled Android Resources with AAPT.
217
218    Args:
219      ctx: The context.
220      out_r_src_jar: A File. The R.java outputted by linking resources in a srcjar.
221      out_r_txt: A File. The resource IDs outputted by linking resources in text.
222      out_symbols: A File. The output zip containing compiled resources.
223      out_manifest: A File. The output processed manifest.
224      out_proguard_cfg: A File. The proguard config to be generated.
225      out_main_dex_proguard_cfg: A File. The main dex proguard config to be generated.
226      out_resource_files_zip: A File. The resource files zipped by linking resources.
227      out_file: A File. The Resource APK outputted by linking resources.
228      package_type: A string. The configuration type to use when packaging.
229      java_package: A string. The Java package for the generated R.java.
230      manifest: A File. The AndroidManifest.xml.
231      assets: sequence of Files. A list of Android assets files to be processed.
232      assets_dir: String. The name of the assets directory.
233      resource_files: A list of Files. The resource files.
234      resource_configs: A list of strings. The list of resource configuration
235        filters.
236      densities: A list of strings. The list of screen densities to filter for when
237        building the apk.
238      application_id: An optional string. The applicationId set in manifest values.
239      package_id: An optional integer in [2,255]. This is the prefix byte for
240        all generated resource IDs, defaults to 0x7F (127). 1 is reserved by the
241        framework, and some builds are known to crash when given IDs > 127.
242        Shared libraries are also assigned monotonically increasing IDs in
243        [2,126], so care should be taken that there is room at the lower end.
244      direct_resources_nodes: Depset of ResourcesNodeInfo providers. The set of
245        ResourcesNodeInfo from direct dependencies.
246      transitive_resources_nodes: Depset of ResourcesNodeInfo providers. The set
247        of ResourcesNodeInfo from transitive dependencies (not including directs).
248      transitive_manifests: List of Depsets. Depsets contain all transitive manifests.
249      transitive_assets: List of Depsets. Depsets contain all transitive assets.
250      transitive_compiled_assets: List of Depsets. Depsets contain all transitive
251        compiled_assets.
252      transitive_resource_files: List of Depsets. Depsets contain all transitive
253        resource files.
254      transitive_compiled_resources: List of Depsets. Depsets contain all transitive
255        compiled_resources.
256      transitive_r_txts: List of Depsets. Depsets contain all transitive R txt files.
257      additional_apks_to_link_against: A list of Files. Additional APKs to link
258        against. Optional.
259      resource_apks: Depset of resource only apk files to link against.
260      nocompress_extensions: A list of strings. File extension to leave uncompressed
261        in the apk.
262      proto_format: Boolean, whether to generate the resource table in proto format.
263      shrink_resource_cycles: Boolean, flag that enables more shrinking of
264        code and resources by instructing AAPT2 to emit conditional Proguard keep rules.
265      version_name: A string. The version name to stamp the generated manifest with. Optional.
266      version_code: A string. The version code to stamp the generated manifest with. Optional.
267      android_jar: A File. The Android Jar.
268      aapt: A FilesToRunProvider. The AAPT executable.
269      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
270      host_javabase: Target. The host javabase.
271      should_throw_on_conflict: A boolean. Determines whether an error should be thrown
272        when a resource conflict occurs.
273      debug: A boolean. Determines whether to enable debugging.
274    """
275    if not manifest:
276        fail("No manifest given, the manifest is mandatory.")
277
278    direct_data_flag = []
279    direct_compiled_resources = []
280
281    output_files = []
282    input_files = []
283    transitive_input_files = []
284
285    args = ctx.actions.args()
286    args.use_param_file("@%s")
287    args.add("--tool", "AAPT2_PACKAGE")
288    args.add("--")
289    args.add("--aapt2", aapt.executable)
290    args.add_joined(
291        "--data",
292        transitive_resources_nodes,
293        map_each = _make_package_resources_flags,
294        join_with = ",",
295    )
296    args.add_joined(
297        "--directData",
298        direct_resources_nodes,
299        map_each = _make_package_resources_flags,
300        join_with = ",",
301    )
302    args.add_joined(
303        "--directAssets",
304        direct_resources_nodes,
305        map_each = _make_package_assets_flags,
306        join_with = "&",
307        omit_if_empty = True,
308    )
309    args.add_joined(
310        "--assets",
311        transitive_resources_nodes,
312        map_each = _make_package_assets_flags,
313        join_with = "&",
314        omit_if_empty = True,
315    )
316    transitive_input_files.extend(transitive_resource_files)
317    transitive_input_files.extend(transitive_assets)
318    transitive_input_files.extend(transitive_compiled_assets)
319    transitive_input_files.extend(transitive_compiled_resources)
320    transitive_input_files.extend(transitive_manifests)
321    transitive_input_files.extend(transitive_r_txts)
322    args.add(
323        "--primaryData",
324        _make_resources_flag(
325            manifest = manifest,
326            assets = assets,
327            assets_dir = assets_dir,
328            resource_files = resource_files,
329        ),
330    )
331    input_files.append(manifest)
332    input_files.extend(resource_files)
333    input_files.extend(assets)
334    args.add("--androidJar", android_jar)
335    input_files.append(android_jar)
336    args.add("--rOutput", out_r_txt)
337    output_files.append(out_r_txt)
338    if out_symbols:
339        args.add("--symbolsOut", out_symbols)
340        output_files.append(out_symbols)
341    args.add("--srcJarOutput", out_r_src_jar)
342    output_files.append(out_r_src_jar)
343    if out_proguard_cfg:
344        args.add("--proguardOutput", out_proguard_cfg)
345        output_files.append(out_proguard_cfg)
346    if out_main_dex_proguard_cfg:
347        args.add("--mainDexProguardOutput", out_main_dex_proguard_cfg)
348        output_files.append(out_main_dex_proguard_cfg)
349    args.add("--manifestOutput", out_manifest)
350    output_files.append(out_manifest)
351    if out_resource_files_zip:
352        args.add("--resourcesOutput", out_resource_files_zip)
353        output_files.append(out_resource_files_zip)
354    if out_file:
355        args.add("--packagePath", out_file)
356        output_files.append(out_file)
357    args.add("--useAaptCruncher=no")  # Unnecessary, used for AAPT1 only but added here to minimize diffs.
358    if package_type:
359        args.add("--packageType", package_type)
360    if debug:
361        args.add("--debug")
362    if should_throw_on_conflict:
363        args.add("--throwOnResourceConflict")
364    if resource_configs:
365        args.add_joined("--resourceConfigs", _extract_filters(resource_configs), join_with = ",")
366    if densities:
367        args.add_joined("--densities", _extract_filters(densities), join_with = ",")
368    if application_id:
369        args.add("--applicationId", application_id)
370    if package_id:
371        args.add("--packageId", package_id)
372    if additional_apks_to_link_against:
373        args.add_joined(
374            "--additionalApksToLinkAgainst",
375            additional_apks_to_link_against,
376            join_with = ":",
377            map_each = _path,
378        )
379        input_files.extend(additional_apks_to_link_against)
380    if nocompress_extensions:
381        args.add_joined("--uncompressedExtensions", nocompress_extensions, join_with = ",")
382    if proto_format:
383        args.add("--resourceTableAsProto")
384    if shrink_resource_cycles:
385        args.add("--conditionalKeepRules=yes")
386    if version_name:
387        args.add("--versionName", version_name)
388    if version_code:
389        args.add("--versionCode", version_code)
390    if java_package:
391        args.add("--packageForR", java_package)
392
393    args.add_joined(
394        "--resourceApks",
395        resource_apks,
396        join_with = ":",
397    )
398    transitive_input_files.append(resource_apks)
399
400    _java.run(
401        ctx = ctx,
402        host_javabase = host_javabase,
403        executable = busybox,
404        tools = [aapt],
405        arguments = [args],
406        inputs = depset(input_files, transitive = transitive_input_files),
407        outputs = output_files,
408        mnemonic = "PackageAndroidResources",
409        progress_message = "Packaging Android Resources in %s" % ctx.label,
410    )
411
412def _parse(
413        ctx,
414        out_symbols = None,
415        assets = [],
416        assets_dir = None,
417        busybox = None,
418        host_javabase = None):
419    """Parses Android assets.
420
421    Args:
422      ctx: The context.
423      out_symbols: A File. The output bin containing parsed assets.
424      assets: sequence of Files. A list of Android assets files to be processed.
425      assets_dir: String. The name of the assets directory.
426      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
427      host_javabase: Target. The host javabase.
428    """
429    args = ctx.actions.args()
430    args.use_param_file("@%s")
431    args.add("--tool", "PARSE")
432    args.add("--")
433    args.add(
434        "--primaryData",
435        _make_resources_flag(
436            assets = assets,
437            assets_dir = assets_dir,
438        ),
439    )
440    args.add("--output", out_symbols)
441
442    _java.run(
443        ctx = ctx,
444        host_javabase = host_javabase,
445        executable = busybox,
446        arguments = [args],
447        inputs = assets,
448        outputs = [out_symbols],
449        mnemonic = "ParseAndroidResources",
450        progress_message = "Parsing Android Resources in %s" % out_symbols.short_path,
451    )
452
453def _make_merge_assets_flags(resources_node):
454    assets = resources_node.assets.to_list()
455    if not (assets or resources_node.assets_dir):
456        return None
457    return _make_serialized_resources_flag(
458        assets = assets,
459        assets_dir = resources_node.assets_dir,
460        label = str(resources_node.label),
461        symbols = resources_node.assets_symbols,
462    )
463
464def _merge_assets(
465        ctx,
466        out_assets_zip = None,
467        assets = [],
468        assets_dir = None,
469        symbols = None,
470        transitive_assets = [],
471        transitive_assets_symbols = [],
472        direct_resources_nodes = [],
473        transitive_resources_nodes = [],
474        busybox = None,
475        host_javabase = None):
476    """Merges Android assets.
477
478    Args:
479      ctx: The context.
480      out_assets_zip: A File.
481      assets: sequence of Files. A list of Android assets files to be processed.
482      assets_dir: String. The name of the assets directory.
483      symbols: A File. The parsed assets.
484      transitive_assets: Sequence of Depsets. The list of transitive
485        assets from deps.
486      transitive_assets_symbols: Sequence of Depsets. The list of
487        transitive assets_symbols files from deps.
488      direct_resources_nodes: Sequence of ResourcesNodeInfo providers. The list
489        of ResourcesNodeInfo providers that are direct depencies.
490      transitive_resources_nodes: Sequence of ResourcesNodeInfo providers. The
491        list of ResourcesNodeInfo providers that are transitive depencies.
492      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
493      host_javabase: Target. The host javabase.
494    """
495    args = ctx.actions.args()
496    args.use_param_file("@%s")
497    args.add("--tool", "MERGE_ASSETS")
498    args.add("--")
499    args.add("--assetsOutput", out_assets_zip)
500    args.add(
501        "--primaryData",
502        _make_serialized_resources_flag(
503            assets = assets,
504            assets_dir = assets_dir,
505            label = str(ctx.label),
506            symbols = symbols,
507        ),
508    )
509    args.add_joined(
510        "--directData",
511        direct_resources_nodes,
512        map_each = _make_merge_assets_flags,
513        join_with = "&",
514    )
515    args.add_joined(
516        "--data",
517        transitive_resources_nodes,
518        map_each = _make_merge_assets_flags,
519        join_with = "&",
520    )
521
522    _java.run(
523        ctx = ctx,
524        host_javabase = host_javabase,
525        executable = busybox,
526        arguments = [args],
527        inputs = depset(
528            assets + [symbols],
529            transitive = transitive_assets + transitive_assets_symbols,
530        ),
531        outputs = [out_assets_zip],
532        mnemonic = "MergeAndroidAssets",
533        progress_message =
534            "Merging Android Assets in %s" % out_assets_zip.short_path,
535    )
536
537def _validate_and_link(
538        ctx,
539        out_r_src_jar = None,
540        out_r_txt = None,
541        out_file = None,
542        compiled_resources = None,
543        transitive_compiled_resources = depset(),
544        java_package = None,
545        manifest = None,
546        resource_apks = [],
547        android_jar = None,
548        busybox = None,
549        host_javabase = None,
550        aapt = None):
551    """Links compiled Android Resources with AAPT.
552
553    Args:
554      ctx: The context.
555      out_r_src_jar: A File. The R.java outputted by linking resources in a srcjar.
556      out_r_txt: A File. The resource IDs outputted by linking resources in text.
557      out_file: A File. The Resource APK outputted by linking resources.
558      compiled_resources: A File. The symbols.zip of compiled resources for
559        this target.
560      transitive_compiled_resources: Depset of Files. The symbols.zip of the
561        compiled resources from the transitive dependencies of this target.
562      java_package: A string. The Java package for the generated R.java.
563      manifest: A File. The AndroidManifest.xml.
564      resource_apks: List of direct resource only apk files.
565      android_jar: A File. The Android Jar.
566      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
567      host_javabase: Target. The host javabase.
568      aapt: A FilesToRunProvider. The AAPT executable.
569    """
570    output_files = []
571    input_files = [android_jar]
572    transitive_input_files = []
573
574    # Retrieves the list of files at runtime when a directory is passed.
575    args = ctx.actions.args()
576    args.use_param_file("@%s")
577    args.add("--tool", "LINK_STATIC_LIBRARY")
578    args.add("--")
579    args.add("--aapt2", aapt.executable)
580    args.add("--libraries", android_jar)
581    if compiled_resources:
582        args.add("--compiled", compiled_resources)
583        input_files.append(compiled_resources)
584    args.add_joined(
585        "--compiledDep",
586        transitive_compiled_resources,
587        join_with = ":",
588    )
589    transitive_input_files.append(transitive_compiled_resources)
590    args.add("--manifest", manifest)
591    input_files.append(manifest)
592    if java_package:
593        args.add("--packageForR", java_package)
594    args.add("--sourceJarOut", out_r_src_jar)
595    output_files.append(out_r_src_jar)
596    args.add("--rTxtOut", out_r_txt)
597    output_files.append(out_r_txt)
598    args.add("--staticLibraryOut", out_file)
599    output_files.append(out_file)
600    args.add_joined(
601        "--resourceApks",
602        resource_apks,
603        join_with = ":",
604    )
605    input_files.extend(resource_apks)
606
607    _java.run(
608        ctx = ctx,
609        host_javabase = host_javabase,
610        executable = busybox,
611        tools = [aapt],
612        arguments = [args],
613        inputs = depset(input_files, transitive = transitive_input_files),
614        outputs = output_files,
615        mnemonic = "LinkAndroidResources",
616        progress_message =
617            "Linking Android Resources in " + out_file.short_path,
618    )
619
620def _compile(
621        ctx,
622        out_file = None,
623        assets = [],
624        assets_dir = None,
625        resource_files = [],
626        busybox = None,
627        aapt = None,
628        host_javabase = None):
629    """Compile and store resources in a single archive.
630
631    Args:
632      ctx: The context.
633      out_file: File. The output zip containing compiled resources.
634      resource_files: A list of Files. The list of resource files or directories
635      assets: A list of Files. The list of assets files or directories
636        to process.
637      assets_dir: String. The name of the assets directory.
638      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
639      aapt: AAPT. Tool for compiling resources.
640      host_javabase: Target. The host javabase.
641    """
642    if not out_file:
643        fail("No output directory specified.")
644
645    # Retrieves the list of files at runtime when a directory is passed.
646    args = ctx.actions.args()
647    args.use_param_file("@%s")
648    args.add("--tool", "COMPILE_LIBRARY_RESOURCES")
649    args.add("--")
650    args.add("--aapt2", aapt.executable)
651    args.add(
652        "--resources",
653        _make_resources_flag(
654            resource_files = resource_files,
655            assets = assets,
656            assets_dir = assets_dir,
657        ),
658    )
659    args.add("--output", out_file)
660
661    _java.run(
662        ctx = ctx,
663        host_javabase = host_javabase,
664        executable = busybox,
665        tools = [aapt],
666        arguments = [args],
667        inputs = resource_files + assets,
668        outputs = [out_file],
669        mnemonic = "CompileAndroidResources",
670        progress_message = "Compiling Android Resources in %s" % out_file.short_path,
671    )
672
673def _make_merge_compiled_flags(resources_node_info):
674    if not resources_node_info.compiled_resources:
675        return None
676    return _make_serialized_resources_flag(
677        label = str(resources_node_info.label),
678        symbols = resources_node_info.compiled_resources,
679    )
680
681def _merge_compiled(
682        ctx,
683        out_class_jar = None,
684        out_manifest = None,
685        out_aapt2_r_txt = None,
686        java_package = None,
687        manifest = None,
688        compiled_resources = None,
689        direct_resources_nodes = [],
690        transitive_resources_nodes = [],
691        direct_compiled_resources = depset(),
692        transitive_compiled_resources = depset(),
693        android_jar = None,
694        busybox = None,
695        host_javabase = None):
696    """Merges the compile resources.
697
698    Args:
699      ctx: The context.
700      out_class_jar: A File. The compiled R.java outputted by linking resources.
701      out_manifest: A File. The list of resource files or directories
702      out_aapt2_r_txt: A File. The resource IDs outputted by linking resources in text.
703      java_package: A string. The Java package for the generated R.java.
704      manifest: A File. The AndroidManifest.xml.
705      compiled_resources: A File. The symbols.zip of compiled resources for this target.
706      direct_resources_nodes: Sequence of ResourcesNodeInfo providers. The list
707        of ResourcesNodeInfo providers that are direct depencies.
708      transitive_resources_nodes: Sequence of ResourcesNodeInfo providers. The
709        list of ResourcesNodeInfo providers that are transitive depencies.
710      direct_compiled_resources: Depset of Files. A depset of symbols.zip of
711        compiled resources from direct dependencies.
712      transitive_compiled_resources: Depset of Files. A depset of symbols.zip of
713        compiled resources from transitive dependencies.
714      android_jar: A File. The Android Jar.
715      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
716      host_javabase: Target. The host javabase.
717    """
718    output_files = []
719    input_files = [android_jar]
720    transitive_input_files = []
721
722    args = ctx.actions.args()
723    args.use_param_file("@%s")
724    args.add("--tool", "MERGE_COMPILED")
725    args.add("--")
726    args.add("--classJarOutput", out_class_jar)
727    output_files.append(out_class_jar)
728    args.add("--targetLabel", ctx.label)
729    args.add("--manifestOutput", out_manifest)
730    output_files.append(out_manifest)
731    args.add("--rTxtOut", out_aapt2_r_txt)
732    output_files.append(out_aapt2_r_txt)
733    args.add("--androidJar", android_jar)
734    args.add("--primaryManifest", manifest)
735    input_files.append(manifest)
736    if java_package:
737        args.add("--packageForR", java_package)
738    args.add(
739        "--primaryData",
740        _make_serialized_resources_flag(
741            label = str(ctx.label),
742            symbols = compiled_resources,
743        ),
744    )
745    input_files.append(compiled_resources)
746    args.add_joined(
747        "--directData",
748        direct_resources_nodes,
749        map_each = _make_merge_compiled_flags,
750        join_with = "&",
751    )
752    transitive_input_files.append(direct_compiled_resources)
753    if _ANDROID_RESOURCES_STRICT_DEPS in ctx.disabled_features:
754        args.add_joined(
755            "--data",
756            transitive_resources_nodes,
757            map_each = _make_merge_compiled_flags,
758            join_with = "&",
759        )
760        transitive_input_files.append(transitive_compiled_resources)
761
762    _java.run(
763        ctx = ctx,
764        host_javabase = host_javabase,
765        executable = busybox,
766        arguments = [args],
767        inputs = depset(input_files, transitive = transitive_input_files),
768        outputs = output_files,
769        mnemonic = "StarlarkMergeCompiledAndroidResources",
770        progress_message =
771            "Merging compiled Android Resources in " + out_class_jar.short_path,
772    )
773
774def _escape_mv(s):
775    """Escapes `:` and `,` in manifest values so they can be used as a busybox flag."""
776    return s.replace(":", "\\:").replace(",", "\\,")
777
778def _owner_label(file):
779    return "//" + file.owner.package + ":" + file.owner.name
780
781# We need to remove the "/_migrated/" path segment from file paths in order for sorting to
782# match the order of the native manifest merging action.
783def _manifest_short_path(manifest):
784    return manifest.short_path.replace("/_migrated/", "/")
785
786def _mergee_manifests_flag(manifests):
787    ordered_manifests = sorted(manifests.to_list(), key = _manifest_short_path)
788    entries = []
789    for manifest in ordered_manifests:
790        label = _owner_label(manifest).replace(":", "\\:")
791        entries.append((manifest.path + ":" + label).replace(",", "\\,"))
792    flag_entry = ",".join(entries)
793    if not flag_entry:
794        return None
795    return flag_entry
796
797def _merge_manifests(
798        ctx,
799        out_file = None,
800        out_log_file = None,
801        merge_type = "APPLICATION",
802        manifest = None,
803        mergee_manifests = depset(),
804        manifest_values = None,
805        java_package = None,
806        busybox = None,
807        host_javabase = None):
808    """Merge multiple AndroidManifest.xml files into a single one.
809
810    Args:
811      ctx: The context.
812      out_file: A File. The output merged manifest.
813      out_log_file: A File. The output log from the merge tool.
814      merge_type: A string, either APPLICATION or LIBRARY. Type of merging.
815      manifest: A File. The primary AndroidManifest.xml.
816      mergee_manifests: A depset of Files. All transitive manifests to be merged.
817      manifest_values: A dictionary. Manifest values to substitute.
818      java_package: A string. Custom java package to insert in manifest package attribute.
819      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
820      host_javabase: Target. The host javabase.
821    """
822    if merge_type not in ["APPLICATION", "LIBRARY"]:
823        fail("Unexpected manifest merge type: " + merge_type)
824
825    outputs = [out_file]
826    directs = [manifest] if manifest else []
827    transitives = [mergee_manifests]
828
829    # Args for busybox
830    args = ctx.actions.args()
831    args.use_param_file("@%s", use_always = True)
832    args.add("--tool", "MERGE_MANIFEST")
833    args.add("--")
834    if manifest:
835        args.add("--manifest", manifest)
836    args.add_all(
837        "--mergeeManifests",
838        [mergee_manifests],
839        map_each = _mergee_manifests_flag,
840    )
841    if manifest_values:
842        args.add(
843            "--manifestValues",
844            ",".join(["%s:%s" % (_escape_mv(k), _escape_mv(v)) for k, v in manifest_values.items()]),
845        )
846    args.add("--mergeType", merge_type)
847    if java_package:
848        args.add("--customPackage", java_package)
849    args.add("--manifestOutput", out_file)
850    if out_log_file:
851        args.add("--log", out_log_file)
852        outputs.append(out_log_file)
853
854    _java.run(
855        ctx = ctx,
856        host_javabase = host_javabase,
857        executable = busybox,
858        arguments = [args],
859        inputs = depset(directs, transitive = transitives),
860        outputs = outputs,
861        mnemonic = "MergeManifests",
862        progress_message = "Merging Android Manifests in %s" % out_file.short_path,
863    )
864
865def _process_databinding(
866        ctx,
867        out_databinding_info = None,
868        out_databinding_processed_resources = None,
869        databinding_resources_dirname = None,
870        resource_files = None,
871        java_package = None,
872        busybox = None,
873        host_javabase = None):
874    """Processes databinding for android_binary.
875
876    Processes databinding declarations over resources, populates the databinding layout
877    info file, and generates new resources with databinding expressions stripped out.
878
879    Args:
880      ctx: The context.
881      out_databinding_info: File. The output databinding layout info zip file.
882      out_databinding_processed_resources: List of Files. The generated databinding
883        processed resource files.
884      databinding_resources_dirname: String. The execution path to the directory where
885      the out_databinding_processed_resources are generated.
886      resource_files: List of Files. The resource files to be processed.
887      java_package: String. Java package for which java sources will be
888        generated. By default the package is inferred from the directory where
889        the BUILD file containing the rule is.
890      busybox: FilesToRunProvider. The ResourceBusyBox executable or
891        FilesToRunprovider
892      host_javabase: A Target. The host javabase.
893    """
894    res_dirs = _get_unique_res_dirs(resource_files)
895
896    args = ctx.actions.args()
897    args.add("--tool", "PROCESS_DATABINDING")
898    args.add("--")
899    args.add("--output_resource_directory", databinding_resources_dirname)
900    args.add_all(res_dirs, before_each = "--resource_root")
901    args.add("--dataBindingInfoOut", out_databinding_info)
902    args.add("--appId", java_package)
903
904    _java.run(
905        ctx = ctx,
906        host_javabase = host_javabase,
907        executable = busybox,
908        arguments = [args],
909        inputs = resource_files,
910        outputs = [out_databinding_info] + out_databinding_processed_resources,
911        mnemonic = "StarlarkProcessDatabinding",
912        progress_message = "Processing data binding",
913    )
914
915def _make_generate_binay_r_flags(resources_node):
916    if not (resources_node.r_txt or resources_node.manifest):
917        return None
918    return ",".join(
919        [
920            resources_node.r_txt.path if resources_node.r_txt else "",
921            resources_node.manifest.path if resources_node.manifest else "",
922        ],
923    )
924
925def _generate_binary_r(
926        ctx,
927        out_class_jar = None,
928        r_txt = None,
929        manifest = None,
930        package_for_r = None,
931        final_fields = None,
932        resources_nodes = depset(),
933        transitive_r_txts = [],
934        transitive_manifests = [],
935        busybox = None,
936        host_javabase = None):
937    """Generate compiled resources class jar.
938
939    Args:
940      ctx: The context.
941      out_class_jar: File. The output class jar file.
942      r_txt: File. The resource IDs outputted by linking resources in text.
943      manifest: File. The primary AndroidManifest.xml.
944      package_for_r: String. The Java package for the generated R class files.
945      final_fields: Bool. Whether fields get declared as final.
946      busybox: FilesToRunProvider. The ResourceBusyBox executable or
947        FilesToRunprovider
948      host_javabase: A Target. The host javabase.
949    """
950    args = ctx.actions.args()
951    args.add("--tool", "GENERATE_BINARY_R")
952    args.add("--")
953    args.add("--primaryRTxt", r_txt)
954    args.add("--primaryManifest", manifest)
955    if package_for_r:
956        args.add("--packageForR", package_for_r)
957    args.add_all(
958        resources_nodes,
959        map_each = _make_generate_binay_r_flags,
960        before_each = "--library",
961    )
962    if final_fields:
963        args.add("--finalFields")
964    else:
965        args.add("--nofinalFields")
966
967    # TODO(b/154003916): support transitive "--library transitive_r_txt_path,transitive_manifest_path" flags
968    args.add("--classJarOutput", out_class_jar)
969    args.add("--targetLabel", str(ctx.label))
970    args.use_param_file("@%s")
971
972    _java.run(
973        ctx = ctx,
974        host_javabase = host_javabase,
975        executable = busybox,
976        arguments = [args],
977        inputs = depset([r_txt, manifest], transitive = transitive_r_txts + transitive_manifests),
978        outputs = [out_class_jar],
979        mnemonic = "StarlarkRClassGenerator",
980        progress_message = "Generating R classes",
981    )
982
983def _make_aar(
984        ctx,
985        out_aar = None,
986        assets = [],
987        assets_dir = None,
988        resource_files = [],
989        class_jar = None,
990        r_txt = None,
991        manifest = None,
992        proguard_specs = [],
993        should_throw_on_conflict = False,
994        busybox = None,
995        host_javabase = None):
996    """Generate an android archive file.
997
998    Args:
999      ctx: The context.
1000      out_aar: File. The output AAR file.
1001      assets: sequence of Files. A list of Android assets files to be processed.
1002      assets_dir: String. The name of the assets directory.
1003      resource_files: A list of Files. The resource files.
1004      class_jar: File. The class jar file.
1005      r_txt: File. The resource IDs outputted by linking resources in text.
1006      manifest: File. The primary AndroidManifest.xml.
1007      proguard_specs: List of File. The proguard spec files.
1008      busybox: FilesToRunProvider. The ResourceBusyBox executable or
1009        FilesToRunprovider
1010      host_javabase: A Target. The host javabase.
1011      should_throw_on_conflict: A boolean. Determines whether an error should be thrown
1012        when a resource conflict occurs.
1013    """
1014    args = ctx.actions.args()
1015    args.add("--tool", "GENERATE_AAR")
1016    args.add("--")
1017    args.add(
1018        "--mainData",
1019        _make_resources_flag(
1020            manifest = manifest,
1021            assets = assets,
1022            assets_dir = assets_dir,
1023            resource_files = resource_files,
1024        ),
1025    )
1026    args.add("--manifest", manifest)
1027    args.add("--rtxt", r_txt)
1028    args.add("--classes", class_jar)
1029    args.add("--aarOutput", out_aar)
1030    args.add_all(proguard_specs, before_each = "--proguardSpec")
1031    if should_throw_on_conflict:
1032        args.add("--throwOnResourceConflict")
1033
1034    _java.run(
1035        ctx = ctx,
1036        host_javabase = host_javabase,
1037        executable = busybox,
1038        arguments = [args],
1039        inputs = (
1040            resource_files +
1041            assets +
1042            proguard_specs +
1043            [r_txt, manifest, class_jar]
1044        ),
1045        outputs = [out_aar],
1046        mnemonic = "StarlarkAARGenerator",
1047        progress_message = "Generating AAR package for %s" % ctx.label,
1048    )
1049
1050def _shrink(
1051        ctx,
1052        out_apk,
1053        out_zip,
1054        out_log,
1055        out_config = None,
1056        resources_zip = None,
1057        aapt = None,
1058        android_jar = None,
1059        r_txt = None,
1060        shrunk_jar = None,
1061        proguard_mapping = None,
1062        debug = True,
1063        busybox = None,
1064        host_javabase = None):
1065    """Shrinks the resource apk by removing the resources unused from the packaged app.
1066
1067    Args:
1068        ctx: The context.
1069        out_apk: File. The output shrunk resource ap_ package.
1070        out_zip: File. The output shrunk resources file zip.
1071        out_log: File. The output shrinker log.
1072        out_config: File. The output config for the optimizer.
1073        resources_zip: File. The input resources file zip.
1074        aapt: FilesToRunProvider. The AAPT executable.
1075        android_jar: File. The Android Jar.
1076        r_txt: File. The resource IDs outputted by linking resources in text.
1077        shrunk_jar: File. The proguarded jar.
1078        proguard_mapping: File. The Proguard Mapping file.
1079        debug: Boolean. Whether to enable debug mode.
1080        busybox: FilesToRunProvider. The ResourceBusyBox executable.
1081        host_javabase: Target. The host javabase.
1082    """
1083
1084    args = ctx.actions.args()
1085    args.use_param_file("@%s")
1086    args.add("--tool", "SHRINK_AAPT2")
1087    args.add("--")
1088    args.add("--aapt2", aapt.executable)
1089    args.add("--androidJar", android_jar)
1090    args.add("--resources", resources_zip)
1091    args.add("--shrunkJar", shrunk_jar)
1092    args.add("--proguardMapping", proguard_mapping)
1093    args.add("--rTxt", r_txt)
1094    args.add("--shrunkResourceApk", out_apk)
1095    args.add("--shrunkResources", out_zip)
1096    args.add("--log", out_log)
1097    args.add("--useDataBindingAndroidX")
1098    if debug:
1099        args.add("--debug")
1100
1101    input_files = [
1102        android_jar,
1103        resources_zip,
1104        shrunk_jar,
1105        proguard_mapping,
1106        r_txt,
1107    ]
1108    output_files = [
1109        out_apk,
1110        out_zip,
1111        out_log,
1112    ]
1113    if out_config:
1114        args.add("--resourcesConfigOutput", out_config)
1115        output_files.append(out_config)
1116
1117    _java.run(
1118        ctx = ctx,
1119        executable = busybox,
1120        tools = [aapt],
1121        outputs = output_files,
1122        inputs = input_files,
1123        arguments = [args],
1124        mnemonic = "ResourceShrinker",
1125        progress_message =
1126            "Shrinking resources for " + str(ctx.label),
1127        host_javabase = host_javabase,
1128        use_default_shell_env = True,
1129    )
1130
1131def _optimize(
1132        ctx,
1133        out_apk,
1134        in_apk,
1135        resource_path_shortening_map = None,
1136        resource_optimization_config = None,
1137        aapt = None,
1138        busybox = None,
1139        host_javabase = None):
1140    """Optimizes the resource apk including resource obfuscation, sparse encoding and path shortening.
1141
1142    Args:
1143        ctx: The context.
1144        out_apk: File. The output optimized resource ap_ package.
1145        in_apk: File. The resource ap_ package to be optimized.
1146        resource_path_shortening_map: File. The output path shortening map. Optional.
1147        resource_optimization_config: File. The input optimization config. Optional.
1148        aapt: FilesToRunProvider. The AAPT executable.
1149        busybox: FilesToRunProvider. The ResourceBusyBox executable.
1150        host_javabase: Target. The host javabase.
1151    """
1152
1153    output_files = [out_apk]
1154    input_files = [in_apk]
1155
1156    args = ctx.actions.args()
1157    args.use_param_file("@%s")
1158    args.add("--tool", "AAPT2_OPTIMIZE")
1159    args.add("--")
1160    args.add("--aapt2", aapt.executable)
1161    args.add("--")
1162    if resource_path_shortening_map:
1163        args.add("--shorten-resource-paths")
1164        args.add("--resource-path-shortening-map", resource_path_shortening_map)
1165        output_files.append(resource_path_shortening_map)
1166    if resource_optimization_config:
1167        args.add("--collapse-resource-names")
1168        args.add("--resources-config-path", resource_optimization_config)
1169        input_files.append(resource_optimization_config)
1170    args.add("-o", out_apk)
1171    args.add(in_apk)
1172
1173    _java.run(
1174        ctx = ctx,
1175        host_javabase = host_javabase,
1176        executable = busybox,
1177        tools = [aapt],
1178        arguments = [args],
1179        inputs = input_files,
1180        outputs = output_files,
1181        mnemonic = "Aapt2Optimize",
1182        progress_message =
1183            "Optimizing Android resources for " + str(ctx.label),
1184        use_default_shell_env = True,
1185    )
1186
1187busybox = struct(
1188    compile = _compile,
1189    merge_compiled = _merge_compiled,
1190    validate_and_link = _validate_and_link,
1191    merge_manifests = _merge_manifests,
1192    package = _package,
1193    parse = _parse,
1194    merge_assets = _merge_assets,
1195    make_resources_flag = _make_resources_flag,
1196    process_databinding = _process_databinding,
1197    generate_binary_r = _generate_binary_r,
1198    make_aar = _make_aar,
1199    shrink = _shrink,
1200    optimize = _optimize,
1201
1202    # Exposed for testing
1203    mergee_manifests_flag = _mergee_manifests_flag,
1204    get_unique_res_dirs = _get_unique_res_dirs,
1205    sanitize_assets_dir = _sanitize_assets_dir,
1206    extract_filters = _extract_filters,
1207)
1208