diff --git a/OWNERS b/OWNERS index dbb491d8b..8355d1047 100644 --- a/OWNERS +++ b/OWNERS @@ -6,9 +6,8 @@ per-file * = jungjw@google.com per-file * = patricearruda@google.com per-file * = paulduffin@google.com -per-file ndk_*.go, *gen_stub_libs.py = danalbert@google.com +per-file ndk_*.go = danalbert@google.com per-file clang.go,global.go = srhines@google.com, chh@google.com, pirama@google.com, yikong@google.com per-file tidy.go = srhines@google.com, chh@google.com per-file lto.go,pgo.go = srhines@google.com, pirama@google.com, yikong@google.com per-file docs/map_files.md = danalbert@google.com, enh@google.com, jiyong@google.com -per-file *ndk_api_coverage_parser.py = sophiez@google.com \ No newline at end of file diff --git a/cc/ndk_api_coverage_parser/.gitignore b/cc/ndk_api_coverage_parser/.gitignore new file mode 100644 index 000000000..fd94eac3f --- /dev/null +++ b/cc/ndk_api_coverage_parser/.gitignore @@ -0,0 +1,140 @@ +# From https://github.com/github/gitignore/blob/master/Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/cc/scriptlib/Android.bp b/cc/ndk_api_coverage_parser/Android.bp similarity index 73% rename from cc/scriptlib/Android.bp rename to cc/ndk_api_coverage_parser/Android.bp index ff9a2f0f3..8d9827ca6 100644 --- a/cc/scriptlib/Android.bp +++ b/cc/ndk_api_coverage_parser/Android.bp @@ -14,19 +14,33 @@ // limitations under the License. // +python_library_host { + name: "ndk_api_coverage_parser_lib", + pkg_path: "ndk_api_coverage_parser", + srcs: [ + "__init__.py", + ], +} + python_test_host { name: "test_ndk_api_coverage_parser", main: "test_ndk_api_coverage_parser.py", srcs: [ "test_ndk_api_coverage_parser.py", ], + libs: [ + "ndk_api_coverage_parser_lib", + "symbolfile", + ], } python_binary_host { name: "ndk_api_coverage_parser", - main: "ndk_api_coverage_parser.py", + main: "__init__.py", srcs: [ - "gen_stub_libs.py", - "ndk_api_coverage_parser.py", + "__init__.py", + ], + libs: [ + "symbolfile", ], } diff --git a/cc/ndk_api_coverage_parser/OWNERS b/cc/ndk_api_coverage_parser/OWNERS new file mode 100644 index 000000000..a90c48c15 --- /dev/null +++ b/cc/ndk_api_coverage_parser/OWNERS @@ -0,0 +1 @@ +sophiez@google.com diff --git a/cc/scriptlib/ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/__init__.py similarity index 97% rename from cc/scriptlib/ndk_api_coverage_parser.py rename to cc/ndk_api_coverage_parser/__init__.py index d74035b2a..7817c7832 100755 --- a/cc/scriptlib/ndk_api_coverage_parser.py +++ b/cc/ndk_api_coverage_parser/__init__.py @@ -21,7 +21,7 @@ import os import sys from xml.etree.ElementTree import Element, SubElement, tostring -from gen_stub_libs import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser +from symbolfile import ALL_ARCHITECTURES, FUTURE_API_LEVEL, MultiplyDefinedSymbolError, SymbolFileParser ROOT_ELEMENT_TAG = 'ndk-library' diff --git a/cc/scriptlib/test_ndk_api_coverage_parser.py b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py similarity index 97% rename from cc/scriptlib/test_ndk_api_coverage_parser.py rename to cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py index a3c9b708c..f1c925ca9 100644 --- a/cc/scriptlib/test_ndk_api_coverage_parser.py +++ b/cc/ndk_api_coverage_parser/test_ndk_api_coverage_parser.py @@ -20,7 +20,7 @@ import textwrap import unittest from xml.etree.ElementTree import tostring -from gen_stub_libs import FUTURE_API_LEVEL, SymbolFileParser +from symbolfile import FUTURE_API_LEVEL, SymbolFileParser import ndk_api_coverage_parser as nparser diff --git a/cc/ndk_library.go b/cc/ndk_library.go index 6299b009e..4578fd329 100644 --- a/cc/ndk_library.go +++ b/cc/ndk_library.go @@ -26,17 +26,16 @@ import ( ) func init() { + pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser") } var ( - toolPath = pctx.SourcePathVariable("toolPath", "build/soong/cc/scriptlib/gen_stub_libs.py") - genStubSrc = pctx.AndroidStaticRule("genStubSrc", blueprint.RuleParams{ - Command: "$toolPath --arch $arch --api $apiLevel --api-map " + - "$apiMap $flags $in $out", - CommandDeps: []string{"$toolPath"}, + Command: "$ndkStubGenerator --arch $arch --api $apiLevel " + + "--api-map $apiMap $flags $in $out", + CommandDeps: []string{"$ndkStubGenerator"}, }, "arch", "apiLevel", "apiMap", "flags") parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule", @@ -78,9 +77,9 @@ type libraryProperties struct { // https://github.com/android-ndk/ndk/issues/265. Unversioned_until *string - // Private property for use by the mutator that splits per-API level. - // can be one of or or "current" - // passed to "gen_stub_libs.py" as it is + // Private property for use by the mutator that splits per-API level. Can be + // one of or or "current" passed to + // "ndkstubgen.py" as it is ApiLevel string `blueprint:"mutated"` // True if this API is not yet ready to be shipped in the NDK. It will be diff --git a/cc/ndkstubgen/.gitignore b/cc/ndkstubgen/.gitignore new file mode 100644 index 000000000..fd94eac3f --- /dev/null +++ b/cc/ndkstubgen/.gitignore @@ -0,0 +1,140 @@ +# From https://github.com/github/gitignore/blob/master/Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/cc/ndkstubgen/Android.bp b/cc/ndkstubgen/Android.bp new file mode 100644 index 000000000..85dfaee18 --- /dev/null +++ b/cc/ndkstubgen/Android.bp @@ -0,0 +1,48 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +python_binary_host { + name: "ndkstubgen", + pkg_path: "ndkstubgen", + main: "__init__.py", + srcs: [ + "__init__.py", + ], + libs: [ + "symbolfile", + ], +} + +python_library_host { + name: "ndkstubgenlib", + pkg_path: "ndkstubgen", + srcs: [ + "__init__.py", + ], + libs: [ + "symbolfile", + ], +} + +python_test_host { + name: "test_ndkstubgen", + srcs: [ + "test_ndkstubgen.py", + ], + libs: [ + "ndkstubgenlib", + ], +} diff --git a/cc/ndkstubgen/OWNERS b/cc/ndkstubgen/OWNERS new file mode 100644 index 000000000..f0d873359 --- /dev/null +++ b/cc/ndkstubgen/OWNERS @@ -0,0 +1 @@ +danalbert@google.com diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py new file mode 100755 index 000000000..2f4326a9c --- /dev/null +++ b/cc/ndkstubgen/__init__.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Generates source for stub shared libraries for the NDK.""" +import argparse +import json +import logging +import os +import sys + +import symbolfile + + +class Generator: + """Output generator that writes stub source files and version scripts.""" + def __init__(self, src_file, version_script, arch, api, llndk, apex): + self.src_file = src_file + self.version_script = version_script + self.arch = arch + self.api = api + self.llndk = llndk + self.apex = apex + + def write(self, versions): + """Writes all symbol data to the output files.""" + for version in versions: + self.write_version(version) + + def write_version(self, version): + """Writes a single version block's data to the output files.""" + if symbolfile.should_omit_version(version, self.arch, self.api, + self.llndk, self.apex): + return + + section_versioned = symbolfile.symbol_versioned_in_api( + version.tags, self.api) + version_empty = True + pruned_symbols = [] + for symbol in version.symbols: + if symbolfile.should_omit_symbol(symbol, self.arch, self.api, + self.llndk, self.apex): + continue + + if symbolfile.symbol_versioned_in_api(symbol.tags, self.api): + version_empty = False + pruned_symbols.append(symbol) + + if len(pruned_symbols) > 0: + if not version_empty and section_versioned: + self.version_script.write(version.name + ' {\n') + self.version_script.write(' global:\n') + for symbol in pruned_symbols: + emit_version = symbolfile.symbol_versioned_in_api( + symbol.tags, self.api) + if section_versioned and emit_version: + self.version_script.write(' ' + symbol.name + ';\n') + + weak = '' + if 'weak' in symbol.tags: + weak = '__attribute__((weak)) ' + + if 'var' in symbol.tags: + self.src_file.write('{}int {} = 0;\n'.format( + weak, symbol.name)) + else: + self.src_file.write('{}void {}() {{}}\n'.format( + weak, symbol.name)) + + if not version_empty and section_versioned: + base = '' if version.base is None else ' ' + version.base + self.version_script.write('}' + base + ';\n') + + +def parse_args(): + """Parses and returns command line arguments.""" + parser = argparse.ArgumentParser() + + parser.add_argument('-v', '--verbose', action='count', default=0) + + parser.add_argument( + '--api', required=True, help='API level being targeted.') + parser.add_argument( + '--arch', choices=symbolfile.ALL_ARCHITECTURES, required=True, + help='Architecture being targeted.') + parser.add_argument( + '--llndk', action='store_true', help='Use the LLNDK variant.') + parser.add_argument( + '--apex', action='store_true', help='Use the APEX variant.') + + parser.add_argument( + '--api-map', type=os.path.realpath, required=True, + help='Path to the API level map JSON file.') + + parser.add_argument( + 'symbol_file', type=os.path.realpath, help='Path to symbol file.') + parser.add_argument( + 'stub_src', type=os.path.realpath, + help='Path to output stub source file.') + parser.add_argument( + 'version_script', type=os.path.realpath, + help='Path to output version script.') + + return parser.parse_args() + + +def main(): + """Program entry point.""" + args = parse_args() + + with open(args.api_map) as map_file: + api_map = json.load(map_file) + api = symbolfile.decode_api_level(args.api, api_map) + + verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) + verbosity = args.verbose + if verbosity > 2: + verbosity = 2 + logging.basicConfig(level=verbose_map[verbosity]) + + with open(args.symbol_file) as symbol_file: + try: + versions = symbolfile.SymbolFileParser(symbol_file, api_map, + args.arch, api, args.llndk, + args.apex).parse() + except symbolfile.MultiplyDefinedSymbolError as ex: + sys.exit('{}: error: {}'.format(args.symbol_file, ex)) + + with open(args.stub_src, 'w') as src_file: + with open(args.version_script, 'w') as version_file: + generator = Generator(src_file, version_file, args.arch, api, + args.llndk, args.apex) + generator.write(versions) + + +if __name__ == '__main__': + main() diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py new file mode 100755 index 000000000..70bcf781c --- /dev/null +++ b/cc/ndkstubgen/test_ndkstubgen.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for ndkstubgen.py.""" +import io +import textwrap +import unittest + +import ndkstubgen +import symbolfile + + +# pylint: disable=missing-docstring + + +class GeneratorTest(unittest.TestCase): + def test_omit_version(self): + # Thorough testing of the cases involved here is handled by + # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + + version = symbolfile.Version('VERSION_PRIVATE', None, [], [ + symbolfile.Symbol('foo', []), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION', None, ['x86'], [ + symbolfile.Symbol('foo', []), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION', None, ['introduced=14'], [ + symbolfile.Symbol('foo', []), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + def test_omit_symbol(self): + # Thorough testing of the cases involved here is handled by + # SymbolPresenceTest. + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['x86']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['introduced=14']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['llndk']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + version = symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', ['apex']), + ]) + generator.write_version(version) + self.assertEqual('', src_file.getvalue()) + self.assertEqual('', version_file.getvalue()) + + def test_write(self): + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + + versions = [ + symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', []), + symbolfile.Symbol('bar', ['var']), + symbolfile.Symbol('woodly', ['weak']), + symbolfile.Symbol('doodly', ['weak', 'var']), + ]), + symbolfile.Version('VERSION_2', 'VERSION_1', [], [ + symbolfile.Symbol('baz', []), + ]), + symbolfile.Version('VERSION_3', 'VERSION_1', [], [ + symbolfile.Symbol('qux', ['versioned=14']), + ]), + ] + + generator.write(versions) + expected_src = textwrap.dedent("""\ + void foo() {} + int bar = 0; + __attribute__((weak)) void woodly() {} + __attribute__((weak)) int doodly = 0; + void baz() {} + void qux() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + bar; + woodly; + doodly; + }; + VERSION_2 { + global: + baz; + } VERSION_1; + """) + self.assertEqual(expected_version, version_file.getvalue()) + + +class IntegrationTest(unittest.TestCase): + def test_integration(self): + api_map = { + 'O': 9000, + 'P': 9001, + } + + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; # var + bar; # x86 + fizz; # introduced=O + buzz; # introduced=P + local: + *; + }; + + VERSION_2 { # arm + baz; # introduced=9 + qux; # versioned=14 + } VERSION_1; + + VERSION_3 { # introduced=14 + woodly; + doodly; # var + } VERSION_2; + + VERSION_4 { # versioned=9 + wibble; + wizzes; # llndk + waggle; # apex + } VERSION_2; + + VERSION_5 { # versioned=14 + wobble; + } VERSION_4; + """)) + parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, + False, False) + versions = parser.parse() + + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, False) + generator.write(versions) + + expected_src = textwrap.dedent("""\ + int foo = 0; + void baz() {} + void qux() {} + void wibble() {} + void wobble() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + }; + VERSION_2 { + global: + baz; + } VERSION_1; + VERSION_4 { + global: + wibble; + } VERSION_2; + """) + self.assertEqual(expected_version, version_file.getvalue()) + + def test_integration_future_api(self): + api_map = { + 'O': 9000, + 'P': 9001, + 'Q': 9002, + } + + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; # introduced=O + bar; # introduced=P + baz; # introduced=Q + local: + *; + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9001, + False, False) + versions = parser.parse() + + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9001, + False, False) + generator.write(versions) + + expected_src = textwrap.dedent("""\ + void foo() {} + void bar() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + bar; + }; + """) + self.assertEqual(expected_version, version_file.getvalue()) + + def test_multiple_definition(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; + foo; + bar; + baz; + qux; # arm + local: + *; + }; + + VERSION_2 { + global: + bar; + qux; # arm64 + } VERSION_1; + + VERSION_PRIVATE { + global: + baz; + } VERSION_2; + + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, + False) + + with self.assertRaises( + symbolfile.MultiplyDefinedSymbolError) as ex_context: + parser.parse() + self.assertEqual(['bar', 'foo'], + ex_context.exception.multiply_defined_symbols) + + def test_integration_with_apex(self): + api_map = { + 'O': 9000, + 'P': 9001, + } + + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + global: + foo; # var + bar; # x86 + fizz; # introduced=O + buzz; # introduced=P + local: + *; + }; + + VERSION_2 { # arm + baz; # introduced=9 + qux; # versioned=14 + } VERSION_1; + + VERSION_3 { # introduced=14 + woodly; + doodly; # var + } VERSION_2; + + VERSION_4 { # versioned=9 + wibble; + wizzes; # llndk + waggle; # apex + bubble; # apex llndk + duddle; # llndk apex + } VERSION_2; + + VERSION_5 { # versioned=14 + wobble; + } VERSION_4; + """)) + parser = symbolfile.SymbolFileParser(input_file, api_map, 'arm', 9, + False, True) + versions = parser.parse() + + src_file = io.StringIO() + version_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, version_file, 'arm', 9, + False, True) + generator.write(versions) + + expected_src = textwrap.dedent("""\ + int foo = 0; + void baz() {} + void qux() {} + void wibble() {} + void waggle() {} + void bubble() {} + void duddle() {} + void wobble() {} + """) + self.assertEqual(expected_src, src_file.getvalue()) + + expected_version = textwrap.dedent("""\ + VERSION_1 { + global: + foo; + }; + VERSION_2 { + global: + baz; + } VERSION_1; + VERSION_4 { + global: + wibble; + waggle; + bubble; + duddle; + } VERSION_2; + """) + self.assertEqual(expected_version, version_file.getvalue()) + +def main(): + suite = unittest.TestLoader().loadTestsFromName(__name__) + unittest.TextTestRunner(verbosity=3).run(suite) + + +if __name__ == '__main__': + main() diff --git a/cc/pylintrc b/cc/pylintrc index ed49dd7ea..2032d4e14 100644 --- a/cc/pylintrc +++ b/cc/pylintrc @@ -1,280 +1,11 @@ -[MASTER] - -# Specify a configuration file. -#rcfile= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Profiled execution. -profile=no - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=CVS - -# Pickle collected data for later comparisons. -persistent=yes - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - - [MESSAGES CONTROL] - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time. See also the "--disable" option for examples. -#enable= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" disable=design,fixme - -[REPORTS] - -# Set the output format. Available formats are text, parseable, colorized, msvs -# (visual studio) and html. You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Put messages in a separate file for each module / package specified on the -# command line instead of printing them on stdout. Reports (if any) will be -# written in a file name "pylint_global.[txt|html]". -files-output=no - -# Tells whether to display a full report or only the messages -reports=yes - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Add a comment according to your evaluation note. This is used by the global -# evaluation report (RP0004). -comment=no - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - - [BASIC] - -# Required attributes for module, separated by a comma -required-attributes= - -# List of builtins function names that should not be used, separated by a comma -bad-functions=map,filter,apply,input - -# Regular expression which should only match correct module names -module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ - -# Regular expression which should only match correct module level names -const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ - -# Regular expression which should only match correct class names -class-rgx=[A-Z_][a-zA-Z0-9]+$ - -# Regular expression which should only match correct function names -function-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct method names -method-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct instance attribute names -attr-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct argument names -argument-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct variable names -variable-rgx=[a-z_][a-z0-9_]{2,30}$ - -# Regular expression which should only match correct attribute names in class -# bodies -class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ - -# Regular expression which should only match correct list comprehension / -# generator expression variable names -inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ - -# Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ -# Bad variable names which should always be refused, separated by a comma -bad-names=foo,bar,baz,toto,tutu,tata - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=__.*__ - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - - -[TYPECHECK] - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# List of classes names for which member attributes should not be checked -# (useful for classes with attributes dynamically set). -ignored-classes=SQLObject - -# When zope mode is activated, add a predefined set of Zope acquired attributes -# to generated-members. -zope=no - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E0201 when accessed. Python regular -# expressions are accepted. -generated-members=REQUEST,acl_users,aq_parent - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME,XXX,TODO - - [SIMILARITIES] - -# Minimum lines number of a similarity. -min-similarity-lines=4 - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - +ignore-imports=yes [VARIABLES] - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# A regular expression matching the beginning of the name of dummy variables -# (i.e. not used). dummy-variables-rgx=_|dummy - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - - -[FORMAT] - -# Maximum number of characters on a single line. -max-line-length=80 - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - -# List of optional constructs for which whitespace checking is disabled -no-space-check=trailing-comma,dict-separator - -# Maximum number of lines in a module -max-module-lines=1000 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - - -[IMPORTS] - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=regsub,TERMIOS,Bastion,rexec - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=5 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.* - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - - -[CLASSES] - -# List of interface methods to ignore, separated by a comma. This is used for -# instance to not check methods defines in Zope's Interface base class. -ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__,__new__,setUp - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/cc/scriptlib/__init__.py b/cc/scriptlib/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/cc/scriptlib/test_gen_stub_libs.py b/cc/scriptlib/test_gen_stub_libs.py deleted file mode 100755 index 0b45e7110..000000000 --- a/cc/scriptlib/test_gen_stub_libs.py +++ /dev/null @@ -1,807 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2016 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -"""Tests for gen_stub_libs.py.""" -import io -import textwrap -import unittest - -import gen_stub_libs as gsl - - -# pylint: disable=missing-docstring - - -class DecodeApiLevelTest(unittest.TestCase): - def test_decode_api_level(self): - self.assertEqual(9, gsl.decode_api_level('9', {})) - self.assertEqual(9000, gsl.decode_api_level('O', {'O': 9000})) - - with self.assertRaises(KeyError): - gsl.decode_api_level('O', {}) - - -class TagsTest(unittest.TestCase): - def test_get_tags_no_tags(self): - self.assertEqual([], gsl.get_tags('')) - self.assertEqual([], gsl.get_tags('foo bar baz')) - - def test_get_tags(self): - self.assertEqual(['foo', 'bar'], gsl.get_tags('# foo bar')) - self.assertEqual(['bar', 'baz'], gsl.get_tags('foo # bar baz')) - - def test_split_tag(self): - self.assertTupleEqual(('foo', 'bar'), gsl.split_tag('foo=bar')) - self.assertTupleEqual(('foo', 'bar=baz'), gsl.split_tag('foo=bar=baz')) - with self.assertRaises(ValueError): - gsl.split_tag('foo') - - def test_get_tag_value(self): - self.assertEqual('bar', gsl.get_tag_value('foo=bar')) - self.assertEqual('bar=baz', gsl.get_tag_value('foo=bar=baz')) - with self.assertRaises(ValueError): - gsl.get_tag_value('foo') - - def test_is_api_level_tag(self): - self.assertTrue(gsl.is_api_level_tag('introduced=24')) - self.assertTrue(gsl.is_api_level_tag('introduced-arm=24')) - self.assertTrue(gsl.is_api_level_tag('versioned=24')) - - # Shouldn't try to process things that aren't a key/value tag. - self.assertFalse(gsl.is_api_level_tag('arm')) - self.assertFalse(gsl.is_api_level_tag('introduced')) - self.assertFalse(gsl.is_api_level_tag('versioned')) - - # We don't support arch specific `versioned` tags. - self.assertFalse(gsl.is_api_level_tag('versioned-arm=24')) - - def test_decode_api_level_tags(self): - api_map = { - 'O': 9000, - 'P': 9001, - } - - tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=O', - 'introduced=P', - ] - expected_tags = [ - 'introduced=9', - 'introduced-arm=14', - 'versioned=16', - 'arm', - 'introduced=9000', - 'introduced=9001', - ] - self.assertListEqual( - expected_tags, gsl.decode_api_level_tags(tags, api_map)) - - with self.assertRaises(gsl.ParseError): - gsl.decode_api_level_tags(['introduced=O'], {}) - - -class PrivateVersionTest(unittest.TestCase): - def test_version_is_private(self): - self.assertFalse(gsl.version_is_private('foo')) - self.assertFalse(gsl.version_is_private('PRIVATE')) - self.assertFalse(gsl.version_is_private('PLATFORM')) - self.assertFalse(gsl.version_is_private('foo_private')) - self.assertFalse(gsl.version_is_private('foo_platform')) - self.assertFalse(gsl.version_is_private('foo_PRIVATE_')) - self.assertFalse(gsl.version_is_private('foo_PLATFORM_')) - - self.assertTrue(gsl.version_is_private('foo_PRIVATE')) - self.assertTrue(gsl.version_is_private('foo_PLATFORM')) - - -class SymbolPresenceTest(unittest.TestCase): - def test_symbol_in_arch(self): - self.assertTrue(gsl.symbol_in_arch([], 'arm')) - self.assertTrue(gsl.symbol_in_arch(['arm'], 'arm')) - - self.assertFalse(gsl.symbol_in_arch(['x86'], 'arm')) - - def test_symbol_in_api(self): - self.assertTrue(gsl.symbol_in_api([], 'arm', 9)) - self.assertTrue(gsl.symbol_in_api(['introduced=9'], 'arm', 9)) - self.assertTrue(gsl.symbol_in_api(['introduced=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api(['introduced-arm=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api(['introduced-arm=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api(['introduced-x86=14'], 'arm', 9)) - self.assertTrue(gsl.symbol_in_api( - ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api( - ['introduced=9', 'introduced-x86=21'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api( - ['introduced=21', 'introduced-arm=9'], 'arm', 14)) - self.assertTrue(gsl.symbol_in_api( - ['future'], 'arm', gsl.FUTURE_API_LEVEL)) - - self.assertFalse(gsl.symbol_in_api(['introduced=14'], 'arm', 9)) - self.assertFalse(gsl.symbol_in_api(['introduced-arm=14'], 'arm', 9)) - self.assertFalse(gsl.symbol_in_api(['future'], 'arm', 9)) - self.assertFalse(gsl.symbol_in_api( - ['introduced=9', 'future'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced-arm=9', 'future'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced=9', 'introduced-arm=21'], 'arm', 14)) - self.assertFalse(gsl.symbol_in_api( - ['introduced=21', 'introduced-x86=9'], 'arm', 14)) - - # Interesting edge case: this symbol should be omitted from the - # library, but this call should still return true because none of the - # tags indiciate that it's not present in this API level. - self.assertTrue(gsl.symbol_in_api(['x86'], 'arm', 9)) - - def test_verioned_in_api(self): - self.assertTrue(gsl.symbol_versioned_in_api([], 9)) - self.assertTrue(gsl.symbol_versioned_in_api(['versioned=9'], 9)) - self.assertTrue(gsl.symbol_versioned_in_api(['versioned=9'], 14)) - - self.assertFalse(gsl.symbol_versioned_in_api(['versioned=14'], 9)) - - -class OmitVersionTest(unittest.TestCase): - def test_omit_private(self): - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, [], []), 'arm', 9, False, False)) - - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo_PRIVATE', None, [], []), 'arm', 9, False, False)) - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo_PLATFORM', None, [], []), 'arm', 9, False, False)) - - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo', None, ['platform-only'], []), 'arm', 9, - False, False)) - - def test_omit_llndk(self): - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo', None, ['llndk'], []), 'arm', 9, False, False)) - - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, [], []), 'arm', 9, True, False)) - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, ['llndk'], []), 'arm', 9, True, False)) - - def test_omit_apex(self): - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo', None, ['apex'], []), 'arm', 9, False, False)) - - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, [], []), 'arm', 9, False, True)) - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, ['apex'], []), 'arm', 9, False, True)) - - def test_omit_arch(self): - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, [], []), 'arm', 9, False, False)) - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, ['arm'], []), 'arm', 9, False, False)) - - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo', None, ['x86'], []), 'arm', 9, False, False)) - - def test_omit_api(self): - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, [], []), 'arm', 9, False, False)) - self.assertFalse( - gsl.should_omit_version( - gsl.Version('foo', None, ['introduced=9'], []), 'arm', 9, - False, False)) - - self.assertTrue( - gsl.should_omit_version( - gsl.Version('foo', None, ['introduced=14'], []), 'arm', 9, - False, False)) - - -class OmitSymbolTest(unittest.TestCase): - def test_omit_llndk(self): - self.assertTrue( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['llndk']), 'arm', 9, False, False)) - - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, True, False)) - self.assertFalse( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['llndk']), 'arm', 9, True, False)) - - def test_omit_apex(self): - self.assertTrue( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['apex']), 'arm', 9, False, False)) - - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, True)) - self.assertFalse( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['apex']), 'arm', 9, False, True)) - - def test_omit_arch(self): - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, False)) - self.assertFalse( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['arm']), 'arm', 9, False, False)) - - self.assertTrue( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['x86']), 'arm', 9, False, False)) - - def test_omit_api(self): - self.assertFalse( - gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False, False)) - self.assertFalse( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['introduced=9']), 'arm', 9, False, False)) - - self.assertTrue( - gsl.should_omit_symbol( - gsl.Symbol('foo', ['introduced=14']), 'arm', 9, False, False)) - - -class SymbolFileParseTest(unittest.TestCase): - def test_next_line(self): - input_file = io.StringIO(textwrap.dedent("""\ - foo - - bar - # baz - qux - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - self.assertIsNone(parser.current_line) - - self.assertEqual('foo', parser.next_line().strip()) - self.assertEqual('foo', parser.current_line.strip()) - - self.assertEqual('bar', parser.next_line().strip()) - self.assertEqual('bar', parser.current_line.strip()) - - self.assertEqual('qux', parser.next_line().strip()) - self.assertEqual('qux', parser.current_line.strip()) - - self.assertEqual('', parser.next_line()) - self.assertEqual('', parser.current_line) - - def test_parse_version(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { # foo bar - baz; - qux; # woodly doodly - }; - - VERSION_2 { - } VERSION_1; # asdf - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - - parser.next_line() - version = parser.parse_version() - self.assertEqual('VERSION_1', version.name) - self.assertIsNone(version.base) - self.assertEqual(['foo', 'bar'], version.tags) - - expected_symbols = [ - gsl.Symbol('baz', []), - gsl.Symbol('qux', ['woodly', 'doodly']), - ] - self.assertEqual(expected_symbols, version.symbols) - - parser.next_line() - version = parser.parse_version() - self.assertEqual('VERSION_2', version.name) - self.assertEqual('VERSION_1', version.base) - self.assertEqual([], version.tags) - - def test_parse_version_eof(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_unknown_scope_label(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - foo: - } - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_parse_symbol(self): - input_file = io.StringIO(textwrap.dedent("""\ - foo; - bar; # baz qux - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - - parser.next_line() - symbol = parser.parse_symbol() - self.assertEqual('foo', symbol.name) - self.assertEqual([], symbol.tags) - - parser.next_line() - symbol = parser.parse_symbol() - self.assertEqual('bar', symbol.name) - self.assertEqual(['baz', 'qux'], symbol.tags) - - def test_wildcard_symbol_global(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - *; - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_wildcard_symbol_local(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - local: - *; - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - version = parser.parse_version() - self.assertEqual([], version.symbols) - - def test_missing_semicolon(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - foo - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.next_line() - with self.assertRaises(gsl.ParseError): - parser.parse_version() - - def test_parse_fails_invalid_input(self): - with self.assertRaises(gsl.ParseError): - input_file = io.StringIO('foo') - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - parser.parse() - - def test_parse(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - local: - hidden1; - global: - foo; - bar; # baz - }; - - VERSION_2 { # wasd - # Implicit global scope. - woodly; - doodly; # asdf - local: - qwerty; - } VERSION_1; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - versions = parser.parse() - - expected = [ - gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', []), - gsl.Symbol('bar', ['baz']), - ]), - gsl.Version('VERSION_2', 'VERSION_1', ['wasd'], [ - gsl.Symbol('woodly', []), - gsl.Symbol('doodly', ['asdf']), - ]), - ] - - self.assertEqual(expected, versions) - - def test_parse_llndk_apex_symbol(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - foo; - bar; # llndk - baz; # llndk apex - qux; # apex - }; - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, True) - - parser.next_line() - version = parser.parse_version() - self.assertEqual('VERSION_1', version.name) - self.assertIsNone(version.base) - - expected_symbols = [ - gsl.Symbol('foo', []), - gsl.Symbol('bar', ['llndk']), - gsl.Symbol('baz', ['llndk', 'apex']), - gsl.Symbol('qux', ['apex']), - ] - self.assertEqual(expected_symbols, version.symbols) - - -class GeneratorTest(unittest.TestCase): - def test_omit_version(self): - # Thorough testing of the cases involved here is handled by - # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False) - - version = gsl.Version('VERSION_PRIVATE', None, [], [ - gsl.Symbol('foo', []), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION', None, ['x86'], [ - gsl.Symbol('foo', []), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION', None, ['introduced=14'], [ - gsl.Symbol('foo', []), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - def test_omit_symbol(self): - # Thorough testing of the cases involved here is handled by - # SymbolPresenceTest. - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['x86']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['introduced=14']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['llndk']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - version = gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', ['apex']), - ]) - generator.write_version(version) - self.assertEqual('', src_file.getvalue()) - self.assertEqual('', version_file.getvalue()) - - def test_write(self): - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False) - - versions = [ - gsl.Version('VERSION_1', None, [], [ - gsl.Symbol('foo', []), - gsl.Symbol('bar', ['var']), - gsl.Symbol('woodly', ['weak']), - gsl.Symbol('doodly', ['weak', 'var']), - ]), - gsl.Version('VERSION_2', 'VERSION_1', [], [ - gsl.Symbol('baz', []), - ]), - gsl.Version('VERSION_3', 'VERSION_1', [], [ - gsl.Symbol('qux', ['versioned=14']), - ]), - ] - - generator.write(versions) - expected_src = textwrap.dedent("""\ - void foo() {} - int bar = 0; - __attribute__((weak)) void woodly() {} - __attribute__((weak)) int doodly = 0; - void baz() {} - void qux() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - bar; - woodly; - doodly; - }; - VERSION_2 { - global: - baz; - } VERSION_1; - """) - self.assertEqual(expected_version, version_file.getvalue()) - - -class IntegrationTest(unittest.TestCase): - def test_integration(self): - api_map = { - 'O': 9000, - 'P': 9001, - } - - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; # var - bar; # x86 - fizz; # introduced=O - buzz; # introduced=P - local: - *; - }; - - VERSION_2 { # arm - baz; # introduced=9 - qux; # versioned=14 - } VERSION_1; - - VERSION_3 { # introduced=14 - woodly; - doodly; # var - } VERSION_2; - - VERSION_4 { # versioned=9 - wibble; - wizzes; # llndk - waggle; # apex - } VERSION_2; - - VERSION_5 { # versioned=14 - wobble; - } VERSION_4; - """)) - parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False, False) - versions = parser.parse() - - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, False) - generator.write(versions) - - expected_src = textwrap.dedent("""\ - int foo = 0; - void baz() {} - void qux() {} - void wibble() {} - void wobble() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - }; - VERSION_2 { - global: - baz; - } VERSION_1; - VERSION_4 { - global: - wibble; - } VERSION_2; - """) - self.assertEqual(expected_version, version_file.getvalue()) - - def test_integration_future_api(self): - api_map = { - 'O': 9000, - 'P': 9001, - 'Q': 9002, - } - - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; # introduced=O - bar; # introduced=P - baz; # introduced=Q - local: - *; - }; - """)) - parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9001, False, False) - versions = parser.parse() - - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9001, False, False) - generator.write(versions) - - expected_src = textwrap.dedent("""\ - void foo() {} - void bar() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - bar; - }; - """) - self.assertEqual(expected_version, version_file.getvalue()) - - def test_multiple_definition(self): - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; - foo; - bar; - baz; - qux; # arm - local: - *; - }; - - VERSION_2 { - global: - bar; - qux; # arm64 - } VERSION_1; - - VERSION_PRIVATE { - global: - baz; - } VERSION_2; - - """)) - parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False, False) - - with self.assertRaises(gsl.MultiplyDefinedSymbolError) as cm: - parser.parse() - self.assertEquals(['bar', 'foo'], - cm.exception.multiply_defined_symbols) - - def test_integration_with_apex(self): - api_map = { - 'O': 9000, - 'P': 9001, - } - - input_file = io.StringIO(textwrap.dedent("""\ - VERSION_1 { - global: - foo; # var - bar; # x86 - fizz; # introduced=O - buzz; # introduced=P - local: - *; - }; - - VERSION_2 { # arm - baz; # introduced=9 - qux; # versioned=14 - } VERSION_1; - - VERSION_3 { # introduced=14 - woodly; - doodly; # var - } VERSION_2; - - VERSION_4 { # versioned=9 - wibble; - wizzes; # llndk - waggle; # apex - bubble; # apex llndk - duddle; # llndk apex - } VERSION_2; - - VERSION_5 { # versioned=14 - wobble; - } VERSION_4; - """)) - parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False, True) - versions = parser.parse() - - src_file = io.StringIO() - version_file = io.StringIO() - generator = gsl.Generator(src_file, version_file, 'arm', 9, False, True) - generator.write(versions) - - expected_src = textwrap.dedent("""\ - int foo = 0; - void baz() {} - void qux() {} - void wibble() {} - void waggle() {} - void bubble() {} - void duddle() {} - void wobble() {} - """) - self.assertEqual(expected_src, src_file.getvalue()) - - expected_version = textwrap.dedent("""\ - VERSION_1 { - global: - foo; - }; - VERSION_2 { - global: - baz; - } VERSION_1; - VERSION_4 { - global: - wibble; - waggle; - bubble; - duddle; - } VERSION_2; - """) - self.assertEqual(expected_version, version_file.getvalue()) - -def main(): - suite = unittest.TestLoader().loadTestsFromName(__name__) - unittest.TextTestRunner(verbosity=3).run(suite) - - -if __name__ == '__main__': - main() diff --git a/cc/symbolfile/.gitignore b/cc/symbolfile/.gitignore new file mode 100644 index 000000000..fd94eac3f --- /dev/null +++ b/cc/symbolfile/.gitignore @@ -0,0 +1,140 @@ +# From https://github.com/github/gitignore/blob/master/Python.gitignore + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ diff --git a/cc/symbolfile/Android.bp b/cc/symbolfile/Android.bp new file mode 100644 index 000000000..5b4330916 --- /dev/null +++ b/cc/symbolfile/Android.bp @@ -0,0 +1,33 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +python_library_host { + name: "symbolfile", + pkg_path: "symbolfile", + srcs: [ + "__init__.py", + ], +} + +python_test_host { + name: "test_symbolfile", + srcs: [ + "test_symbolfile.py", + ], + libs: [ + "symbolfile", + ], +} diff --git a/cc/symbolfile/OWNERS b/cc/symbolfile/OWNERS new file mode 100644 index 000000000..f0d873359 --- /dev/null +++ b/cc/symbolfile/OWNERS @@ -0,0 +1 @@ +danalbert@google.com diff --git a/cc/scriptlib/gen_stub_libs.py b/cc/symbolfile/__init__.py old mode 100755 new mode 100644 similarity index 72% rename from cc/scriptlib/gen_stub_libs.py rename to cc/symbolfile/__init__.py index d61dfbb07..faa3823f3 --- a/cc/scriptlib/gen_stub_libs.py +++ b/cc/symbolfile/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # # Copyright (C) 2016 The Android Open Source Project # @@ -14,13 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # -"""Generates source for stub shared libraries for the NDK.""" -import argparse -import json +"""Parser for Android's version script information.""" import logging -import os import re -import sys ALL_ARCHITECTURES = ( @@ -57,6 +52,24 @@ def is_api_level_tag(tag): return False +def decode_api_level(api, api_map): + """Decodes the API level argument into the API level number. + + For the average case, this just decodes the integer value from the string, + but for unreleased APIs we need to translate from the API codename (like + "O") to the future API level for that codename. + """ + try: + return int(api) + except ValueError: + pass + + if api == "current": + return FUTURE_API_LEVEL + + return api_map[api] + + def decode_api_level_tags(tags, api_map): """Decodes API level code names in a list of tags. @@ -118,7 +131,8 @@ def should_omit_version(version, arch, api, llndk, apex): if 'platform-only' in version.tags: return True - no_llndk_no_apex = 'llndk' not in version.tags and 'apex' not in version.tags + no_llndk_no_apex = ('llndk' not in version.tags + and 'apex' not in version.tags) keep = no_llndk_no_apex or \ ('llndk' in version.tags and llndk) or \ ('apex' in version.tags and apex) @@ -205,7 +219,6 @@ def symbol_versioned_in_api(tags, api): class ParseError(RuntimeError): """An error that occurred while parsing a symbol file.""" - pass class MultiplyDefinedSymbolError(RuntimeError): @@ -217,7 +230,7 @@ class MultiplyDefinedSymbolError(RuntimeError): self.multiply_defined_symbols = multiply_defined_symbols -class Version(object): +class Version: """A version block of a symbol file.""" def __init__(self, name, base, tags, symbols): self.name = name @@ -237,7 +250,7 @@ class Version(object): return True -class Symbol(object): +class Symbol: """A symbol definition from a symbol file.""" def __init__(self, name, tags): self.name = name @@ -247,7 +260,7 @@ class Symbol(object): return self.name == other.name and set(self.tags) == set(other.tags) -class SymbolFileParser(object): +class SymbolFileParser: """Parses NDK symbol files.""" def __init__(self, input_file, api_map, arch, api, llndk, apex): self.input_file = input_file @@ -283,11 +296,13 @@ class SymbolFileParser(object): symbol_names = set() multiply_defined_symbols = set() for version in versions: - if should_omit_version(version, self.arch, self.api, self.llndk, self.apex): + if should_omit_version(version, self.arch, self.api, self.llndk, + self.apex): continue for symbol in version.symbols: - if should_omit_symbol(symbol, self.arch, self.api, self.llndk, self.apex): + if should_omit_symbol(symbol, self.arch, self.api, self.llndk, + self.apex): continue if symbol.name in symbol_names: @@ -367,141 +382,3 @@ class SymbolFileParser(object): break self.current_line = line return self.current_line - - -class Generator(object): - """Output generator that writes stub source files and version scripts.""" - def __init__(self, src_file, version_script, arch, api, llndk, apex): - self.src_file = src_file - self.version_script = version_script - self.arch = arch - self.api = api - self.llndk = llndk - self.apex = apex - - def write(self, versions): - """Writes all symbol data to the output files.""" - for version in versions: - self.write_version(version) - - def write_version(self, version): - """Writes a single version block's data to the output files.""" - if should_omit_version(version, self.arch, self.api, self.llndk, self.apex): - return - - section_versioned = symbol_versioned_in_api(version.tags, self.api) - version_empty = True - pruned_symbols = [] - for symbol in version.symbols: - if should_omit_symbol(symbol, self.arch, self.api, self.llndk, self.apex): - continue - - if symbol_versioned_in_api(symbol.tags, self.api): - version_empty = False - pruned_symbols.append(symbol) - - if len(pruned_symbols) > 0: - if not version_empty and section_versioned: - self.version_script.write(version.name + ' {\n') - self.version_script.write(' global:\n') - for symbol in pruned_symbols: - emit_version = symbol_versioned_in_api(symbol.tags, self.api) - if section_versioned and emit_version: - self.version_script.write(' ' + symbol.name + ';\n') - - weak = '' - if 'weak' in symbol.tags: - weak = '__attribute__((weak)) ' - - if 'var' in symbol.tags: - self.src_file.write('{}int {} = 0;\n'.format( - weak, symbol.name)) - else: - self.src_file.write('{}void {}() {{}}\n'.format( - weak, symbol.name)) - - if not version_empty and section_versioned: - base = '' if version.base is None else ' ' + version.base - self.version_script.write('}' + base + ';\n') - - -def decode_api_level(api, api_map): - """Decodes the API level argument into the API level number. - - For the average case, this just decodes the integer value from the string, - but for unreleased APIs we need to translate from the API codename (like - "O") to the future API level for that codename. - """ - try: - return int(api) - except ValueError: - pass - - if api == "current": - return FUTURE_API_LEVEL - - return api_map[api] - - -def parse_args(): - """Parses and returns command line arguments.""" - parser = argparse.ArgumentParser() - - parser.add_argument('-v', '--verbose', action='count', default=0) - - parser.add_argument( - '--api', required=True, help='API level being targeted.') - parser.add_argument( - '--arch', choices=ALL_ARCHITECTURES, required=True, - help='Architecture being targeted.') - parser.add_argument( - '--llndk', action='store_true', help='Use the LLNDK variant.') - parser.add_argument( - '--apex', action='store_true', help='Use the APEX variant.') - - parser.add_argument( - '--api-map', type=os.path.realpath, required=True, - help='Path to the API level map JSON file.') - - parser.add_argument( - 'symbol_file', type=os.path.realpath, help='Path to symbol file.') - parser.add_argument( - 'stub_src', type=os.path.realpath, - help='Path to output stub source file.') - parser.add_argument( - 'version_script', type=os.path.realpath, - help='Path to output version script.') - - return parser.parse_args() - - -def main(): - """Program entry point.""" - args = parse_args() - - with open(args.api_map) as map_file: - api_map = json.load(map_file) - api = decode_api_level(args.api, api_map) - - verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG) - verbosity = args.verbose - if verbosity > 2: - verbosity = 2 - logging.basicConfig(level=verbose_map[verbosity]) - - with open(args.symbol_file) as symbol_file: - try: - versions = SymbolFileParser(symbol_file, api_map, args.arch, api, - args.llndk, args.apex).parse() - except MultiplyDefinedSymbolError as ex: - sys.exit('{}: error: {}'.format(args.symbol_file, ex)) - - with open(args.stub_src, 'w') as src_file: - with open(args.version_script, 'w') as version_file: - generator = Generator(src_file, version_file, args.arch, api, - args.llndk, args.apex) - generator.write(versions) - - -if __name__ == '__main__': - main() diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py new file mode 100644 index 000000000..c91131fee --- /dev/null +++ b/cc/symbolfile/test_symbolfile.py @@ -0,0 +1,493 @@ +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Tests for symbolfile.""" +import io +import textwrap +import unittest + +import symbolfile + +# pylint: disable=missing-docstring + + +class DecodeApiLevelTest(unittest.TestCase): + def test_decode_api_level(self): + self.assertEqual(9, symbolfile.decode_api_level('9', {})) + self.assertEqual(9000, symbolfile.decode_api_level('O', {'O': 9000})) + + with self.assertRaises(KeyError): + symbolfile.decode_api_level('O', {}) + + +class TagsTest(unittest.TestCase): + def test_get_tags_no_tags(self): + self.assertEqual([], symbolfile.get_tags('')) + self.assertEqual([], symbolfile.get_tags('foo bar baz')) + + def test_get_tags(self): + self.assertEqual(['foo', 'bar'], symbolfile.get_tags('# foo bar')) + self.assertEqual(['bar', 'baz'], symbolfile.get_tags('foo # bar baz')) + + def test_split_tag(self): + self.assertTupleEqual(('foo', 'bar'), symbolfile.split_tag('foo=bar')) + self.assertTupleEqual(('foo', 'bar=baz'), symbolfile.split_tag('foo=bar=baz')) + with self.assertRaises(ValueError): + symbolfile.split_tag('foo') + + def test_get_tag_value(self): + self.assertEqual('bar', symbolfile.get_tag_value('foo=bar')) + self.assertEqual('bar=baz', symbolfile.get_tag_value('foo=bar=baz')) + with self.assertRaises(ValueError): + symbolfile.get_tag_value('foo') + + def test_is_api_level_tag(self): + self.assertTrue(symbolfile.is_api_level_tag('introduced=24')) + self.assertTrue(symbolfile.is_api_level_tag('introduced-arm=24')) + self.assertTrue(symbolfile.is_api_level_tag('versioned=24')) + + # Shouldn't try to process things that aren't a key/value tag. + self.assertFalse(symbolfile.is_api_level_tag('arm')) + self.assertFalse(symbolfile.is_api_level_tag('introduced')) + self.assertFalse(symbolfile.is_api_level_tag('versioned')) + + # We don't support arch specific `versioned` tags. + self.assertFalse(symbolfile.is_api_level_tag('versioned-arm=24')) + + def test_decode_api_level_tags(self): + api_map = { + 'O': 9000, + 'P': 9001, + } + + tags = [ + 'introduced=9', + 'introduced-arm=14', + 'versioned=16', + 'arm', + 'introduced=O', + 'introduced=P', + ] + expected_tags = [ + 'introduced=9', + 'introduced-arm=14', + 'versioned=16', + 'arm', + 'introduced=9000', + 'introduced=9001', + ] + self.assertListEqual( + expected_tags, symbolfile.decode_api_level_tags(tags, api_map)) + + with self.assertRaises(symbolfile.ParseError): + symbolfile.decode_api_level_tags(['introduced=O'], {}) + + +class PrivateVersionTest(unittest.TestCase): + def test_version_is_private(self): + self.assertFalse(symbolfile.version_is_private('foo')) + self.assertFalse(symbolfile.version_is_private('PRIVATE')) + self.assertFalse(symbolfile.version_is_private('PLATFORM')) + self.assertFalse(symbolfile.version_is_private('foo_private')) + self.assertFalse(symbolfile.version_is_private('foo_platform')) + self.assertFalse(symbolfile.version_is_private('foo_PRIVATE_')) + self.assertFalse(symbolfile.version_is_private('foo_PLATFORM_')) + + self.assertTrue(symbolfile.version_is_private('foo_PRIVATE')) + self.assertTrue(symbolfile.version_is_private('foo_PLATFORM')) + + +class SymbolPresenceTest(unittest.TestCase): + def test_symbol_in_arch(self): + self.assertTrue(symbolfile.symbol_in_arch([], 'arm')) + self.assertTrue(symbolfile.symbol_in_arch(['arm'], 'arm')) + + self.assertFalse(symbolfile.symbol_in_arch(['x86'], 'arm')) + + def test_symbol_in_api(self): + self.assertTrue(symbolfile.symbol_in_api([], 'arm', 9)) + self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 9)) + self.assertTrue(symbolfile.symbol_in_api(['introduced=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api(['introduced-arm=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api(['introduced-x86=14'], 'arm', 9)) + self.assertTrue(symbolfile.symbol_in_api( + ['introduced-arm=9', 'introduced-x86=21'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api( + ['introduced=9', 'introduced-x86=21'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api( + ['introduced=21', 'introduced-arm=9'], 'arm', 14)) + self.assertTrue(symbolfile.symbol_in_api( + ['future'], 'arm', symbolfile.FUTURE_API_LEVEL)) + + self.assertFalse(symbolfile.symbol_in_api(['introduced=14'], 'arm', 9)) + self.assertFalse(symbolfile.symbol_in_api(['introduced-arm=14'], 'arm', 9)) + self.assertFalse(symbolfile.symbol_in_api(['future'], 'arm', 9)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced=9', 'future'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced-arm=9', 'future'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced-arm=21', 'introduced-x86=9'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced=9', 'introduced-arm=21'], 'arm', 14)) + self.assertFalse(symbolfile.symbol_in_api( + ['introduced=21', 'introduced-x86=9'], 'arm', 14)) + + # Interesting edge case: this symbol should be omitted from the + # library, but this call should still return true because none of the + # tags indiciate that it's not present in this API level. + self.assertTrue(symbolfile.symbol_in_api(['x86'], 'arm', 9)) + + def test_verioned_in_api(self): + self.assertTrue(symbolfile.symbol_versioned_in_api([], 9)) + self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 9)) + self.assertTrue(symbolfile.symbol_versioned_in_api(['versioned=9'], 14)) + + self.assertFalse(symbolfile.symbol_versioned_in_api(['versioned=14'], 9)) + + +class OmitVersionTest(unittest.TestCase): + def test_omit_private(self): + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo_PRIVATE', None, [], []), 'arm', 9, + False, False)) + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo_PLATFORM', None, [], []), 'arm', 9, + False, False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['platform-only'], []), 'arm', + 9, False, False)) + + def test_omit_llndk(self): + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, + False, False)) + + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, True, + False)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['llndk'], []), 'arm', 9, True, + False)) + + def test_omit_apex(self): + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, + False)) + + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + True)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['apex'], []), 'arm', 9, False, + True)) + + def test_omit_arch(self): + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + False)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['arm'], []), 'arm', 9, False, + False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['x86'], []), 'arm', 9, False, + False)) + + def test_omit_api(self): + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, [], []), 'arm', 9, False, + False)) + self.assertFalse( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['introduced=9'], []), 'arm', + 9, False, False)) + + self.assertTrue( + symbolfile.should_omit_version( + symbolfile.Version('foo', None, ['introduced=14'], []), 'arm', + 9, False, False)) + + +class OmitSymbolTest(unittest.TestCase): + def test_omit_llndk(self): + self.assertTrue( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), + 'arm', 9, False, False)) + + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, True, False)) + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['llndk']), + 'arm', 9, True, False)) + + def test_omit_apex(self): + self.assertTrue( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']), + 'arm', 9, False, False)) + + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, False, True)) + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['apex']), + 'arm', 9, False, True)) + + def test_omit_arch(self): + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, False, False)) + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['arm']), + 'arm', 9, False, False)) + + self.assertTrue( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', ['x86']), + 'arm', 9, False, False)) + + def test_omit_api(self): + self.assertFalse( + symbolfile.should_omit_symbol(symbolfile.Symbol('foo', []), 'arm', + 9, False, False)) + self.assertFalse( + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', ['introduced=9']), 'arm', 9, False, + False)) + + self.assertTrue( + symbolfile.should_omit_symbol( + symbolfile.Symbol('foo', ['introduced=14']), 'arm', 9, False, + False)) + + +class SymbolFileParseTest(unittest.TestCase): + def test_next_line(self): + input_file = io.StringIO(textwrap.dedent("""\ + foo + + bar + # baz + qux + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + self.assertIsNone(parser.current_line) + + self.assertEqual('foo', parser.next_line().strip()) + self.assertEqual('foo', parser.current_line.strip()) + + self.assertEqual('bar', parser.next_line().strip()) + self.assertEqual('bar', parser.current_line.strip()) + + self.assertEqual('qux', parser.next_line().strip()) + self.assertEqual('qux', parser.current_line.strip()) + + self.assertEqual('', parser.next_line()) + self.assertEqual('', parser.current_line) + + def test_parse_version(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { # foo bar + baz; + qux; # woodly doodly + }; + + VERSION_2 { + } VERSION_1; # asdf + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + + parser.next_line() + version = parser.parse_version() + self.assertEqual('VERSION_1', version.name) + self.assertIsNone(version.base) + self.assertEqual(['foo', 'bar'], version.tags) + + expected_symbols = [ + symbolfile.Symbol('baz', []), + symbolfile.Symbol('qux', ['woodly', 'doodly']), + ] + self.assertEqual(expected_symbols, version.symbols) + + parser.next_line() + version = parser.parse_version() + self.assertEqual('VERSION_2', version.name) + self.assertEqual('VERSION_1', version.base) + self.assertEqual([], version.tags) + + def test_parse_version_eof(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_unknown_scope_label(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + foo: + } + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_parse_symbol(self): + input_file = io.StringIO(textwrap.dedent("""\ + foo; + bar; # baz qux + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + + parser.next_line() + symbol = parser.parse_symbol() + self.assertEqual('foo', symbol.name) + self.assertEqual([], symbol.tags) + + parser.next_line() + symbol = parser.parse_symbol() + self.assertEqual('bar', symbol.name) + self.assertEqual(['baz', 'qux'], symbol.tags) + + def test_wildcard_symbol_global(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + *; + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_wildcard_symbol_local(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + local: + *; + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + version = parser.parse_version() + self.assertEqual([], version.symbols) + + def test_missing_semicolon(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + foo + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + parser.next_line() + with self.assertRaises(symbolfile.ParseError): + parser.parse_version() + + def test_parse_fails_invalid_input(self): + with self.assertRaises(symbolfile.ParseError): + input_file = io.StringIO('foo') + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, + False, False) + parser.parse() + + def test_parse(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + local: + hidden1; + global: + foo; + bar; # baz + }; + + VERSION_2 { # wasd + # Implicit global scope. + woodly; + doodly; # asdf + local: + qwerty; + } VERSION_1; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, False) + versions = parser.parse() + + expected = [ + symbolfile.Version('VERSION_1', None, [], [ + symbolfile.Symbol('foo', []), + symbolfile.Symbol('bar', ['baz']), + ]), + symbolfile.Version('VERSION_2', 'VERSION_1', ['wasd'], [ + symbolfile.Symbol('woodly', []), + symbolfile.Symbol('doodly', ['asdf']), + ]), + ] + + self.assertEqual(expected, versions) + + def test_parse_llndk_apex_symbol(self): + input_file = io.StringIO(textwrap.dedent("""\ + VERSION_1 { + foo; + bar; # llndk + baz; # llndk apex + qux; # apex + }; + """)) + parser = symbolfile.SymbolFileParser(input_file, {}, 'arm', 16, False, True) + + parser.next_line() + version = parser.parse_version() + self.assertEqual('VERSION_1', version.name) + self.assertIsNone(version.base) + + expected_symbols = [ + symbolfile.Symbol('foo', []), + symbolfile.Symbol('bar', ['llndk']), + symbolfile.Symbol('baz', ['llndk', 'apex']), + symbolfile.Symbol('qux', ['apex']), + ] + self.assertEqual(expected_symbols, version.symbols) + + +def main(): + suite = unittest.TestLoader().loadTestsFromName(__name__) + unittest.TextTestRunner(verbosity=3).run(suite) + + +if __name__ == '__main__': + main()