1// Copyright 2016 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package cc 16 17import ( 18 "fmt" 19 "strconv" 20 "strings" 21 "sync" 22 23 "github.com/google/blueprint" 24 25 "android/soong/android" 26) 27 28func init() { 29 pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") 30 pctx.HostBinToolVariable("ndk_api_coverage_parser", "ndk_api_coverage_parser") 31} 32 33var ( 34 genStubSrc = pctx.AndroidStaticRule("genStubSrc", 35 blueprint.RuleParams{ 36 Command: "$ndkStubGenerator --arch $arch --api $apiLevel " + 37 "--api-map $apiMap $flags $in $out", 38 CommandDeps: []string{"$ndkStubGenerator"}, 39 }, "arch", "apiLevel", "apiMap", "flags") 40 41 parseNdkApiRule = pctx.AndroidStaticRule("parseNdkApiRule", 42 blueprint.RuleParams{ 43 Command: "$ndk_api_coverage_parser $in $out --api-map $apiMap", 44 CommandDeps: []string{"$ndk_api_coverage_parser"}, 45 }, "apiMap") 46 47 ndkLibrarySuffix = ".ndk" 48 49 // Added as a variation dependency via depsMutator. 50 ndkKnownLibs = []string{} 51 // protects ndkKnownLibs writes during parallel BeginMutator. 52 ndkKnownLibsLock sync.Mutex 53) 54 55// Creates a stub shared library based on the provided version file. 56// 57// Example: 58// 59// ndk_library { 60// name: "libfoo", 61// symbol_file: "libfoo.map.txt", 62// first_version: "9", 63// } 64// 65type libraryProperties struct { 66 // Relative path to the symbol map. 67 // An example file can be seen here: TODO(danalbert): Make an example. 68 Symbol_file *string 69 70 // The first API level a library was available. A library will be generated 71 // for every API level beginning with this one. 72 First_version *string 73 74 // The first API level that library should have the version script applied. 75 // This defaults to the value of first_version, and should almost never be 76 // used. This is only needed to work around platform bugs like 77 // https://github.com/android-ndk/ndk/issues/265. 78 Unversioned_until *string 79 80 // Private property for use by the mutator that splits per-API level. Can be 81 // one of <number:sdk_version> or <codename> or "current" passed to 82 // "ndkstubgen.py" as it is 83 ApiLevel string `blueprint:"mutated"` 84 85 // True if this API is not yet ready to be shipped in the NDK. It will be 86 // available in the platform for testing, but will be excluded from the 87 // sysroot provided to the NDK proper. 88 Draft bool 89} 90 91type stubDecorator struct { 92 *libraryDecorator 93 94 properties libraryProperties 95 96 versionScriptPath android.ModuleGenPath 97 parsedCoverageXmlPath android.ModuleOutPath 98 installPath android.Path 99} 100 101// OMG GO 102func intMax(a int, b int) int { 103 if a > b { 104 return a 105 } else { 106 return b 107 } 108} 109 110func normalizeNdkApiLevel(ctx android.BaseModuleContext, apiLevel string, 111 arch android.Arch) (string, error) { 112 113 if apiLevel == "current" { 114 return apiLevel, nil 115 } 116 117 minVersion := ctx.Config().MinSupportedSdkVersion() 118 firstArchVersions := map[android.ArchType]int{ 119 android.Arm: minVersion, 120 android.Arm64: 21, 121 android.X86: minVersion, 122 android.X86_64: 21, 123 } 124 125 firstArchVersion, ok := firstArchVersions[arch.ArchType] 126 if !ok { 127 panic(fmt.Errorf("Arch %q not found in firstArchVersions", arch.ArchType)) 128 } 129 130 if apiLevel == "minimum" { 131 return strconv.Itoa(firstArchVersion), nil 132 } 133 134 // If the NDK drops support for a platform version, we don't want to have to 135 // fix up every module that was using it as its SDK version. Clip to the 136 // supported version here instead. 137 version, err := strconv.Atoi(apiLevel) 138 if err != nil { 139 return "", fmt.Errorf("API level must be an integer (is %q)", apiLevel) 140 } 141 version = intMax(version, minVersion) 142 143 return strconv.Itoa(intMax(version, firstArchVersion)), nil 144} 145 146func getFirstGeneratedVersion(firstSupportedVersion string, platformVersion int) (int, error) { 147 if firstSupportedVersion == "current" { 148 return platformVersion + 1, nil 149 } 150 151 return strconv.Atoi(firstSupportedVersion) 152} 153 154func shouldUseVersionScript(ctx android.BaseModuleContext, stub *stubDecorator) (bool, error) { 155 // unversioned_until is normally empty, in which case we should use the version script. 156 if String(stub.properties.Unversioned_until) == "" { 157 return true, nil 158 } 159 160 if String(stub.properties.Unversioned_until) == "current" { 161 if stub.properties.ApiLevel == "current" { 162 return true, nil 163 } else { 164 return false, nil 165 } 166 } 167 168 if stub.properties.ApiLevel == "current" { 169 return true, nil 170 } 171 172 unversionedUntil, err := android.ApiStrToNum(ctx, String(stub.properties.Unversioned_until)) 173 if err != nil { 174 return true, err 175 } 176 177 version, err := android.ApiStrToNum(ctx, stub.properties.ApiLevel) 178 if err != nil { 179 return true, err 180 } 181 182 return version >= unversionedUntil, nil 183} 184 185func generateStubApiVariants(mctx android.BottomUpMutatorContext, c *stubDecorator) { 186 platformVersion := mctx.Config().PlatformSdkVersionInt() 187 188 firstSupportedVersion, err := normalizeNdkApiLevel(mctx, String(c.properties.First_version), 189 mctx.Arch()) 190 if err != nil { 191 mctx.PropertyErrorf("first_version", err.Error()) 192 } 193 194 firstGenVersion, err := getFirstGeneratedVersion(firstSupportedVersion, platformVersion) 195 if err != nil { 196 // In theory this is impossible because we've already run this through 197 // normalizeNdkApiLevel above. 198 mctx.PropertyErrorf("first_version", err.Error()) 199 } 200 201 var versionStrs []string 202 for version := firstGenVersion; version <= platformVersion; version++ { 203 versionStrs = append(versionStrs, strconv.Itoa(version)) 204 } 205 versionStrs = append(versionStrs, mctx.Config().PlatformVersionActiveCodenames()...) 206 versionStrs = append(versionStrs, "current") 207 208 modules := mctx.CreateVariations(versionStrs...) 209 for i, module := range modules { 210 module.(*Module).compiler.(*stubDecorator).properties.ApiLevel = versionStrs[i] 211 } 212} 213 214func NdkApiMutator(mctx android.BottomUpMutatorContext) { 215 if m, ok := mctx.Module().(*Module); ok { 216 if m.Enabled() { 217 if compiler, ok := m.compiler.(*stubDecorator); ok { 218 generateStubApiVariants(mctx, compiler) 219 } 220 } 221 } 222} 223 224func (c *stubDecorator) compilerInit(ctx BaseModuleContext) { 225 c.baseCompiler.compilerInit(ctx) 226 227 name := ctx.baseModuleName() 228 if strings.HasSuffix(name, ndkLibrarySuffix) { 229 ctx.PropertyErrorf("name", "Do not append %q manually, just use the base name", ndkLibrarySuffix) 230 } 231 232 ndkKnownLibsLock.Lock() 233 defer ndkKnownLibsLock.Unlock() 234 for _, lib := range ndkKnownLibs { 235 if lib == name { 236 return 237 } 238 } 239 ndkKnownLibs = append(ndkKnownLibs, name) 240} 241 242func addStubLibraryCompilerFlags(flags Flags) Flags { 243 flags.Global.CFlags = append(flags.Global.CFlags, 244 // We're knowingly doing some otherwise unsightly things with builtin 245 // functions here. We're just generating stub libraries, so ignore it. 246 "-Wno-incompatible-library-redeclaration", 247 "-Wno-incomplete-setjmp-declaration", 248 "-Wno-builtin-requires-header", 249 "-Wno-invalid-noreturn", 250 "-Wall", 251 "-Werror", 252 // These libraries aren't actually used. Don't worry about unwinding 253 // (avoids the need to link an unwinder into a fake library). 254 "-fno-unwind-tables", 255 ) 256 // All symbols in the stubs library should be visible. 257 if inList("-fvisibility=hidden", flags.Local.CFlags) { 258 flags.Local.CFlags = append(flags.Local.CFlags, "-fvisibility=default") 259 } 260 return flags 261} 262 263func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags { 264 flags = stub.baseCompiler.compilerFlags(ctx, flags, deps) 265 return addStubLibraryCompilerFlags(flags) 266} 267 268func compileStubLibrary(ctx ModuleContext, flags Flags, symbolFile, apiLevel, genstubFlags string) (Objects, android.ModuleGenPath) { 269 arch := ctx.Arch().ArchType.String() 270 271 stubSrcPath := android.PathForModuleGen(ctx, "stub.c") 272 versionScriptPath := android.PathForModuleGen(ctx, "stub.map") 273 symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) 274 apiLevelsJson := android.GetApiLevelsJson(ctx) 275 ctx.Build(pctx, android.BuildParams{ 276 Rule: genStubSrc, 277 Description: "generate stubs " + symbolFilePath.Rel(), 278 Outputs: []android.WritablePath{stubSrcPath, versionScriptPath}, 279 Input: symbolFilePath, 280 Implicits: []android.Path{apiLevelsJson}, 281 Args: map[string]string{ 282 "arch": arch, 283 "apiLevel": apiLevel, 284 "apiMap": apiLevelsJson.String(), 285 "flags": genstubFlags, 286 }, 287 }) 288 289 subdir := "" 290 srcs := []android.Path{stubSrcPath} 291 return compileObjs(ctx, flagsToBuilderFlags(flags), subdir, srcs, nil, nil), versionScriptPath 292} 293 294func parseSymbolFileForCoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath { 295 apiLevelsJson := android.GetApiLevelsJson(ctx) 296 symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) 297 outputFileName := strings.Split(symbolFilePath.Base(), ".")[0] 298 parsedApiCoveragePath := android.PathForModuleOut(ctx, outputFileName+".xml") 299 ctx.Build(pctx, android.BuildParams{ 300 Rule: parseNdkApiRule, 301 Description: "parse ndk api symbol file for api coverage: " + symbolFilePath.Rel(), 302 Outputs: []android.WritablePath{parsedApiCoveragePath}, 303 Input: symbolFilePath, 304 Implicits: []android.Path{apiLevelsJson}, 305 Args: map[string]string{ 306 "apiMap": apiLevelsJson.String(), 307 }, 308 }) 309 return parsedApiCoveragePath 310} 311 312func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects { 313 if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") { 314 ctx.PropertyErrorf("symbol_file", "must end with .map.txt") 315 } 316 317 symbolFile := String(c.properties.Symbol_file) 318 objs, versionScript := compileStubLibrary(ctx, flags, symbolFile, 319 c.properties.ApiLevel, "") 320 c.versionScriptPath = versionScript 321 if c.properties.ApiLevel == "current" && ctx.PrimaryArch() { 322 c.parsedCoverageXmlPath = parseSymbolFileForCoverage(ctx, symbolFile) 323 } 324 return objs 325} 326 327func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps { 328 return Deps{} 329} 330 331func (linker *stubDecorator) Name(name string) string { 332 return name + ndkLibrarySuffix 333} 334 335func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags { 336 stub.libraryDecorator.libName = ctx.baseModuleName() 337 return stub.libraryDecorator.linkerFlags(ctx, flags) 338} 339 340func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, 341 objs Objects) android.Path { 342 343 useVersionScript, err := shouldUseVersionScript(ctx, stub) 344 if err != nil { 345 ctx.ModuleErrorf(err.Error()) 346 } 347 348 if useVersionScript { 349 linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() 350 flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag) 351 flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) 352 } 353 354 return stub.libraryDecorator.link(ctx, flags, deps, objs) 355} 356 357func (stub *stubDecorator) nativeCoverage() bool { 358 return false 359} 360 361func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) { 362 arch := ctx.Target().Arch.ArchType.Name 363 apiLevel := stub.properties.ApiLevel 364 365 // arm64 isn't actually a multilib toolchain, so unlike the other LP64 366 // architectures it's just installed to lib. 367 libDir := "lib" 368 if ctx.toolchain().Is64Bit() && arch != "arm64" { 369 libDir = "lib64" 370 } 371 372 installDir := getNdkInstallBase(ctx).Join(ctx, fmt.Sprintf( 373 "platforms/android-%s/arch-%s/usr/%s", apiLevel, arch, libDir)) 374 stub.installPath = ctx.InstallFile(installDir, path.Base(), path) 375} 376 377func newStubLibrary() *Module { 378 module, library := NewLibrary(android.DeviceSupported) 379 library.BuildOnlyShared() 380 module.stl = nil 381 module.sanitize = nil 382 library.StripProperties.Strip.None = BoolPtr(true) 383 384 stub := &stubDecorator{ 385 libraryDecorator: library, 386 } 387 module.compiler = stub 388 module.linker = stub 389 module.installer = stub 390 391 module.Properties.AlwaysSdk = true 392 module.Properties.Sdk_version = StringPtr("current") 393 394 module.AddProperties(&stub.properties, &library.MutatedProperties) 395 396 return module 397} 398 399// ndk_library creates a library that exposes a stub implementation of functions 400// and variables for use at build time only. 401func NdkLibraryFactory() android.Module { 402 module := newStubLibrary() 403 android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth) 404 module.ModuleBase.EnableNativeBridgeSupportByDefault() 405 return module 406} 407