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 rule for Android sdk repository.""" 16 17load("//rules:android_revision.bzl", "compare_android_revisions", "parse_android_revision") 18 19_SDK_REPO_TEMPLATE = Label(":template.bzl") 20_EMPTY_SDK_REPO_TEMPLATE = Label(":empty.template.bzl") 21 22_BUILD_TOOLS_DIR = "build-tools" 23_PLATFORMS_DIR = "platforms" 24_SYSTEM_IMAGES_DIR = "system-images" 25_LOCAL_MAVEN_REPOS = [ 26 "extras/android/m2repository", 27 "extras/google/m2repository", 28 "extras/m2repository", 29] 30_DIRS_TO_LINK = [ 31 _BUILD_TOOLS_DIR, 32 "emulator", 33 "platform-tools", 34 _PLATFORMS_DIR, 35 _SYSTEM_IMAGES_DIR, 36] + _LOCAL_MAVEN_REPOS 37 38_MIN_BUILD_TOOLS_VERSION = parse_android_revision("30.0.0") 39 40def _read_api_levels(repo_ctx, android_sdk_path): 41 platforms_dir = "%s/%s" % (android_sdk_path, _PLATFORMS_DIR) 42 api_levels = [] 43 platforms_path = repo_ctx.path(platforms_dir) 44 if not platforms_path.exists: 45 return api_levels 46 for entry in platforms_path.readdir(): 47 name = entry.basename 48 if name.startswith("android-"): 49 level = int(name[len("android-"):]) 50 api_levels.append(level) 51 return api_levels 52 53def _newest_build_tools(repo_ctx, android_sdk_path): 54 build_tools_dir = "%s/%s" % (android_sdk_path, _BUILD_TOOLS_DIR) 55 highest = None 56 build_tools_path = repo_ctx.path(build_tools_dir) 57 if not build_tools_path.exists: 58 return None 59 for entry in build_tools_path.readdir(): 60 name = entry.basename 61 revision = parse_android_revision(name) 62 highest = compare_android_revisions(highest, revision) 63 return highest 64 65def _find_system_images(repo_ctx, android_sdk_path): 66 system_images_dir = "%s/%s" % (android_sdk_path, _SYSTEM_IMAGES_DIR) 67 system_images = [] 68 69 system_images_path = repo_ctx.path(system_images_dir) 70 if not system_images_path.exists: 71 return system_images 72 73 # The directory structure needed is "system-images/android-API/apis-enabled/arch" 74 for api_entry in system_images_path.readdir(): 75 for enabled_entry in api_entry.readdir(): 76 for arch_entry in enabled_entry.readdir(): 77 image_path = "%s/%s/%s/%s" % ( 78 _SYSTEM_IMAGES_DIR, 79 api_entry.basename, 80 enabled_entry.basename, 81 arch_entry.basename, 82 ) 83 system_images.append(image_path) 84 85 return system_images 86 87def _android_sdk_repository_impl(repo_ctx): 88 # Determine the SDK path to use, either from the attribute or the environment. 89 android_sdk_path = repo_ctx.attr.path 90 if not android_sdk_path: 91 android_sdk_path = repo_ctx.os.environ.get("ANDROID_HOME") 92 if not android_sdk_path: 93 # Create an empty repository that allows non-Android code to build. 94 repo_ctx.template("BUILD.bazel", _EMPTY_SDK_REPO_TEMPLATE) 95 return None 96 97 # Symlink the needed contents to this repository. 98 for dir_to_link in _DIRS_TO_LINK: 99 source = "%s/%s" % (android_sdk_path, dir_to_link) 100 dest = dir_to_link 101 repo_ctx.symlink(source, dest) 102 103 # Read list of supported SDK levels 104 api_levels = _read_api_levels(repo_ctx, android_sdk_path) 105 if len(api_levels) == 0: 106 fail("No Android SDK apis found in the Android SDK at %s. Please install APIs from the Android SDK Manager." % android_sdk_path) 107 108 # Determine default SDK level. 109 default_api_level = max(api_levels) 110 if repo_ctx.attr.api_level: 111 default_api_level = int(repo_ctx.attr.api_level) 112 if default_api_level not in api_levels: 113 fail("Android SDK api level %s was requested but it is not installed in the Android SDK at %s. The api levels found were %s. Please choose an available api level or install api level %s from the Android SDK Manager." % ( 114 default_api_level, 115 android_sdk_path, 116 api_levels, 117 default_api_level, 118 )) 119 120 # Determine build_tools directory (and version) 121 build_tools = None 122 if repo_ctx.attr.build_tools_version: 123 build_tools = parse_android_revision(repo_ctx.attr.build_tools_version) 124 else: 125 build_tools = _newest_build_tools(repo_ctx, android_sdk_path) 126 127 # Check validity of build_tools 128 if not build_tools: 129 fail("Unable to determine build tools version") 130 if compare_android_revisions(build_tools, _MIN_BUILD_TOOLS_VERSION) != build_tools: 131 fail("Bazel requires Android build tools version %s or newer, %s was provided" % ( 132 _MIN_BUILD_TOOLS_VERSION.dir, 133 build_tools.dir, 134 )) 135 136 # Determine system image dirs 137 system_images = _find_system_images(repo_ctx, android_sdk_path) 138 139 # Write the build file. 140 repo_ctx.symlink(Label(":helper.bzl"), "helper.bzl") 141 repo_ctx.template( 142 "BUILD.bazel", 143 _SDK_REPO_TEMPLATE, 144 substitutions = { 145 "__repository_name__": repo_ctx.name, 146 "__build_tools_version__": build_tools.version, 147 "__build_tools_directory__": build_tools.dir, 148 "__api_levels__": ",".join([str(level) for level in api_levels]), 149 "__default_api_level__": str(default_api_level), 150 "__system_image_dirs__": "\n".join(["'%s'," % d for d in system_images]), 151 # TODO(katre): implement these. 152 #"__exported_files__": "", 153 }, 154 ) 155 156 # repo is reproducible 157 return None 158 159_android_sdk_repository = repository_rule( 160 implementation = _android_sdk_repository_impl, 161 attrs = { 162 "api_level": attr.int(default = 0), 163 "build_tools_version": attr.string(), 164 "path": attr.string(), 165 }, 166 environ = ["ANDROID_HOME"], 167 local = True, 168) 169 170def _bind(repo_name, bind_name, target): 171 native.bind(name = bind_name, actual = "@%s//%s" % (repo_name, target)) 172 173def android_sdk_repository( 174 name, 175 path = "", 176 api_level = 0, 177 build_tools_version = ""): 178 """Create a repository with Android SDK bindings and toolchains. 179 180 The SDK will be located at the given path, or via the ANDROID_HOME 181 environment variable if the path attribute is unset. 182 183 Args: 184 name: The repository name. 185 api_level: The SDK API level to use. 186 build_tools_version: The build_tools in the SDK to use. 187 path: The path to the Android SDK. 188 """ 189 190 _android_sdk_repository( 191 name = name, 192 path = path, 193 api_level = api_level, 194 build_tools_version = build_tools_version, 195 ) 196 197 _bind(name, "android/sdk", ":sdk") 198 _bind(name, "android/d8_jar_import", ":d8_jar_import") 199 _bind(name, "android/dx_jar_import", ":dx_jar_import") 200 _bind(name, "android_sdk_for_testing", ":files") 201 _bind(name, "has_android_sdk", ":has_android_sdk") 202 native.register_toolchains("@%s//:all" % name) 203