// 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" "strings" "sync" "github.com/google/blueprint" "android/soong/android" ) func init() { pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser") } 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") parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule", blueprint.RuleParams{ Command: "$ndk_api_coverage_parser $in $out --api-map $apiMap", CommandDeps: []string{"$ndk_api_coverage_parser"}, }, "apiMap") ndkLibrarySuffix = ".ndk" // Added as a variation dependency via depsMutator. ndkKnownLibs = []string{} // protects ndkKnownLibs writes during parallel BeginMutator. ndkKnownLibsLock sync.Mutex ) // 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 // 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 // Use via apiLevel on the stubDecorator. ApiLevel string `blueprint:"mutated"` // True if this API is not yet ready to be shipped in the NDK. It will be // available in the platform for testing, but will be excluded from the // sysroot provided to the NDK proper. Draft bool } type stubDecorator struct { *libraryDecorator properties libraryProperties versionScriptPath android.ModuleGenPath parsedCoverageXmlPath android.ModuleOutPath installPath android.Path apiLevel android.ApiLevel firstVersion android.ApiLevel unversionedUntil android.ApiLevel } func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool { return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil) } func generatePerApiVariants(ctx android.BottomUpMutatorContext, m *Module, from android.ApiLevel, perSplit func(*Module, android.ApiLevel)) { 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()) } } versions = append(versions, android.FutureApiLevel) versionStrs = append(versionStrs, android.FutureApiLevel.String()) modules := ctx.CreateVariations(versionStrs...) for i, module := range modules { perSplit(module.(*Module), versions[i]) } } func NdkApiMutator(ctx android.BottomUpMutatorContext) { if m, ok := ctx.Module().(*Module); ok { if m.Enabled() { if compiler, ok := m.compiler.(*stubDecorator); ok { 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 } firstVersion, err := nativeApiLevelFromUser(ctx, String(compiler.properties.First_version)) if err != nil { ctx.PropertyErrorf("first_version", err.Error()) return } generatePerApiVariants(ctx, m, firstVersion, func(m *Module, version android.ApiLevel) { m.compiler.(*stubDecorator).properties.ApiLevel = version.String() }) } else if m.SplitPerApiLevel() && m.IsSdkVariant() { if ctx.Os() != android.Android { return } from, err := nativeApiLevelFromUser(ctx, m.MinSdkVersion()) if err != nil { ctx.PropertyErrorf("min_sdk_version", err.Error()) return } generatePerApiVariants(ctx, m, from, func(m *Module, version android.ApiLevel) { m.Properties.Sdk_version = StringPtr(version.String()) }) } } } } func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool { this.apiLevel = nativeApiLevelOrPanic(ctx, this.properties.ApiLevel) var err error this.firstVersion, err = nativeApiLevelFromUser(ctx, String(this.properties.First_version)) if err != nil { ctx.PropertyErrorf("first_version", err.Error()) return false } this.unversionedUntil, err = nativeApiLevelFromUserWithDefault(ctx, String(this.properties.Unversioned_until), "minimum") if err != nil { ctx.PropertyErrorf("unversioned_until", err.Error()) return false } return true } 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() for _, lib := range ndkKnownLibs { if lib == name { return } } ndkKnownLibs = append(ndkKnownLibs, name) } func addStubLibraryCompilerFlags(flags Flags) Flags { flags.Global.CFlags = append(flags.Global.CFlags, // 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", ) // 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) } func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, genstubFlags string) (Objects, android.ModuleGenPath) { arch := ctx.Arch().ArchType.String() stubSrcPath := android.PathForModuleGen(ctx, "stub.c") versionScriptPath := android.PathForModuleGen(ctx, "stub.map") symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) 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}, Args: map[string]string{ "arch": arch, "apiLevel": apiLevel, "apiMap": apiLevelsJson.String(), "flags": genstubFlags, }, }) subdir := "" srcs := []android.Path{stubSrcPath} return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil, nil), versionScriptPath } func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath { apiLevelsJson := android.GetApiLevelsJson(ctx) symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) outputFileName := strings.Split(symbolFilePath.Base(), ".")[0] parsedApiCoveragePath := android.PathForModuleOut(ctx, outputFileName+".xml") ctx.Build(pctx, android.BuildParams{ Rule: parseNdkApiRule, Description: "parse ndk api symbol file for api coverage: " + symbolFilePath.Rel(), Outputs: []android.WritablePath{parsedApiCoveragePath}, Input: symbolFilePath, Implicits: []android.Path{apiLevelsJson}, Args: map[string]string{ "apiMap": apiLevelsJson.String(), }, }) return parsedApiCoveragePath } 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.initializeProperties(ctx) { // Emits its own errors, so we don't need to. return Objects{} } symbolFile := String(c.properties.Symbol_file) objs, versionScript := compileStubLibrary(ctx, flags, symbolFile, c.apiLevel.String(), "") c.versionScriptPath = versionScript if c.apiLevel.IsCurrent() && ctx.PrimaryArch() { c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile) } return objs } func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps { return Deps{} } 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 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) } return stub.libraryDecorator.link(ctx, flags, deps, objs) } func (stub *stubDecorator) nativeCoverage() bool { return false } func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) { arch := ctx.Target().Arch.ArchType.Name // arm64 isn't actually a multilib toolchain, so unlike the other LP64 // architectures it's just installed to lib. libDir := "lib" if ctx.toolchain().Is64Bit() && arch != "arm64" { libDir = "lib64" } installDir := getNdkInstallBase(ctx).Join(ctx, fmt.Sprintf( "platforms/android-%s/arch-%s/usr/%s", stub.apiLevel, arch, libDir)) stub.installPath = ctx.InstallFile(installDir, path.Base(), path) } 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.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) module.ModuleBase.EnableNativeBridgeSupportByDefault() return module }