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 "path/filepath" 20 "runtime" 21 "strings" 22 "sync" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/proptools" 26 27 "android/soong/android" 28 "android/soong/cc/config" 29) 30 31func init() { 32 pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") 33 pctx.HostBinToolVariable("stg", "stg") 34 pctx.HostBinToolVariable("stgdiff", "stgdiff") 35} 36 37var ( 38 genStubSrc = pctx.AndroidStaticRule("genStubSrc", 39 blueprint.RuleParams{ 40 Command: "$ndkStubGenerator --arch $arch --api $apiLevel " + 41 "--api-map $apiMap $flags $in $out", 42 CommandDeps: []string{"$ndkStubGenerator"}, 43 }, "arch", "apiLevel", "apiMap", "flags") 44 45 // $headersList should include paths to public headers. All types 46 // that are defined outside of public headers will be excluded from 47 // ABI monitoring. 48 // 49 // STG tool doesn't access content of files listed in $headersList, 50 // so there is no need to add them to dependencies. 51 stg = pctx.AndroidStaticRule("stg", 52 blueprint.RuleParams{ 53 Command: "$stg -S :$symbolList --file-filter :$headersList --elf $in -o $out", 54 CommandDeps: []string{"$stg"}, 55 }, "symbolList", "headersList") 56 57 stgdiff = pctx.AndroidStaticRule("stgdiff", 58 blueprint.RuleParams{ 59 // Need to create *some* output for ninja. We don't want to use tee 60 // because we don't want to spam the build output with "nothing 61 // changed" messages, so redirect output message to $out, and if 62 // changes were detected print the output and fail. 63 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)", 64 CommandDeps: []string{"$stgdiff"}, 65 }, "args") 66 67 ndkLibrarySuffix = ".ndk" 68 69 ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey") 70 // protects ndkKnownLibs writes during parallel BeginMutator. 71 ndkKnownLibsLock sync.Mutex 72 73 stubImplementation = dependencyTag{name: "stubImplementation"} 74) 75 76// The First_version and Unversioned_until properties of this struct should not 77// be used directly, but rather through the ApiLevel returning methods 78// firstVersion() and unversionedUntil(). 79 80// Creates a stub shared library based on the provided version file. 81// 82// Example: 83// 84// ndk_library { 85// 86// name: "libfoo", 87// symbol_file: "libfoo.map.txt", 88// first_version: "9", 89// 90// } 91type libraryProperties struct { 92 // Relative path to the symbol map. 93 // An example file can be seen here: TODO(danalbert): Make an example. 94 Symbol_file *string `android:"path"` 95 96 // The first API level a library was available. A library will be generated 97 // for every API level beginning with this one. 98 First_version *string 99 100 // The first API level that library should have the version script applied. 101 // This defaults to the value of first_version, and should almost never be 102 // used. This is only needed to work around platform bugs like 103 // https://github.com/android-ndk/ndk/issues/265. 104 Unversioned_until *string 105 106 // DO NOT USE THIS 107 // NDK libraries should not export their headers. Headers belonging to NDK 108 // libraries should be added to the NDK with an ndk_headers module. 109 Export_header_libs []string 110 111 // Do not add other export_* properties without consulting with danalbert@. 112 // Consumers of ndk_library modules should emulate the typical NDK build 113 // behavior as closely as possible (that is, all NDK APIs are exposed to 114 // builds via --sysroot). Export behaviors used in Soong will not be present 115 // for app developers as they don't use Soong, and reliance on these export 116 // behaviors can mask issues with the NDK sysroot. 117} 118 119type stubDecorator struct { 120 *libraryDecorator 121 122 properties libraryProperties 123 124 versionScriptPath android.ModuleGenPath 125 parsedCoverageXmlPath android.ModuleOutPath 126 installPath android.Path 127 abiDumpPath android.OutputPath 128 hasAbiDump bool 129 abiDiffPaths android.Paths 130 131 apiLevel android.ApiLevel 132 firstVersion android.ApiLevel 133 unversionedUntil android.ApiLevel 134} 135 136var _ VersionedInterface = (*stubDecorator)(nil) 137 138func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool { 139 return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil) 140} 141 142func (stub *stubDecorator) ImplementationModuleName(name string) string { 143 return strings.TrimSuffix(name, ndkLibrarySuffix) 144} 145 146func ndkLibraryVersions(ctx android.BaseModuleContext, from android.ApiLevel) []string { 147 versionStrs := []string{} 148 for _, version := range ctx.Config().FinalApiLevels() { 149 if version.GreaterThanOrEqualTo(from) { 150 versionStrs = append(versionStrs, version.String()) 151 } 152 } 153 versionStrs = append(versionStrs, android.FutureApiLevel.String()) 154 155 return versionStrs 156} 157 158func (this *stubDecorator) StubsVersions(ctx android.BaseModuleContext) []string { 159 if !ctx.Module().Enabled(ctx) { 160 return nil 161 } 162 if ctx.Target().NativeBridge == android.NativeBridgeEnabled { 163 ctx.Module().Disable() 164 return nil 165 } 166 firstVersion, err := NativeApiLevelFromUser(ctx, 167 String(this.properties.First_version)) 168 if err != nil { 169 ctx.PropertyErrorf("first_version", err.Error()) 170 return nil 171 } 172 return ndkLibraryVersions(ctx, firstVersion) 173} 174 175func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool { 176 this.apiLevel = nativeApiLevelOrPanic(ctx, this.StubsVersion()) 177 178 var err error 179 this.firstVersion, err = NativeApiLevelFromUser(ctx, 180 String(this.properties.First_version)) 181 if err != nil { 182 ctx.PropertyErrorf("first_version", err.Error()) 183 return false 184 } 185 186 str := proptools.StringDefault(this.properties.Unversioned_until, "minimum") 187 this.unversionedUntil, err = NativeApiLevelFromUser(ctx, str) 188 if err != nil { 189 ctx.PropertyErrorf("unversioned_until", err.Error()) 190 return false 191 } 192 193 return true 194} 195 196func getNDKKnownLibs(config android.Config) *[]string { 197 return config.Once(ndkKnownLibsKey, func() interface{} { 198 return &[]string{} 199 }).(*[]string) 200} 201 202func (c *stubDecorator) compilerInit(ctx BaseModuleContext) { 203 c.baseCompiler.compilerInit(ctx) 204 205 name := ctx.baseModuleName() 206 if strings.HasSuffix(name, ndkLibrarySuffix) { 207 ctx.PropertyErrorf("name", "Do not append %q manually, just use the base name", ndkLibrarySuffix) 208 } 209 210 ndkKnownLibsLock.Lock() 211 defer ndkKnownLibsLock.Unlock() 212 ndkKnownLibs := getNDKKnownLibs(ctx.Config()) 213 for _, lib := range *ndkKnownLibs { 214 if lib == name { 215 return 216 } 217 } 218 *ndkKnownLibs = append(*ndkKnownLibs, name) 219} 220 221var stubLibraryCompilerFlags = []string{ 222 // We're knowingly doing some otherwise unsightly things with builtin 223 // functions here. We're just generating stub libraries, so ignore it. 224 "-Wno-incompatible-library-redeclaration", 225 "-Wno-incomplete-setjmp-declaration", 226 "-Wno-builtin-requires-header", 227 "-Wno-invalid-noreturn", 228 "-Wall", 229 "-Werror", 230 // These libraries aren't actually used. Don't worry about unwinding 231 // (avoids the need to link an unwinder into a fake library). 232 "-fno-unwind-tables", 233} 234 235func init() { 236 pctx.StaticVariable("StubLibraryCompilerFlags", strings.Join(stubLibraryCompilerFlags, " ")) 237} 238 239func AddStubLibraryCompilerFlags(flags Flags) Flags { 240 flags.Global.CFlags = append(flags.Global.CFlags, stubLibraryCompilerFlags...) 241 // All symbols in the stubs library should be visible. 242 if inList("-fvisibility=hidden", flags.Local.CFlags) { 243 flags.Local.CFlags = append(flags.Local.CFlags, "-fvisibility=default") 244 } 245 return flags 246} 247 248func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags { 249 flags = stub.baseCompiler.compilerFlags(ctx, flags, deps) 250 return AddStubLibraryCompilerFlags(flags) 251} 252 253type NdkApiOutputs struct { 254 StubSrc android.ModuleGenPath 255 VersionScript android.ModuleGenPath 256 symbolList android.ModuleGenPath 257} 258 259func ParseNativeAbiDefinition(ctx android.ModuleContext, symbolFile string, 260 apiLevel android.ApiLevel, genstubFlags string) NdkApiOutputs { 261 262 stubSrcPath := android.PathForModuleGen(ctx, "stub.c") 263 versionScriptPath := android.PathForModuleGen(ctx, "stub.map") 264 symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) 265 symbolListPath := android.PathForModuleGen(ctx, "abi_symbol_list.txt") 266 apiLevelsJson := android.GetApiLevelsJson(ctx) 267 ctx.Build(pctx, android.BuildParams{ 268 Rule: genStubSrc, 269 Description: "generate stubs " + symbolFilePath.Rel(), 270 Outputs: []android.WritablePath{stubSrcPath, versionScriptPath, 271 symbolListPath}, 272 Input: symbolFilePath, 273 Implicits: []android.Path{apiLevelsJson}, 274 Args: map[string]string{ 275 "arch": ctx.Arch().ArchType.String(), 276 "apiLevel": apiLevel.String(), 277 "apiMap": apiLevelsJson.String(), 278 "flags": genstubFlags, 279 }, 280 }) 281 282 return NdkApiOutputs{ 283 StubSrc: stubSrcPath, 284 VersionScript: versionScriptPath, 285 symbolList: symbolListPath, 286 } 287} 288 289func CompileStubLibrary(ctx android.ModuleContext, flags Flags, src android.Path, sharedFlags *SharedFlags) Objects { 290 // libc/libm stubs libraries end up mismatching with clang's internal definition of these 291 // functions (which have noreturn attributes and other things). Because we just want to create a 292 // stub with symbol definitions, and types aren't important in C, ignore the mismatch. 293 flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, "-fno-builtin") 294 return compileObjs(ctx, flagsToBuilderFlags(flags), "", 295 android.Paths{src}, nil, nil, nil, nil, sharedFlags) 296} 297 298func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path { 299 dep := ctx.GetDirectDepProxyWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix), 300 stubImplementation) 301 if dep == nil { 302 ctx.ModuleErrorf("Could not find implementation for stub: ") 303 return nil 304 } 305 if _, ok := android.OtherModuleProvider(ctx, *dep, CcInfoProvider); !ok { 306 ctx.ModuleErrorf("Implementation for stub is not correct module type") 307 return nil 308 } 309 output := android.OtherModuleProviderOrDefault(ctx, *dep, LinkableInfoProvider).UnstrippedOutputFile 310 if output == nil { 311 ctx.ModuleErrorf("implementation module (%s) has no output", *dep) 312 return nil 313 } 314 315 return output 316} 317 318func (this *stubDecorator) libraryName(ctx ModuleContext) string { 319 return strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix) 320} 321 322func (this *stubDecorator) findPrebuiltAbiDump(ctx ModuleContext, 323 apiLevel android.ApiLevel) android.OptionalPath { 324 325 subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(), 326 ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.stg") 327 return android.ExistentPathForSource(ctx, subpath) 328} 329 330func (this *stubDecorator) builtAbiDumpLocation(ctx ModuleContext, apiLevel android.ApiLevel) android.OutputPath { 331 return getNdkAbiDumpInstallBase(ctx).Join(ctx, 332 apiLevel.String(), ctx.Arch().ArchType.String(), 333 this.libraryName(ctx), "abi.stg") 334} 335 336// Feature flag. 337func (this *stubDecorator) canDumpAbi(ctx ModuleContext) bool { 338 if runtime.GOOS == "darwin" { 339 return false 340 } 341 if strings.HasPrefix(ctx.ModuleDir(), "bionic/") { 342 // Bionic has enough uncommon implementation details like ifuncs and asm 343 // code that the ABI tracking here has a ton of false positives. That's 344 // causing pretty extreme friction for development there, so disabling 345 // it until the workflow can be improved. 346 // 347 // http://b/358653811 348 return false 349 } 350 351 // http://b/156513478 352 return ctx.Config().ReleaseNdkAbiMonitored() 353} 354 355// Feature flag to disable diffing against prebuilts. 356func (this *stubDecorator) canDiffAbi(config android.Config) bool { 357 if this.apiLevel.IsCurrent() { 358 // Diffs are performed from this to next, and there's nothing after 359 // current. 360 return false 361 } 362 363 return config.ReleaseNdkAbiMonitored() 364} 365 366func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) { 367 implementationLibrary := this.findImplementationLibrary(ctx) 368 this.abiDumpPath = this.builtAbiDumpLocation(ctx, this.apiLevel) 369 this.hasAbiDump = true 370 headersList := getNdkABIHeadersFile(ctx) 371 ctx.Build(pctx, android.BuildParams{ 372 Rule: stg, 373 Description: fmt.Sprintf("stg %s", implementationLibrary), 374 Input: implementationLibrary, 375 Implicits: []android.Path{ 376 symbolList, 377 headersList, 378 }, 379 Output: this.abiDumpPath, 380 Args: map[string]string{ 381 "symbolList": symbolList.String(), 382 "headersList": headersList.String(), 383 }, 384 }) 385} 386 387func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel { 388 apiLevels := append(ctx.Config().FinalApiLevels(), 389 android.FutureApiLevel) 390 for _, api := range apiLevels { 391 if api.GreaterThan(apiLevel) { 392 return &api 393 } 394 } 395 return nil 396} 397 398func (this *stubDecorator) diffAbi(ctx ModuleContext) { 399 // Catch any ABI changes compared to the checked-in definition of this API 400 // level. 401 abiDiffPath := android.PathForModuleOut(ctx, "stgdiff.timestamp") 402 prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel) 403 missingPrebuiltErrorTemplate := 404 "Did not find prebuilt ABI dump for %q (%q). Generate with " + 405 "//development/tools/ndk/update_ndk_abi.sh." 406 missingPrebuiltError := fmt.Sprintf( 407 missingPrebuiltErrorTemplate, this.libraryName(ctx), 408 prebuiltAbiDump.InvalidReason()) 409 if !prebuiltAbiDump.Valid() { 410 ctx.Build(pctx, android.BuildParams{ 411 Rule: android.ErrorRule, 412 Output: abiDiffPath, 413 Args: map[string]string{ 414 "error": missingPrebuiltError, 415 }, 416 }) 417 } else { 418 ctx.Build(pctx, android.BuildParams{ 419 Rule: stgdiff, 420 Description: fmt.Sprintf("Comparing ABI %s %s", prebuiltAbiDump, 421 this.abiDumpPath), 422 Output: abiDiffPath, 423 Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath}, 424 Args: map[string]string{ 425 "args": "--format=small", 426 }, 427 }) 428 } 429 this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath) 430 431 // Also ensure that the ABI of the next API level (if there is one) matches 432 // this API level. *New* ABI is allowed, but any changes to APIs that exist 433 // in this API level are disallowed. 434 if prebuiltAbiDump.Valid() { 435 nextApiLevel := findNextApiLevel(ctx, this.apiLevel) 436 if nextApiLevel == nil { 437 panic(fmt.Errorf("could not determine which API level follows "+ 438 "non-current API level %s", this.apiLevel)) 439 } 440 441 // Preview ABI levels are not recorded in prebuilts. ABI compatibility 442 // for preview APIs is still monitored via "current" so we have early 443 // warning rather than learning about an ABI break during finalization, 444 // but is only checked against the "current" API dumps in the out 445 // directory. 446 nextAbiDiffPath := android.PathForModuleOut(ctx, 447 "abidiff_next.timestamp") 448 449 var nextAbiDump android.OptionalPath 450 if nextApiLevel.IsCurrent() { 451 nextAbiDump = android.OptionalPathForPath( 452 this.builtAbiDumpLocation(ctx, *nextApiLevel), 453 ) 454 } else { 455 nextAbiDump = this.findPrebuiltAbiDump(ctx, *nextApiLevel) 456 } 457 458 if !nextAbiDump.Valid() { 459 missingNextPrebuiltError := fmt.Sprintf( 460 missingPrebuiltErrorTemplate, this.libraryName(ctx), 461 nextAbiDump.InvalidReason()) 462 ctx.Build(pctx, android.BuildParams{ 463 Rule: android.ErrorRule, 464 Output: nextAbiDiffPath, 465 Args: map[string]string{ 466 "error": missingNextPrebuiltError, 467 }, 468 }) 469 } else { 470 ctx.Build(pctx, android.BuildParams{ 471 Rule: stgdiff, 472 Description: fmt.Sprintf( 473 "Comparing ABI to the next API level %s %s", 474 prebuiltAbiDump, nextAbiDump), 475 Output: nextAbiDiffPath, 476 Inputs: android.Paths{ 477 prebuiltAbiDump.Path(), nextAbiDump.Path()}, 478 Args: map[string]string{ 479 "args": "--format=small --ignore=interface_addition", 480 }, 481 }) 482 } 483 this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath) 484 } 485} 486 487func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects { 488 if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") { 489 ctx.PropertyErrorf("symbol_file", "must end with .map.txt") 490 } 491 492 if !c.BuildStubs() { 493 // NDK libraries have no implementation variant, nothing to do 494 return Objects{} 495 } 496 497 if !c.initializeProperties(ctx) { 498 // Emits its own errors, so we don't need to. 499 return Objects{} 500 } 501 502 symbolFile := String(c.properties.Symbol_file) 503 nativeAbiResult := ParseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "") 504 objs := CompileStubLibrary(ctx, flags, nativeAbiResult.StubSrc, ctx.getSharedFlags()) 505 c.versionScriptPath = nativeAbiResult.VersionScript 506 if c.canDumpAbi(ctx) { 507 c.dumpAbi(ctx, nativeAbiResult.symbolList) 508 if c.canDiffAbi(ctx.Config()) { 509 c.diffAbi(ctx) 510 } 511 } 512 if c.apiLevel.IsCurrent() && ctx.PrimaryArch() { 513 c.parsedCoverageXmlPath = ParseSymbolFileForAPICoverage(ctx, symbolFile) 514 } 515 return objs 516} 517 518// Add a dependency on the header modules of this ndk_library 519func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps { 520 return Deps{ 521 ReexportHeaderLibHeaders: linker.properties.Export_header_libs, 522 HeaderLibs: linker.properties.Export_header_libs, 523 } 524} 525 526func (linker *stubDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) { 527 linker.libraryDecorator.moduleInfoJSON(ctx, moduleInfoJSON) 528 // Overwrites the SubName computed by libraryDecorator 529 moduleInfoJSON.SubName = ndkLibrarySuffix + "." + linker.apiLevel.String() 530} 531 532func (linker *stubDecorator) Name(name string) string { 533 return name + ndkLibrarySuffix 534} 535 536func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags { 537 stub.libraryDecorator.libName = ctx.baseModuleName() 538 return stub.libraryDecorator.linkerFlags(ctx, flags) 539} 540 541func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, 542 objs Objects) android.Path { 543 544 if !stub.BuildStubs() { 545 // NDK libraries have no implementation variant, nothing to do 546 return nil 547 } 548 549 if shouldUseVersionScript(ctx, stub) { 550 linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() 551 flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag) 552 flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) 553 } 554 555 stub.libraryDecorator.skipAPIDefine = true 556 return stub.libraryDecorator.link(ctx, flags, deps, objs) 557} 558 559func (stub *stubDecorator) nativeCoverage() bool { 560 return false 561} 562 563func (stub *stubDecorator) defaultDistFiles() []android.Path { 564 return nil 565} 566 567// Returns the install path for unversioned NDK libraries (currently only static 568// libraries). 569func getUnversionedLibraryInstallPath(ctx ModuleContext) android.OutputPath { 570 return getNdkSysrootBase(ctx).Join(ctx, "usr/lib", config.NDKTriple(ctx.toolchain())) 571} 572 573// Returns the install path for versioned NDK libraries. These are most often 574// stubs, but the same paths are used for CRT objects. 575func getVersionedLibraryInstallPath(ctx ModuleContext, apiLevel android.ApiLevel) android.OutputPath { 576 return getUnversionedLibraryInstallPath(ctx).Join(ctx, apiLevel.String()) 577} 578 579func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) { 580 installDir := getVersionedLibraryInstallPath(ctx, stub.apiLevel) 581 out := installDir.Join(ctx, path.Base()) 582 ctx.Build(pctx, android.BuildParams{ 583 Rule: android.Cp, 584 Input: path, 585 Output: out, 586 }) 587 stub.installPath = out 588} 589 590func newStubLibrary() *Module { 591 module, library := NewLibrary(android.DeviceSupported) 592 library.BuildOnlyShared() 593 module.stl = nil 594 module.sanitize = nil 595 library.disableStripping() 596 597 stub := &stubDecorator{ 598 libraryDecorator: library, 599 } 600 module.compiler = stub 601 module.linker = stub 602 module.installer = stub 603 module.library = stub 604 605 module.Properties.AlwaysSdk = true 606 module.Properties.Sdk_version = StringPtr("current") 607 608 module.AddProperties(&stub.properties, &library.MutatedProperties) 609 610 return module 611} 612 613// ndk_library creates a library that exposes a stub implementation of functions 614// and variables for use at build time only. 615func NdkLibraryFactory() android.Module { 616 module := newStubLibrary() 617 android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth) 618 return module 619} 620