Merge changes Ic32cfb3e,I282be134

* changes:
  Add support for named versions in NDK map files.
  Generate stub libraries for unreleased API levels.
This commit is contained in:
Treehugger Robot
2017-04-30 21:31:21 +00:00
committed by Gerrit Code Review
3 changed files with 203 additions and 39 deletions

View File

@@ -16,6 +16,7 @@
# #
"""Generates source for stub shared libraries for the NDK.""" """Generates source for stub shared libraries for the NDK."""
import argparse import argparse
import json
import logging import logging
import os import os
import re import re
@@ -40,27 +41,56 @@ def logger():
return logging.getLogger(__name__) 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): def get_tags(line):
"""Returns a list of all tags on this line.""" """Returns a list of all tags on this line."""
_, _, all_tags = line.strip().partition('#') _, _, all_tags = line.strip().partition('#')
return [e for e in re.split(r'\s+', all_tags) if e.strip()] 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): def get_tag_value(tag):
"""Returns the value of a key/value tag. """Returns the value of a key/value tag.
@@ -69,9 +99,7 @@ def get_tag_value(tag):
Returns: Value part of tag as a string. Returns: Value part of tag as a string.
""" """
if '=' not in tag: return split_tag(tag)[1]
raise ValueError('Not a key/value tag: ' + tag)
return tag.partition('=')[2]
def version_is_private(version): def version_is_private(version):
@@ -193,8 +221,9 @@ class Symbol(object):
class SymbolFileParser(object): class SymbolFileParser(object):
"""Parses NDK symbol files.""" """Parses NDK symbol files."""
def __init__(self, input_file): def __init__(self, input_file, api_map):
self.input_file = input_file self.input_file = input_file
self.api_map = api_map
self.current_line = None self.current_line = None
def parse(self): def parse(self):
@@ -212,6 +241,7 @@ class SymbolFileParser(object):
"""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()
tags = get_tags(self.current_line) tags = get_tags(self.current_line)
tags = decode_api_level_tags(tags, self.api_map)
symbols = [] symbols = []
global_scope = True global_scope = True
while self.next_line() != '': while self.next_line() != '':
@@ -253,6 +283,7 @@ class SymbolFileParser(object):
# Line is now in the format "<symbol-name>; # tags" # Line is now in the format "<symbol-name>; # tags"
name, _, _ = self.current_line.strip().partition(';') name, _, _ = self.current_line.strip().partition(';')
tags = get_tags(self.current_line) tags = get_tags(self.current_line)
tags = decode_api_level_tags(tags, self.api_map)
return Symbol(name, tags) return Symbol(name, tags)
def next_line(self): def next_line(self):
@@ -326,6 +357,24 @@ class Generator(object):
self.version_script.write('}' + base + ';\n') 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(): def parse_args():
"""Parses and returns command line arguments.""" """Parses and returns command line arguments."""
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@@ -333,14 +382,17 @@ def parse_args():
parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument( parser.add_argument(
'--api', type=api_level_arg, required=True, '--api', required=True, help='API level being targeted.')
help='API level being targeted.')
parser.add_argument( parser.add_argument(
'--arch', choices=ALL_ARCHITECTURES, required=True, '--arch', choices=ALL_ARCHITECTURES, required=True,
help='Architecture being targeted.') help='Architecture being targeted.')
parser.add_argument( parser.add_argument(
'--vndk', action='store_true', help='Use the VNDK variant.') '--vndk', action='store_true', help='Use the VNDK variant.')
parser.add_argument(
'--api-map', type=os.path.realpath, required=True,
help='Path to the API level map JSON file.')
parser.add_argument( parser.add_argument(
'symbol_file', type=os.path.realpath, help='Path to symbol file.') 'symbol_file', type=os.path.realpath, help='Path to symbol file.')
parser.add_argument( parser.add_argument(
@@ -357,6 +409,10 @@ def main():
"""Program entry point.""" """Program entry point."""
args = parse_args() 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) verbose_map = (logging.WARNING, logging.INFO, logging.DEBUG)
verbosity = args.verbose verbosity = args.verbose
if verbosity > 2: if verbosity > 2:
@@ -364,11 +420,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).parse() versions = SymbolFileParser(symbol_file, api_map).parse()
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:
generator = Generator(src_file, version_file, args.arch, args.api, generator = Generator(src_file, version_file, args.arch, api,
args.vndk) args.vndk)
generator.write(versions) generator.write(versions)

View File

@@ -30,10 +30,11 @@ var (
genStubSrc = pctx.AndroidStaticRule("genStubSrc", genStubSrc = pctx.AndroidStaticRule("genStubSrc",
blueprint.RuleParams{ blueprint.RuleParams{
Command: "$toolPath --arch $arch --api $apiLevel $vndk $in $out", Command: "$toolPath --arch $arch --api $apiLevel --api-map " +
"$apiMap $vndk $in $out",
Description: "genStubSrc $out", Description: "genStubSrc $out",
CommandDeps: []string{"$toolPath"}, CommandDeps: []string{"$toolPath"},
}, "arch", "apiLevel", "vndk") }, "arch", "apiLevel", "apiMap", "vndk")
ndkLibrarySuffix = ".ndk" ndkLibrarySuffix = ".ndk"
@@ -205,6 +206,7 @@ func generateStubApiVariants(mctx android.BottomUpMutatorContext, c *stubDecorat
for version := firstGenVersion; version <= platformVersion; version++ { for version := firstGenVersion; version <= platformVersion; version++ {
versionStrs = append(versionStrs, strconv.Itoa(version)) versionStrs = append(versionStrs, strconv.Itoa(version))
} }
versionStrs = append(versionStrs, mctx.AConfig().PlatformVersionAllCodenames()...)
versionStrs = append(versionStrs, "current") versionStrs = append(versionStrs, "current")
modules := mctx.CreateVariations(versionStrs...) modules := mctx.CreateVariations(versionStrs...)
@@ -247,13 +249,16 @@ func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, vn
stubSrcPath := android.PathForModuleGen(ctx, "stub.c") stubSrcPath := android.PathForModuleGen(ctx, "stub.c")
versionScriptPath := android.PathForModuleGen(ctx, "stub.map") versionScriptPath := android.PathForModuleGen(ctx, "stub.map")
symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
apiLevelsJson := android.GetApiLevelsJson(ctx)
ctx.ModuleBuild(pctx, android.ModuleBuildParams{ ctx.ModuleBuild(pctx, android.ModuleBuildParams{
Rule: genStubSrc, Rule: genStubSrc,
Outputs: []android.WritablePath{stubSrcPath, versionScriptPath}, Outputs: []android.WritablePath{stubSrcPath, versionScriptPath},
Input: symbolFilePath, Input: symbolFilePath,
Implicits: []android.Path{apiLevelsJson},
Args: map[string]string{ Args: map[string]string{
"arch": arch, "arch": arch,
"apiLevel": apiLevel, "apiLevel": apiLevel,
"apiMap": apiLevelsJson.String(),
"vndk": vndk, "vndk": vndk,
}, },
}) })

View File

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