• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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