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("@bazel_skylib//lib:paths.bzl", "paths") 16 17AidlGenInfo = provider( 18 fields = [ 19 "srcs", 20 "hdrs", 21 "hash_file", 22 "transitive_srcs", 23 "transitive_include_dirs", 24 "flags", 25 ], 26) 27 28def _symlink_aidl_srcs(ctx, srcs, strip_import_prefix): 29 virtual_imports = paths.join("_virtual_imports", ctx.label.name) 30 include_path = paths.join(ctx.genfiles_dir.path, ctx.label.package, virtual_imports) 31 workspace_root_strip_import_prefix = paths.join(ctx.label.package, strip_import_prefix) 32 33 direct_srcs = [] 34 for src in srcs: 35 src_path = src.short_path 36 37 if not paths.normalize(src_path).startswith(paths.normalize(workspace_root_strip_import_prefix)): 38 fail(".aidl file '%s' is not under the specified strip prefix '%s'" % 39 (src_path, workspace_root_strip_import_prefix)) 40 41 import_path = paths.relativize(src_path, workspace_root_strip_import_prefix) 42 virtual_src = ctx.actions.declare_file(paths.join(virtual_imports, import_path)) 43 ctx.actions.symlink( 44 output = virtual_src, 45 target_file = src, 46 progress_message = "Symlinking virtual .aidl sources for %{label}", 47 ) 48 direct_srcs.append(virtual_src) 49 return include_path, direct_srcs 50 51# https://cs.android.com/android/platform/system/tools/aidl/+/master:build/aidl_api.go;l=718-724;drc=87bcb923b4ed9cf6e6837f4cc02d954f211c0b12 52def _version_for_hash_gen(version): 53 if int(version) > 1: 54 return int(version) - 1 55 return "latest-version" 56 57def _get_aidl_interface_name(versioned_name): 58 parts = versioned_name.split("-V") 59 if len(parts) == 1 or not parts[-1].isdigit(): 60 fail("{}'s version is not parsable", versioned_name) 61 62 return parts[0] 63 64def _verify_hash_file(ctx): 65 timestamp = ctx.actions.declare_file(ctx.label.name + "_checkhash_" + ctx.attr.version + ".timestamp") 66 67 api_dir = "{package_dir}/aidl_api/{aidl_interface_name}/{version}".format( 68 package_dir = paths.dirname(ctx.build_file_path), 69 aidl_interface_name = _get_aidl_interface_name(ctx.label.name), 70 version = ctx.attr.version, 71 ) 72 73 shell_command = """ 74 cd {api_dir} 75 aidl_files_checksums=$(find ./ -name "*.aidl" -print0 | LC_ALL=C sort -z | xargs -0 sha1sum && echo {version}) 76 cd - 77 78 if [[ $(echo "$aidl_files_checksums" | sha1sum | cut -d " " -f 1) = $(tail -1 {hash_file}) ]]; then 79 touch {timestamp}; 80 else 81 cat "{message_check_equality}" 82 exit 1; 83 fi; 84 """.format( 85 api_dir = api_dir, 86 aidl_files = " ".join([src.path for src in ctx.files.srcs]), 87 version = _version_for_hash_gen(ctx.attr.version), 88 hash_file = ctx.file.hash_file.path, 89 timestamp = timestamp.path, 90 message_check_equality = ctx.file._message_check_equality.path, 91 ) 92 93 ctx.actions.run_shell( 94 inputs = ctx.files.srcs + [ctx.file.hash_file, ctx.file._message_check_equality], 95 outputs = [timestamp], 96 command = shell_command, 97 mnemonic = "AidlHashValidation", 98 progress_message = "Validating AIDL .hash file", 99 ) 100 101 return timestamp 102 103def _aidl_library_rule_impl(ctx): 104 transitive_srcs = [] 105 transitive_include_dirs = [] 106 107 validation_output = [] 108 109 if ctx.attr.hash_file and ctx.attr.version: 110 # if the aidl_library represents an aidl_interface frozen version, 111 # hash_file and version attributes are set 112 validation_output.append(_verify_hash_file(ctx)) 113 114 aidl_import_infos = [d[AidlGenInfo] for d in ctx.attr.deps] 115 for info in aidl_import_infos: 116 transitive_srcs.append(info.transitive_srcs) 117 transitive_include_dirs.append(info.transitive_include_dirs) 118 119 include_path, srcs = _symlink_aidl_srcs(ctx, ctx.files.srcs, ctx.attr.strip_import_prefix) 120 _, hdrs = _symlink_aidl_srcs(ctx, ctx.files.hdrs, ctx.attr.strip_import_prefix) 121 122 return [ 123 DefaultInfo(files = depset(ctx.files.srcs)), 124 OutputGroupInfo( 125 _validation = depset(direct = validation_output), 126 ), 127 AidlGenInfo( 128 srcs = depset(srcs), 129 hdrs = depset(hdrs), 130 hash_file = ctx.file.hash_file, 131 transitive_srcs = depset( 132 direct = srcs + hdrs, 133 transitive = transitive_srcs, 134 ), 135 transitive_include_dirs = depset( 136 direct = [include_path], 137 transitive = transitive_include_dirs, 138 # build with preorder so that transitive_include_dirs.to_list() 139 # return direct include path in the first element 140 order = "preorder", 141 ), 142 flags = ctx.attr.flags, 143 ), 144 ] 145 146aidl_library = rule( 147 implementation = _aidl_library_rule_impl, 148 attrs = { 149 "srcs": attr.label_list( 150 allow_files = [".aidl"], 151 doc = "AIDL source files that contain StructuredParcelable" + 152 " AIDL defintions. These files can be compiled to language" + 153 " bindings.", 154 ), 155 "hdrs": attr.label_list( 156 allow_files = [".aidl"], 157 doc = "AIDL source files that contain UnstructuredParcelable" + 158 " AIDL defintions. These files cannot be compiled to language" + 159 " bindings, but can be referenced by other AIDL sources.", 160 ), 161 "version": attr.string( 162 doc = "The version of the upstream aidl_interface that" + 163 " the aidl_library is created for", 164 ), 165 "hash_file": attr.label( 166 doc = "The .hash file in the api directory of an aidl_interface frozen version", 167 allow_single_file = [".hash"], 168 ), 169 "_message_check_equality": attr.label( 170 allow_single_file = [".txt"], 171 default = "//system/tools/aidl/build:message_check_equality.txt", 172 ), 173 "deps": attr.label_list( 174 providers = [AidlGenInfo], 175 doc = "Targets listed here provide AIDL sources referenced" + 176 "by this library.", 177 ), 178 "strip_import_prefix": attr.string( 179 doc = "The prefix to strip from the paths of the .aidl files in " + 180 "this rule. When set, aidl source files in the srcs " + 181 "attribute of this rule are accessible at their path with " + 182 "this prefix cut off.", 183 ), 184 "flags": attr.string_list( 185 doc = "Flags to pass to AIDL tool", 186 ), 187 }, 188 provides = [AidlGenInfo], 189) 190 191def _generate_aidl_bindings(ctx, lang, aidl_info): 192 """ Utility function for creating AIDL bindings from aidl_libraries. 193 194 Args: 195 ctx: context, used for declaring actions and new files and providing _aidl_tool 196 lang: string, defines the language of the generated binding code 197 aidl_src_infos: AidlGenInfo, list of sources to provide to AIDL compiler 198 199 Returns: 200 list of output files 201 """ 202 203 #TODO(b/235113507) support C++ AIDL binding 204 ext = "" 205 if lang == "java": 206 ext = ".java" 207 else: 208 fail("Cannot generate AIDL language bindings for `{}`.".format(lang)) 209 210 out_files = [] 211 for aidl_file in aidl_info.srcs.to_list(): 212 out_filename = paths.replace_extension(aidl_file.basename, ext) 213 out_file = ctx.actions.declare_file(out_filename, sibling = aidl_file) 214 out_files.append(out_file) 215 216 args = ctx.actions.args() 217 args.add_all(aidl_info.flags) 218 219 #TODO(b/241139797) allow this flag to be controlled by an attribute 220 args.add("--structured") 221 222 args.add_all([ 223 "-I {}".format(i) 224 for i in aidl_info.transitive_include_dirs.to_list() 225 ]) 226 args.add(aidl_file.path) 227 args.add(out_file) 228 229 ctx.actions.run( 230 inputs = aidl_info.transitive_srcs, 231 outputs = [out_file], 232 arguments = [args], 233 progress_message = "Generating {} AIDL binding from {}".format(lang, aidl_file.short_path), 234 executable = ctx.executable._aidl_tool, 235 ) 236 237 return out_files 238 239aidl_file_utils = struct( 240 generate_aidl_bindings = _generate_aidl_bindings, 241) 242