diff --git a/android/arch.go b/android/arch.go index 9ff439ccc..f0573424f 100644 --- a/android/arch.go +++ b/android/arch.go @@ -1565,7 +1565,10 @@ type archConfig struct { abi []string } -// getNdkAbisConfig returns a list of archConfigs for the ABIs supported by the NDK. +// getNdkAbisConfig returns the list of archConfigs that are used for bulding +// the API stubs and static libraries that are included in the NDK. These are +// built *without Neon*, because non-Neon is still supported and building these +// with Neon will break those users. func getNdkAbisConfig() []archConfig { return []archConfig{ {"arm", "armv7-a", "", []string{"armeabi-v7a"}}, diff --git a/cc/Android.bp b/cc/Android.bp index 1fc8d9f68..46740dcf2 100644 --- a/cc/Android.bp +++ b/cc/Android.bp @@ -65,9 +65,10 @@ bootstrap_go_package { "test.go", "toolchain_library.go", - "ndk_prebuilt.go", + "ndk_abi.go", "ndk_headers.go", "ndk_library.go", + "ndk_prebuilt.go", "ndk_sysroot.go", "llndk_library.go", diff --git a/cc/cc.go b/cc/cc.go index 91c441782..e4a52f1bf 100644 --- a/cc/cc.go +++ b/cc/cc.go @@ -2112,6 +2112,15 @@ func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) { } } + if c.isNDKStubLibrary() { + // NDK stubs depend on their implementation because the ABI dumps are + // generated from the implementation library. + actx.AddFarVariationDependencies(append(ctx.Target().Variations(), + c.ImageVariation(), + blueprint.Variation{Mutator: "link", Variation: "shared"}, + ), stubImplementation, c.BaseModuleName()) + } + // sysprop_library has to support both C++ and Java. So sysprop_library internally creates one // C++ implementation library and one Java implementation library. When a module links against // sysprop_library, the C++ implementation library has to be linked. syspropImplLibraries is a diff --git a/cc/library.go b/cc/library.go index 5e70c51e9..aec2e4df4 100644 --- a/cc/library.go +++ b/cc/library.go @@ -836,16 +836,23 @@ func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps Pa if library.stubsVersion() != "" { vndkVer = library.stubsVersion() } - objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Llndk.Symbol_file), vndkVer, "--llndk") + nativeAbiResult := parseNativeAbiDefinition(ctx, + String(library.Properties.Llndk.Symbol_file), + android.ApiLevelOrPanic(ctx, vndkVer), "--llndk") + objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc) if !Bool(library.Properties.Llndk.Unversioned) { - library.versionScriptPath = android.OptionalPathForPath(versionScript) + library.versionScriptPath = android.OptionalPathForPath( + nativeAbiResult.versionScript) } return objs } if ctx.IsVendorPublicLibrary() { - objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Vendor_public_library.Symbol_file), "current", "") + nativeAbiResult := parseNativeAbiDefinition(ctx, + String(library.Properties.Vendor_public_library.Symbol_file), + android.FutureApiLevel, "") + objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc) if !Bool(library.Properties.Vendor_public_library.Unversioned) { - library.versionScriptPath = android.OptionalPathForPath(versionScript) + library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.versionScript) } return objs } @@ -855,8 +862,12 @@ func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps Pa ctx.PropertyErrorf("symbol_file", "%q doesn't have .map.txt suffix", symbolFile) return Objects{} } - objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Stubs.Symbol_file), library.MutatedProperties.StubsVersion, "--apex") - library.versionScriptPath = android.OptionalPathForPath(versionScript) + nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, + android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion), + "--apex") + objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc) + library.versionScriptPath = android.OptionalPathForPath( + nativeAbiResult.versionScript) return objs } diff --git a/cc/ndk_abi.go b/cc/ndk_abi.go new file mode 100644 index 000000000..b9b57af2b --- /dev/null +++ b/cc/ndk_abi.go @@ -0,0 +1,102 @@ +// Copyright 2021 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cc + +import ( + "android/soong/android" +) + +func init() { + android.RegisterSingletonType("ndk_abi_dump", NdkAbiDumpSingleton) + android.RegisterSingletonType("ndk_abi_diff", NdkAbiDiffSingleton) +} + +func getNdkAbiDumpInstallBase(ctx android.PathContext) android.OutputPath { + return android.PathForOutput(ctx).Join(ctx, "abi-dumps/ndk") +} + +func getNdkAbiDumpTimestampFile(ctx android.PathContext) android.OutputPath { + return android.PathForOutput(ctx, "ndk_abi_dump.timestamp") +} + +func NdkAbiDumpSingleton() android.Singleton { + return &ndkAbiDumpSingleton{} +} + +type ndkAbiDumpSingleton struct{} + +func (n *ndkAbiDumpSingleton) GenerateBuildActions(ctx android.SingletonContext) { + var depPaths android.Paths + ctx.VisitAllModules(func(module android.Module) { + if !module.Enabled() { + return + } + + if m, ok := module.(*Module); ok { + if installer, ok := m.installer.(*stubDecorator); ok { + if canDumpAbi(m) { + depPaths = append(depPaths, installer.abiDumpPath) + } + } + } + }) + + // `m dump-ndk-abi` will dump the NDK ABI. + // `development/tools/ndk/update_ndk_abi.sh` will dump the NDK ABI and + // update the golden copies in prebuilts/abi-dumps/ndk. + ctx.Build(pctx, android.BuildParams{ + Rule: android.Touch, + Output: getNdkAbiDumpTimestampFile(ctx), + Implicits: depPaths, + }) + + ctx.Phony("dump-ndk-abi", getNdkAbiDumpTimestampFile(ctx)) +} + +func getNdkAbiDiffTimestampFile(ctx android.PathContext) android.WritablePath { + return android.PathForOutput(ctx, "ndk_abi_diff.timestamp") +} + +func NdkAbiDiffSingleton() android.Singleton { + return &ndkAbiDiffSingleton{} +} + +type ndkAbiDiffSingleton struct{} + +func (n *ndkAbiDiffSingleton) GenerateBuildActions(ctx android.SingletonContext) { + var depPaths android.Paths + ctx.VisitAllModules(func(module android.Module) { + if m, ok := module.(android.Module); ok && !m.Enabled() { + return + } + + if m, ok := module.(*Module); ok { + if installer, ok := m.installer.(*stubDecorator); ok { + depPaths = append(depPaths, installer.abiDiffPaths...) + } + } + }) + + depPaths = append(depPaths, getNdkAbiDumpTimestampFile(ctx)) + + // `m diff-ndk-abi` will diff the NDK ABI. + ctx.Build(pctx, android.BuildParams{ + Rule: android.Touch, + Output: getNdkAbiDiffTimestampFile(ctx), + Implicits: depPaths, + }) + + ctx.Phony("diff-ndk-abi", getNdkAbiDiffTimestampFile(ctx)) +} diff --git a/cc/ndk_library.go b/cc/ndk_library.go index 95d84777b..f3d2ba169 100644 --- a/cc/ndk_library.go +++ b/cc/ndk_library.go @@ -16,6 +16,7 @@ package cc import ( "fmt" + "path/filepath" "strings" "sync" @@ -28,6 +29,9 @@ import ( func init() { pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser") + pctx.HostBinToolVariable("abidiff", "abidiff") + pctx.HostBinToolVariable("abitidy", "abitidy") + pctx.HostBinToolVariable("abidw", "abidw") } var ( @@ -44,11 +48,31 @@ var ( CommandDeps: []string{"$ndk_api_coverage_parser"}, }, "apiMap") + abidw = pctx.AndroidStaticRule("abidw", + blueprint.RuleParams{ + Command: "$abidw --type-id-style hash --no-corpus-path " + + "--no-show-locs --no-comp-dir-path -w $symbolList $in | " + + "$abitidy --all -o $out", + CommandDeps: []string{"$abitidy", "$abidw"}, + }, "symbolList") + + abidiff = pctx.AndroidStaticRule("abidiff", + blueprint.RuleParams{ + // Need to create *some* output for ninja. We don't want to use tee + // because we don't want to spam the build output with "nothing + // changed" messages, so redirect output message to $out, and if + // changes were detected print the output and fail. + Command: "$abidiff $args $in > $out || (cat $out && false)", + CommandDeps: []string{"$abidiff"}, + }, "args") + ndkLibrarySuffix = ".ndk" ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey") // protects ndkKnownLibs writes during parallel BeginMutator. ndkKnownLibsLock sync.Mutex + + stubImplementation = dependencyTag{name: "stubImplementation"} ) // The First_version and Unversioned_until properties of this struct should not @@ -89,6 +113,8 @@ type stubDecorator struct { versionScriptPath android.ModuleGenPath parsedCoverageXmlPath android.ModuleOutPath installPath android.Path + abiDumpPath android.OutputPath + abiDiffPaths android.Paths apiLevel android.ApiLevel firstVersion android.ApiLevel @@ -123,6 +149,16 @@ func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []strin if !ctx.Module().Enabled() { return nil } + if ctx.Os() != android.Android { + // These modules are always android.DeviceEnabled only, but + // those include Fuchsia devices, which we don't support. + ctx.Module().Disable() + return nil + } + if ctx.Target().NativeBridge == android.NativeBridgeEnabled { + ctx.Module().Disable() + return nil + } firstVersion, err := nativeApiLevelFromUser(ctx, String(this.properties.First_version)) if err != nil { @@ -204,30 +240,45 @@ func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps Pa return addStubLibraryCompilerFlags(flags) } -func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, genstubFlags string) (Objects, android.ModuleGenPath) { - arch := ctx.Arch().ArchType.String() +type ndkApiOutputs struct { + stubSrc android.ModuleGenPath + versionScript android.ModuleGenPath + symbolList android.ModuleGenPath +} + +func parseNativeAbiDefinition(ctx ModuleContext, symbolFile string, + apiLevel android.ApiLevel, genstubFlags string) ndkApiOutputs { stubSrcPath := android.PathForModuleGen(ctx, "stub.c") versionScriptPath := android.PathForModuleGen(ctx, "stub.map") symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) + symbolListPath := android.PathForModuleGen(ctx, "abi_symbol_list.txt") apiLevelsJson := android.GetApiLevelsJson(ctx) ctx.Build(pctx, android.BuildParams{ Rule: genStubSrc, Description: "generate stubs " + symbolFilePath.Rel(), - Outputs: []android.WritablePath{stubSrcPath, versionScriptPath}, - Input: symbolFilePath, - Implicits: []android.Path{apiLevelsJson}, + Outputs: []android.WritablePath{stubSrcPath, versionScriptPath, + symbolListPath}, + Input: symbolFilePath, + Implicits: []android.Path{apiLevelsJson}, Args: map[string]string{ - "arch": arch, - "apiLevel": apiLevel, + "arch": ctx.Arch().ArchType.String(), + "apiLevel": apiLevel.String(), "apiMap": apiLevelsJson.String(), "flags": genstubFlags, }, }) - subdir := "" - srcs := []android.Path{stubSrcPath} - return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil, nil), versionScriptPath + return ndkApiOutputs{ + stubSrc: stubSrcPath, + versionScript: versionScriptPath, + symbolList: symbolListPath, + } +} + +func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects { + return compileObjs(ctx, flagsToBuilderFlags(flags), "", + android.Paths{src}, nil, nil) } func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath { @@ -248,6 +299,140 @@ func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.Mo return parsedApiCoveragePath } +func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path { + dep := ctx.GetDirectDepWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix), + stubImplementation) + if dep == nil { + ctx.ModuleErrorf("Could not find implementation for stub") + return nil + } + impl, ok := dep.(*Module) + if !ok { + ctx.ModuleErrorf("Implementation for stub is not correct module type") + } + output := impl.UnstrippedOutputFile() + if output == nil { + ctx.ModuleErrorf("implementation module (%s) has no output", impl) + return nil + } + + return output +} + +func (this *stubDecorator) libraryName(ctx ModuleContext) string { + return strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix) +} + +func (this *stubDecorator) findPrebuiltAbiDump(ctx ModuleContext, + apiLevel android.ApiLevel) android.OptionalPath { + + subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(), + ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.xml") + return android.ExistentPathForSource(ctx, subpath) +} + +// Feature flag. +func canDumpAbi(module android.Module) bool { + return true +} + +// Feature flag to disable diffing against prebuilts. +func canDiffAbi(module android.Module) bool { + return false +} + +func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) { + implementationLibrary := this.findImplementationLibrary(ctx) + this.abiDumpPath = getNdkAbiDumpInstallBase(ctx).Join(ctx, + this.apiLevel.String(), ctx.Arch().ArchType.String(), + this.libraryName(ctx), "abi.xml") + ctx.Build(pctx, android.BuildParams{ + Rule: abidw, + Description: fmt.Sprintf("abidw %s", implementationLibrary), + Output: this.abiDumpPath, + Input: implementationLibrary, + Implicit: symbolList, + Args: map[string]string{ + "symbolList": symbolList.String(), + }, + }) +} + +func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel { + apiLevels := append(ctx.Config().AllSupportedApiLevels(), + android.FutureApiLevel) + for _, api := range apiLevels { + if api.GreaterThan(apiLevel) { + return &api + } + } + return nil +} + +func (this *stubDecorator) diffAbi(ctx ModuleContext) { + missingPrebuiltError := fmt.Sprintf( + "Did not find prebuilt ABI dump for %q. Generate with "+ + "//development/tools/ndk/update_ndk_abi.sh.", this.libraryName(ctx)) + + // Catch any ABI changes compared to the checked-in definition of this API + // level. + abiDiffPath := android.PathForModuleOut(ctx, "abidiff.timestamp") + prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel) + if !prebuiltAbiDump.Valid() { + ctx.Build(pctx, android.BuildParams{ + Rule: android.ErrorRule, + Output: abiDiffPath, + Args: map[string]string{ + "error": missingPrebuiltError, + }, + }) + } else { + ctx.Build(pctx, android.BuildParams{ + Rule: abidiff, + Description: fmt.Sprintf("abidiff %s %s", prebuiltAbiDump, + this.abiDumpPath), + Output: abiDiffPath, + Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath}, + }) + } + this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath) + + // Also ensure that the ABI of the next API level (if there is one) matches + // this API level. *New* ABI is allowed, but any changes to APIs that exist + // in this API level are disallowed. + if !this.apiLevel.IsCurrent() { + nextApiLevel := findNextApiLevel(ctx, this.apiLevel) + if nextApiLevel == nil { + panic(fmt.Errorf("could not determine which API level follows "+ + "non-current API level %s", this.apiLevel)) + } + nextAbiDiffPath := android.PathForModuleOut(ctx, + "abidiff_next.timestamp") + nextAbiDump := this.findPrebuiltAbiDump(ctx, *nextApiLevel) + if !nextAbiDump.Valid() { + ctx.Build(pctx, android.BuildParams{ + Rule: android.ErrorRule, + Output: nextAbiDiffPath, + Args: map[string]string{ + "error": missingPrebuiltError, + }, + }) + } else { + ctx.Build(pctx, android.BuildParams{ + Rule: abidiff, + Description: fmt.Sprintf("abidiff %s %s", this.abiDumpPath, + nextAbiDump), + Output: nextAbiDiffPath, + Inputs: android.Paths{this.abiDumpPath, nextAbiDump.Path()}, + Args: map[string]string{ + "args": "--no-added-syms", + }, + }) + } + this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath) + } +} + func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects { if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") { ctx.PropertyErrorf("symbol_file", "must end with .map.txt") @@ -264,9 +449,15 @@ func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) O } symbolFile := String(c.properties.Symbol_file) - objs, versionScript := compileStubLibrary(ctx, flags, symbolFile, - c.apiLevel.String(), "") - c.versionScriptPath = versionScript + nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "") + objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc) + c.versionScriptPath = nativeAbiResult.versionScript + if canDumpAbi(ctx.Module()) { + c.dumpAbi(ctx, nativeAbiResult.symbolList) + if canDiffAbi(ctx.Module()) { + c.diffAbi(ctx) + } + } if c.apiLevel.IsCurrent() && ctx.PrimaryArch() { c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile) } diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go index d8c500e18..4a9601bca 100644 --- a/cc/ndk_sysroot.go +++ b/cc/ndk_sysroot.go @@ -144,10 +144,9 @@ func (n *ndkSingleton) GenerateBuildActions(ctx android.SingletonContext) { Inputs: licensePaths, }) - baseDepPaths := append(installPaths, combinedLicense) + baseDepPaths := append(installPaths, combinedLicense, + getNdkAbiDiffTimestampFile(ctx)) - // There's a dummy "ndk" rule defined in ndk/Android.mk that depends on - // this. `m ndk` will build the sysroots. ctx.Build(pctx, android.BuildParams{ Rule: android.Touch, Output: getNdkBaseTimestampFile(ctx), @@ -156,6 +155,11 @@ func (n *ndkSingleton) GenerateBuildActions(ctx android.SingletonContext) { fullDepPaths := append(staticLibInstallPaths, getNdkBaseTimestampFile(ctx)) + // There's a phony "ndk" rule defined in core/main.mk that depends on this. + // `m ndk` will build the sysroots for the architectures in the current + // lunch target. `build/soong/scripts/build-ndk-prebuilts.sh` will build the + // sysroots for all the NDK architectures and package them so they can be + // imported into the NDK's build. ctx.Build(pctx, android.BuildParams{ Rule: android.Touch, Output: getNdkFullTimestampFile(ctx), diff --git a/cc/ndkstubgen/__init__.py b/cc/ndkstubgen/__init__.py index 86bf6ff85..2387e697d 100755 --- a/cc/ndkstubgen/__init__.py +++ b/cc/ndkstubgen/__init__.py @@ -18,7 +18,7 @@ import argparse import json import logging -import os +from pathlib import Path import sys from typing import Iterable, TextIO @@ -28,10 +28,12 @@ from symbolfile import Arch, Version class Generator: """Output generator that writes stub source files and version scripts.""" - def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch, - api: int, llndk: bool, apex: bool) -> None: + def __init__(self, src_file: TextIO, version_script: TextIO, + symbol_list: TextIO, arch: Arch, api: int, llndk: bool, + apex: bool) -> None: self.src_file = src_file self.version_script = version_script + self.symbol_list = symbol_list self.arch = arch self.api = api self.llndk = llndk @@ -39,6 +41,7 @@ class Generator: def write(self, versions: Iterable[Version]) -> None: """Writes all symbol data to the output files.""" + self.symbol_list.write('[abi_symbol_list]\n') for version in versions: self.write_version(version) @@ -76,11 +79,11 @@ class Generator: weak = '__attribute__((weak)) ' if 'var' in symbol.tags: - self.src_file.write('{}int {} = 0;\n'.format( - weak, symbol.name)) + self.src_file.write(f'{weak}int {symbol.name} = 0;\n') else: - self.src_file.write('{}void {}() {{}}\n'.format( - weak, symbol.name)) + self.src_file.write(f'{weak}void {symbol.name}() {{}}\n') + + self.symbol_list.write(f'{symbol.name}\n') if not version_empty and section_versioned: base = '' if version.base is None else ' ' + version.base @@ -91,6 +94,10 @@ def parse_args() -> argparse.Namespace: """Parses and returns command line arguments.""" parser = argparse.ArgumentParser() + def resolved_path(raw: str) -> Path: + """Returns a resolved Path for the given string.""" + return Path(raw).resolve() + parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument( @@ -103,26 +110,23 @@ def parse_args() -> argparse.Namespace: parser.add_argument( '--apex', action='store_true', help='Use the APEX variant.') - # https://github.com/python/mypy/issues/1317 - # mypy has issues with using os.path.realpath as an argument here. - parser.add_argument( - '--api-map', - type=os.path.realpath, # type: ignore - required=True, - help='Path to the API level map JSON file.') + parser.add_argument('--api-map', + type=resolved_path, + required=True, + help='Path to the API level map JSON file.') - parser.add_argument( - 'symbol_file', - type=os.path.realpath, # type: ignore - help='Path to symbol file.') - parser.add_argument( - 'stub_src', - type=os.path.realpath, # type: ignore - help='Path to output stub source file.') - parser.add_argument( - 'version_script', - type=os.path.realpath, # type: ignore - help='Path to output version script.') + parser.add_argument('symbol_file', + type=resolved_path, + help='Path to symbol file.') + parser.add_argument('stub_src', + type=resolved_path, + help='Path to output stub source file.') + parser.add_argument('version_script', + type=resolved_path, + help='Path to output version script.') + parser.add_argument('symbol_list', + type=resolved_path, + help='Path to output abigail symbol list.') return parser.parse_args() @@ -131,7 +135,7 @@ def main() -> None: """Program entry point.""" args = parse_args() - with open(args.api_map) as map_file: + with args.api_map.open() as map_file: api_map = json.load(map_file) api = symbolfile.decode_api_level(args.api, api_map) @@ -141,19 +145,20 @@ def main() -> None: verbosity = 2 logging.basicConfig(level=verbose_map[verbosity]) - with open(args.symbol_file) as symbol_file: + with args.symbol_file.open() as symbol_file: try: versions = symbolfile.SymbolFileParser(symbol_file, api_map, args.arch, api, args.llndk, args.apex).parse() except symbolfile.MultiplyDefinedSymbolError as ex: - sys.exit('{}: error: {}'.format(args.symbol_file, ex)) + sys.exit(f'{args.symbol_file}: error: {ex}') - 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, api, - args.llndk, args.apex) - generator.write(versions) + with args.stub_src.open('w') as src_file: + with args.version_script.open('w') as version_script: + with args.symbol_list.open('w') as symbol_list: + generator = Generator(src_file, version_script, symbol_list, + args.arch, api, args.llndk, args.apex) + generator.write(versions) if __name__ == '__main__': diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py index 6d2c9d673..3dbab6180 100755 --- a/cc/ndkstubgen/test_ndkstubgen.py +++ b/cc/ndkstubgen/test_ndkstubgen.py @@ -33,8 +33,10 @@ class GeneratorTest(unittest.TestCase): # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), - 9, False, False) + symbol_list_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, + version_file, symbol_list_file, + Arch('arm'), 9, False, False) version = symbolfile.Version('VERSION_PRIVATE', None, [], [ symbolfile.Symbol('foo', []), @@ -62,8 +64,10 @@ class GeneratorTest(unittest.TestCase): # SymbolPresenceTest. src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), - 9, False, False) + symbol_list_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, + version_file, symbol_list_file, + Arch('arm'), 9, False, False) version = symbolfile.Version('VERSION_1', None, [], [ symbolfile.Symbol('foo', [Tag('x86')]), @@ -96,8 +100,10 @@ class GeneratorTest(unittest.TestCase): def test_write(self) -> None: src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), - 9, False, False) + symbol_list_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, + version_file, symbol_list_file, + Arch('arm'), 9, False, False) versions = [ symbolfile.Version('VERSION_1', None, [], [ @@ -141,6 +147,17 @@ class GeneratorTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) + expected_allowlist = textwrap.dedent("""\ + [abi_symbol_list] + foo + bar + woodly + doodly + baz + qux + """) + self.assertEqual(expected_allowlist, symbol_list_file.getvalue()) + class IntegrationTest(unittest.TestCase): def test_integration(self) -> None: @@ -186,8 +203,10 @@ class IntegrationTest(unittest.TestCase): src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), - 9, False, False) + symbol_list_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, + version_file, symbol_list_file, + Arch('arm'), 9, False, False) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -215,6 +234,16 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) + expected_allowlist = textwrap.dedent("""\ + [abi_symbol_list] + foo + baz + qux + wibble + wobble + """) + self.assertEqual(expected_allowlist, symbol_list_file.getvalue()) + def test_integration_future_api(self) -> None: api_map = { 'O': 9000, @@ -238,8 +267,10 @@ class IntegrationTest(unittest.TestCase): src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), - 9001, False, False) + symbol_list_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, + version_file, symbol_list_file, + Arch('arm'), 9001, False, False) generator.write(versions) expected_src = textwrap.dedent("""\ @@ -257,6 +288,13 @@ class IntegrationTest(unittest.TestCase): """) self.assertEqual(expected_version, version_file.getvalue()) + expected_allowlist = textwrap.dedent("""\ + [abi_symbol_list] + foo + bar + """) + self.assertEqual(expected_allowlist, symbol_list_file.getvalue()) + def test_multiple_definition(self) -> None: input_file = io.StringIO(textwrap.dedent("""\ VERSION_1 { @@ -336,8 +374,10 @@ class IntegrationTest(unittest.TestCase): src_file = io.StringIO() version_file = io.StringIO() - generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), - 9, False, True) + symbol_list_file = io.StringIO() + generator = ndkstubgen.Generator(src_file, + version_file, symbol_list_file, + Arch('arm'), 9, False, True) generator.write(versions) expected_src = textwrap.dedent("""\