1#!/usr/bin/python3 2# 3# Copyright (C) 2022 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import collections 18import json 19import os 20import sys 21 22from cc.api_assembly import CcApiAssemblyContext 23from java.api_assembly import JavaApiAssemblyContext 24from build_file_generator import BuildFileGenerator 25import ninja_tools 26 27ContributionData = collections.namedtuple("ContributionData", 28 ("inner_tree", "json_data")) 29 30 31def assemble_apis(context, inner_trees): 32 # Find all of the contributions from the inner tree 33 contribution_files_dict = inner_trees.for_each_tree( 34 api_contribution_files_for_inner_tree) 35 36 # Load and validate the contribution files 37 # TODO: Check timestamps and skip unnecessary work 38 contributions = [] 39 for tree_key, filenames in contribution_files_dict.items(): 40 for filename in filenames: 41 json_data = load_contribution_file(context, filename) 42 if not json_data: 43 continue 44 # TODO: Validate the configs, especially that the domains match what we asked for 45 # from the lunch config. 46 contributions.append( 47 ContributionData(inner_trees.get(tree_key), json_data)) 48 49 # Group contributions by language and API surface 50 stub_libraries = collate_contributions(contributions) 51 52 # Initialize the build file writer 53 build_file_generator = BuildFileGenerator() 54 55 # Initialize the ninja file writer 56 with open(context.out.api_ninja_file(), "w", 57 encoding='iso-8859-1') as ninja_file: 58 ninja = ninja_tools.Ninja(context, ninja_file) 59 60 # Iterate through all of the stub libraries and generate rules to assemble them 61 # and Android.bp/BUILD files to make those available to inner trees. 62 # TODO: Parallelize? Skip unnecessary work? 63 for stub_library in stub_libraries: 64 # TODO (b/265962882): Export APIs of version < current. 65 # API files of older versions (29,30,...) are currently not 66 # available in out/api_surfaces. 67 # This cause isssues during CC API import, since Soong 68 # cannot resolve the dependency for rdeps that specify 69 # `sdk_version:<num>`. 70 # Create a short-term hack that unconditionally generates Soong 71 # modules for all NDK libraries, starting from version=1. 72 # This does not compromise on API correctness though, since the correct 73 # version number will be passed to the ndkstubgen invocation. 74 # TODO(b/266830850): Revisit stubs versioning for Module-lib API 75 # surface. 76 if stub_library.language == "cc_libraries" and stub_library.api_surface in ["publicapi", "module-libapi"]: 77 versions = list(range(1,34)) # 34 is current 78 versions.append("current") 79 for version in versions: 80 stub_library.api_surface_version = str(version) 81 STUB_LANGUAGE_HANDLERS[stub_library.language]( 82 context, ninja, build_file_generator, stub_library) 83 else: 84 STUB_LANGUAGE_HANDLERS[stub_library.language](context, ninja, 85 build_file_generator, 86 stub_library) 87 88 # TODO: Handle host_executables separately or as a StubLibrary language? 89 90 # Finish writing the ninja file 91 ninja.write() 92 93 build_file_generator.write() 94 build_file_generator.clean( 95 context.out.api_surfaces_dir()) # delete stale Android.bp files 96 97 98def api_contribution_files_for_inner_tree(tree_key, inner_tree, cookie): 99 "Scan an inner_tree's out dir for the api contribution files." 100 directory = inner_tree.out.api_contributions_dir() 101 result = [] 102 with os.scandir(directory) as it: 103 for dirent in it: 104 if not dirent.is_file(): 105 break 106 if dirent.name.endswith(".json"): 107 result.append(os.path.join(directory, dirent.name)) 108 return result 109 110 111def load_contribution_file(context, filename): 112 "Load and return the API contribution at filename. On error report error and return None." 113 with open(filename, encoding='iso-8859-1') as f: 114 try: 115 return json.load(f) 116 except json.decoder.JSONDecodeError as ex: 117 # TODO: Error reporting 118 context.errors.error(ex.msg, filename, ex.lineno, ex.colno) 119 raise ex 120 121 122class StubLibraryContribution(object): 123 124 def __init__(self, inner_tree, api_domain, library_contribution): 125 self.inner_tree = inner_tree 126 self.api_domain = api_domain 127 self.library_contribution = library_contribution 128 129 130class StubLibrary(object): 131 132 def __init__(self, language, api_surface, api_surface_version, name): 133 self.language = language 134 self.api_surface = api_surface 135 self.api_surface_version = api_surface_version 136 self.name = name 137 self.contributions = [] 138 139 def add_contribution(self, contrib): 140 self.contributions.append(contrib) 141 142 143def collate_contributions(contributions): 144 """Take the list of parsed API contribution files, and group targets by API Surface, version, 145 language and library name, and return a StubLibrary object for each of those. 146 """ 147 grouped = {} 148 for contribution in contributions: 149 for language in STUB_LANGUAGE_HANDLERS.keys(): 150 for library in contribution.json_data.get(language, []): 151 key = (language, contribution.json_data["name"], 152 contribution.json_data["version"], library["name"]) 153 stub_library = grouped.get(key) 154 if not stub_library: 155 stub_library = StubLibrary( 156 language, contribution.json_data["name"], 157 contribution.json_data["version"], library["name"]) 158 grouped[key] = stub_library 159 stub_library.add_contribution( 160 StubLibraryContribution( 161 contribution.inner_tree, 162 contribution.json_data["api_domain"], library)) 163 return list(grouped.values()) 164 165 166def assemble_resource_api_library(context, ninja, build_file, stub_library): 167 print("assembling resource_api_library %s-%s %s from:" % 168 (stub_library.api_surface, stub_library.api_surface_version, 169 stub_library.name)) 170 for contrib in stub_library.contributions: 171 print(" %s %s" % 172 (contrib.api_domain, contrib.library_contribution["api"])) 173 # TODO: Implement me 174 175 176STUB_LANGUAGE_HANDLERS = { 177 "cc_libraries": CcApiAssemblyContext().get_cc_api_assembler(), 178 "java_libraries": JavaApiAssemblyContext().get_java_api_assembler(), 179 "resource_libraries": assemble_resource_api_library, 180} 181