Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ed9de3b authored by Jiyong Park's avatar Jiyong Park
Browse files

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
parent 72be5901
Loading
Loading
Loading
Loading
+69 −19
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ func init() {
		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 @@ func (c *Module) beginMutator(actx android.BottomUpMutatorContext) {
	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 @@ func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) {
			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 @@ func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) {
		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 @@ func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) {
		{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 @@ func imageMutator(mctx android.BottomUpMutatorContext) {
		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 @@ func imageMutator(mctx android.BottomUpMutatorContext) {

			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 @@ func imageMutator(mctx android.BottomUpMutatorContext) {
			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
				}
			}
		}
	}

+61 −6
Original line number Diff line number Diff line
@@ -66,6 +66,7 @@ func createTestContext(t *testing.T, config android.Config, bp string) *android.
		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()
@@ -211,6 +212,7 @@ func createTestContext(t *testing.T, config android.Config, bp string) *android.
		"a.proto":     nil,
		"b.aidl":      nil,
		"my_include":  nil,
		"foo.map.txt": nil,
	})

	return ctx
@@ -1730,5 +1732,58 @@ func TestRecovery(t *testing.T) {
	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)
	}
}
+3 −0
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ func init() {
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.
+79 −3
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import (

	"android/soong/android"
	"android/soong/cc/config"
	"android/soong/genrule"
)

type LibraryProperties struct {
@@ -65,6 +66,15 @@ type LibraryProperties struct {
	}

	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 @@ type LibraryMutatedProperties struct {
	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 @@ type libraryDecorator struct {
	// Location of the linked, unstripped library for shared libraries
	unstrippedOutputFile android.Path

	versionScriptPath android.ModuleGenPath

	// Decorated interafaces
	*baseCompiler
	*baseLinker
@@ -313,7 +330,11 @@ func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags, d
		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 extractExportIncludesFromFlags(flags []string) []string {
}

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 @@ func (library *libraryDecorator) linkerInit(ctx BaseModuleContext) {
		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 @@ func (library *libraryDecorator) install(ctx ModuleContext, file android.Path) {

	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 @@ func (library *libraryDecorator) HeaderOnly() {
	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 @@ func LinkageMutator(mctx android.BottomUpMutatorContext) {
		}
	}
}

// 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
		}
	}
}
+32 −23
Original line number Diff line number Diff line
@@ -161,6 +161,7 @@ type baseLinker struct {
	MoreProperties    MoreBaseLinkerProperties
	dynamicProperties struct {
		RunPaths   []string `blueprint:"mutated"`
		BuildStubs bool     `blueprint:"mutated"`
	}

	sanitize *sanitize
@@ -269,9 +270,13 @@ func (linker *baseLinker) linkerDeps(ctx DepsContext, deps Deps) Deps {
		deps.LateStaticLibs = append(deps.LateStaticLibs, "libwinpthread")
	}

	// 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,6 +380,9 @@ func (linker *baseLinker) linkerFlags(ctx ModuleContext, flags Flags) Flags {
		flags.GroupStaticLibs = true
	}

	// 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")

@@ -400,6 +408,7 @@ func (linker *baseLinker) linkerFlags(ctx ModuleContext, flags Flags) Flags {
				}
			}
		}
	}

	return flags
}