• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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