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        direct_resources_nodes = [],
195        transitive_resources_nodes = [],
196        transitive_manifests = [],
197        transitive_assets = [],
198        transitive_compiled_assets = [],
199        transitive_resource_files = [],
200        transitive_compiled_resources = [],
201        transitive_r_txts = [],
202        additional_apks_to_link_against = [],
203        nocompress_extensions = [],
204        proto_format = False,
205        version_name = None,
206        version_code = None,
207        android_jar = None,
208        aapt = None,
209        busybox = None,
210        host_javabase = None,
211        should_throw_on_conflict = True,  # TODO: read this from allowlist at caller
212        debug = True):  # TODO: we will set this to false in prod builds
213    """Packages the compiled Android Resources with AAPT.
214
215    Args:
216      ctx: The context.
217      out_r_src_jar: A File. The R.java outputted by linking resources in a srcjar.
218      out_r_txt: A File. The resource IDs outputted by linking resources in text.
219      out_symbols: A File. The output zip containing compiled resources.
220      out_manifest: A File. The output processed manifest.
221      out_proguard_cfg: A File. The proguard config to be generated.
222      out_main_dex_proguard_cfg: A File. The main dex proguard config to be generated.
223      out_resource_files_zip: A File. The resource files zipped by linking resources.
224      out_file: A File. The Resource APK outputted by linking resources.
225      package_type: A string. The configuration type to use when packaging.
226      java_package: A string. The Java package for the generated R.java.
227      manifest: A File. The AndroidManifest.xml.
228      assets: sequence of Files. A list of Android assets files to be processed.
229      assets_dir: String. The name of the assets directory.
230      resource_files: A list of Files. The resource files.
231      resource_configs: A list of strings. The list of resource configuration
232        filters.
233      densities: A list of strings. The list of screen densities to filter for when
234        building the apk.
235      application_id: An optional string. The applicationId set in manifest values.
236      direct_resources_nodes: Depset of ResourcesNodeInfo providers. The set of
237        ResourcesNodeInfo from direct dependencies.
238      transitive_resources_nodes: Depset of ResourcesNodeInfo providers. The set
239        of ResourcesNodeInfo from transitive dependencies (not including directs).
240      transitive_manifests: List of Depsets. Depsets contain all transitive manifests.
241      transitive_assets: List of Depsets. Depsets contain all transitive assets.
242      transitive_compiled_assets: List of Depsets. Depsets contain all transitive
243        compiled_assets.
244      transitive_resource_files: List of Depsets. Depsets contain all transitive
245        resource files.
246      transitive_compiled_resources: List of Depsets. Depsets contain all transitive
247        compiled_resources.
248      transitive_r_txts: List of Depsets. Depsets contain all transitive R txt files.
249      additional_apks_to_link_against: A list of Files. Additional APKs to link
250        against. Optional.
251      nocompress_extensions: A list of strings. File extension to leave uncompressed
252        in the apk.
253      proto_format: Boolean, whether to generate the resource table in proto format.
254      version_name: A string. The version name to stamp the generated manifest with. Optional.
255      version_code: A string. The version code to stamp the generated manifest with. Optional.
256      android_jar: A File. The Android Jar.
257      aapt: A FilesToRunProvider. The AAPT executable.
258      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
259      host_javabase: Target. The host javabase.
260      should_throw_on_conflict: A boolean. Determines whether an error should be thrown
261        when a resource conflict occurs.
262      debug: A boolean. Determines whether to enable debugging.
263    """
264    if not manifest:
265        fail("No manifest given, the manifest is mandatory.")
266
267    direct_data_flag = []
268    direct_compiled_resources = []
269
270    output_files = []
271    input_files = []
272    transitive_input_files = []
273
274    args = ctx.actions.args()
275    args.use_param_file("@%s")
276    args.add("--tool", "AAPT2_PACKAGE")
277    args.add("--")
278    args.add("--aapt2", aapt.executable)
279    args.add_joined(
280        "--data",
281        transitive_resources_nodes,
282        map_each = _make_package_resources_flags,
283        join_with = ",",
284    )
285    args.add_joined(
286        "--directData",
287        direct_resources_nodes,
288        map_each = _make_package_resources_flags,
289        join_with = ",",
290    )
291    args.add_joined(
292        "--directAssets",
293        direct_resources_nodes,
294        map_each = _make_package_assets_flags,
295        join_with = "&",
296        omit_if_empty = True,
297    )
298    args.add_joined(
299        "--assets",
300        transitive_resources_nodes,
301        map_each = _make_package_assets_flags,
302        join_with = "&",
303        omit_if_empty = True,
304    )
305    transitive_input_files.extend(transitive_resource_files)
306    transitive_input_files.extend(transitive_assets)
307    transitive_input_files.extend(transitive_compiled_assets)
308    transitive_input_files.extend(transitive_compiled_resources)
309    transitive_input_files.extend(transitive_manifests)
310    transitive_input_files.extend(transitive_r_txts)
311    args.add(
312        "--primaryData",
313        _make_resources_flag(
314            manifest = manifest,
315            assets = assets,
316            assets_dir = assets_dir,
317            resource_files = resource_files,
318        ),
319    )
320    input_files.append(manifest)
321    input_files.extend(resource_files)
322    input_files.extend(assets)
323    args.add("--androidJar", android_jar)
324    input_files.append(android_jar)
325    args.add("--rOutput", out_r_txt)
326    output_files.append(out_r_txt)
327    if out_symbols:
328        args.add("--symbolsOut", out_symbols)
329        output_files.append(out_symbols)
330    args.add("--srcJarOutput", out_r_src_jar)
331    output_files.append(out_r_src_jar)
332    if out_proguard_cfg:
333        args.add("--proguardOutput", out_proguard_cfg)
334        output_files.append(out_proguard_cfg)
335    if out_main_dex_proguard_cfg:
336        args.add("--mainDexProguardOutput", out_main_dex_proguard_cfg)
337        output_files.append(out_main_dex_proguard_cfg)
338    args.add("--manifestOutput", out_manifest)
339    output_files.append(out_manifest)
340    if out_resource_files_zip:
341        args.add("--resourcesOutput", out_resource_files_zip)
342        output_files.append(out_resource_files_zip)
343    if out_file:
344        args.add("--packagePath", out_file)
345        output_files.append(out_file)
346    args.add("--useAaptCruncher=no")  # Unnecessary, used for AAPT1 only but added here to minimize diffs.
347    if package_type:
348        args.add("--packageType", package_type)
349    if debug:
350        args.add("--debug")
351    if should_throw_on_conflict:
352        args.add("--throwOnResourceConflict")
353    if resource_configs:
354        args.add_joined("--resourceConfigs", _extract_filters(resource_configs), join_with = ",")
355    if densities:
356        args.add_joined("--densities", _extract_filters(densities), join_with = ",")
357    if application_id:
358        args.add("--applicationId", application_id)
359    if additional_apks_to_link_against:
360        args.add_joined(
361            "--additionalApksToLinkAgainst",
362            additional_apks_to_link_against,
363            join_with = ",",
364            map_each = _path,
365        )
366        input_files.extend(additional_apks_to_link_against)
367    if nocompress_extensions:
368        args.add_joined("--uncompressedExtensions", nocompress_extensions, join_with = ",")
369    if proto_format:
370        args.add("--resourceTableAsProto")
371    if version_name:
372        args.add("--versionName", version_name)
373    if version_code:
374        args.add("--versionCode", version_code)
375    args.add("--packageForR", java_package)
376
377    _java.run(
378        ctx = ctx,
379        host_javabase = host_javabase,
380        executable = busybox,
381        tools = [aapt],
382        arguments = [args],
383        inputs = depset(input_files, transitive = transitive_input_files),
384        outputs = output_files,
385        mnemonic = "PackageAndroidResources",
386        progress_message = "Packaging Android Resources in %s" % ctx.label,
387    )
388
389def _parse(
390        ctx,
391        out_symbols = None,
392        assets = [],
393        assets_dir = None,
394        busybox = None,
395        host_javabase = None):
396    """Parses Android assets.
397
398    Args:
399      ctx: The context.
400      out_symbols: A File. The output bin containing parsed assets.
401      assets: sequence of Files. A list of Android assets files to be processed.
402      assets_dir: String. The name of the assets directory.
403      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
404      host_javabase: Target. The host javabase.
405    """
406    args = ctx.actions.args()
407    args.use_param_file("@%s")
408    args.add("--tool", "PARSE")
409    args.add("--")
410    args.add(
411        "--primaryData",
412        _make_resources_flag(
413            assets = assets,
414            assets_dir = assets_dir,
415        ),
416    )
417    args.add("--output", out_symbols)
418
419    _java.run(
420        ctx = ctx,
421        host_javabase = host_javabase,
422        executable = busybox,
423        arguments = [args],
424        inputs = assets,
425        outputs = [out_symbols],
426        mnemonic = "ParseAndroidResources",
427        progress_message = "Parsing Android Resources in %s" % out_symbols.short_path,
428    )
429
430def _make_merge_assets_flags(resources_node):
431    assets = resources_node.assets.to_list()
432    if not (assets or resources_node.assets_dir):
433        return None
434    return _make_serialized_resources_flag(
435        assets = assets,
436        assets_dir = resources_node.assets_dir,
437        label = str(resources_node.label),
438        symbols = resources_node.assets_symbols,
439    )
440
441def _merge_assets(
442        ctx,
443        out_assets_zip = None,
444        assets = [],
445        assets_dir = None,
446        symbols = None,
447        transitive_assets = [],
448        transitive_assets_symbols = [],
449        direct_resources_nodes = [],
450        transitive_resources_nodes = [],
451        busybox = None,
452        host_javabase = None):
453    """Merges Android assets.
454
455    Args:
456      ctx: The context.
457      out_assets_zip: A File.
458      assets: sequence of Files. A list of Android assets files to be processed.
459      assets_dir: String. The name of the assets directory.
460      symbols: A File. The parsed assets.
461      transitive_assets: Sequence of Depsets. The list of transitive
462        assets from deps.
463      transitive_assets_symbols: Sequence of Depsets. The list of
464        transitive assets_symbols files from deps.
465      direct_resources_nodes: Sequence of ResourcesNodeInfo providers. The list
466        of ResourcesNodeInfo providers that are direct depencies.
467      transitive_resources_nodes: Sequence of ResourcesNodeInfo providers. The
468        list of ResourcesNodeInfo providers that are transitive depencies.
469      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
470      host_javabase: Target. The host javabase.
471    """
472    args = ctx.actions.args()
473    args.use_param_file("@%s")
474    args.add("--tool", "MERGE_ASSETS")
475    args.add("--")
476    args.add("--assetsOutput", out_assets_zip)
477    args.add(
478        "--primaryData",
479        _make_serialized_resources_flag(
480            assets = assets,
481            assets_dir = assets_dir,
482            label = str(ctx.label),
483            symbols = symbols,
484        ),
485    )
486    args.add_joined(
487        "--directData",
488        direct_resources_nodes,
489        map_each = _make_merge_assets_flags,
490        join_with = "&",
491    )
492    args.add_joined(
493        "--data",
494        transitive_resources_nodes,
495        map_each = _make_merge_assets_flags,
496        join_with = "&",
497    )
498
499    _java.run(
500        ctx = ctx,
501        host_javabase = host_javabase,
502        executable = busybox,
503        arguments = [args],
504        inputs = depset(
505            assets + [symbols],
506            transitive = transitive_assets + transitive_assets_symbols,
507        ),
508        outputs = [out_assets_zip],
509        mnemonic = "MergeAndroidAssets",
510        progress_message =
511            "Merging Android Assets in %s" % out_assets_zip.short_path,
512    )
513
514def _validate_and_link(
515        ctx,
516        out_r_src_jar = None,
517        out_r_txt = None,
518        out_file = None,
519        compiled_resources = None,
520        transitive_compiled_resources = depset(),
521        java_package = None,
522        manifest = None,
523        android_jar = None,
524        busybox = None,
525        host_javabase = None,
526        aapt = None):
527    """Links compiled Android Resources with AAPT.
528
529    Args:
530      ctx: The context.
531      out_r_src_jar: A File. The R.java outputted by linking resources in a srcjar.
532      out_r_txt: A File. The resource IDs outputted by linking resources in text.
533      out_file: A File. The Resource APK outputted by linking resources.
534      compiled_resources: A File. The symbols.zip of compiled resources for
535        this target.
536      transitive_compiled_resources: Depset of Files. The symbols.zip of the
537        compiled resources from the transitive dependencies of this target.
538      java_package: A string. The Java package for the generated R.java.
539      manifest: A File. The AndroidManifest.xml.
540      android_jar: A File. The Android Jar.
541      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
542      host_javabase: Target. The host javabase.
543      aapt: A FilesToRunProvider. The AAPT executable.
544    """
545    output_files = []
546    input_files = [android_jar]
547    transitive_input_files = []
548
549    # Retrieves the list of files at runtime when a directory is passed.
550    args = ctx.actions.args()
551    args.use_param_file("@%s")
552    args.add("--tool", "LINK_STATIC_LIBRARY")
553    args.add("--")
554    args.add("--aapt2", aapt.executable)
555    args.add("--libraries", android_jar)
556    if compiled_resources:
557        args.add("--compiled", compiled_resources)
558        input_files.append(compiled_resources)
559    args.add_joined(
560        "--compiledDep",
561        transitive_compiled_resources,
562        join_with = ":",
563    )
564    transitive_input_files.append(transitive_compiled_resources)
565    args.add("--manifest", manifest)
566    input_files.append(manifest)
567    if java_package:
568        args.add("--packageForR", java_package)
569    args.add("--sourceJarOut", out_r_src_jar)
570    output_files.append(out_r_src_jar)
571    args.add("--rTxtOut", out_r_txt)
572    output_files.append(out_r_txt)
573    args.add("--staticLibraryOut", out_file)
574    output_files.append(out_file)
575
576    _java.run(
577        ctx = ctx,
578        host_javabase = host_javabase,
579        executable = busybox,
580        tools = [aapt],
581        arguments = [args],
582        inputs = depset(input_files, transitive = transitive_input_files),
583        outputs = output_files,
584        mnemonic = "LinkAndroidResources",
585        progress_message =
586            "Linking Android Resources in " + out_file.short_path,
587    )
588
589def _compile(
590        ctx,
591        out_file = None,
592        assets = [],
593        assets_dir = None,
594        resource_files = [],
595        busybox = None,
596        aapt = None,
597        host_javabase = None):
598    """Compile and store resources in a single archive.
599
600    Args:
601      ctx: The context.
602      out_file: File. The output zip containing compiled resources.
603      resource_files: A list of Files. The list of resource files or directories
604      assets: A list of Files. The list of assets files or directories
605        to process.
606      assets_dir: String. The name of the assets directory.
607      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
608      aapt: AAPT. Tool for compiling resources.
609      host_javabase: Target. The host javabase.
610    """
611    if not out_file:
612        fail("No output directory specified.")
613
614    # Retrieves the list of files at runtime when a directory is passed.
615    args = ctx.actions.args()
616    args.use_param_file("@%s")
617    args.add("--tool", "COMPILE_LIBRARY_RESOURCES")
618    args.add("--")
619    args.add("--aapt2", aapt.executable)
620    args.add(
621        "--resources",
622        _make_resources_flag(
623            resource_files = resource_files,
624            assets = assets,
625            assets_dir = assets_dir,
626        ),
627    )
628    args.add("--output", out_file)
629
630    _java.run(
631        ctx = ctx,
632        host_javabase = host_javabase,
633        executable = busybox,
634        tools = [aapt],
635        arguments = [args],
636        inputs = resource_files + assets,
637        outputs = [out_file],
638        mnemonic = "CompileAndroidResources",
639        progress_message = "Compiling Android Resources in %s" % out_file.short_path,
640    )
641
642def _make_merge_compiled_flags(resources_node_info):
643    if not resources_node_info.compiled_resources:
644        return None
645    return _make_serialized_resources_flag(
646        label = str(resources_node_info.label),
647        symbols = resources_node_info.compiled_resources,
648    )
649
650def _merge_compiled(
651        ctx,
652        out_class_jar = None,
653        out_manifest = None,
654        out_aapt2_r_txt = None,
655        java_package = None,
656        manifest = None,
657        compiled_resources = None,
658        direct_resources_nodes = [],
659        transitive_resources_nodes = [],
660        direct_compiled_resources = depset(),
661        transitive_compiled_resources = depset(),
662        android_jar = None,
663        busybox = None,
664        host_javabase = None):
665    """Merges the compile resources.
666
667    Args:
668      ctx: The context.
669      out_class_jar: A File. The compiled R.java outputted by linking resources.
670      out_manifest: A File. The list of resource files or directories
671      out_aapt2_r_txt: A File. The resource IDs outputted by linking resources in text.
672      java_package: A string. The Java package for the generated R.java.
673      manifest: A File. The AndroidManifest.xml.
674      compiled_resources: A File. The symbols.zip of compiled resources for this target.
675      direct_resources_nodes: Sequence of ResourcesNodeInfo providers. The list
676        of ResourcesNodeInfo providers that are direct depencies.
677      transitive_resources_nodes: Sequence of ResourcesNodeInfo providers. The
678        list of ResourcesNodeInfo providers that are transitive depencies.
679      direct_compiled_resources: Depset of Files. A depset of symbols.zip of
680        compiled resources from direct dependencies.
681      transitive_compiled_resources: Depset of Files. A depset of symbols.zip of
682        compiled resources from transitive dependencies.
683      android_jar: A File. The Android Jar.
684      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
685      host_javabase: Target. The host javabase.
686    """
687    output_files = []
688    input_files = [android_jar]
689    transitive_input_files = []
690
691    args = ctx.actions.args()
692    args.use_param_file("@%s")
693    args.add("--tool", "MERGE_COMPILED")
694    args.add("--")
695    args.add("--classJarOutput", out_class_jar)
696    output_files.append(out_class_jar)
697    args.add("--targetLabel", ctx.label)
698    args.add("--manifestOutput", out_manifest)
699    output_files.append(out_manifest)
700    args.add("--rTxtOut", out_aapt2_r_txt)
701    output_files.append(out_aapt2_r_txt)
702    args.add("--androidJar", android_jar)
703    args.add("--primaryManifest", manifest)
704    input_files.append(manifest)
705    if java_package:
706        args.add("--packageForR", java_package)
707    args.add(
708        "--primaryData",
709        _make_serialized_resources_flag(
710            label = str(ctx.label),
711            symbols = compiled_resources,
712        ),
713    )
714    input_files.append(compiled_resources)
715    args.add_joined(
716        "--directData",
717        direct_resources_nodes,
718        map_each = _make_merge_compiled_flags,
719        join_with = "&",
720    )
721    transitive_input_files.append(direct_compiled_resources)
722    if _ANDROID_RESOURCES_STRICT_DEPS in ctx.disabled_features:
723        args.add_joined(
724            "--data",
725            transitive_resources_nodes,
726            map_each = _make_merge_compiled_flags,
727            join_with = "&",
728        )
729        transitive_input_files.append(transitive_compiled_resources)
730
731    _java.run(
732        ctx = ctx,
733        host_javabase = host_javabase,
734        executable = busybox,
735        arguments = [args],
736        inputs = depset(input_files, transitive = transitive_input_files),
737        outputs = output_files,
738        mnemonic = "StarlarkMergeCompiledAndroidResources",
739        progress_message =
740            "Merging compiled Android Resources in " + out_class_jar.short_path,
741    )
742
743def _escape_mv(s):
744    """Escapes `:` and `,` in manifest values so they can be used as a busybox flag."""
745    return s.replace(":", "\\:").replace(",", "\\,")
746
747def _owner_label(file):
748    return "//" + file.owner.package + ":" + file.owner.name
749
750# We need to remove the "/_migrated/" path segment from file paths in order for sorting to
751# match the order of the native manifest merging action.
752def _manifest_short_path(manifest):
753    return manifest.short_path.replace("/_migrated/", "/")
754
755def _mergee_manifests_flag(manifests):
756    ordered_manifests = sorted(manifests.to_list(), key = _manifest_short_path)
757    entries = []
758    for manifest in ordered_manifests:
759        label = _owner_label(manifest).replace(":", "\\:")
760        entries.append((manifest.path + ":" + label).replace(",", "\\,"))
761    flag_entry = ",".join(entries)
762    if not flag_entry:
763        return None
764    return flag_entry
765
766def _merge_manifests(
767        ctx,
768        out_file = None,
769        out_log_file = None,
770        merge_type = "APPLICATION",
771        manifest = None,
772        mergee_manifests = depset(),
773        manifest_values = None,
774        java_package = None,
775        busybox = None,
776        host_javabase = None):
777    """Merge multiple AndroidManifest.xml files into a single one.
778
779    Args:
780      ctx: The context.
781      out_file: A File. The output merged manifest.
782      out_log_file: A File. The output log from the merge tool.
783      merge_type: A string, either APPLICATION or LIBRARY. Type of merging.
784      manifest: A File. The primary AndroidManifest.xml.
785      mergee_manifests: A depset of Files. All transitive manifests to be merged.
786      manifest_values: A dictionary. Manifest values to substitute.
787      java_package: A string. Custom java package to insert in manifest package attribute.
788      busybox: A FilesToRunProvider. The ResourceProcessorBusyBox executable.
789      host_javabase: Target. The host javabase.
790    """
791    if merge_type not in ["APPLICATION", "LIBRARY"]:
792        fail("Unexpected manifest merge type: " + merge_type)
793
794    outputs = [out_file]
795    directs = [manifest]
796    transitives = [mergee_manifests]
797
798    # Args for busybox
799    args = ctx.actions.args()
800    args.use_param_file("@%s", use_always = True)
801    args.add("--tool", "MERGE_MANIFEST")
802    args.add("--")
803    args.add("--manifest", manifest)
804    args.add_all(
805        "--mergeeManifests",
806        [mergee_manifests],
807        map_each = _mergee_manifests_flag,
808    )
809    if manifest_values:
810        args.add(
811            "--manifestValues",
812            ",".join(["%s:%s" % (_escape_mv(k), _escape_mv(v)) for k, v in manifest_values.items()]),
813        )
814    args.add("--mergeType", merge_type)
815    args.add("--customPackage", java_package)
816    args.add("--manifestOutput", out_file)
817    if out_log_file:
818        args.add("--log", out_log_file)
819        outputs.append(out_log_file)
820
821    _java.run(
822        ctx = ctx,
823        host_javabase = host_javabase,
824        executable = busybox,
825        arguments = [args],
826        inputs = depset(directs, transitive = transitives),
827        outputs = outputs,
828        mnemonic = "MergeManifests",
829        progress_message = "Merging Android Manifests in %s" % out_file.short_path,
830    )
831
832def _process_databinding(
833        ctx,
834        out_databinding_info = None,
835        out_databinding_processed_resources = None,
836        databinding_resources_dirname = None,
837        resource_files = None,
838        java_package = None,
839        busybox = None,
840        host_javabase = None):
841    """Processes databinding for android_binary.
842
843    Processes databinding declarations over resources, populates the databinding layout
844    info file, and generates new resources with databinding expressions stripped out.
845
846    Args:
847      ctx: The context.
848      out_databinding_info: File. The output databinding layout info zip file.
849      out_databinding_processed_resources: List of Files. The generated databinding
850        processed resource files.
851      databinding_resources_dirname: String. The execution path to the directory where
852      the out_databinding_processed_resources are generated.
853      resource_files: List of Files. The resource files to be processed.
854      java_package: String. Java package for which java sources will be
855        generated. By default the package is inferred from the directory where
856        the BUILD file containing the rule is.
857      busybox: FilesToRunProvider. The ResourceBusyBox executable or
858        FilesToRunprovider
859      host_javabase: A Target. The host javabase.
860    """
861    res_dirs = _get_unique_res_dirs(resource_files)
862
863    args = ctx.actions.args()
864    args.add("--tool", "PROCESS_DATABINDING")
865    args.add("--")
866    args.add("--output_resource_directory", databinding_resources_dirname)
867    args.add_all(res_dirs, before_each = "--resource_root")
868    args.add("--dataBindingInfoOut", out_databinding_info)
869    args.add("--appId", java_package)
870
871    _java.run(
872        ctx = ctx,
873        host_javabase = host_javabase,
874        executable = busybox,
875        arguments = [args],
876        inputs = resource_files,
877        outputs = [out_databinding_info] + out_databinding_processed_resources,
878        mnemonic = "StarlarkProcessDatabinding",
879        progress_message = "Processing data binding",
880    )
881
882def _make_generate_binay_r_flags(resources_node):
883    if not (resources_node.r_txt or resources_node.manifest):
884        return None
885    return ",".join(
886        [
887            resources_node.r_txt.path if resources_node.r_txt else "",
888            resources_node.manifest.path if resources_node.manifest else "",
889        ],
890    )
891
892def _generate_binary_r(
893        ctx,
894        out_class_jar = None,
895        r_txt = None,
896        manifest = None,
897        package_for_r = None,
898        final_fields = None,
899        resources_nodes = depset(),
900        transitive_r_txts = [],
901        transitive_manifests = [],
902        busybox = None,
903        host_javabase = None):
904    """Generate compiled resources class jar.
905
906    Args:
907      ctx: The context.
908      out_class_jar: File. The output class jar file.
909      r_txt: File. The resource IDs outputted by linking resources in text.
910      manifest: File. The primary AndroidManifest.xml.
911      package_for_r: String. The Java package for the generated R class files.
912      final_fields: Bool. Whether fields get declared as final.
913      busybox: FilesToRunProvider. The ResourceBusyBox executable or
914        FilesToRunprovider
915      host_javabase: A Target. The host javabase.
916    """
917    args = ctx.actions.args()
918    args.add("--tool", "GENERATE_BINARY_R")
919    args.add("--")
920    args.add("--primaryRTxt", r_txt)
921    args.add("--primaryManifest", manifest)
922    args.add("--packageForR", package_for_r)
923    args.add_all(
924        resources_nodes,
925        map_each = _make_generate_binay_r_flags,
926        before_each = "--library",
927    )
928    if final_fields:
929        args.add("--finalFields")
930    else:
931        args.add("--nofinalFields")
932
933    # TODO(b/154003916): support transitive "--library transitive_r_txt_path,transitive_manifest_path" flags
934    args.add("--classJarOutput", out_class_jar)
935    args.add("--targetLabel", str(ctx.label))
936
937    _java.run(
938        ctx = ctx,
939        host_javabase = host_javabase,
940        executable = busybox,
941        arguments = [args],
942        inputs = depset([r_txt, manifest], transitive = transitive_r_txts + transitive_manifests),
943        outputs = [out_class_jar],
944        mnemonic = "StarlarkRClassGenerator",
945        progress_message = "Generating R classes",
946    )
947
948def _make_aar(
949        ctx,
950        out_aar = None,
951        assets = [],
952        assets_dir = None,
953        resource_files = [],
954        class_jar = None,
955        r_txt = None,
956        manifest = None,
957        proguard_specs = [],
958        should_throw_on_conflict = False,
959        busybox = None,
960        host_javabase = None):
961    """Generate an android archive file.
962
963    Args:
964      ctx: The context.
965      out_aar: File. The output AAR file.
966      assets: sequence of Files. A list of Android assets files to be processed.
967      assets_dir: String. The name of the assets directory.
968      resource_files: A list of Files. The resource files.
969      class_jar: File. The class jar file.
970      r_txt: File. The resource IDs outputted by linking resources in text.
971      manifest: File. The primary AndroidManifest.xml.
972      proguard_specs: List of File. The proguard spec files.
973      busybox: FilesToRunProvider. The ResourceBusyBox executable or
974        FilesToRunprovider
975      host_javabase: A Target. The host javabase.
976      should_throw_on_conflict: A boolean. Determines whether an error should be thrown
977        when a resource conflict occurs.
978    """
979    args = ctx.actions.args()
980    args.add("--tool", "GENERATE_AAR")
981    args.add("--")
982    args.add(
983        "--mainData",
984        _make_resources_flag(
985            manifest = manifest,
986            assets = assets,
987            assets_dir = assets_dir,
988            resource_files = resource_files,
989        ),
990    )
991    args.add("--manifest", manifest)
992    args.add("--rtxt", r_txt)
993    args.add("--classes", class_jar)
994    args.add("--aarOutput", out_aar)
995    args.add_all(proguard_specs, before_each = "--proguardSpec")
996    if should_throw_on_conflict:
997        args.add("--throwOnResourceConflict")
998
999    _java.run(
1000        ctx = ctx,
1001        host_javabase = host_javabase,
1002        executable = busybox,
1003        arguments = [args],
1004        inputs = (
1005            resource_files +
1006            assets +
1007            proguard_specs +
1008            [r_txt, manifest, class_jar]
1009        ),
1010        outputs = [out_aar],
1011        mnemonic = "StarlarkAARGenerator",
1012        progress_message = "Generating AAR package for %s" % ctx.label,
1013    )
1014
1015busybox = struct(
1016    compile = _compile,
1017    merge_compiled = _merge_compiled,
1018    validate_and_link = _validate_and_link,
1019    merge_manifests = _merge_manifests,
1020    package = _package,
1021    parse = _parse,
1022    merge_assets = _merge_assets,
1023    make_resources_flag = _make_resources_flag,
1024    process_databinding = _process_databinding,
1025    generate_binary_r = _generate_binary_r,
1026    make_aar = _make_aar,
1027
1028    # Exposed for testing
1029    mergee_manifests_flag = _mergee_manifests_flag,
1030    get_unique_res_dirs = _get_unique_res_dirs,
1031    sanitize_assets_dir = _sanitize_assets_dir,
1032    extract_filters = _extract_filters,
1033)
1034