Add support for named versions in NDK map files.

Test: nose2
Test: make checkbuild
Bug: None
Change-Id: Ic32cfb3e0db767f695b617c787733a6ef75030aa
This commit is contained in:
Dan Albert 2017-03-28 16:04:25 -07:00
parent 49927d29d5
commit 3f6fb2db5f
2 changed files with 168 additions and 36 deletions

View File

@ -41,27 +41,56 @@ def logger():
return logging.getLogger(__name__)
def api_level_arg(api_str):
"""Parses an API level, handling the "current" special case.
Args:
api_str: (string) Either a numeric API level or "current".
Returns:
(int) FUTURE_API_LEVEL if `api_str` is "current", else `api_str` parsed
as an integer.
"""
if api_str == "current":
return FUTURE_API_LEVEL
return int(api_str)
def get_tags(line):
"""Returns a list of all tags on this line."""
_, _, all_tags = line.strip().partition('#')
return [e for e in re.split(r'\s+', all_tags) if e.strip()]
def is_api_level_tag(tag):
"""Returns true if this tag has an API level that may need decoding."""
if tag.startswith('introduced='):
return True
if tag.startswith('introduced-'):
return True
if tag.startswith('versioned='):
return True
return False
def decode_api_level_tags(tags, api_map):
"""Decodes API level code names in a list of tags.
Raises:
ParseError: An unknown version name was found in a tag.
"""
for idx, tag in enumerate(tags):
if not is_api_level_tag(tag):
continue
name, value = split_tag(tag)
try:
decoded = str(decode_api_level(value, api_map))
tags[idx] = '='.join([name, decoded])
except KeyError:
raise ParseError('Unknown version name in tag: {}'.format(tag))
return tags
def split_tag(tag):
"""Returns a key/value tuple of the tag.
Raises:
ValueError: Tag is not a key/value type tag.
Returns: Tuple of (key, value) of the tag. Both components are strings.
"""
if '=' not in tag:
raise ValueError('Not a key/value tag: ' + tag)
key, _, value = tag.partition('=')
return key, value
def get_tag_value(tag):
"""Returns the value of a key/value tag.
@ -70,9 +99,7 @@ def get_tag_value(tag):
Returns: Value part of tag as a string.
"""
if '=' not in tag:
raise ValueError('Not a key/value tag: ' + tag)
return tag.partition('=')[2]
return split_tag(tag)[1]
def version_is_private(version):
@ -194,8 +221,9 @@ class Symbol(object):
class SymbolFileParser(object):
"""Parses NDK symbol files."""
def __init__(self, input_file):
def __init__(self, input_file, api_map):
self.input_file = input_file
self.api_map = api_map
self.current_line = None
def parse(self):
@ -213,6 +241,7 @@ class SymbolFileParser(object):
"""Parses a single version section and returns a Version object."""
name = self.current_line.split('{')[0].strip()
tags = get_tags(self.current_line)
tags = decode_api_level_tags(tags, self.api_map)
symbols = []
global_scope = True
while self.next_line() != '':
@ -254,6 +283,7 @@ class SymbolFileParser(object):
# Line is now in the format "<symbol-name>; # tags"
name, _, _ = self.current_line.strip().partition(';')
tags = get_tags(self.current_line)
tags = decode_api_level_tags(tags, self.api_map)
return Symbol(name, tags)
def next_line(self):
@ -342,10 +372,7 @@ def decode_api_level(api, api_map):
if api == "current":
return FUTURE_API_LEVEL
with open(api_map) as map_file:
api_levels = json.load(map_file)
return api_levels[api]
return api_map[api]
def parse_args():
@ -382,7 +409,9 @@ def main():
"""Program entry point."""
args = parse_args()
api = decode_api_level(args.api, args.api_map)
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
@ -391,7 +420,7 @@ def main():
logging.basicConfig(level=verbose_map[verbosity])
with open(args.symbol_file) as symbol_file:
versions = SymbolFileParser(symbol_file).parse()
versions = SymbolFileParser(symbol_file, api_map).parse()
with open(args.stub_src, 'w') as src_file:
with open(args.version_script, 'w') as version_file:

View File

@ -25,6 +25,15 @@ 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(''))
@ -34,12 +43,59 @@ class TagsTest(unittest.TestCase):
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):
@ -151,7 +207,7 @@ class SymbolFileParseTest(unittest.TestCase):
# baz
qux
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
self.assertIsNone(parser.current_line)
self.assertEqual('foo', parser.next_line().strip())
@ -176,7 +232,7 @@ class SymbolFileParseTest(unittest.TestCase):
VERSION_2 {
} VERSION_1; # asdf
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.next_line()
version = parser.parse_version()
@ -200,7 +256,7 @@ class SymbolFileParseTest(unittest.TestCase):
input_file = cStringIO.StringIO(textwrap.dedent("""\
VERSION_1 {
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -211,7 +267,7 @@ class SymbolFileParseTest(unittest.TestCase):
foo:
}
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -221,7 +277,7 @@ class SymbolFileParseTest(unittest.TestCase):
foo;
bar; # baz qux
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.next_line()
symbol = parser.parse_symbol()
@ -239,7 +295,7 @@ class SymbolFileParseTest(unittest.TestCase):
*;
};
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -251,7 +307,7 @@ class SymbolFileParseTest(unittest.TestCase):
*;
};
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.next_line()
version = parser.parse_version()
self.assertEqual([], version.symbols)
@ -262,7 +318,7 @@ class SymbolFileParseTest(unittest.TestCase):
foo
};
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -270,7 +326,7 @@ class SymbolFileParseTest(unittest.TestCase):
def test_parse_fails_invalid_input(self):
with self.assertRaises(gsl.ParseError):
input_file = cStringIO.StringIO('foo')
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
parser.parse()
def test_parse(self):
@ -291,7 +347,7 @@ class SymbolFileParseTest(unittest.TestCase):
qwerty;
} VERSION_1;
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, {})
versions = parser.parse()
expected = [
@ -408,11 +464,18 @@ class GeneratorTest(unittest.TestCase):
class IntegrationTest(unittest.TestCase):
def test_integration(self):
api_map = {
'O': 9000,
'P': 9001,
}
input_file = cStringIO.StringIO(textwrap.dedent("""\
VERSION_1 {
global:
foo; # var
bar; # x86
fizz; # introduced=O
buzz; # introduced=P
local:
*;
};
@ -436,7 +499,7 @@ class IntegrationTest(unittest.TestCase):
wobble;
} VERSION_4;
"""))
parser = gsl.SymbolFileParser(input_file)
parser = gsl.SymbolFileParser(input_file, api_map)
versions = parser.parse()
src_file = cStringIO.StringIO()
@ -469,6 +532,46 @@ class IntegrationTest(unittest.TestCase):
""")
self.assertEqual(expected_version, version_file.getvalue())
def test_integration_future_api(self):
api_map = {
'O': 9000,
'P': 9001,
'Q': 9002,
}
input_file = cStringIO.StringIO(textwrap.dedent("""\
VERSION_1 {
global:
foo; # introduced=O
bar; # introduced=P
baz; # introduced=Q
local:
*;
};
"""))
parser = gsl.SymbolFileParser(input_file, api_map)
versions = parser.parse()
src_file = cStringIO.StringIO()
version_file = cStringIO.StringIO()
generator = gsl.Generator(src_file, version_file, 'arm', 9001, 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 main():
suite = unittest.TestLoader().loadTestsFromName(__name__)