• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2#
3# Copyright (C) 2016 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#
17"""Annotates an existing version script with data for the NDK."""
18import argparse
19import collections
20import json
21import logging
22import os
23import sys
24
25
26ALL_ARCHITECTURES = (
27    'arm',
28    'arm64',
29    'mips',
30    'mips64',
31    'x86',
32    'x86_64',
33)
34
35
36def logger():
37    """Returns the default logger for this module."""
38    return logging.getLogger(__name__)
39
40
41def verify_version_script(lines, json_db):
42    """Checks that every symbol in the NDK is in the version script."""
43    symbols = dict(json_db)
44    for line in lines:
45        if ';' in line:
46            name, _ = line.split(';')
47            name = name.strip()
48
49            if name in symbols:
50                del symbols[name]
51    if len(symbols) > 0:
52        for symbol in symbols.keys():
53            logger().error(
54                'NDK symbol not present in version script: {}'.format(symbol))
55        sys.exit(1)
56
57
58def was_always_present(db_entry, arches):
59    """Returns whether the symbol has always been present or not."""
60    for arch in arches:
61        is_64 = arch.endswith('64')
62        introduced_tag = 'introduced-' + arch
63        if introduced_tag not in db_entry:
64            return False
65        if is_64 and db_entry[introduced_tag] != 21:
66            return False
67        elif not is_64 and db_entry[introduced_tag] != 9:
68            return False
69        # Else we have the symbol in this arch and was introduced in the first
70        # version of it.
71    return True
72
73
74def get_common_introduced(db_entry, arches):
75    """Returns the common introduction API level or None.
76
77    If the symbol was introduced in the same API level for all architectures,
78    return that API level. If the symbol is not present in all architectures or
79    was introduced to them at different times, return None.
80    """
81    introduced = None
82    for arch in arches:
83        introduced_tag = 'introduced-' + arch
84        if introduced_tag not in db_entry:
85            return None
86        if introduced is None:
87            introduced = db_entry[introduced_tag]
88        elif db_entry[introduced_tag] != introduced:
89            return None
90        # Else we have the symbol in this arch and it's the same introduction
91        # level. Keep going.
92    return introduced
93
94
95def annotate_symbol(line, json_db):
96    """Returns the line with NDK data appended."""
97    name_part, rest = line.split(';')
98    name = name_part.strip()
99    if name not in json_db:
100        return line
101
102    rest = rest.rstrip()
103    tags = []
104    db_entry = json_db[name]
105    if db_entry['is_var'] == 'true':
106        tags.append('var')
107
108    arches = ALL_ARCHITECTURES
109    if '#' in rest:
110        had_tags = True
111        # Current tags aren't necessarily arch tags. Check them before using
112        # them.
113        _, old_tags = rest.split('#')
114        arch_tags = []
115        for tag in old_tags.strip().split(' '):
116            if tag in ALL_ARCHITECTURES:
117                arch_tags.append(tag)
118        if len(arch_tags) > 0:
119            arches = arch_tags
120    else:
121        had_tags = False
122
123    always_present = was_always_present(db_entry, arches)
124    common_introduced = get_common_introduced(db_entry, arches)
125    if always_present:
126        # No need to tag things that have always been there.
127        pass
128    elif common_introduced is not None:
129        tags.append('introduced={}'.format(common_introduced))
130    else:
131        for arch in ALL_ARCHITECTURES:
132            introduced_tag = 'introduced-' + arch
133            if introduced_tag not in db_entry:
134                continue
135            tags.append(
136                '{}={}'.format(introduced_tag, db_entry[introduced_tag]))
137
138    if tags:
139        if not had_tags:
140            rest += ' #'
141        rest += ' ' + ' '.join(tags)
142    return name_part + ';' + rest + '\n'
143
144
145def annotate_version_script(version_script, json_db, lines):
146    """Rewrites a version script with NDK annotations."""
147    for line in lines:
148        # Lines contain a semicolon iff they contain a symbol name.
149        if ';' in line:
150            version_script.write(annotate_symbol(line, json_db))
151        else:
152            version_script.write(line)
153
154
155def create_version_script(version_script, json_db):
156    """Creates a new version script based on an NDK library definition."""
157    json_db = collections.OrderedDict(sorted(json_db.items()))
158
159    version_script.write('LIB {\n')
160    version_script.write('  global:\n')
161    for symbol in json_db.keys():
162        line = annotate_symbol('    {};\n'.format(symbol), json_db)
163        version_script.write(line)
164    version_script.write('  local:\n')
165    version_script.write('    *;\n')
166    version_script.write('};')
167
168
169def parse_args():
170    """Returns parsed command line arguments."""
171    parser = argparse.ArgumentParser()
172
173    parser.add_argument(
174        '--create', action='store_true',
175        help='Create a new version script instead of annotating.')
176
177    parser.add_argument(
178        'data_file', metavar='DATA_FILE', type=os.path.realpath,
179        help='Path to JSON DB generated by build_symbol_db.py.')
180
181    parser.add_argument(
182        'version_script', metavar='VERSION_SCRIPT', type=os.path.realpath,
183        help='Version script to be annotated.')
184
185    parser.add_argument('-v', '--verbose', action='count', default=0)
186
187    return parser.parse_args()
188
189
190def main():
191    """Program entry point."""
192    args = parse_args()
193
194    verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
195    verbosity = args.verbose
196    if verbosity > 2:
197        verbosity = 2
198
199    logging.basicConfig(level=verbose_map[verbosity])
200    with open(args.data_file) as json_db_file:
201        json_db = json.load(json_db_file)
202
203    if args.create:
204        with open(args.version_script, 'w') as version_script:
205            create_version_script(version_script, json_db)
206    else:
207        with open(args.version_script, 'r') as version_script:
208            file_data = version_script.readlines()
209        verify_version_script(file_data, json_db)
210        with open(args.version_script, 'w') as version_script:
211            annotate_version_script(version_script, json_db, file_data)
212
213
214if __name__ == '__main__':
215    main()
216