Merge "Use libabigail to track NDK ABIs."

This commit is contained in:
Dan Albert
2021-06-04 21:06:00 +00:00
committed by Gerrit Code Review
9 changed files with 436 additions and 70 deletions

View File

@@ -1566,7 +1566,10 @@ type archConfig struct {
abi []string 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 { func getNdkAbisConfig() []archConfig {
return []archConfig{ return []archConfig{
{"arm", "armv7-a", "", []string{"armeabi-v7a"}}, {"arm", "armv7-a", "", []string{"armeabi-v7a"}},

View File

@@ -65,9 +65,10 @@ bootstrap_go_package {
"test.go", "test.go",
"toolchain_library.go", "toolchain_library.go",
"ndk_prebuilt.go", "ndk_abi.go",
"ndk_headers.go", "ndk_headers.go",
"ndk_library.go", "ndk_library.go",
"ndk_prebuilt.go",
"ndk_sysroot.go", "ndk_sysroot.go",
"llndk_library.go", "llndk_library.go",

View File

@@ -2117,6 +2117,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 // 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 // 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 // sysprop_library, the C++ implementation library has to be linked. syspropImplLibraries is a

View File

@@ -865,16 +865,23 @@ func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps Pa
if library.stubsVersion() != "" { if library.stubsVersion() != "" {
vndkVer = 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) { if !Bool(library.Properties.Llndk.Unversioned) {
library.versionScriptPath = android.OptionalPathForPath(versionScript) library.versionScriptPath = android.OptionalPathForPath(
nativeAbiResult.versionScript)
} }
return objs return objs
} }
if ctx.IsVendorPublicLibrary() { 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) { if !Bool(library.Properties.Vendor_public_library.Unversioned) {
library.versionScriptPath = android.OptionalPathForPath(versionScript) library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.versionScript)
} }
return objs return objs
} }
@@ -884,8 +891,12 @@ func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps Pa
ctx.PropertyErrorf("symbol_file", "%q doesn't have .map.txt suffix", symbolFile) ctx.PropertyErrorf("symbol_file", "%q doesn't have .map.txt suffix", symbolFile)
return Objects{} return Objects{}
} }
objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Stubs.Symbol_file), library.MutatedProperties.StubsVersion, "--apex") nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
library.versionScriptPath = android.OptionalPathForPath(versionScript) android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion),
"--apex")
objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
library.versionScriptPath = android.OptionalPathForPath(
nativeAbiResult.versionScript)
return objs return objs
} }

102
cc/ndk_abi.go Normal file
View File

@@ -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))
}

View File

