xref: /aosp_15_r20/external/bazelbuild-rules_android/rules/data_binding.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
15"""Bazel Android Data Binding."""
16
17load(":utils.bzl", "ANDROID_TOOLCHAIN_TYPE", _utils = "utils")
18
19# Data Binding context attributes.
20_JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_INPUTS = \
21    "java_annotation_processor_additional_inputs"
22_JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_OUTPUTS = \
23    "java_annotation_processor_additional_outputs"
24_JAVA_PLUGINS = "java_plugins"
25_JAVA_SRCS = "java_srcs"
26_JAVAC_OPTS = "javac_opts"
27_PROVIDERS = "providers"
28
29DataBindingContextInfo = provider(
30    doc = "Contains data from processing Android Data Binding.",
31    fields = {
32        _JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_INPUTS: (
33            "Additional inputs required by the Java annotation processor."
34        ),
35        _JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_OUTPUTS: (
36            "Additional outputs produced by the Java annotation processor."
37        ),
38        _JAVA_PLUGINS: "Data Binding Java annotation processor",
39        _JAVA_SRCS: "Java sources required by the Java annotation processor.",
40        _JAVAC_OPTS: (
41            "Additional Javac opts required by the Java annotation processor."
42        ),
43        _PROVIDERS: "The list of all providers to propagate.",
44    },
45)
46
47# Path used when resources have not been defined.
48_NO_RESOURCES_PATH = "/tmp/no_resources"
49
50def _copy_annotation_file(ctx, output_dir, annotation_template):
51    annotation_out = ctx.actions.declare_file(
52        output_dir + "/android/databinding/layouts/DataBindingInfo.java",
53    )
54    _utils.copy_file(ctx, annotation_template, annotation_out)
55    return annotation_out
56
57def _gen_sources(ctx, output_dir, java_package, deps, layout_info, data_binding_exec):
58    class_info = ctx.actions.declare_file(output_dir + "class-info.zip")
59    srcjar = ctx.actions.declare_file(output_dir + "baseClassSrc.srcjar")
60
61    args = ctx.actions.args()
62    args.add("-layoutInfoFiles", layout_info)
63    args.add("-package", java_package)
64    args.add("-classInfoOut", class_info)
65    args.add("-sourceOut", srcjar)
66    args.add("-zipSourceOutput", "true")
67    args.add("-useAndroidX", "false")
68
69    if deps:
70        if type(deps[0].class_infos) == "depset":
71            class_infos = depset(transitive = [info.class_infos for info in deps])
72            inputs = depset(direct = [layout_info], transitive = [class_infos])
73        elif type(deps[0].class_infos) == "list":
74            class_infos = []
75            for info in deps:
76                class_infos.extend(info.class_infos)
77            inputs = class_infos + [layout_info]
78        else:
79            fail("Expected list or depset. Got %s" % type(deps[0].class_infos))
80    else:
81        class_infos = []
82        inputs = [layout_info]
83
84    args.add_all(class_infos, before_each = "-dependencyClassInfoList")
85
86    ctx.actions.run(
87        executable = data_binding_exec,
88        arguments = ["GEN_BASE_CLASSES", args],
89        inputs = inputs,
90        outputs = [class_info, srcjar],
91        mnemonic = "GenerateDataBindingBaseClasses",
92        progress_message = (
93            "GenerateDataBindingBaseClasses %s" % class_info.short_path
94        ),
95        toolchain = ANDROID_TOOLCHAIN_TYPE,
96    )
97    return srcjar, class_info
98
99def _setup_dependent_lib_artifacts(ctx, output_dir, deps):
100    # DataBinding requires files in very specific locations.
101    # The following expand_template (copy actions) are moving the files
102    # to the correct locations.
103    dep_lib_artifacts = []
104    for info in deps:
105        # Yes, DataBinding requires depsets iterations.
106        for artifact in (info.transitive_br_files.to_list() +
107                         _utils.list_or_depset_to_list(info.setter_stores) +
108                         _utils.list_or_depset_to_list(info.class_infos)):
109            # short_path might contain a parent directory reference if the
110            # databinding artifact is from an external repository (e.g. an aar
111            # from Maven). If that's the case, just remove the parent directory
112            # reference, otherwise the "dependent-lib-artifacts" directory will
113            # get removed by the "..".
114            path = artifact.short_path
115            if path.startswith("../"):
116                path = path[3:]
117            dep_lib_artifact = ctx.actions.declare_file(
118                output_dir + "dependent-lib-artifacts/" + path,
119            )
120
121            # Copy file to a location required by the DataBinding annotation
122            # processor.
123            # TODO(djwhang): Look into SymlinkAction.
124            if artifact.is_directory:
125                _utils.copy_dir(ctx, artifact, dep_lib_artifact)
126            else:
127                _utils.copy_file(ctx, artifact, dep_lib_artifact)
128            dep_lib_artifacts.append(dep_lib_artifact)
129    return dep_lib_artifacts
130
131def _get_javac_opts(
132        ctx,
133        java_package,
134        artifact_type,
135        dependency_artifacts_dir,
136        aar_out_dir,
137        class_info_path,
138        layout_info_path,
139        deps):
140    java_packages = []
141    for info in deps:
142        for label_and_java_package in info.label_and_java_packages:
143            java_packages.append(label_and_java_package.java_package)
144
145    javac_opts = []
146    javac_opts.append("-Aandroid.databinding.dependencyArtifactsDir=" +
147                      dependency_artifacts_dir)
148    javac_opts.append("-Aandroid.databinding.aarOutDir=" + aar_out_dir)
149    javac_opts.append("-Aandroid.databinding.sdkDir=/not/used")
150    javac_opts.append("-Aandroid.databinding.artifactType=" + artifact_type)
151    javac_opts.append("-Aandroid.databinding.exportClassListOutFile=" +
152                      "/tmp/exported_classes")
153    javac_opts.append("-Aandroid.databinding.modulePackage=" + java_package)
154    javac_opts.append("-Aandroid.databinding.directDependencyPkgs=[%s]" %
155                      ",".join(java_packages))
156
157    # The minimum Android SDK compatible with this rule.
158    # TODO(djwhang): This probably should be based on the actual min-sdk from
159    # the manifest, or an appropriate rule attribute.
160    javac_opts.append("-Aandroid.databinding.minApi=14")
161    javac_opts.append("-Aandroid.databinding.enableV2=1")
162
163    javac_opts.append("-Aandroid.databinding.classLogDir=" + class_info_path)
164    javac_opts.append("-Aandroid.databinding.layoutInfoDir=" + layout_info_path)
165    return javac_opts
166
167def _process(
168        ctx,
169        resources_ctx = None,
170        defines_resources = False,
171        enable_data_binding = False,
172        java_package = None,
173        layout_info = None,
174        artifact_type = "LIBRARY",
175        deps = [],
176        exports = [],
177        data_binding_exec = None,
178        data_binding_annotation_processor = None,
179        data_binding_annotation_template = None):
180    """Processes Android Data Binding.
181
182    Args:
183      ctx: The context.
184      resources_ctx: The Android Resources context.
185      defines_resources: boolean. Determines whether resources were defined.
186      enable_data_binding: boolean. Determines whether Data Binding should be
187        enabled.
188      java_package: String. The Java package.
189      layout_info: A file. The layout-info zip file.
190      artifact_type: String. Either LIBRARY or APPLICATION.
191      deps: sequence of DataBindingV2Info providers. A list of deps. Optional.
192      exports: sequence of DataBindingV2Info providers. A list of exports.
193        Optional.
194      data_binding_exec: The DataBinding executable.
195      data_binding_annotation_processor: JavaInfo. The JavaInfo for the
196        annotation processor.
197      data_binding_annotation_template: A file. Used to generate data binding
198        classes.
199
200    Returns:
201      A DataBindingContextInfo provider.
202    """
203
204    if artifact_type not in ["LIBRARY", "APPLICATION"]:
205        fail("Unexpected artifact type: " + artifact_type)
206
207    # TODO(b/154513292): Clean up bad usages of context objects.
208    if resources_ctx:
209        defines_resources = resources_ctx.defines_resources
210
211    # The Android Data Binding context object.
212    db_info = {
213        _JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_INPUTS: [],
214        _JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_OUTPUTS: [],
215        _JAVA_PLUGINS: [],
216        _JAVA_SRCS: [],
217        _JAVAC_OPTS: [],
218        _PROVIDERS: [],
219    }
220
221    if not enable_data_binding:
222        db_info[_PROVIDERS] = [
223            DataBindingV2Info(
224                databinding_v2_providers_in_deps = deps,
225                databinding_v2_providers_in_exports = exports,
226            ),
227        ]
228        return struct(**db_info)
229
230    output_dir = "databinding/%s/" % ctx.label.name
231
232    db_info[_JAVA_SRCS].append(_copy_annotation_file(
233        ctx,
234        output_dir,
235        data_binding_annotation_template,
236    ))
237    db_info[_JAVA_PLUGINS].append(data_binding_annotation_processor)
238
239    br_out = None
240    setter_store_out = None
241    class_info = None
242    if defines_resources:
243        # Outputs of the Data Binding annotation processor.
244        br_out = ctx.actions.declare_file(
245            output_dir + "bin-files/%s-br.bin" % java_package,
246        )
247        db_info[_JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_OUTPUTS].append(br_out)
248        setter_store_out = ctx.actions.declare_file(
249            output_dir + "bin-files/%s-setter_store.json" % java_package,
250        )
251        db_info[_JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_OUTPUTS].append(
252            setter_store_out,
253        )
254
255        srcjar, class_info = _gen_sources(
256            ctx,
257            output_dir,
258            java_package,
259            deps,
260            layout_info,
261            data_binding_exec,
262        )
263        db_info[_JAVA_SRCS].append(srcjar)
264        db_info[_JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_INPUTS].append(class_info)
265        db_info[_JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_INPUTS].append(
266            layout_info,
267        )
268
269    dep_lib_artifacts = _setup_dependent_lib_artifacts(ctx, output_dir, deps)
270    db_info[_JAVA_ANNOTATION_PROCESSOR_ADDITIONAL_INPUTS].extend(
271        dep_lib_artifacts,
272    )
273
274    db_info[_JAVAC_OPTS] = _get_javac_opts(
275        ctx,
276        java_package,
277        artifact_type,
278        (
279            br_out.path.rpartition(br_out.short_path)[0] +
280            ctx.label.package +
281            "/" +
282            output_dir +
283            "dependent-lib-artifacts"
284        ),
285        br_out.dirname,
286        class_info.path if class_info else _NO_RESOURCES_PATH,
287        layout_info.path if layout_info else _NO_RESOURCES_PATH,
288        deps,
289    )
290
291    db_info[_PROVIDERS] = [
292        DataBindingV2Info(
293            setter_store_file = setter_store_out,
294            class_info_file = class_info,
295            br_file = br_out,
296            label = str(ctx.label),
297            java_package = java_package,
298            databinding_v2_providers_in_deps = deps,
299            databinding_v2_providers_in_exports = exports,
300        ),
301    ]
302
303    return DataBindingContextInfo(**db_info)
304
305data_binding = struct(
306    process = _process,
307)
308