Add support for versioned stubs.
A cc_library or cc_library_shared can be configured to have stubs
variants of the lib.
cc_library_shared {
name: "libfoo",
srcs: ["foo.cpp"],
stubs: {
symbol_file: "foo.map.txt",
versions: ["1", "2", "3"],
},
}
then, stubs variants of libfoo for version 1, 2, and 3 are created
from foo.map.txt. Each version has the symbols from the map file where
each symbol is annotated with the version that the symbol was introduced
via the 'introduced=<ver>' syntax. The versions don't need to be in sync
with the platform versions (e.g., P for 28). The versions are local to
the library.
For another library or executable to use the versioned stubs lib, use
the new 'name#ver' syntax to specify the version:
cc_binary {
name: "test",
....
shared_libs: ["libFoo#2"],
}
Internally, a new mutator 'version' is applied to all cc.Module objects.
By default, a variant named 'impl' is created for the non-stub version.
If the versions property is set, additional variations are created per a
version with the mutable property BuildStubs set as true, which lets the
compiler and the linker to build a stubs lib from the symbol file
instead from the source files.
This feature will be used to enforce stable interfaces among APEXs. When
a lib foo in an APEX is depending on a lib bar in another APEX, then bar
should have stable interface (in C lang) and foo should be depending on
one of the stubs libs of bar. Only libraries in the same APEX as foo can
link against non-stub version of it.
Bug: 112672359
Test: m (cc_test added)
Change-Id: I2488be0b9d7b7b8d7761234dc1c9c0e3add8601c
diff --git a/cc/cc.go b/cc/cc.go
index 8b68489..0569563 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -39,6 +39,7 @@
ctx.BottomUp("vndk", vndkMutator).Parallel()
ctx.BottomUp("ndk_api", ndkApiMutator).Parallel()
ctx.BottomUp("test_per_src", testPerSrcMutator).Parallel()
+ ctx.BottomUp("version", versionMutator).Parallel()
ctx.BottomUp("begin", BeginMutator).Parallel()
})
@@ -945,6 +946,16 @@
c.begin(ctx)
}
+// Split name#version into name and version
+func stubsLibNameAndVersion(name string) (string, string) {
+ if sharp := strings.LastIndex(name, "#"); sharp != -1 && sharp != len(name)-1 {
+ version := name[sharp+1:]
+ libname := name[:sharp]
+ return libname, version
+ }
+ return name, ""
+}
+
func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) {
ctx := &depsContext{
BottomUpMutatorContext: actx,
@@ -979,25 +990,28 @@
variantLibs = []string{}
nonvariantLibs = []string{}
for _, entry := range list {
- if ctx.useSdk() && inList(entry, ndkPrebuiltSharedLibraries) {
- if !inList(entry, ndkMigratedLibs) {
- nonvariantLibs = append(nonvariantLibs, entry+".ndk."+version)
+ // strip #version suffix out
+ name, _ := stubsLibNameAndVersion(entry)
+ if ctx.useSdk() && inList(name, ndkPrebuiltSharedLibraries) {
+ if !inList(name, ndkMigratedLibs) {
+ nonvariantLibs = append(nonvariantLibs, name+".ndk."+version)
} else {
- variantLibs = append(variantLibs, entry+ndkLibrarySuffix)
+ variantLibs = append(variantLibs, name+ndkLibrarySuffix)
}
- } else if ctx.useVndk() && inList(entry, llndkLibraries) {
- nonvariantLibs = append(nonvariantLibs, entry+llndkLibrarySuffix)
- } else if (ctx.Platform() || ctx.ProductSpecific()) && inList(entry, vendorPublicLibraries) {
- vendorPublicLib := entry + vendorPublicLibrarySuffix
+ } else if ctx.useVndk() && inList(name, llndkLibraries) {
+ nonvariantLibs = append(nonvariantLibs, name+llndkLibrarySuffix)
+ } else if (ctx.Platform() || ctx.ProductSpecific()) && inList(name, vendorPublicLibraries) {
+ vendorPublicLib := name + vendorPublicLibrarySuffix
if actx.OtherModuleExists(vendorPublicLib) {
nonvariantLibs = append(nonvariantLibs, vendorPublicLib)
} else {
// This can happen if vendor_public_library module is defined in a
// namespace that isn't visible to the current module. In that case,
// link to the original library.
- nonvariantLibs = append(nonvariantLibs, entry)
+ nonvariantLibs = append(nonvariantLibs, name)
}
} else {
+ // put name#version back
nonvariantLibs = append(nonvariantLibs, entry)
}
}
@@ -1009,6 +1023,15 @@
deps.ReexportSharedLibHeaders, _ = rewriteNdkLibs(deps.ReexportSharedLibHeaders)
}
+ if c.linker != nil {
+ if library, ok := c.linker.(*libraryDecorator); ok {
+ if library.buildStubs() {
+ // Stubs lib does not have dependency to other libraries. Don't proceed.
+ return
+ }
+ }
+ }
+
for _, lib := range deps.HeaderLibs {
depTag := headerDepTag
if inList(lib, deps.ReexportHeaderLibHeaders) {
@@ -1035,19 +1058,40 @@
{Mutator: "link", Variation: "static"},
}, lateStaticDepTag, deps.LateStaticLibs...)
+ // shared lib names without the #version suffix
+ var sharedLibNames []string
+
for _, lib := range deps.SharedLibs {
+ name, version := stubsLibNameAndVersion(lib)
+ sharedLibNames = append(sharedLibNames, name)
depTag := sharedDepTag
if inList(lib, deps.ReexportSharedLibHeaders) {
depTag = sharedExportDepTag
}
- actx.AddVariationDependencies([]blueprint.Variation{
- {Mutator: "link", Variation: "shared"},
- }, depTag, lib)
+ var variations []blueprint.Variation
+ variations = append(variations, blueprint.Variation{Mutator: "link", Variation: "shared"})
+ if version != "" && ctx.Os() == android.Android && !ctx.useVndk() && !c.inRecovery() {
+ variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
+ }
+ actx.AddVariationDependencies(variations, depTag, name)
}
- actx.AddVariationDependencies([]blueprint.Variation{
- {Mutator: "link", Variation: "shared"},
- }, lateSharedDepTag, deps.LateSharedLibs...)
+ for _, lib := range deps.LateSharedLibs {
+ name, version := stubsLibNameAndVersion(lib)
+ if inList(name, sharedLibNames) {
+ // This is to handle the case that some of the late shared libs (libc, libdl, libm, ...)
+ // are added also to SharedLibs with version (e.g., libc#10). If not skipped, we will be
+ // linking against both the stubs lib and the non-stubs lib at the same time.
+ continue
+ }
+ depTag := lateSharedDepTag
+ var variations []blueprint.Variation
+ variations = append(variations, blueprint.Variation{Mutator: "link", Variation: "shared"})
+ if version != "" && ctx.Os() == android.Android && !ctx.useVndk() && !c.inRecovery() {
+ variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
+ }
+ actx.AddVariationDependencies(variations, depTag, name)
+ }
actx.AddVariationDependencies([]blueprint.Variation{
{Mutator: "link", Variation: "shared"},
@@ -1629,8 +1673,8 @@
return
}
- if genrule, ok := mctx.Module().(*genrule.Module); ok {
- if props, ok := genrule.Extra.(*GenruleExtraProperties); ok {
+ if g, ok := mctx.Module().(*genrule.Module); ok {
+ if props, ok := g.Extra.(*GenruleExtraProperties); ok {
var coreVariantNeeded bool = false
var vendorVariantNeeded bool = false
var recoveryVariantNeeded bool = false
@@ -1650,7 +1694,7 @@
if recoveryVariantNeeded {
primaryArch := mctx.Config().DevicePrimaryArchType()
- moduleArch := genrule.Target().Arch.ArchType
+ moduleArch := g.Target().Arch.ArchType
if moduleArch != primaryArch {
recoveryVariantNeeded = false
}
@@ -1666,7 +1710,13 @@
if recoveryVariantNeeded {
variants = append(variants, recoveryMode)
}
- mctx.CreateVariations(variants...)
+ mod := mctx.CreateVariations(variants...)
+ for i, v := range variants {
+ if v == recoveryMode {
+ m := mod[i].(*genrule.Module)
+ m.Extra.(*GenruleExtraProperties).InRecovery = true
+ }
+ }
}
}
diff --git a/cc/cc_test.go b/cc/cc_test.go
index ead89f6..fba38a2 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -66,6 +66,7 @@
ctx.BottomUp("image", imageMutator).Parallel()
ctx.BottomUp("link", LinkageMutator).Parallel()
ctx.BottomUp("vndk", vndkMutator).Parallel()
+ ctx.BottomUp("version", versionMutator).Parallel()
ctx.BottomUp("begin", BeginMutator).Parallel()
})
ctx.Register()
@@ -205,12 +206,13 @@
`
ctx.MockFileSystem(map[string][]byte{
- "Android.bp": []byte(bp),
- "foo.c": nil,
- "bar.c": nil,
- "a.proto": nil,
- "b.aidl": nil,
- "my_include": nil,
+ "Android.bp": []byte(bp),
+ "foo.c": nil,
+ "bar.c": nil,
+ "a.proto": nil,
+ "b.aidl": nil,
+ "my_include": nil,
+ "foo.map.txt": nil,
})
return ctx
@@ -1730,5 +1732,58 @@
if !recoveryModule.Platform() {
t.Errorf("recovery variant of libHalInRecovery must not specific to device, soc, or product")
}
+}
+func TestVersionedStubs(t *testing.T) {
+ ctx := testCc(t, `
+ cc_library_shared {
+ name: "libFoo",
+ stubs: {
+ symbol_file: "foo.map.txt",
+ versions: ["1", "2", "3"],
+ },
+ }
+ cc_library_shared {
+ name: "libBar",
+ shared_libs: ["libFoo#1"],
+ }`)
+
+ variants := ctx.ModuleVariantsForTests("libFoo")
+ expectedVariants := []string{
+ "android_arm64_armv8-a_core_shared",
+ "android_arm64_armv8-a_core_shared_1",
+ "android_arm64_armv8-a_core_shared_2",
+ "android_arm64_armv8-a_core_shared_3",
+ "android_arm_armv7-a-neon_core_shared",
+ "android_arm_armv7-a-neon_core_shared_1",
+ "android_arm_armv7-a-neon_core_shared_2",
+ "android_arm_armv7-a-neon_core_shared_3",
+ }
+ variantsMismatch := false
+ if len(variants) != len(expectedVariants) {
+ variantsMismatch = true
+ } else {
+ for _, v := range expectedVariants {
+ if !inList(v, variants) {
+ variantsMismatch = false
+ }
+ }
+ }
+ if variantsMismatch {
+ t.Errorf("variants of libFoo expected:\n")
+ for _, v := range expectedVariants {
+ t.Errorf("%q\n", v)
+ }
+ t.Errorf(", but got:\n")
+ for _, v := range variants {
+ t.Errorf("%q\n", v)
+ }
+ }
+
+ libBarLinkRule := ctx.ModuleForTests("libBar", "android_arm64_armv8-a_core_shared").Rule("ld")
+ libFlags := libBarLinkRule.Args["libFlags"]
+ libFoo1StubPath := "libFoo/android_arm64_armv8-a_core_shared_1/libFoo.so"
+ if !strings.Contains(libFlags, libFoo1StubPath) {
+ t.Errorf("%q is not found in %q", libFoo1StubPath, libFlags)
+ }
}
diff --git a/cc/genrule.go b/cc/genrule.go
index a672992..decf6ea 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -26,6 +26,9 @@
type GenruleExtraProperties struct {
Vendor_available *bool
Recovery_available *bool
+
+ // This genrule is for recovery variant
+ InRecovery bool `blueprint:"mutated"`
}
// cc_genrule is a genrule that can depend on other cc_* objects.
diff --git a/cc/library.go b/cc/library.go
index 920292d..aafe365 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -22,6 +22,7 @@
"android/soong/android"
"android/soong/cc/config"
+ "android/soong/genrule"
)
type LibraryProperties struct {
@@ -65,6 +66,15 @@
}
Static_ndk_lib *bool
+
+ Stubs struct {
+ // Relative path to the symbol map. The symbol map provides the list of
+ // symbols that are exported for stubs variant of this library.
+ Symbol_file *string
+
+ // List versions to generate stubs libs for.
+ Versions []string
+ }
}
type LibraryMutatedProperties struct {
@@ -78,6 +88,11 @@
VariantIsShared bool `blueprint:"mutated"`
// This variant is static
VariantIsStatic bool `blueprint:"mutated"`
+
+ // This variant is a stubs lib
+ BuildStubs bool `blueprint:"mutated"`
+ // Version of the stubs lib
+ StubsVersion string `blueprint:"mutated"`
}
type FlagExporterProperties struct {
@@ -240,6 +255,8 @@
// Location of the linked, unstripped library for shared libraries
unstrippedOutputFile android.Path
+ versionScriptPath android.ModuleGenPath
+
// Decorated interafaces
*baseCompiler
*baseLinker
@@ -313,7 +330,11 @@
flags.YasmFlags = append(flags.YasmFlags, f)
}
- return library.baseCompiler.compilerFlags(ctx, flags, deps)
+ flags = library.baseCompiler.compilerFlags(ctx, flags, deps)
+ if library.buildStubs() {
+ flags = addStubLibraryCompilerFlags(flags)
+ }
+ return flags
}
func extractExportIncludesFromFlags(flags []string) []string {
@@ -336,6 +357,12 @@
}
func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+ if library.buildStubs() {
+ objs, versionScript := compileStubLibrary(ctx, flags, String(library.Properties.Stubs.Symbol_file), library.MutatedProperties.StubsVersion, "")
+ library.versionScriptPath = versionScript
+ return objs
+ }
+
if !library.buildShared() && !library.buildStatic() {
if len(library.baseCompiler.Properties.Srcs) > 0 {
ctx.PropertyErrorf("srcs", "cc_library_headers must not have any srcs")
@@ -422,8 +449,10 @@
location = InstallInSanitizerDir
}
library.baseInstaller.location = location
-
library.baseLinker.linkerInit(ctx)
+ // Let baseLinker know whether this variant is for stubs or not, so that
+ // it can omit things that are not required for linking stubs.
+ library.baseLinker.dynamicProperties.BuildStubs = library.buildStubs()
}
func (library *libraryDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
@@ -735,7 +764,8 @@
if Bool(library.Properties.Static_ndk_lib) && library.static() &&
!ctx.useVndk() && !ctx.inRecovery() && ctx.Device() &&
- library.baseLinker.sanitize.isUnsanitizedVariant() {
+ library.baseLinker.sanitize.isUnsanitizedVariant() &&
+ !library.buildStubs() {
installPath := getNdkSysrootBase(ctx).Join(
ctx, "usr/lib", config.NDKTriple(ctx.toolchain()), file.Base())
@@ -785,6 +815,14 @@
library.MutatedProperties.BuildStatic = false
}
+func (library *libraryDecorator) buildStubs() bool {
+ return library.MutatedProperties.BuildStubs
+}
+
+func (library *libraryDecorator) stubsVersion() string {
+ return library.MutatedProperties.StubsVersion
+}
+
func NewLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
module := newModule(hod, android.MultilibBoth)
@@ -847,3 +885,41 @@
}
}
}
+
+// Version mutator splits a module into the mandatory non-stubs variant
+// (which is named "impl") and zero or more stubs variants.
+func versionMutator(mctx android.BottomUpMutatorContext) {
+ if mctx.Os() != android.Android {
+ return
+ }
+
+ if m, ok := mctx.Module().(*Module); ok && !m.inRecovery() && m.linker != nil {
+ if library, ok := m.linker.(*libraryDecorator); ok && library.buildShared() {
+ versions := []string{""}
+ for _, v := range library.Properties.Stubs.Versions {
+ versions = append(versions, v)
+ }
+ modules := mctx.CreateVariations(versions...)
+ for i, m := range modules {
+ l := m.(*Module).linker.(*libraryDecorator)
+ if i == 0 {
+ l.MutatedProperties.BuildStubs = false
+ continue
+ }
+ // Mark that this variant is for stubs.
+ l.MutatedProperties.BuildStubs = true
+ l.MutatedProperties.StubsVersion = versions[i]
+ m.(*Module).Properties.HideFromMake = true
+ }
+ } else {
+ mctx.CreateVariations("")
+ }
+ return
+ }
+ if genrule, ok := mctx.Module().(*genrule.Module); ok {
+ if props, ok := genrule.Extra.(*GenruleExtraProperties); ok && !props.InRecovery {
+ mctx.CreateVariations("")
+ return
+ }
+ }
+}
diff --git a/cc/linker.go b/cc/linker.go
index 28f4747..e96bfcb 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -160,7 +160,8 @@
Properties BaseLinkerProperties
MoreProperties MoreBaseLinkerProperties
dynamicProperties struct {
- RunPaths []string `blueprint:"mutated"`
+ RunPaths []string `blueprint:"mutated"`
+ BuildStubs bool `blueprint:"mutated"`
}
sanitize *sanitize
@@ -269,9 +270,13 @@
deps.LateStaticLibs = append(deps.LateStaticLibs, "libwinpthread")
}
- android.ExtractSourceDeps(ctx, linker.MoreProperties.Version_script)
- android.ExtractSourceDeps(ctx,
- linker.MoreProperties.Target.Vendor.Version_script)
+ // Version_script is not needed when linking stubs lib where the version
+ // script is created from the symbol map file.
+ if !linker.dynamicProperties.BuildStubs {
+ android.ExtractSourceDeps(ctx, linker.MoreProperties.Version_script)
+ android.ExtractSourceDeps(ctx,
+ linker.MoreProperties.Target.Vendor.Version_script)
+ }
return deps
}
@@ -375,28 +380,32 @@
flags.GroupStaticLibs = true
}
- versionScript := ctx.ExpandOptionalSource(
- linker.MoreProperties.Version_script, "version_script")
+ // Version_script is not needed when linking stubs lib where the version
+ // script is created from the symbol map file.
+ if !linker.dynamicProperties.BuildStubs {
+ versionScript := ctx.ExpandOptionalSource(
+ linker.MoreProperties.Version_script, "version_script")
- if ctx.useVndk() && linker.MoreProperties.Target.Vendor.Version_script != nil {
- versionScript = ctx.ExpandOptionalSource(
- linker.MoreProperties.Target.Vendor.Version_script,
- "target.vendor.version_script")
- }
+ if ctx.useVndk() && linker.MoreProperties.Target.Vendor.Version_script != nil {
+ versionScript = ctx.ExpandOptionalSource(
+ linker.MoreProperties.Target.Vendor.Version_script,
+ "target.vendor.version_script")
+ }
- if versionScript.Valid() {
- if ctx.Darwin() {
- ctx.PropertyErrorf("version_script", "Not supported on Darwin")
- } else {
- flags.LdFlags = append(flags.LdFlags,
- "-Wl,--version-script,"+versionScript.String())
- flags.LdFlagsDeps = append(flags.LdFlagsDeps, versionScript.Path())
-
- if linker.sanitize.isSanitizerEnabled(cfi) {
- cfiExportsMap := android.PathForSource(ctx, cfiExportsMapPath)
+ if versionScript.Valid() {
+ if ctx.Darwin() {
+ ctx.PropertyErrorf("version_script", "Not supported on Darwin")
+ } else {
flags.LdFlags = append(flags.LdFlags,
- "-Wl,--version-script,"+cfiExportsMap.String())
- flags.LdFlagsDeps = append(flags.LdFlagsDeps, cfiExportsMap)
+ "-Wl,--version-script,"+versionScript.String())
+ flags.LdFlagsDeps = append(flags.LdFlagsDeps, versionScript.Path())
+
+ if linker.sanitize.isSanitizerEnabled(cfi) {
+ cfiExportsMap := android.PathForSource(ctx, cfiExportsMapPath)
+ flags.LdFlags = append(flags.LdFlags,
+ "-Wl,--version-script,"+cfiExportsMap.String())
+ flags.LdFlagsDeps = append(flags.LdFlagsDeps, cfiExportsMap)
+ }
}
}
}