1# Copyright 2021 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"""android_feature_module rule."""
16
17load(":attrs.bzl", "ANDROID_FEATURE_MODULE_ATTRS")
18load("//rules:java.bzl", _java = "java")
19load(
20    "//rules:providers.bzl",
21    "AndroidFeatureModuleInfo",
22)
23load("//rules:acls.bzl", "acls")
24load(
25    "//rules:utils.bzl",
26    "get_android_toolchain",
27    "utils",
28)
29
30def _impl(ctx):
31    validation = ctx.actions.declare_file(ctx.label.name + "_validation")
32    inputs = [ctx.attr.binary[ApkInfo].unsigned_apk]
33    args = ctx.actions.args()
34    args.add(validation.path)
35    if ctx.file.manifest:
36        args.add(ctx.file.manifest.path)
37        inputs.append(ctx.file.manifest)
38    else:
39        args.add("")
40    args.add(ctx.attr.binary[ApkInfo].unsigned_apk.path)
41    args.add(ctx.configuration.coverage_enabled)
42    args.add(ctx.fragments.android.desugar_java8_libs)
43    args.add(utils.dedupe_split_attr(ctx.split_attr.library).label)
44    args.add(get_android_toolchain(ctx).xmllint_tool.files_to_run.executable)
45    args.add(get_android_toolchain(ctx).unzip_tool.files_to_run.executable)
46
47    ctx.actions.run(
48        executable = ctx.executable._feature_module_validation_script,
49        inputs = inputs,
50        outputs = [validation],
51        arguments = [args],
52        tools = [
53            get_android_toolchain(ctx).xmllint_tool.files_to_run.executable,
54            get_android_toolchain(ctx).unzip_tool.files_to_run.executable,
55        ],
56        mnemonic = "ValidateFeatureModule",
57        progress_message = "Validating feature module %s" % str(ctx.label),
58        toolchain = None,
59    )
60
61    return [
62        AndroidFeatureModuleInfo(
63            binary = ctx.attr.binary,
64            library = utils.dedupe_split_attr(ctx.split_attr.library),
65            title_id = ctx.attr.title_id,
66            title_lib = ctx.attr.title_lib,
67            feature_name = ctx.attr.feature_name,
68            fused = ctx.attr.fused,
69            manifest = ctx.file.manifest,
70        ),
71        OutputGroupInfo(_validation = depset([validation])),
72    ]
73
74android_feature_module = rule(
75    attrs = ANDROID_FEATURE_MODULE_ATTRS,
76    fragments = [
77        "android",
78        "java",
79    ],
80    implementation = _impl,
81    provides = [AndroidFeatureModuleInfo],
82    toolchains = ["//toolchains/android:toolchain_type"],
83    _skylark_testable = True,
84)
85
86def get_feature_module_paths(fqn):
87    # Given a fqn to an android_feature_module, returns the absolute paths to
88    # all implicitly generated targets
89    return struct(
90        binary = Label("%s_bin" % fqn),
91        manifest_lib = Label("%s_AndroidManifest" % fqn),
92        title_strings_xml = Label("%s_title_strings_xml" % fqn),
93        title_lib = Label("%s_title_lib" % fqn),
94    )
95
96def android_feature_module_macro(_android_binary, _android_library, **attrs):
97    """android_feature_module_macro.
98
99    Args:
100      _android_binary: The android_binary rule to use.
101      _android_library: The android_library rule to use.
102      **attrs: android_feature_module attributes.
103    """
104
105    # Enable dot syntax
106    attrs = struct(**attrs)
107    fqn = "//%s:%s" % (native.package_name(), attrs.name)
108
109    required_attrs = ["name", "library", "title"]
110    if not acls.in_android_feature_splits_dogfood(fqn):
111        required_attrs.append("manifest")
112
113    # Check for required macro attributes
114    for attr in required_attrs:
115        if not getattr(attrs, attr, None):
116            fail("%s missing required attr <%s>" % (fqn, attr))
117
118    if hasattr(attrs, "fused") and hasattr(attrs, "manifest"):
119        fail("%s cannot specify <fused> and <manifest>. Prefer <manifest>")
120
121    targets = get_feature_module_paths(fqn)
122
123    tags = getattr(attrs, "tags", [])
124    transitive_configs = getattr(attrs, "transitive_configs", [])
125    visibility = getattr(attrs, "visibility", None)
126    testonly = getattr(attrs, "testonly", None)
127
128    # Create strings.xml containing split title
129    title_id = "split_" + str(hash(fqn)).replace("-", "N")
130    native.genrule(
131        name = targets.title_strings_xml.name,
132        outs = [attrs.name + "/res/values/strings.xml"],
133        cmd = """cat > $@ <<EOF
134<?xml version="1.0" encoding="utf-8"?>
135<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
136xmlns:tools="http://schemas.android.com/tools"
137tools:keep="@string/{title_id}">
138    <string name="{title_id}">{title}</string>
139</resources>
140EOF
141""".format(title = attrs.title, title_id = title_id),
142    )
143
144    # Create AndroidManifest.xml
145    min_sdk_version = getattr(attrs, "min_sdk_version", "21") or "21"
146    package = _java.resolve_package_from_label(Label(fqn), getattr(attrs, "custom_package", None))
147    native.genrule(
148        name = targets.manifest_lib.name,
149        outs = [attrs.name + "/AndroidManifest.xml"],
150        cmd = """cat > $@ <<EOF
151<?xml version="1.0" encoding="utf-8"?>
152<manifest xmlns:android="http://schemas.android.com/apk/res/android"
153    package="{package}">
154    <uses-sdk
155      android:minSdkVersion="{min_sdk_version}"/>
156</manifest>
157EOF
158""".format(package = package, min_sdk_version = min_sdk_version),
159    )
160
161    # Resource processing requires an android_library target
162    _android_library(
163        name = targets.title_lib.name,
164        custom_package = getattr(attrs, "custom_package", None),
165        manifest = str(targets.manifest_lib),
166        resource_files = [str(targets.title_strings_xml)],
167        tags = tags,
168        transitive_configs = transitive_configs,
169        visibility = visibility,
170        testonly = testonly,
171    )
172
173    # Wrap any deps in an android_binary. Will be validated to ensure does not contain any dexes
174    binary_attrs = {
175        "name": targets.binary.name,
176        "custom_package": getattr(attrs, "custom_package", None),
177        "manifest": str(targets.manifest_lib),
178        "deps": [attrs.library],
179        "multidex": "native",
180        "tags": tags,
181        "transitive_configs": transitive_configs,
182        "visibility": visibility,
183        "feature_flags": getattr(attrs, "feature_flags", None),
184        "$enable_manifest_merging": False,
185        "testonly": testonly,
186    }
187    _android_binary(**binary_attrs)
188
189    android_feature_module(
190        name = attrs.name,
191        library = attrs.library,
192        binary = str(targets.binary),
193        title_id = title_id,
194        title_lib = str(targets.title_lib),
195        feature_name = getattr(attrs, "feature_name", attrs.name),
196        fused = getattr(attrs, "fused", True),
197        manifest = getattr(attrs, "manifest", None),
198        tags = tags,
199        transitive_configs = transitive_configs,
200        visibility = visibility,
201        testonly = testonly,
202    )
203