// Copyright 2016 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 ( "fmt" "path/filepath" "runtime" "strings" "sync" "github.com/google/blueprint" "github.com/google/blueprint/proptools" "android/soong/android" "android/soong/cc/config" ) func init() { pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") pctx.HostBinToolVariable("stg", "stg") pctx.HostBinToolVariable("stgdiff", "stgdiff") } var ( genStubSrc = pctx.AndroidStaticRule("genStubSrc", blueprint.RuleParams{ Command: "$ndkStubGenerator --arch $arch --api $apiLevel " + "--api-map $apiMap $flags $in $out", CommandDeps: []string{"$ndkStubGenerator"}, }, "arch", "apiLevel", "apiMap", "flags") // $headersList should include paths to public headers. All types // that are defined outside of public headers will be excluded from // ABI monitoring. // // STG tool doesn't access content of files listed in $headersList, // so there is no need to add them to dependencies. stg = pctx.AndroidStaticRule("stg", blueprint.RuleParams{ Command: "$stg -S :$symbolList --file-filter :$headersList --elf $in -o $out", CommandDeps: []string{"$stg"}, }, "symbolList", "headersList") stgdiff = pctx.AndroidStaticRule("stgdiff", 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: "$stgdiff $args --stg $in -o $out || (cat $out && echo 'Run $$ANDROID_BUILD_TOP/development/tools/ndk/update_ndk_abi.sh to update the ABI dumps.' && false)", CommandDeps: []string{"$stgdiff"}, }, "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 // be used directly, but rather through the ApiLevel returning methods // firstVersion() and unversionedUntil(). // Creates a stub shared library based on the provided version file. // // Example: // // ndk_library { // // name: "libfoo", // symbol_file: "libfoo.map.txt", // first_version: "9", // // } type libraryProperties struct { // Relative path to the symbol map. // An example file can be seen here: TODO(danalbert): Make an example. Symbol_file *string `android:"path"` // The first API level a library was available. A library will be generated // for every API level beginning with this one. First_version *string // The first API level that library should have the version script applied. // This defaults to the value of first_version, and should almost never be // used. This is only needed to work around platform bugs like // https://github.com/android-ndk/ndk/issues/265. Unversioned_until *string // Headers presented by this library to the Public API Surface Export_header_libs []string } type stubDecorator struct { *libraryDecorator properties libraryProperties versionScriptPath android.ModuleGenPath parsedCoverageXmlPath android.ModuleOutPath installPath android.Path abiDumpPath android.OutputPath abiDiffPaths android.Paths apiLevel android.ApiLevel firstVersion android.ApiLevel unversionedUntil android.ApiLevel } var _ versionedInterface = (*stubDecorator)(nil) func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool { return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil) } func (stub *stubDecorator) implementationModuleName(name string) string { return strings.TrimSuffix(name, ndkLibrarySuffix) } func ndkLibraryVersions(ctx android.BaseMutatorContext, from android.ApiLevel) []string { var versions []android.ApiLevel versionStrs := []string{} for _, version := range ctx.Config().AllSupportedApiLevels() { if version.GreaterThanOrEqualTo(from) { versions = append(versions, version) versionStrs = append(versionStrs, version.String()) } } versionStrs = append(versionStrs, android.FutureApiLevel.String()) return versionStrs } func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string { if !ctx.Module().Enabled(ctx) { 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 { ctx.PropertyErrorf("first_version", err.Error()) return nil } return ndkLibraryVersions(ctx, firstVersion) } func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool { this.apiLevel = nativeApiLevelOrPanic(ctx, this.stubsVersion()) var err error this.firstVersion, err = nativeApiLevelFromUser(ctx, String(this.properties.First_version)) if err != nil { ctx.PropertyErrorf("first_version", err.Error()) return false } str := proptools.StringDefault(this.properties.Unversioned_until, "minimum") this.unversionedUntil, err = nativeApiLevelFromUser(ctx, str) if err != nil { ctx.PropertyErrorf("unversioned_until", err.Error()) return false } return true } func getNDKKnownLibs(config android.Config) *[]string { return config.Once(ndkKnownLibsKey, func() interface{} { return &[]string{} }).(*[]string) } func (c *stubDecorator) compilerInit(ctx BaseModuleContext) { c.baseCompiler.compilerInit(ctx) name := ctx.baseModuleName() if strings.HasSuffix(name, ndkLibrarySuffix) { ctx.PropertyErrorf("name", "Do not append %q manually, just use the base name", ndkLibrarySuffix) } ndkKnownLibsLock.Lock() defer ndkKnownLibsLock.Unlock() ndkKnownLibs := getNDKKnownLibs(ctx.Config()) for _, lib := range *ndkKnownLibs { if lib == name { return } } *ndkKnownLibs = append(*ndkKnownLibs, name) } var stubLibraryCompilerFlags = []string{ // We're knowingly doing some otherwise unsightly things with builtin // functions here. We're just generating stub libraries, so ignore it. "-Wno-incompatible-library-redeclaration", "-Wno-incomplete-setjmp-declaration", "-Wno-builtin-requires-header", "-Wno-invalid-noreturn", "-Wall", "-Werror", // These libraries aren't actually used. Don't worry about unwinding // (avoids the need to link an unwinder into a fake library). "-fno-unwind-tables", } func init() { pctx.StaticVariable("StubLibraryCompilerFlags", strings.Join(stubLibraryCompilerFlags, " ")) } func addStubLibraryCompilerFlags(flags Flags) Flags { flags.Global.CFlags = append(flags.Global.CFlags, stubLibraryCompilerFlags...) // All symbols in the stubs library should be visible. if inList("-fvisibility=hidden", flags.Local.CFlags) { flags.Local.CFlags = append(flags.Local.CFlags, "-fvisibility=default") } return flags } func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags { flags = stub.baseCompiler.compilerFlags(ctx, flags, deps) return addStubLibraryCompilerFlags(flags) } 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, symbolListPath}, Input: symbolFilePath, Implicits: []android.Path{apiLevelsJson}, Args: map[string]string{ "arch": ctx.Arch().ArchType.String(), "apiLevel": apiLevel.String(), "apiMap": apiLevelsJson.String(), "flags": genstubFlags, }, }) return ndkApiOutputs{ stubSrc: stubSrcPath, versionScript: versionScriptPath, symbolList: symbolListPath, } } func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects { // libc/libm stubs libraries end up mismatching with clang's internal definition of these // functions (which have noreturn attributes and other things). Because we just want to create a // stub with symbol definitions, and types aren't important in C, ignore the mismatch. flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, "-fno-builtin") return compileObjs(ctx, flagsToBuilderFlags(flags), "", android.Paths{src}, nil, nil, nil, nil) } 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") return nil } 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.stg") return android.ExistentPathForSource(ctx, subpath) } // Feature flag. func canDumpAbi(config android.Config) bool { if runtime.GOOS == "darwin" { return false } // http://b/156513478 return config.ReleaseNdkAbiMonitored() } // Feature flag to disable diffing against prebuilts. func canDiffAbi(config android.Config) bool { return config.ReleaseNdkAbiMonitored() } 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.stg") headersList := getNdkABIHeadersFile(ctx) ctx.Build(pctx, android.BuildParams{ Rule: stg, Description: fmt.Sprintf("stg %s", implementationLibrary), Input: implementationLibrary, Implicits: []android.Path{ symbolList, headersList, }, Output: this.abiDumpPath, Args: map[string]string{ "symbolList": symbolList.String(), "headersList": headersList.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) { // Catch any ABI changes compared to the checked-in definition of this API // level. abiDiffPath := android.PathForModuleOut(ctx, "stgdiff.timestamp") prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel) missingPrebuiltErrorTemplate := "Did not find prebuilt ABI dump for %q (%q). Generate with " + "//development/tools/ndk/update_ndk_abi.sh." missingPrebuiltError := fmt.Sprintf( missingPrebuiltErrorTemplate, this.libraryName(ctx), prebuiltAbiDump.InvalidReason()) 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: stgdiff, Description: fmt.Sprintf("Comparing ABI %s %s", prebuiltAbiDump, this.abiDumpPath), Output: abiDiffPath, Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath}, Args: map[string]string{ "args": "--format=small", }, }) } 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() && prebuiltAbiDump.Valid() { 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) missingNextPrebuiltError := fmt.Sprintf( missingPrebuiltErrorTemplate, this.libraryName(ctx), nextAbiDump.InvalidReason()) if !nextAbiDump.Valid() { ctx.Build(pctx, android.BuildParams{ Rule: android.ErrorRule, Output: nextAbiDiffPath, Args: map[string]string{ "error": missingNextPrebuiltError, }, }) } else { ctx.Build(pctx, android.BuildParams{ Rule: stgdiff, Description: fmt.Sprintf( "Comparing ABI to the next API level %s %s", prebuiltAbiDump, nextAbiDump), Output: nextAbiDiffPath, Inputs: android.Paths{ prebuiltAbiDump.Path(), nextAbiDump.Path()}, Args: map[string]string{ "args": "--format=small --ignore=interface_addition", }, }) } 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") } if !c.buildStubs() { // NDK libraries have no implementation variant, nothing to do return Objects{} } if !c.initializeProperties(ctx) { // Emits its own errors, so we don't need to. return Objects{} } symbolFile := String(c.properties.Symbol_file) nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "") objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc) c.versionScriptPath = nativeAbiResult.versionScript if canDumpAbi(ctx.Config()) { c.dumpAbi(ctx, nativeAbiResult.symbolList) if canDiffAbi(ctx.Config()) { c.diffAbi(ctx) } } if c.apiLevel.IsCurrent() && ctx.PrimaryArch() { c.parsedCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile) } return objs } // Add a dependency on the header modules of this ndk_library func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps { return Deps{ HeaderLibs: linker.properties.Export_header_libs, } } func (linker *stubDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) { linker.libraryDecorator.moduleInfoJSON(ctx, moduleInfoJSON) // Overwrites the SubName computed by libraryDecorator moduleInfoJSON.SubName = ndkLibrarySuffix + "." + linker.apiLevel.String() } func (linker *stubDecorator) Name(name string) string { return name + ndkLibrarySuffix } func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags { stub.libraryDecorator.libName = ctx.baseModuleName() return stub.libraryDecorator.linkerFlags(ctx, flags) } func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path { if !stub.buildStubs() { // NDK libraries have no implementation variant, nothing to do return nil } if shouldUseVersionScript(ctx, stub) { linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag) flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) } stub.libraryDecorator.skipAPIDefine = true return stub.libraryDecorator.link(ctx, flags, deps, objs) } func (stub *stubDecorator) nativeCoverage() bool { return false } // Returns the install path for unversioned NDK libraries (currently only static // libraries). func getUnversionedLibraryInstallPath(ctx ModuleContext) android.OutputPath { return getNdkSysrootBase(ctx).Join(ctx, "usr/lib", config.NDKTriple(ctx.toolchain())) } // Returns the install path for versioned NDK libraries. These are most often // stubs, but the same paths are used for CRT objects. func getVersionedLibraryInstallPath(ctx ModuleContext, apiLevel android.ApiLevel) android.OutputPath { return getUnversionedLibraryInstallPath(ctx).Join(ctx, apiLevel.String()) } func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) { installDir := getVersionedLibraryInstallPath(ctx, stub.apiLevel) out := installDir.Join(ctx, path.Base()) ctx.Build(pctx, android.BuildParams{ Rule: android.Cp, Input: path, Output: out, }) stub.installPath = out } func newStubLibrary() *Module { module, library := NewLibrary(android.DeviceSupported) library.BuildOnlyShared() module.stl = nil module.sanitize = nil library.disableStripping() stub := &stubDecorator{ libraryDecorator: library, } module.compiler = stub module.linker = stub module.installer = stub module.library = stub module.Properties.AlwaysSdk = true module.Properties.Sdk_version = StringPtr("current") module.AddProperties(&stub.properties, &library.MutatedProperties) return module } // ndk_library creates a library that exposes a stub implementation of functions // and variables for use at build time only. func NdkLibraryFactory() android.Module { module := newStubLibrary() android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth) return module }