Merge changes I30719eaf,I84812c55,Ifa397b2b

* changes:
  Better error message for multiple defined symbols.
  Python 3 fix.
  Pylint fix.
This commit is contained in:
Treehugger Robot
2018-10-12 03:16:53 +00:00
committed by Gerrit Code Review
2 changed files with 221 additions and 83 deletions

View File

@@ -20,6 +20,7 @@ import json
import logging import logging
import os import os
import re import re
import sys
ALL_ARCHITECTURES = ( ALL_ARCHITECTURES = (
@@ -107,22 +108,33 @@ def version_is_private(version):
return version.endswith('_PRIVATE') or version.endswith('_PLATFORM') return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
def should_omit_version(name, tags, arch, api, vndk): def should_omit_version(version, arch, api, vndk):
"""Returns True if the version section should be ommitted. """Returns True if the version section should be ommitted.
We want to omit any sections that do not have any symbols we'll have in the We want to omit any sections that do not have any symbols we'll have in the
stub library. Sections that contain entirely future symbols or only symbols stub library. Sections that contain entirely future symbols or only symbols
for certain architectures. for certain architectures.
""" """
if version_is_private(name): if version_is_private(version.name):
return True return True
if 'platform-only' in tags: if 'platform-only' in version.tags:
return True return True
if 'vndk' in tags and not vndk: if 'vndk' in version.tags and not vndk:
return True return True
if not symbol_in_arch(tags, arch): if not symbol_in_arch(version.tags, arch):
return True return True
if not symbol_in_api(tags, arch, api): if not symbol_in_api(version.tags, arch, api):
return True
return False
def should_omit_symbol(symbol, arch, api, vndk):
"""Returns True if the symbol should be omitted."""
if not vndk and 'vndk' in symbol.tags:
return True
if not symbol_in_arch(symbol.tags, arch):
return True
if not symbol_in_api(symbol.tags, arch, api):
return True return True
return False return False
@@ -189,6 +201,15 @@ class ParseError(RuntimeError):
pass pass
class MultiplyDefinedSymbolError(RuntimeError):
"""A symbol name was multiply defined."""
def __init__(self, multiply_defined_symbols):
super(MultiplyDefinedSymbolError, self).__init__(
'Version script contains multiple definitions for: {}'.format(
', '.join(multiply_defined_symbols)))
self.multiply_defined_symbols = multiply_defined_symbols
class Version(object): class Version(object):
"""A version block of a symbol file.""" """A version block of a symbol file."""
def __init__(self, name, base, tags, symbols): def __init__(self, name, base, tags, symbols):
@@ -221,9 +242,12 @@ class Symbol(object):
class SymbolFileParser(object): class SymbolFileParser(object):
"""Parses NDK symbol files.""" """Parses NDK symbol files."""
def __init__(self, input_file, api_map): def __init__(self, input_file, api_map, arch, api, vndk):
self.input_file = input_file self.input_file = input_file
self.api_map = api_map self.api_map = api_map
self.arch = arch
self.api = api
self.vndk = vndk
self.current_line = None self.current_line = None
def parse(self): def parse(self):
@@ -235,8 +259,36 @@ class SymbolFileParser(object):
else: else:
raise ParseError( raise ParseError(
'Unexpected contents at top level: ' + self.current_line) 'Unexpected contents at top level: ' + self.current_line)
self.check_no_duplicate_symbols(versions)
return versions return versions
def check_no_duplicate_symbols(self, versions):
"""Raises errors for multiply defined symbols.
This situation is the normal case when symbol versioning is actually
used, but this script doesn't currently handle that. The error message
will be a not necessarily obvious "error: redefition of 'foo'" from
stub.c, so it's better for us to catch this situation and raise a
better error.
"""
symbol_names = set()
multiply_defined_symbols = set()
for version in versions:
if should_omit_version(version, self.arch, self.api, self.vndk):
continue
for symbol in version.symbols:
if should_omit_symbol(symbol, self.arch, self.api, self.vndk):
continue
if symbol.name in symbol_names:
multiply_defined_symbols.add(symbol.name)
symbol_names.add(symbol.name)
if multiply_defined_symbols:
raise MultiplyDefinedSymbolError(
sorted(list(multiply_defined_symbols)))
def parse_version(self): def parse_version(self):
"""Parses a single version section and returns a Version object.""" """Parses a single version section and returns a Version object."""
name = self.current_line.split('{')[0].strip() name = self.current_line.split('{')[0].strip()
@@ -274,7 +326,8 @@ class SymbolFileParser(object):
elif global_scope and not cpp_symbols: elif global_scope and not cpp_symbols:
symbols.append(self.parse_symbol()) symbols.append(self.parse_symbol())
else: else:
# We're in a hidden scope or in 'extern "C++"' block. Ignore everything. # We're in a hidden scope or in 'extern "C++"' block. Ignore
# everything.
pass pass
raise ParseError('Unexpected EOF in version block.') raise ParseError('Unexpected EOF in version block.')
@@ -324,20 +377,14 @@ class Generator(object):
def write_version(self, version): def write_version(self, version):
"""Writes a single version block's data to the output files.""" """Writes a single version block's data to the output files."""
name = version.name if should_omit_version(version, self.arch, self.api, self.vndk):
tags = version.tags
if should_omit_version(name, tags, self.arch, self.api, self.vndk):
return return
section_versioned = symbol_versioned_in_api(tags, self.api) section_versioned = symbol_versioned_in_api(version.tags, self.api)
version_empty = True version_empty = True
pruned_symbols = [] pruned_symbols = []
for symbol in version.symbols: for symbol in version.symbols:
if not self.vndk and 'vndk' in symbol.tags: if should_omit_symbol(symbol, self.arch, self.api, self.vndk):
continue
if not symbol_in_arch(symbol.tags, self.arch):
continue
if not symbol_in_api(symbol.tags, self.arch, self.api):
continue continue
if symbol_versioned_in_api(symbol.tags, self.api): if symbol_versioned_in_api(symbol.tags, self.api):
@@ -432,7 +479,11 @@ def main():
logging.basicConfig(level=verbose_map[verbosity]) logging.basicConfig(level=verbose_map[verbosity])
with open(args.symbol_file) as symbol_file: with open(args.symbol_file) as symbol_file:
versions = SymbolFileParser(symbol_file, api_map).parse() try:
versions = SymbolFileParser(symbol_file, api_map, args.arch, api,
args.vndk).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.stub_src, 'w') as src_file:
with open(args.version_script, 'w') as version_file: with open(args.version_script, 'w') as version_file:

View File

@@ -15,7 +15,7 @@
# limitations under the License. # limitations under the License.
# #
"""Tests for gen_stub_libs.py.""" """Tests for gen_stub_libs.py."""
import cStringIO import io
import textwrap import textwrap
import unittest import unittest
@@ -163,51 +163,106 @@ class SymbolPresenceTest(unittest.TestCase):
class OmitVersionTest(unittest.TestCase): class OmitVersionTest(unittest.TestCase):
def test_omit_private(self): def test_omit_private(self):
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo_PRIVATE', [], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo_PLATFORM', [], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo', ['platform-only'], 'arm', 9, False))
def test_omit_vndk(self):
self.assertTrue(gsl.should_omit_version(
'foo', ['vndk'], 'arm', 9, False))
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, True))
self.assertFalse(gsl.should_omit_version(
'foo', ['vndk'], 'arm', 9, True))
def test_omit_arch(self):
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertFalse(gsl.should_omit_version(
'foo', ['arm'], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo', ['x86'], 'arm', 9, False))
def test_omit_api(self):
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertFalse( self.assertFalse(
gsl.should_omit_version('foo', ['introduced=9'], 'arm', 9, False)) gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, False))
self.assertTrue( self.assertTrue(
gsl.should_omit_version('foo', ['introduced=14'], 'arm', 9, False)) gsl.should_omit_version(
gsl.Version('foo_PRIVATE', None, [], []), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo_PLATFORM', None, [], []), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['platform-only'], []), 'arm', 9,
False))
def test_omit_vndk(self):
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['vndk'], []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, True))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, ['vndk'], []), 'arm', 9, True))
def test_omit_arch(self):
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, ['arm'], []), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['x86'], []), 'arm', 9, False))
def test_omit_api(self):
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, ['introduced=9'], []), 'arm', 9,
False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['introduced=14'], []), 'arm', 9,
False))
class OmitSymbolTest(unittest.TestCase):
def test_omit_vndk(self):
self.assertTrue(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['vndk']), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, True))
self.assertFalse(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['vndk']), 'arm', 9, True))
def test_omit_arch(self):
self.assertFalse(
gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['arm']), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['x86']), 'arm', 9, False))
def test_omit_api(self):
self.assertFalse(
gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['introduced=9']), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['introduced=14']), 'arm', 9, False))
class SymbolFileParseTest(unittest.TestCase): class SymbolFileParseTest(unittest.TestCase):
def test_next_line(self): def test_next_line(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
foo foo
bar bar
# baz # baz
qux qux
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
self.assertIsNone(parser.current_line) self.assertIsNone(parser.current_line)
self.assertEqual('foo', parser.next_line().strip()) self.assertEqual('foo', parser.next_line().strip())
@@ -223,7 +278,7 @@ class SymbolFileParseTest(unittest.TestCase):
self.assertEqual('', parser.current_line) self.assertEqual('', parser.current_line)
def test_parse_version(self): def test_parse_version(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { # foo bar VERSION_1 { # foo bar
baz; baz;
qux; # woodly doodly qux; # woodly doodly
@@ -232,7 +287,7 @@ class SymbolFileParseTest(unittest.TestCase):
VERSION_2 { VERSION_2 {
} VERSION_1; # asdf } VERSION_1; # asdf
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line() parser.next_line()
version = parser.parse_version() version = parser.parse_version()
@@ -253,31 +308,31 @@ class SymbolFileParseTest(unittest.TestCase):
self.assertEqual([], version.tags) self.assertEqual([], version.tags)
def test_parse_version_eof(self): def test_parse_version_eof(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line() parser.next_line()
with self.assertRaises(gsl.ParseError): with self.assertRaises(gsl.ParseError):
parser.parse_version() parser.parse_version()
def test_unknown_scope_label(self): def test_unknown_scope_label(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
foo: foo:
} }
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line() parser.next_line()
with self.assertRaises(gsl.ParseError): with self.assertRaises(gsl.ParseError):
parser.parse_version() parser.parse_version()
def test_parse_symbol(self): def test_parse_symbol(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
foo; foo;
bar; # baz qux bar; # baz qux
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line() parser.next_line()
symbol = parser.parse_symbol() symbol = parser.parse_symbol()
@@ -290,47 +345,47 @@ class SymbolFileParseTest(unittest.TestCase):
self.assertEqual(['baz', 'qux'], symbol.tags) self.assertEqual(['baz', 'qux'], symbol.tags)
def test_wildcard_symbol_global(self): def test_wildcard_symbol_global(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
*; *;
}; };
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line() parser.next_line()
with self.assertRaises(gsl.ParseError): with self.assertRaises(gsl.ParseError):
parser.parse_version() parser.parse_version()
def test_wildcard_symbol_local(self): def test_wildcard_symbol_local(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
local: local:
*; *;
}; };
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line() parser.next_line()
version = parser.parse_version() version = parser.parse_version()
self.assertEqual([], version.symbols) self.assertEqual([], version.symbols)
def test_missing_semicolon(self): def test_missing_semicolon(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
foo foo
}; };
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line() parser.next_line()
with self.assertRaises(gsl.ParseError): with self.assertRaises(gsl.ParseError):
parser.parse_version() parser.parse_version()
def test_parse_fails_invalid_input(self): def test_parse_fails_invalid_input(self):
with self.assertRaises(gsl.ParseError): with self.assertRaises(gsl.ParseError):
input_file = cStringIO.StringIO('foo') input_file = io.StringIO('foo')
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.parse() parser.parse()
def test_parse(self): def test_parse(self):
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
local: local:
hidden1; hidden1;
@@ -347,7 +402,7 @@ class SymbolFileParseTest(unittest.TestCase):
qwerty; qwerty;
} VERSION_1; } VERSION_1;
""")) """))
parser = gsl.SymbolFileParser(input_file, {}) parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
versions = parser.parse() versions = parser.parse()
expected = [ expected = [
@@ -368,8 +423,8 @@ class GeneratorTest(unittest.TestCase):
def test_omit_version(self): def test_omit_version(self):
# Thorough testing of the cases involved here is handled by # Thorough testing of the cases involved here is handled by
# OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
src_file = cStringIO.StringIO() src_file = io.StringIO()
version_file = cStringIO.StringIO() version_file = io.StringIO()
generator = gsl.Generator(src_file, version_file, 'arm', 9, False) generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
version = gsl.Version('VERSION_PRIVATE', None, [], [ version = gsl.Version('VERSION_PRIVATE', None, [], [
@@ -396,8 +451,8 @@ class GeneratorTest(unittest.TestCase):
def test_omit_symbol(self): def test_omit_symbol(self):
# Thorough testing of the cases involved here is handled by # Thorough testing of the cases involved here is handled by
# SymbolPresenceTest. # SymbolPresenceTest.
src_file = cStringIO.StringIO() src_file = io.StringIO()
version_file = cStringIO.StringIO() version_file = io.StringIO()
generator = gsl.Generator(src_file, version_file, 'arm', 9, False) generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
version = gsl.Version('VERSION_1', None, [], [ version = gsl.Version('VERSION_1', None, [], [
@@ -422,8 +477,8 @@ class GeneratorTest(unittest.TestCase):
self.assertEqual('', version_file.getvalue()) self.assertEqual('', version_file.getvalue())
def test_write(self): def test_write(self):
src_file = cStringIO.StringIO() src_file = io.StringIO()
version_file = cStringIO.StringIO() version_file = io.StringIO()
generator = gsl.Generator(src_file, version_file, 'arm', 9, False) generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
versions = [ versions = [
@@ -475,7 +530,7 @@ class IntegrationTest(unittest.TestCase):
'P': 9001, 'P': 9001,
} }
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
global: global:
foo; # var foo; # var
@@ -505,11 +560,11 @@ class IntegrationTest(unittest.TestCase):
wobble; wobble;
} VERSION_4; } VERSION_4;
""")) """))
parser = gsl.SymbolFileParser(input_file, api_map) parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False)
versions = parser.parse() versions = parser.parse()
src_file = cStringIO.StringIO() src_file = io.StringIO()
version_file = cStringIO.StringIO() version_file = io.StringIO()
generator = gsl.Generator(src_file, version_file, 'arm', 9, False) generator = gsl.Generator(src_file, version_file, 'arm', 9, False)
generator.write(versions) generator.write(versions)
@@ -545,7 +600,7 @@ class IntegrationTest(unittest.TestCase):
'Q': 9002, 'Q': 9002,
} }
input_file = cStringIO.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
global: global:
foo; # introduced=O foo; # introduced=O
@@ -555,11 +610,11 @@ class IntegrationTest(unittest.TestCase):
*; *;
}; };
""")) """))
parser = gsl.SymbolFileParser(input_file, api_map) parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9001, False)
versions = parser.parse() versions = parser.parse()
src_file = cStringIO.StringIO() src_file = io.StringIO()
version_file = cStringIO.StringIO() version_file = io.StringIO()
generator = gsl.Generator(src_file, version_file, 'arm', 9001, False) generator = gsl.Generator(src_file, version_file, 'arm', 9001, False)
generator.write(versions) generator.write(versions)
@@ -578,6 +633,38 @@ class IntegrationTest(unittest.TestCase):
""") """)
self.assertEqual(expected_version, version_file.getvalue()) 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)
with self.assertRaises(gsl.MultiplyDefinedSymbolError) as cm:
parser.parse()
self.assertEquals(['bar', 'foo'],
cm.exception.multiply_defined_symbols)
def main(): def main():
suite = unittest.TestLoader().loadTestsFromName(__name__) suite = unittest.TestLoader().loadTestsFromName(__name__)