Merge "ndkstubgen: use llndk=<version> for new llndk stub" into main
This commit is contained in:
@@ -2688,7 +2688,7 @@ func TestLlndkLibrary(t *testing.T) {
|
|||||||
android.AssertArrayString(t, "variants for llndk stubs", expected, actual)
|
android.AssertArrayString(t, "variants for llndk stubs", expected, actual)
|
||||||
|
|
||||||
params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
|
params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
|
||||||
android.AssertSame(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
|
android.AssertSame(t, "use Vendor API level for default stubs", "202404", params.Args["apiLevel"])
|
||||||
|
|
||||||
checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
|
checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
@@ -677,18 +677,16 @@ func (library *libraryDecorator) getHeaderAbiCheckerProperties(ctx android.BaseM
|
|||||||
|
|
||||||
func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
|
func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
|
||||||
if ctx.IsLlndk() {
|
if ctx.IsLlndk() {
|
||||||
|
vendorApiLevel := ctx.Config().VendorApiLevel()
|
||||||
|
if vendorApiLevel == "" {
|
||||||
|
// TODO(b/321892570): Some tests relying on old fixtures which
|
||||||
|
// doesn't set vendorApiLevel. Needs to fix them.
|
||||||
|
vendorApiLevel = ctx.Config().PlatformSdkVersion().String()
|
||||||
|
}
|
||||||
// This is the vendor variant of an LLNDK library, build the LLNDK stubs.
|
// This is the vendor variant of an LLNDK library, build the LLNDK stubs.
|
||||||
vndkVer := ctx.Module().(*Module).VndkVersion()
|
|
||||||
if !inList(vndkVer, ctx.Config().PlatformVersionActiveCodenames()) || vndkVer == "" {
|
|
||||||
// For non-enforcing devices, vndkVer is empty. Use "current" in that case, too.
|
|
||||||
vndkVer = "current"
|
|
||||||
}
|
|
||||||
if library.stubsVersion() != "" {
|
|
||||||
vndkVer = library.stubsVersion()
|
|
||||||
}
|
|
||||||
nativeAbiResult := parseNativeAbiDefinition(ctx,
|
nativeAbiResult := parseNativeAbiDefinition(ctx,
|
||||||
String(library.Properties.Llndk.Symbol_file),
|
String(library.Properties.Llndk.Symbol_file),
|
||||||
android.ApiLevelOrPanic(ctx, vndkVer), "--llndk")
|
android.ApiLevelOrPanic(ctx, vendorApiLevel), "--llndk")
|
||||||
objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
|
objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
|
||||||
if !Bool(library.Properties.Llndk.Unversioned) {
|
if !Bool(library.Properties.Llndk.Unversioned) {
|
||||||
library.versionScriptPath = android.OptionalPathForPath(
|
library.versionScriptPath = android.OptionalPathForPath(
|
||||||
|
@@ -463,6 +463,98 @@ class IntegrationTest(unittest.TestCase):
|
|||||||
""")
|
""")
|
||||||
self.assertEqual(expected_version, version_file.getvalue())
|
self.assertEqual(expected_version, version_file.getvalue())
|
||||||
|
|
||||||
|
def test_integration_with_llndk(self) -> None:
|
||||||
|
input_file = io.StringIO(textwrap.dedent("""\
|
||||||
|
VERSION_34 { # introduced=34
|
||||||
|
global:
|
||||||
|
foo;
|
||||||
|
bar; # llndk
|
||||||
|
};
|
||||||
|
VERSION_35 { # introduced=35
|
||||||
|
global:
|
||||||
|
wiggle;
|
||||||
|
waggle;
|
||||||
|
waggle; # llndk=202404
|
||||||
|
bubble; # llndk=202404
|
||||||
|
duddle;
|
||||||
|
duddle; # llndk=202504
|
||||||
|
} VERSION_34;
|
||||||
|
"""))
|
||||||
|
f = copy(self.filter)
|
||||||
|
f.llndk = True
|
||||||
|
f.api = 202404
|
||||||
|
parser = symbolfile.SymbolFileParser(input_file, {}, f)
|
||||||
|
versions = parser.parse()
|
||||||
|
|
||||||
|
src_file = io.StringIO()
|
||||||
|
version_file = io.StringIO()
|
||||||
|
symbol_list_file = io.StringIO()
|
||||||
|
|
||||||
|
generator = ndkstubgen.Generator(src_file,
|
||||||
|
version_file, symbol_list_file, f)
|
||||||
|
generator.write(versions)
|
||||||
|
|
||||||
|
expected_src = textwrap.dedent("""\
|
||||||
|
void foo() {}
|
||||||
|
void bar() {}
|
||||||
|
void waggle() {}
|
||||||
|
void bubble() {}
|
||||||
|
""")
|
||||||
|
self.assertEqual(expected_src, src_file.getvalue())
|
||||||
|
|
||||||
|
expected_version = textwrap.dedent("""\
|
||||||
|
VERSION_34 {
|
||||||
|
global:
|
||||||
|
foo;
|
||||||
|
bar;
|
||||||
|
};
|
||||||
|
VERSION_35 {
|
||||||
|
global:
|
||||||
|
waggle;
|
||||||
|
bubble;
|
||||||
|
} VERSION_34;
|
||||||
|
""")
|
||||||
|
self.assertEqual(expected_version, version_file.getvalue())
|
||||||
|
|
||||||
|
def test_integration_with_llndk_with_single_version_block(self) -> None:
|
||||||
|
input_file = io.StringIO(textwrap.dedent("""\
|
||||||
|
LIBANDROID {
|
||||||
|
global:
|
||||||
|
foo; # introduced=34
|
||||||
|
bar; # introduced=35
|
||||||
|
bar; # llndk=202404
|
||||||
|
baz; # introduced=35
|
||||||
|
};
|
||||||
|
"""))
|
||||||
|
f = copy(self.filter)
|
||||||
|
f.llndk = True
|
||||||
|
f.api = 202404
|
||||||
|
parser = symbolfile.SymbolFileParser(input_file, {}, f)
|
||||||
|
versions = parser.parse()
|
||||||
|
|
||||||
|
src_file = io.StringIO()
|
||||||
|
version_file = io.StringIO()
|
||||||
|
symbol_list_file = io.StringIO()
|
||||||
|
|
||||||
|
generator = ndkstubgen.Generator(src_file,
|
||||||
|
version_file, symbol_list_file, f)
|
||||||
|
generator.write(versions)
|
||||||
|
|
||||||
|
expected_src = textwrap.dedent("""\
|
||||||
|
void foo() {}
|
||||||
|
void bar() {}
|
||||||
|
""")
|
||||||
|
self.assertEqual(expected_src, src_file.getvalue())
|
||||||
|
|
||||||
|
expected_version = textwrap.dedent("""\
|
||||||
|
LIBANDROID {
|
||||||
|
global:
|
||||||
|
foo;
|
||||||
|
bar;
|
||||||
|
};
|
||||||
|
""")
|
||||||
|
self.assertEqual(expected_version, version_file.getvalue())
|
||||||
|
|
||||||
def test_empty_stub(self) -> None:
|
def test_empty_stub(self) -> None:
|
||||||
"""Tests that empty stubs can be generated.
|
"""Tests that empty stubs can be generated.
|
||||||
|
|
||||||
|
@@ -103,13 +103,24 @@ class Tags:
|
|||||||
@property
|
@property
|
||||||
def has_llndk_tags(self) -> bool:
|
def has_llndk_tags(self) -> bool:
|
||||||
"""Returns True if any LL-NDK tags are set."""
|
"""Returns True if any LL-NDK tags are set."""
|
||||||
return 'llndk' in self.tags
|
for tag in self.tags:
|
||||||
|
if tag == 'llndk' or tag.startswith('llndk='):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_platform_only_tags(self) -> bool:
|
def has_platform_only_tags(self) -> bool:
|
||||||
"""Returns True if any platform-only tags are set."""
|
"""Returns True if any platform-only tags are set."""
|
||||||
return 'platform-only' in self.tags
|
return 'platform-only' in self.tags
|
||||||
|
|
||||||
|
def copy_introduced_from(self, tags: Tags) -> None:
|
||||||
|
"""Copies introduced= or introduced-*= tags."""
|
||||||
|
for tag in tags:
|
||||||
|
if tag.startswith('introduced=') or tag.startswith('introduced-'):
|
||||||
|
name, _ = split_tag(tag)
|
||||||
|
if not any(self_tag.startswith(name + '=') for self_tag in self.tags):
|
||||||
|
self.tags += (tag,)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Symbol:
|
class Symbol:
|
||||||
@@ -147,6 +158,8 @@ def is_api_level_tag(tag: Tag) -> bool:
|
|||||||
"""Returns true if this tag has an API level that may need decoding."""
|
"""Returns true if this tag has an API level that may need decoding."""
|
||||||
if tag.startswith('llndk-deprecated='):
|
if tag.startswith('llndk-deprecated='):
|
||||||
return True
|
return True
|
||||||
|
if tag.startswith('llndk='):
|
||||||
|
return True
|
||||||
if tag.startswith('introduced='):
|
if tag.startswith('introduced='):
|
||||||
return True
|
return True
|
||||||
if tag.startswith('introduced-'):
|
if tag.startswith('introduced-'):
|
||||||
@@ -237,15 +250,22 @@ class Filter:
|
|||||||
|
|
||||||
This defines the rules shared between version tagging and symbol tagging.
|
This defines the rules shared between version tagging and symbol tagging.
|
||||||
"""
|
"""
|
||||||
# The apex and llndk tags will only exclude APIs from other modes. If in
|
# LLNDK mode/tags follow the similar filtering except that API level checking
|
||||||
|
# is based llndk= instead of introduced=.
|
||||||
|
if self.llndk:
|
||||||
|
if tags.has_mode_tags and not tags.has_llndk_tags:
|
||||||
|
return True
|
||||||
|
if not symbol_in_arch(tags, self.arch):
|
||||||
|
return True
|
||||||
|
if not symbol_in_llndk_api(tags, self.arch, self.api):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
# APEX or LLNDK mode and neither tag is provided, we fall back to the
|
# APEX or LLNDK mode and neither tag is provided, we fall back to the
|
||||||
# default behavior because all NDK symbols are implicitly available to
|
# default behavior because all NDK symbols are implicitly available to
|
||||||
# APEX and LLNDK.
|
# APEX and LLNDK.
|
||||||
if tags.has_mode_tags:
|
if tags.has_mode_tags:
|
||||||
if self.apex and tags.has_apex_tags:
|
if self.apex and tags.has_apex_tags:
|
||||||
return False
|
return False
|
||||||
if self.llndk and tags.has_llndk_tags:
|
|
||||||
return False
|
|
||||||
if self.systemapi and tags.has_systemapi_tags:
|
if self.systemapi and tags.has_systemapi_tags:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -266,6 +286,10 @@ class Filter:
|
|||||||
return True
|
return True
|
||||||
if version.tags.has_platform_only_tags:
|
if version.tags.has_platform_only_tags:
|
||||||
return True
|
return True
|
||||||
|
# Include all versions when targeting LLNDK because LLNDK symbols are self-versioned.
|
||||||
|
# Empty version block will be handled separately.
|
||||||
|
if self.llndk:
|
||||||
|
return False
|
||||||
return self._should_omit_tags(version.tags)
|
return self._should_omit_tags(version.tags)
|
||||||
|
|
||||||
def should_omit_symbol(self, symbol: Symbol) -> bool:
|
def should_omit_symbol(self, symbol: Symbol) -> bool:
|
||||||
@@ -292,6 +316,14 @@ def symbol_in_arch(tags: Tags, arch: Arch) -> bool:
|
|||||||
# for the tagged architectures.
|
# for the tagged architectures.
|
||||||
return not has_arch_tags
|
return not has_arch_tags
|
||||||
|
|
||||||
|
def symbol_in_llndk_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
|
||||||
|
"""Returns true if the symbol is present for the given LLNDK API level."""
|
||||||
|
# Check llndk= first.
|
||||||
|
for tag in tags:
|
||||||
|
if tag.startswith('llndk='):
|
||||||
|
return api >= int(get_tag_value(tag))
|
||||||
|
# If not, we keep old behavior: NDK symbols in <= 34 are LLNDK symbols.
|
||||||
|
return symbol_in_api(tags, arch, 34)
|
||||||
|
|
||||||
def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
|
def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
|
||||||
"""Returns true if the symbol is present for the given API level."""
|
"""Returns true if the symbol is present for the given API level."""
|
||||||
@@ -368,6 +400,7 @@ class SymbolFileParser:
|
|||||||
f'Unexpected contents at top level: {self.current_line}')
|
f'Unexpected contents at top level: {self.current_line}')
|
||||||
|
|
||||||
self.check_no_duplicate_symbols(versions)
|
self.check_no_duplicate_symbols(versions)
|
||||||
|
self.check_llndk_introduced(versions)
|
||||||
return versions
|
return versions
|
||||||
|
|
||||||
def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None:
|
def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None:
|
||||||
@@ -396,6 +429,31 @@ class SymbolFileParser:
|
|||||||
raise MultiplyDefinedSymbolError(
|
raise MultiplyDefinedSymbolError(
|
||||||
sorted(list(multiply_defined_symbols)))
|
sorted(list(multiply_defined_symbols)))
|
||||||
|
|
||||||
|
def check_llndk_introduced(self, versions: Iterable[Version]) -> None:
|
||||||
|
"""Raises errors when llndk= is missing for new llndk symbols."""
|
||||||
|
if not self.filter.llndk:
|
||||||
|
return
|
||||||
|
|
||||||
|
def assert_llndk_with_version(tags: Tags, name: str) -> None:
|
||||||
|
has_llndk_introduced = False
|
||||||
|
for tag in tags:
|
||||||
|
if tag.startswith('llndk='):
|
||||||
|
has_llndk_introduced = True
|
||||||
|
break
|
||||||
|
if not has_llndk_introduced:
|
||||||
|
raise ParseError(f'{name}: missing version. `llndk=yyyymm`')
|
||||||
|
|
||||||
|
arch = self.filter.arch
|
||||||
|
for version in versions:
|
||||||
|
# llndk symbols >= introduced=35 should be tagged
|
||||||
|
# explicitly with llndk=yyyymm.
|
||||||
|
for symbol in version.symbols:
|
||||||
|
if not symbol.tags.has_llndk_tags:
|
||||||
|
continue
|
||||||
|
if symbol_in_api(symbol.tags, arch, 34):
|
||||||
|
continue
|
||||||
|
assert_llndk_with_version(symbol.tags, symbol.name)
|
||||||
|
|
||||||
def parse_version(self) -> Version:
|
def parse_version(self) -> Version:
|
||||||
"""Parses a single version section and returns a Version object."""
|
"""Parses a single version section and returns a Version object."""
|
||||||
assert self.current_line is not None
|
assert self.current_line is not None
|
||||||
@@ -429,7 +487,9 @@ class SymbolFileParser:
|
|||||||
else:
|
else:
|
||||||
raise ParseError('Unknown visiblity label: ' + visibility)
|
raise ParseError('Unknown visiblity label: ' + visibility)
|
||||||
elif global_scope and not cpp_symbols:
|
elif global_scope and not cpp_symbols:
|
||||||
symbols.append(self.parse_symbol())
|
symbol = self.parse_symbol()
|
||||||
|
symbol.tags.copy_introduced_from(tags)
|
||||||
|
symbols.append(symbol)
|
||||||
else:
|
else:
|
||||||
# We're in a hidden scope or in 'extern "C++"' block. Ignore
|
# We're in a hidden scope or in 'extern "C++"' block. Ignore
|
||||||
# everything.
|
# everything.
|
||||||
|
@@ -344,6 +344,45 @@ class OmitSymbolTest(unittest.TestCase):
|
|||||||
self.assertInclude(f_llndk, s_none)
|
self.assertInclude(f_llndk, s_none)
|
||||||
self.assertInclude(f_llndk, s_llndk)
|
self.assertInclude(f_llndk, s_llndk)
|
||||||
|
|
||||||
|
def test_omit_llndk_versioned(self) -> None:
|
||||||
|
f_ndk = self.filter
|
||||||
|
f_ndk.api = 35
|
||||||
|
|
||||||
|
f_llndk = copy(f_ndk)
|
||||||
|
f_llndk.llndk = True
|
||||||
|
f_llndk.api = 202404
|
||||||
|
|
||||||
|
s = Symbol('foo', Tags())
|
||||||
|
s_llndk = Symbol('foo', Tags.from_strs(['llndk']))
|
||||||
|
s_llndk_202404 = Symbol('foo', Tags.from_strs(['llndk=202404']))
|
||||||
|
s_34 = Symbol('foo', Tags.from_strs(['introduced=34']))
|
||||||
|
s_34_llndk = Symbol('foo', Tags.from_strs(['introduced=34', 'llndk']))
|
||||||
|
s_35 = Symbol('foo', Tags.from_strs(['introduced=35']))
|
||||||
|
s_35_llndk_202404 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202404']))
|
||||||
|
s_35_llndk_202504 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202504']))
|
||||||
|
|
||||||
|
# When targeting NDK, omit LLNDK tags
|
||||||
|
self.assertInclude(f_ndk, s)
|
||||||
|
self.assertOmit(f_ndk, s_llndk)
|
||||||
|
self.assertOmit(f_ndk, s_llndk_202404)
|
||||||
|
self.assertInclude(f_ndk, s_34)
|
||||||
|
self.assertOmit(f_ndk, s_34_llndk)
|
||||||
|
self.assertInclude(f_ndk, s_35)
|
||||||
|
self.assertOmit(f_ndk, s_35_llndk_202404)
|
||||||
|
self.assertOmit(f_ndk, s_35_llndk_202504)
|
||||||
|
|
||||||
|
# When targeting LLNDK, old symbols without any mode tags are included as LLNDK
|
||||||
|
self.assertInclude(f_llndk, s)
|
||||||
|
# When targeting LLNDK, old symbols with #llndk are included as LLNDK
|
||||||
|
self.assertInclude(f_llndk, s_llndk)
|
||||||
|
self.assertInclude(f_llndk, s_llndk_202404)
|
||||||
|
self.assertInclude(f_llndk, s_34)
|
||||||
|
self.assertInclude(f_llndk, s_34_llndk)
|
||||||
|
# When targeting LLNDK, new symbols(>=35) should be tagged with llndk-introduced=.
|
||||||
|
self.assertOmit(f_llndk, s_35)
|
||||||
|
self.assertInclude(f_llndk, s_35_llndk_202404)
|
||||||
|
self.assertOmit(f_llndk, s_35_llndk_202504)
|
||||||
|
|
||||||
def test_omit_apex(self) -> None:
|
def test_omit_apex(self) -> None:
|
||||||
f_none = self.filter
|
f_none = self.filter
|
||||||
f_apex = copy(f_none)
|
f_apex = copy(f_none)
|
||||||
@@ -451,9 +490,12 @@ class SymbolFileParseTest(unittest.TestCase):
|
|||||||
self.assertIsNone(version.base)
|
self.assertIsNone(version.base)
|
||||||
self.assertEqual(Tags.from_strs(['weak', 'introduced=35']), version.tags)
|
self.assertEqual(Tags.from_strs(['weak', 'introduced=35']), version.tags)
|
||||||
|
|
||||||
|
# Inherit introduced= tags from version block so that
|
||||||
|
# should_omit_tags() can differently based on introduced API level when treating
|
||||||
|
# LLNDK-available symbols.
|
||||||
expected_symbols = [
|
expected_symbols = [
|
||||||
Symbol('baz', Tags()),
|
Symbol('baz', Tags.from_strs(['introduced=35'])),
|
||||||
Symbol('qux', Tags.from_strs(['apex', 'llndk'])),
|
Symbol('qux', Tags.from_strs(['apex', 'llndk', 'introduced=35'])),
|
||||||
]
|
]
|
||||||
self.assertEqual(expected_symbols, version.symbols)
|
self.assertEqual(expected_symbols, version.symbols)
|
||||||
|
|
||||||
@@ -601,6 +643,19 @@ class SymbolFileParseTest(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(expected_symbols, version.symbols)
|
self.assertEqual(expected_symbols, version.symbols)
|
||||||
|
|
||||||
|
def test_parse_llndk_version_is_missing(self) -> None:
|
||||||
|
input_file = io.StringIO(textwrap.dedent("""\
|
||||||
|
VERSION_1 { # introduced=35
|
||||||
|
foo;
|
||||||
|
bar; # llndk
|
||||||
|
};
|
||||||
|
"""))
|
||||||
|
f = copy(self.filter)
|
||||||
|
f.llndk = True
|
||||||
|
parser = symbolfile.SymbolFileParser(input_file, {}, f)
|
||||||
|
with self.assertRaises(symbolfile.ParseError):
|
||||||
|
parser.parse()
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
suite = unittest.TestLoader().loadTestsFromName(__name__)
|
suite = unittest.TestLoader().loadTestsFromName(__name__)
|
||||||
|
Reference in New Issue
Block a user