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
commit f5464ba31a
3 changed files with 203 additions and 39 deletions

View file

@ -16,6 +16,7 @@
#
"""Generates source for stub shared libraries for the NDK."""
import argparse
import json
import logging
import os
import re
@ -40,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.
@ -69,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):
@ -193,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):
@ -212,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() != '':
@ -253,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):
@ -326,6 +357,24 @@ class Generator(object):
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()
@ -333,14 +382,17 @@ def parse_args():
parser.add_argument('-v', '--verbose', action='count', default=0)
parser.add_argument(
'--api', type=api_level_arg, required=True,
help='API level being targeted.')
'--api', required=True, help='API level being targeted.')
parser.add_argument(
'--arch', choices=ALL_ARCHITECTURES, required=True,
help='Architecture being targeted.')
parser.add_argument(
'--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(
'symbol_file', type=os.path.realpath, help='Path to symbol file.')
parser.add_argument(
@ -357,6 +409,10 @@ 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:
@ -364,11 +420,11 @@ 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:
generator = Generator(src_file, version_file, args.arch, args.api,
generator = Generator(src_file, version_file, args.arch, api,
args.vndk)
generator.write(versions)

View file

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

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__)