1# Copyright (C) 2022 The Android Open Source Project
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
15load("//build/bazel/rules/aidl:aidl_library.bzl", "aidl_library")
16load("//build/bazel/rules/cc:cc_aidl_library.bzl", "cc_aidl_library")
17load("//build/bazel/rules/java:java_aidl_library.bzl", "java_aidl_library")
18
19JAVA = "java"
20CPP = "cpp"
21NDK = "ndk"
22#TODO(b/246803961) Add support for rust backend
23
24def _hash_file(name, version):
25    return "aidl_api/{}/{}/.hash".format(name, version)
26
27def _check_versions_with_info(versions_with_info):
28    for version_with_info in versions_with_info:
29        for dep in version_with_info.get("deps", []):
30            parts = dep.split("-V")
31            if len(parts) < 2 or not parts[-1].isdigit():
32                fail("deps in versions_with_info must specify its version, but", dep)
33
34    versions = []
35
36    # ensure that all versions are ints
37    for info in versions_with_info:
38        version = info["version"]
39        if version.isdigit() == False:
40            fail("version %s is not an integer".format(version))
41
42        versions.append(int(version))
43
44    if versions != sorted(versions):
45        fail("versions should be sorted")
46
47    for i, v in enumerate(versions):
48        if i > 0:
49            if v == versions[i - 1]:
50                fail("duplicate version found:", v)
51        if v <= 0:
52            fail("all versions should be > 0, but found version:", v)
53
54def _create_latest_version_aliases(name, last_version_name, backend_configs, **kwargs):
55    latest_name = name + "-latest"
56    native.alias(
57        name = latest_name,
58        actual = ":" + last_version_name,
59        **kwargs
60    )
61    for lang in backend_configs.keys():
62        language_binding_name = last_version_name + "-" + lang
63        native.alias(
64            name = latest_name + "-" + lang,
65            actual = ":" + language_binding_name,
66            **kwargs
67        )
68
69def _versioned_name(name, version):
70    if version == "":
71        return name
72
73    return name + "-V" + version
74
75# https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_interface.go;l=782-799;drc=5390d9a42f5e4f99ccb3a84068f554d948cb62b9
76def _next_version(versions_with_info, unstable):
77    if unstable:
78        return ""
79
80    if versions_with_info == None or len(versions_with_info) == 0:
81        return "1"
82
83    return str(int(versions_with_info[-1]["version"]) + 1)
84
85def _is_config_enabled(config):
86    if config == None:
87        return False
88
89    for key in config:
90        if key not in ["enabled", "min_sdk_version", "tags", "additional_dynamic_deps"]:
91            fail("unknown property in aidl configuration: " + str(key))
92
93    return config.get("enabled", False) == True
94
95def aidl_interface(
96        name,
97        deps = [],
98        hdrs = [],
99        strip_import_prefix = "",
100        srcs = None,
101        flags = None,
102        java_config = None,
103        cpp_config = None,
104        ndk_config = None,
105        stability = None,
106        versions_with_info = [],
107        unstable = False,
108        tags = [],
109        # TODO(b/261208761): Support frozen attr
110        frozen = False,
111        **kwargs):
112    """aidl_interface creates a versioned aidl_libraries and language-specific *_aidl_libraries
113
114    This macro loops over the list of required versions and searches for all
115    *.aidl source files located under the path `aidl_api/<version label/`.
116    For each version, an `aidl_library` is created with the corresponding sources.
117    For each `aidl_library`, a language-binding library *_aidl_library is created
118    based on the values passed to the `backends` argument.
119
120    Arguments:
121        name:                   string, base name of generated targets: <module-name>-V<version number>-<language-type>
122        deps:                   List[AidlGenInfo], a list of other aidl_libraries that all versions of this interface depend on
123        hdrs:                   List[AidlGenInfo], a list of other aidl_libraries that all versions of this interface depend on but will not link against for C++
124        strip_import_prefix:    str, a local directory to pass to the AIDL compiler to satisfy imports
125        srcs:                   List[file], a list of files to include in the development (unversioned) version of the aidl_interface
126        flags:                  List[string], a list of flags to pass to the AIDL compiler
127        java_config:            Dict{"enabled": bool}, config for java backend
128        cpp_config:             Dict{"enabled": bool, "min_sdk_version": string, "additional_dynamic_deps": List[Label]}, config for cpp backend
129        ndk_config:             Dict{"enabled": bool, "min_sdk_version": string, "additional_dynamic_deps": List[Label]}, config for ndk backend
130        stability:              string, stability promise of the interface. Currently, only supports "vintf"
131        backends:               List[string], a list of the languages to generate bindings for
132    """
133
134    # When versions_with_info is set, versions is no-op.
135    # TODO(b/244349745): Modify bp2build to skip convert versions if versions_with_info is set
136    if (len(versions_with_info) == 0 and srcs == None):
137        fail("must specify at least versions_with_info or srcs")
138
139    if len(versions_with_info) == 0:
140        if frozen == True:
141            fail("frozen cannot be set without versions_with_info attr being set")
142    elif unstable == True:
143        # https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_interface.go;l=872;drc=5390d9a42f5e4f99ccb3a84068f554d948cb62b9
144        fail("cannot have versions for unstable interface")
145
146    aidl_flags = ["--structured"]
147
148    enabled_backend_configs = {}
149    if _is_config_enabled(java_config):
150        enabled_backend_configs[JAVA] = java_config
151    if _is_config_enabled(cpp_config):
152        enabled_backend_configs[CPP] = cpp_config
153    if _is_config_enabled(ndk_config):
154        enabled_backend_configs[NDK] = ndk_config
155
156    if stability != None:
157        if unstable == True:
158            fail("stability must be unset when unstable is true")
159        if stability == "vintf":
160            aidl_flags.append("--stability=" + stability)
161
162            # TODO(b/245738285): Add support for vintf stability in java backend
163            if JAVA in enabled_backend_configs:
164                enabled_backend_configs.pop(JAVA)
165        else:
166            # https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_interface.go;l=329;drc=e88d9a9b14eafb064a234d555a5cd96de97ca9e2
167            # only vintf is allowed currently
168            fail("stability must be unset or \"vintf\"")
169
170    # next_version will be the last specified version + 1.
171    # https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_interface.go;l=791?q=system%2Ftools%2Faidl%2Fbuild%2Faidl_interface.go
172    next_version = None
173
174    if len(versions_with_info) > 0:
175        _check_versions_with_info(versions_with_info)
176        next_version = _next_version(versions_with_info, False)
177
178        for version_with_info in versions_with_info:
179            deps_for_version = version_with_info.get("deps", [])
180            version = version_with_info.get("version")
181            flags_for_version = aidl_flags
182
183            if version == next_version and frozen == False and flags != None:
184                flags_for_version.extend(flags)
185
186            create_aidl_binding_for_backends(
187                name = name,
188                version = version_with_info["version"],
189                deps = deps_for_version,
190                hdrs = hdrs,
191                aidl_flags = flags_for_version,
192                backend_configs = enabled_backend_configs,
193                tags = tags,
194                **kwargs
195            )
196
197        _create_latest_version_aliases(
198            name,
199            _versioned_name(name, versions_with_info[-1]["version"]),
200            enabled_backend_configs,
201            tags = tags,
202            **kwargs
203        )
204    else:
205        next_version = _next_version(versions_with_info, unstable)
206
207    # https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_interface.go;l=941;drc=5390d9a42f5e4f99ccb3a84068f554d948cb62b9
208    # Create aidl binding for next_version with srcs
209    if srcs and len(srcs) > 0:
210        create_aidl_binding_for_backends(
211            name = name,
212            version = next_version,
213            srcs = srcs,
214            strip_import_prefix = strip_import_prefix,
215            deps = deps,
216            hdrs = hdrs,
217            aidl_flags = aidl_flags,
218            backend_configs = enabled_backend_configs,
219            tags = tags,
220            **kwargs
221        )
222
223def create_aidl_binding_for_backends(
224        name,
225        version = None,
226        srcs = None,
227        strip_import_prefix = "",
228        deps = None,
229        hdrs = None,
230        aidl_flags = [],
231        backend_configs = {},
232        tags = [],
233        **kwargs):
234    """
235    Create aidl_library target and corrending <backend>_aidl_library target for a given version
236
237    Arguments:
238        name:                   string, base name of the aidl interface
239        version:                string, version of the aidl interface
240        srcs:                   List[Label] list of unversioned AIDL srcs
241        strip_import_prefix     string, the prefix to strip the paths of the .aidl files in srcs
242        deps:                   List[AidlGenInfo], a list of other aidl_libraries that the version depends on
243        hdrs:                   List[AidlGenInfo], a list of other aidl_libraries that the version depends on but will not link against for C++
244                                the label of the targets have format <aidl-interface>-V<version_number>
245        aidl_flags:             List[string], a list of flags to pass to the AIDL compiler
246        backends:               List[string], a list of the languages to generate bindings for
247    """
248    aidl_library_name = _versioned_name(name, version)
249
250    # srcs is None when create_aidl_binding_for_backends is called with a
251    # frozen version specified via versions or versions_with_info.
252    # next_version being equal to "" means this is an unstable version and
253    # we should use srcs instead
254    if version != "":
255        aidl_flags = aidl_flags + ["--version=" + version]
256
257    hash_file = None
258
259    if srcs == None:
260        if version == "":
261            fail("need srcs for unversioned interface")
262        strip_import_prefix = "aidl_api/{}/{}".format(name, version)
263        srcs = native.glob([strip_import_prefix + "/**/*.aidl"])
264        hash_file = _hash_file(name, version)
265
266    aidl_library(
267        name = aidl_library_name,
268        deps = deps + hdrs,
269        hash_file = hash_file,
270        version = version,
271        strip_import_prefix = strip_import_prefix,
272        srcs = srcs,
273        flags = aidl_flags,
274        # The language-specific backends will set more appropriate apex_available values.
275        tags = tags + ["apex_available=//apex_available:anyapex"],
276        **kwargs
277    )
278
279    for lang, config in backend_configs.items():
280        # https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_gen_rule.go;l=207;drc=a858ae7039b876a30002a1130f24196915a859a4
281        min_sdk_version = "current"
282        if "min_sdk_version" in config:
283            min_sdk_version = config["min_sdk_version"]
284
285        if lang == JAVA:
286            #TODO(b/285574832) re-enable Java backend
287            continue
288            java_aidl_library(
289                name = aidl_library_name + "-java",
290                deps = [":" + aidl_library_name],
291                tags = tags + config.get("tags", []),
292                # TODO(b/249276008): Pass min_sdk_version to java_aidl_library
293                **(kwargs | {"target_compatible_with": ["//build/bazel_common_rules/platforms/os:android"]})
294            )
295        elif lang == CPP or lang == NDK:
296            dynamic_deps = []
297            cppflags = []
298
299            # https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_interface_backends.go;l=564;drc=0517d97079d4b08f909e7f35edfa33b88fcc0d0e
300            if deps != None:
301                # For each aidl_library target label versioned_name, there's an
302                # associated cc_library_shared target with label versioned_name-<cpp|ndk>
303                dynamic_deps.extend(["{}-{}".format(dep, lang) for dep in deps])
304
305            # https://cs.android.com/android/platform/superproject/+/master:system/tools/aidl/build/aidl_interface_backends.go;l=111;drc=ef9f1352a1a8fec7bb134b1c713e13fc3ccee651
306            if lang == CPP:
307                dynamic_deps.extend([
308                    "//frameworks/native/libs/binder:libbinder",
309                    "//system/core/libutils:libutils",
310                ])
311            elif lang == NDK:
312                dynamic_deps = dynamic_deps + select({
313                    "//build/bazel/rules/apex:android-in_apex": ["//frameworks/native/libs/binder/ndk:libbinder_ndk_stub_libs_current"],
314                    "//conditions:default": ["//frameworks/native/libs/binder/ndk:libbinder_ndk"],
315                })
316
317                # https://source.corp.google.com/android/system/tools/aidl/build/aidl_interface_backends.go;l=120;rcl=18dd931bde35b502545b7a52987e2363042c151c
318                cppflags = ["-DBINDER_STABILITY_SUPPORT"]
319
320            if "additional_dynamic_deps" in config:
321                dynamic_deps += config["additional_dynamic_deps"]
322
323            if hasattr(kwargs, "tidy_checks_as_errors"):
324                fail("tidy_checks_as_errors cannot be overriden for aidl_interface cc_libraries")
325            tidy_checks_as_errors = [
326                "*",
327                "-clang-analyzer-deadcode.DeadStores",  # b/253079031
328                "-clang-analyzer-cplusplus.NewDeleteLeaks",  # b/253079031
329                "-clang-analyzer-optin.performance.Padding",  # b/253079031
330            ]
331
332            cc_aidl_library(
333                name = "{}-{}".format(aidl_library_name, lang),
334                make_shared = True,
335                cppflags = cppflags,
336                deps = [":" + aidl_library_name],
337                dynamic_deps = dynamic_deps,
338                lang = lang,
339                min_sdk_version = min_sdk_version,
340                tidy = "local",
341                tidy_checks_as_errors = tidy_checks_as_errors,
342                tidy_gen_header_filter = True,
343                tags = tags + config.get("tags", []),
344                **kwargs
345            )
346