xref: /aosp_15_r20/build/soong/cc/ndk_library.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
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 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 ModuleContext, flags Flags, src android.Path) 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)
296}
297
298func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path {
299	dep := ctx.GetDirectDepWithTag(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	impl, ok := dep.(*Module)
306	if !ok {
307		ctx.ModuleErrorf("Implementation for stub is not correct module type")
308		return nil
309	}
310	output := impl.UnstrippedOutputFile()
311	if output == nil {
312		ctx.ModuleErrorf("implementation module (%s) has no output", impl)
313		return nil
314	}
315
316	return output
317}
318
319func (this *stubDecorator) libraryName(ctx ModuleContext) string {
320	return strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix)
321}
322
323func (this *stubDecorator) findPrebuiltAbiDump(ctx ModuleContext,
324	apiLevel android.ApiLevel) android.OptionalPath {
325
326	subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(),
327		ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.stg")
328	return android.ExistentPathForSource(ctx, subpath)
329}
330
331func (this *stubDecorator) builtAbiDumpLocation(ctx ModuleContext, apiLevel android.ApiLevel) android.OutputPath {
332	return getNdkAbiDumpInstallBase(ctx).Join(ctx,
333		apiLevel.String(), ctx.Arch().ArchType.String(),
334		this.libraryName(ctx), "abi.stg")
335}
336
337// Feature flag.
338func (this *stubDecorator) canDumpAbi(ctx ModuleContext) bool {
339	if runtime.GOOS == "darwin" {
340		return false
341	}
342	if strings.HasPrefix(ctx.ModuleDir(), "bionic/") {
343		// Bionic has enough uncommon implementation details like ifuncs and asm
344		// code that the ABI tracking here has a ton of false positives. That's
345		// causing pretty extreme friction for development there, so disabling
346		// it until the workflow can be improved.
347		//
348		// http://b/358653811
349		return false
350	}
351
352	// http://b/156513478
353	return ctx.Config().ReleaseNdkAbiMonitored()
354}
355
356// Feature flag to disable diffing against prebuilts.
357func (this *stubDecorator) canDiffAbi(config android.Config) bool {
358	if this.apiLevel.IsCurrent() {
359		// Diffs are performed from this to next, and there's nothing after
360		// current.
361		return false
362	}
363
364	return config.ReleaseNdkAbiMonitored()
365}
366
367func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) {
368	implementationLibrary := this.findImplementationLibrary(ctx)
369	this.abiDumpPath = this.builtAbiDumpLocation(ctx, this.apiLevel)
370	this.hasAbiDump = true
371	headersList := getNdkABIHeadersFile(ctx)
372	ctx.Build(pctx, android.BuildParams{
373		Rule:        stg,
374		Description: fmt.Sprintf("stg %s", implementationLibrary),
375		Input:       implementationLibrary,
376		Implicits: []android.Path{
377			symbolList,
378			headersList,
379		},
380		Output: this.abiDumpPath,
381		Args: map[string]string{
382			"symbolList":  symbolList.String(),
383			"headersList": headersList.String(),
384		},
385	})
386}
387
388func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel {
389	apiLevels := append(ctx.Config().FinalApiLevels(),
390		android.FutureApiLevel)
391	for _, api := range apiLevels {
392		if api.GreaterThan(apiLevel) {
393			return &api
394		}
395	}
396	return nil
397}
398
399func (this *stubDecorator) diffAbi(ctx ModuleContext) {
400	// Catch any ABI changes compared to the checked-in definition of this API
401	// level.
402	abiDiffPath := android.PathForModuleOut(ctx, "stgdiff.timestamp")
403	prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel)
404	missingPrebuiltErrorTemplate :=
405		"Did not find prebuilt ABI dump for %q (%q). Generate with " +
406			"//development/tools/ndk/update_ndk_abi.sh."
407	missingPrebuiltError := fmt.Sprintf(
408		missingPrebuiltErrorTemplate, this.libraryName(ctx),
409		prebuiltAbiDump.InvalidReason())
410	if !prebuiltAbiDump.Valid() {
411		ctx.Build(pctx, android.BuildParams{
412			Rule:   android.ErrorRule,
413			Output: abiDiffPath,
414			Args: map[string]string{
415				"error": missingPrebuiltError,
416			},
417		})
418	} else {
419		ctx.Build(pctx, android.BuildParams{
420			Rule: stgdiff,
421			Description: fmt.Sprintf("Comparing ABI %s %s", prebuiltAbiDump,
422				this.abiDumpPath),
423			Output: abiDiffPath,
424			Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath},
425			Args: map[string]string{
426				"args": "--format=small",
427			},
428		})
429	}
430	this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath)
431
432	// Also ensure that the ABI of the next API level (if there is one) matches
433	// this API level. *New* ABI is allowed, but any changes to APIs that exist
434	// in this API level are disallowed.
435	if prebuiltAbiDump.Valid() {
436		nextApiLevel := findNextApiLevel(ctx, this.apiLevel)
437		if nextApiLevel == nil {
438			panic(fmt.Errorf("could not determine which API level follows "+
439				"non-current API level %s", this.apiLevel))
440		}
441
442		// Preview ABI levels are not recorded in prebuilts. ABI compatibility
443		// for preview APIs is still monitored via "current" so we have early
444		// warning rather than learning about an ABI break during finalization,
445		// but is only checked against the "current" API dumps in the out
446		// directory.
447		nextAbiDiffPath := android.PathForModuleOut(ctx,
448			"abidiff_next.timestamp")
449
450		var nextAbiDump android.OptionalPath
451		if nextApiLevel.IsCurrent() {
452			nextAbiDump = android.OptionalPathForPath(
453				this.builtAbiDumpLocation(ctx, *nextApiLevel),
454			)
455		} else {
456			nextAbiDump = this.findPrebuiltAbiDump(ctx, *nextApiLevel)
457		}
458
459		if !nextAbiDump.Valid() {
460			missingNextPrebuiltError := fmt.Sprintf(
461				missingPrebuiltErrorTemplate, this.libraryName(ctx),
462				nextAbiDump.InvalidReason())
463			ctx.Build(pctx, android.BuildParams{
464				Rule:   android.ErrorRule,
465				Output: nextAbiDiffPath,
466				Args: map[string]string{
467					"error": missingNextPrebuiltError,
468				},
469			})
470		} else {
471			ctx.Build(pctx, android.BuildParams{
472				Rule: stgdiff,
473				Description: fmt.Sprintf(
474					"Comparing ABI to the next API level %s %s",
475					prebuiltAbiDump, nextAbiDump),
476				Output: nextAbiDiffPath,
477				Inputs: android.Paths{
478					prebuiltAbiDump.Path(), nextAbiDump.Path()},
479				Args: map[string]string{
480					"args": "--format=small --ignore=interface_addition",
481				},
482			})
483		}
484		this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath)
485	}
486}
487
488func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
489	if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") {
490		ctx.PropertyErrorf("symbol_file", "must end with .map.txt")
491	}
492
493	if !c.buildStubs() {
494		// NDK libraries have no implementation variant, nothing to do
495		return Objects{}
496	}
497
498	if !c.initializeProperties(ctx) {
499		// Emits its own errors, so we don't need to.
500		return Objects{}
501	}
502
503	symbolFile := String(c.properties.Symbol_file)
504	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
505	objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
506	c.versionScriptPath = nativeAbiResult.versionScript
507	if c.canDumpAbi(ctx) {
508		c.dumpAbi(ctx, nativeAbiResult.symbolList)
509		if c.canDiffAbi(ctx.Config()) {
510			c.diffAbi(ctx)
511		}
512	}
513	if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
514		c.parsedCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
515	}
516	return objs
517}
518
519// Add a dependency on the header modules of this ndk_library
520func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
521	return Deps{
522		ReexportHeaderLibHeaders: linker.properties.Export_header_libs,
523		HeaderLibs:               linker.properties.Export_header_libs,
524	}
525}
526
527func (linker *stubDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
528	linker.libraryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
529	// Overwrites the SubName computed by libraryDecorator
530	moduleInfoJSON.SubName = ndkLibrarySuffix + "." + linker.apiLevel.String()
531}
532
533func (linker *stubDecorator) Name(name string) string {
534	return name + ndkLibrarySuffix
535}
536
537func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
538	stub.libraryDecorator.libName = ctx.baseModuleName()
539	return stub.libraryDecorator.linkerFlags(ctx, flags)
540}
541
542func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps,
543	objs Objects) android.Path {
544
545	if !stub.buildStubs() {
546		// NDK libraries have no implementation variant, nothing to do
547		return nil
548	}
549
550	if shouldUseVersionScript(ctx, stub) {
551		linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String()
552		flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag)
553		flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath)
554	}
555
556	stub.libraryDecorator.skipAPIDefine = true
557	return stub.libraryDecorator.link(ctx, flags, deps, objs)
558}
559
560func (stub *stubDecorator) nativeCoverage() bool {
561	return false
562}
563
564// Returns the install path for unversioned NDK libraries (currently only static
565// libraries).
566func getUnversionedLibraryInstallPath(ctx ModuleContext) android.OutputPath {
567	return getNdkSysrootBase(ctx).Join(ctx, "usr/lib", config.NDKTriple(ctx.toolchain()))
568}
569
570// Returns the install path for versioned NDK libraries. These are most often
571// stubs, but the same paths are used for CRT objects.
572func getVersionedLibraryInstallPath(ctx ModuleContext, apiLevel android.ApiLevel) android.OutputPath {
573	return getUnversionedLibraryInstallPath(ctx).Join(ctx, apiLevel.String())
574}
575
576func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) {
577	installDir := getVersionedLibraryInstallPath(ctx, stub.apiLevel)
578	out := installDir.Join(ctx, path.Base())
579	ctx.Build(pctx, android.BuildParams{
580		Rule:   android.Cp,
581		Input:  path,
582		Output: out,
583	})
584	stub.installPath = out
585}
586
587func newStubLibrary() *Module {
588	module, library := NewLibrary(android.DeviceSupported)
589	library.BuildOnlyShared()
590	module.stl = nil
591	module.sanitize = nil
592	library.disableStripping()
593
594	stub := &stubDecorator{
595		libraryDecorator: library,
596	}
597	module.compiler = stub
598	module.linker = stub
599	module.installer = stub
600	module.library = stub
601
602	module.Properties.AlwaysSdk = true
603	module.Properties.Sdk_version = StringPtr("current")
604
605	module.AddProperties(&stub.properties, &library.MutatedProperties)
606
607	return module
608}
609
610// ndk_library creates a library that exposes a stub implementation of functions
611// and variables for use at build time only.
612func NdkLibraryFactory() android.Module {
613	module := newStubLibrary()
614	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth)
615	return module
616}
617