@@ -16,6 +16,7 @@ package cc
import ( import (
"fmt" "fmt"
"path/filepath"
"strings" "strings"
"sync" "sync"
@@ -28,6 +29,9 @@ import (
func init() { func init() {
pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen")
pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser") pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser")
pctx.HostBinToolVariable("abidiff", "abidiff")
pctx.HostBinToolVariable("abitidy", "abitidy")
pctx.HostBinToolVariable("abidw", "abidw")
} }
var ( var (
@@ -44,11 +48,31 @@ var (
CommandDeps: []string{"$ndk_api_coverage_parser"}, CommandDeps: []string{"$ndk_api_coverage_parser"},
}, "apiMap") }, "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" ndkLibrarySuffix = ".ndk"
ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey") ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey")
// protects ndkKnownLibs writes during parallel BeginMutator. // protects ndkKnownLibs writes during parallel BeginMutator.
ndkKnownLibsLock sync.Mutex ndkKnownLibsLock sync.Mutex
stubImplementation = dependencyTag{name: "stubImplementation"}
) )
// The First_version and Unversioned_until properties of this struct should not // The First_version and Unversioned_until properties of this struct should not
@@ -89,6 +113,8 @@ type stubDecorator struct {
versionScriptPath android.ModuleGenPath versionScriptPath android.ModuleGenPath
parsedCoverageXmlPath android.ModuleOutPath parsedCoverageXmlPath android.ModuleOutPath
installPath android.Path installPath android.Path
abiDumpPath android.OutputPath
abiDiffPaths android.Paths
apiLevel android.ApiLevel apiLevel android.ApiLevel
firstVersion android.ApiLevel firstVersion android.ApiLevel
@@ -123,6 +149,16 @@ func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []strin
if !ctx.Module().Enabled() { if !ctx.Module().Enabled() {
return nil 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, firstVersion, err := nativeApiLevelFromUser(ctx,
String(this.properties.First_version)) String(this.properties.First_version))
if err != nil { if err != nil {
@@ -204,30 +240,45 @@ func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps Pa
return addStubLibraryCompilerFlags(flags) return addStubLibraryCompilerFlags(flags)
} }
func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, genstubFlags string) (Objects, android.ModuleGenPath) { type ndkApiOutputs struct {
arch := ctx.Arch().ArchType.String() 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") 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)
symbolListPath := android.PathForModuleGen(ctx, "abi_symbol_list.txt")
apiLevelsJson := android.GetApiLevelsJson(ctx) apiLevelsJson := android.GetApiLevelsJson(ctx)
ctx.Build(pctx, android.BuildParams{ ctx.Build(pctx, android.BuildParams{
Rule: genStubSrc, Rule: genStubSrc,
Description: "generate stubs " + symbolFilePath.Rel(), Description: "generate stubs " + symbolFilePath.Rel(),
Outputs: []android.WritablePath{stubSrcPath, versionScriptPath}, Outputs: []android.WritablePath{stubSrcPath, versionScriptPath,
Input: symbolFilePath, symbolListPath},
Implicits: []android.Path{apiLevelsJson}, Input: symbolFilePath,
Implicits: []android.Path{apiLevelsJson},
Args: map[string]string{ Args: map[string]string{
"arch": arch, "arch": ctx.Arch().ArchType.String(),
"apiLevel": apiLevel, "apiLevel": apiLevel.String(),
"apiMap": apiLevelsJson.String(), "apiMap": apiLevelsJson.String(),
"flags": genstubFlags, "flags": genstubFlags,
}, },
}) })
subdir := "" return ndkApiOutputs{
srcs := []android.Path{stubSrcPath} stubSrc: stubSrcPath,
return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil, nil), versionScriptPath 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 { func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath {
@@ -248,6 +299,140 @@ func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.Mo
return parsedApiCoveragePath 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 { func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") { if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") {
ctx.PropertyErrorf("symbol_file", "must end with .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) symbolFile := String(c.properties.Symbol_file)
objs, versionScript := compileStubLibrary(ctx, flags, symbolFile, nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
c.apiLevel.String(), "") objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
c.versionScriptPath = versionScript 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() { if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile) c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile)
} }

View File

@@ -144,10 +144,9 @@ func (n *ndkSingleton) GenerateBuildActions(ctx android.SingletonContext) {
Inputs: licensePaths, 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{ ctx.Build(pctx, android.BuildParams{
Rule: android.Touch, Rule: android.Touch,
Output: getNdkBaseTimestampFile(ctx), Output: getNdkBaseTimestampFile(ctx),
@@ -156,6 +155,11 @@ func (n *ndkSingleton) GenerateBuildActions(ctx android.SingletonContext) {
fullDepPaths := append(staticLibInstallPaths, getNdkBaseTimestampFile(ctx)) 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{ ctx.Build(pctx, android.BuildParams{
Rule: android.Touch, Rule: android.Touch,
Output: getNdkFullTimestampFile(ctx), Output: getNdkFullTimestampFile(ctx),

View File

@@ -18,7 +18,7 @@
import argparse import argparse
import json import json
import logging import logging
import os from pathlib import Path
import sys import sys
from typing import Iterable, TextIO from typing import Iterable, TextIO
@@ -28,10 +28,12 @@ from symbolfile import Arch, Version
class Generator: class Generator:
"""Output generator that writes stub source files and version scripts.""" """Output generator that writes stub source files and version scripts."""
def __init__(self, src_file: TextIO, version_script: TextIO, arch: Arch, def __init__(self, src_file: TextIO, version_script: TextIO,
api: int, llndk: bool, apex: bool) -> None: symbol_list: TextIO, arch: Arch, api: int, llndk: bool,
apex: bool) -> None:
self.src_file = src_file self.src_file = src_file
self.version_script = version_script self.version_script = version_script
self.symbol_list = symbol_list
self.arch = arch self.arch = arch
self.api = api self.api = api
self.llndk = llndk self.llndk = llndk
@@ -39,6 +41,7 @@ class Generator:
def write(self, versions: Iterable[Version]) -> None: def write(self, versions: Iterable[Version]) -> None:
"""Writes all symbol data to the output files.""" """Writes all symbol data to the output files."""
self.symbol_list.write('[abi_symbol_list]\n')
for version in versions: for version in versions:
self.write_version(version) self.write_version(version)
@@ -76,11 +79,11 @@ class Generator:
weak = '__attribute__((weak)) ' weak = '__attribute__((weak)) '
if 'var' in symbol.tags: if 'var' in symbol.tags:
self.src_file.write('{}int {} = 0;\n'.format( self.src_file.write(f'{weak}int {symbol.name} = 0;\n')
weak, symbol.name))
else: else:
self.src_file.write('{}void {}() {{}}\n'.format( self.src_file.write(f'{weak}void {symbol.name}() {{}}\n')
weak, symbol.name))
self.symbol_list.write(f'{symbol.name}\n')
if not version_empty and section_versioned: if not version_empty and section_versioned:
base = '' if version.base is None else ' ' + version.base base = '' if version.base is None else ' ' + version.base
@@ -91,6 +94,10 @@ def parse_args() -> argparse.Namespace:
"""Parses and returns command line arguments.""" """Parses and returns command line arguments."""
parser = argparse.ArgumentParser() 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('-v', '--verbose', action='count', default=0)
parser.add_argument( parser.add_argument(
@@ -103,26 +110,23 @@ def parse_args() -> argparse.Namespace:
parser.add_argument( parser.add_argument(
'--apex', action='store_true', help='Use the APEX variant.') '--apex', action='store_true', help='Use the APEX variant.')
# https://github.com/python/mypy/issues/1317 parser.add_argument('--api-map',
# mypy has issues with using os.path.realpath as an argument here. type=resolved_path,
parser.add_argument( required=True,
'--api-map', help='Path to the API level map JSON file.')
type=os.path.realpath, # type: ignore
required=True,
help='Path to the API level map JSON file.')
parser.add_argument( parser.add_argument('symbol_file',
'symbol_file', type=resolved_path,
type=os.path.realpath, # type: ignore help='Path to symbol file.')
help='Path to symbol file.') parser.add_argument('stub_src',
parser.add_argument( type=resolved_path,
'stub_src', help='Path to output stub source file.')
type=os.path.realpath, # type: ignore parser.add_argument('version_script',
help='Path to output stub source file.') type=resolved_path,
parser.add_argument( help='Path to output version script.')
'version_script', parser.add_argument('symbol_list',
type=os.path.realpath, # type: ignore type=resolved_path,
help='Path to output version script.') help='Path to output abigail symbol list.')
return parser.parse_args() return parser.parse_args()
@@ -131,7 +135,7 @@ def main() -> None:
"""Program entry point.""" """Program entry point."""
args = parse_args() 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_map = json.load(map_file)
api = symbolfile.decode_api_level(args.api, api_map) api = symbolfile.decode_api_level(args.api, api_map)
@@ -141,19 +145,20 @@ def main() -> None:
verbosity = 2 verbosity = 2
logging.basicConfig(level=verbose_map[verbosity]) logging.basicConfig(level=verbose_map[verbosity])
with open(args.symbol_file) as symbol_file: with args.symbol_file.open() as symbol_file:
try: try:
versions = symbolfile.SymbolFileParser(symbol_file, api_map, versions = symbolfile.SymbolFileParser(symbol_file, api_map,
args.arch, api, args.llndk, args.arch, api, args.llndk,
args.apex).parse() args.apex).parse()
except symbolfile.MultiplyDefinedSymbolError as ex: 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 args.stub_src.open('w') as src_file:
with open(args.version_script, 'w') as version_file: with args.version_script.open('w') as version_script:
generator = Generator(src_file, version_file, args.arch, api, with args.symbol_list.open('w') as symbol_list:
args.llndk, args.apex) generator = Generator(src_file, version_script, symbol_list,
generator.write(versions) args.arch, api, args.llndk, args.apex)
generator.write(versions)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -33,8 +33,10 @@ class GeneratorTest(unittest.TestCase):
# OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest. # OmitVersionTest, PrivateVersionTest, and SymbolPresenceTest.
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), symbol_list_file = io.StringIO()
9, False, False) generator = ndkstubgen.Generator(src_file,
version_file, symbol_list_file,
Arch('arm'), 9, False, False)
version = symbolfile.Version('VERSION_PRIVATE', None, [], [ version = symbolfile.Version('VERSION_PRIVATE', None, [], [
symbolfile.Symbol('foo', []), symbolfile.Symbol('foo', []),
@@ -62,8 +64,10 @@ class GeneratorTest(unittest.TestCase):
# SymbolPresenceTest. # SymbolPresenceTest.
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), symbol_list_file = io.StringIO()
9, False, False) generator = ndkstubgen.Generator(src_file,
version_file, symbol_list_file,
Arch('arm'), 9, False, False)
version = symbolfile.Version('VERSION_1', None, [], [ version = symbolfile.Version('VERSION_1', None, [], [
symbolfile.Symbol('foo', [Tag('x86')]), symbolfile.Symbol('foo', [Tag('x86')]),
@@ -96,8 +100,10 @@ class GeneratorTest(unittest.TestCase):
def test_write(self) -> None: def test_write(self) -> None:
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), symbol_list_file = io.StringIO()
9, False, False) generator = ndkstubgen.Generator(src_file,
version_file, symbol_list_file,
Arch('arm'), 9, False, False)
versions = [ versions = [
symbolfile.Version('VERSION_1', None, [], [ symbolfile.Version('VERSION_1', None, [], [
@@ -141,6 +147,17 @@ class GeneratorTest(unittest.TestCase):
""") """)
self.assertEqual(expected_version, version_file.getvalue()) 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): class IntegrationTest(unittest.TestCase):
def test_integration(self) -> None: def test_integration(self) -> None:
@@ -186,8 +203,10 @@ class IntegrationTest(unittest.TestCase):
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), symbol_list_file = io.StringIO()
9, False, False) generator = ndkstubgen.Generator(src_file,
version_file, symbol_list_file,
Arch('arm'), 9, False, False)
generator.write(versions) generator.write(versions)
expected_src = textwrap.dedent("""\ expected_src = textwrap.dedent("""\
@@ -215,6 +234,16 @@ class IntegrationTest(unittest.TestCase):
""") """)
self.assertEqual(expected_version, version_file.getvalue()) 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: def test_integration_future_api(self) -> None:
api_map = { api_map = {
'O': 9000, 'O': 9000,
@@ -238,8 +267,10 @@ class IntegrationTest(unittest.TestCase):
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), symbol_list_file = io.StringIO()
9001, False, False) generator = ndkstubgen.Generator(src_file,
version_file, symbol_list_file,
Arch('arm'), 9001, False, False)
generator.write(versions) generator.write(versions)
expected_src = textwrap.dedent("""\ expected_src = textwrap.dedent("""\
@@ -257,6 +288,13 @@ class IntegrationTest(unittest.TestCase):
""") """)
self.assertEqual(expected_version, version_file.getvalue()) 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: def test_multiple_definition(self) -> None:
input_file = io.StringIO(textwrap.dedent("""\ input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 { VERSION_1 {
@@ -336,8 +374,10 @@ class IntegrationTest(unittest.TestCase):
src_file = io.StringIO() src_file = io.StringIO()
version_file = io.StringIO() version_file = io.StringIO()
generator = ndkstubgen.Generator(src_file, version_file, Arch('arm'), symbol_list_file = io.StringIO()
9, False, True) generator = ndkstubgen.Generator(src_file,
version_file, symbol_list_file,
Arch('arm'), 9, False, True)
generator.write(versions) generator.write(versions)
expected_src = textwrap.dedent("""\ expected_src = textwrap.dedent("""\