Stubs variant is used when building for APEX

When a native module is built for an APEX and is depending on a native
library having stubs (i.e. stubs.versions property is set), the stubs
variant is used unless the dependent lib is directly included in the
same APEX with the depending module.

Example:

apex {
    name: "myapex",
    native_shared_libs: ["libX", "libY"],
}

cc_library {
    name: "libX",
    shared_libs: ["libY", "libZ"],
}

cc_library {
    name: "libY",
    stubs: { versions: ["1", "2"], },
}

cc_library {
    name: "libZ",
    stubs: { versions: ["1", "2"], },
}

In this case, libX is linking to the impl variant of libY (that provides
private APIs) while libY is linking to the version 2 stubs of libZ. This is
because libY is directly included in the same apex via
native_shared_libs property, but libZ isn't.

Bug: 112672359
Test: apex_test added
Change-Id: If9871b70dc74a06bd828dd4cd1aeebd2e68b837c
diff --git a/Android.bp b/Android.bp
index 9711c11..76f6798 100644
--- a/Android.bp
+++ b/Android.bp
@@ -353,6 +353,9 @@
         "apex/apex.go",
         "apex/key.go",
     ],
+    testSrcs: [
+        "apex/apex_test.go",
+    ],
     pluginFor: ["soong_build"],
 }
 
diff --git a/android/apex.go b/android/apex.go
index dae88ce..3a191cf 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -14,6 +14,8 @@
 
 package android
 
+import "sync"
+
 // ApexModule is the interface that a module type is expected to implement if
 // the module has to be built differently depending on whether the module
 // is destined for an apex or not (installed to one of the regular partitions).
@@ -94,6 +96,68 @@
 	return false
 }
 
+// This structure maps a module name to the set of APEX bundle names that the module
+// should be built for. Examples:
+//
+// ...["foo"]["bar"] == true: module foo is directly depended on by APEX bar
+// ...["foo"]["bar"] == false: module foo is indirectly depended on by APEX bar
+// ...["foo"]["bar"] doesn't exist: foo is not built for APEX bar
+// ...["foo"] doesn't exist: foo is not built for any APEX
+func apexBundleNamesMap(config Config) map[string]map[string]bool {
+	return config.Once("apexBundleNames", func() interface{} {
+		return make(map[string]map[string]bool)
+	}).(map[string]map[string]bool)
+}
+
+var bundleNamesMapMutex sync.Mutex
+
+// Mark that a module named moduleName should be built for an apex named bundleName
+// directDep should be set to true if the module is a direct dependency of the apex.
+func BuildModuleForApexBundle(ctx BaseModuleContext, moduleName string, bundleName string, directDep bool) {
+	bundleNamesMapMutex.Lock()
+	defer bundleNamesMapMutex.Unlock()
+	bundleNames, ok := apexBundleNamesMap(ctx.Config())[moduleName]
+	if !ok {
+		bundleNames = make(map[string]bool)
+		apexBundleNamesMap(ctx.Config())[moduleName] = bundleNames
+	}
+	bundleNames[bundleName] = bundleNames[bundleName] || directDep
+}
+
+// Returns the list of apex bundle names that the module named moduleName
+// should be built for.
+func GetApexBundlesForModule(ctx BaseModuleContext, moduleName string) map[string]bool {
+	bundleNamesMapMutex.Lock()
+	defer bundleNamesMapMutex.Unlock()
+	return apexBundleNamesMap(ctx.Config())[moduleName]
+}
+
+// Tests if moduleName is directly depended on by bundleName (i.e. referenced in
+// native_shared_libs, etc.)
+func DirectlyInApex(config Config, bundleName string, moduleName string) bool {
+	bundleNamesMapMutex.Lock()
+	defer bundleNamesMapMutex.Unlock()
+	if bundleNames, ok := apexBundleNamesMap(config)[moduleName]; ok {
+		return bundleNames[bundleName]
+	}
+	return false
+}
+
+// Tests if moduleName is directly depended on by any APEX. If this returns true,
+// that means the module is part of the platform.
+func DirectlyInAnyApex(config Config, moduleName string) bool {
+	bundleNamesMapMutex.Lock()
+	defer bundleNamesMapMutex.Unlock()
+	if bundleNames, ok := apexBundleNamesMap(config)[moduleName]; ok {
+		for bn := range bundleNames {
+			if bundleNames[bn] {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 func InitApexModule(m ApexModule) {
 	base := m.apexModuleBase()
 	base.canHaveApexVariants = true
diff --git a/apex/apex.go b/apex/apex.go
index 8a652db..52fa561 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -128,13 +128,6 @@
 	})
 }
 
-// maps a module name to set of apex bundle names that the module should be built for
-func apexBundleNamesFor(config android.Config) map[string]map[string]bool {
-	return config.Once("apexBundleNames", func() interface{} {
-		return make(map[string]map[string]bool)
-	}).(map[string]map[string]bool)
-}
-
 // Mark the direct and transitive dependencies of apex bundles so that they
 // can be built for the apex bundles.
 func apexDepsMutator(mctx android.TopDownMutatorContext) {
@@ -143,12 +136,9 @@
 		mctx.WalkDeps(func(child, parent android.Module) bool {
 			if am, ok := child.(android.ApexModule); ok && am.CanHaveApexVariants() {
 				moduleName := mctx.OtherModuleName(am) + "-" + am.Target().String()
-				bundleNames, ok := apexBundleNamesFor(mctx.Config())[moduleName]
-				if !ok {
-					bundleNames = make(map[string]bool)
-					apexBundleNamesFor(mctx.Config())[moduleName] = bundleNames
-				}
-				bundleNames[apexBundleName] = true
+				// If the parent is apexBundle, this child is directly depended.
+				_, directDep := parent.(*apexBundle)
+				android.BuildModuleForApexBundle(mctx, moduleName, apexBundleName, directDep)
 				return true
 			} else {
 				return false
@@ -161,7 +151,8 @@
 func apexMutator(mctx android.BottomUpMutatorContext) {
 	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
 		moduleName := mctx.ModuleName() + "-" + am.Target().String()
-		if bundleNames, ok := apexBundleNamesFor(mctx.Config())[moduleName]; ok {
+		bundleNames := android.GetApexBundlesForModule(mctx, moduleName)
+		if len(bundleNames) > 0 {
 			variations := []string{"platform"}
 			for bn := range bundleNames {
 				variations = append(variations, bn)
@@ -495,6 +486,9 @@
 			// indirect dependencies
 			if am, ok := child.(android.ApexModule); ok && am.CanHaveApexVariants() && am.IsInstallableToApex() {
 				if cc, ok := child.(*cc.Module); ok {
+					if cc.IsStubs() || cc.HasStubsVariants() {
+						return false
+					}
 					depName := ctx.OtherModuleName(child)
 					fileToCopy, dirInApex := getCopyManifestForNativeLibrary(cc)
 					filesInfo = append(filesInfo, apexFile{fileToCopy, depName, cc.Arch().ArchType, dirInApex, nativeSharedLib})
diff --git a/apex/apex_test.go b/apex/apex_test.go
new file mode 100644
index 0000000..41d8455
--- /dev/null
+++ b/apex/apex_test.go
@@ -0,0 +1,369 @@
+// Copyright 2018 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package apex
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+)
+
+func testApex(t *testing.T, bp string) *android.TestContext {
+	config, buildDir := setup(t)
+	defer teardown(buildDir)
+
+	ctx := android.NewTestArchContext()
+	ctx.RegisterModuleType("apex", android.ModuleFactoryAdaptor(apexBundleFactory))
+	ctx.RegisterModuleType("apex_key", android.ModuleFactoryAdaptor(apexKeyFactory))
+
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.TopDown("apex_deps", apexDepsMutator)
+		ctx.BottomUp("apex", apexMutator)
+	})
+
+	ctx.RegisterModuleType("cc_library", android.ModuleFactoryAdaptor(cc.LibraryFactory))
+	ctx.RegisterModuleType("cc_library_shared", android.ModuleFactoryAdaptor(cc.LibrarySharedFactory))
+	ctx.RegisterModuleType("cc_object", android.ModuleFactoryAdaptor(cc.ObjectFactory))
+	ctx.RegisterModuleType("toolchain_library", android.ModuleFactoryAdaptor(cc.ToolchainLibraryFactory))
+	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("link", cc.LinkageMutator).Parallel()
+		ctx.BottomUp("version", cc.VersionMutator).Parallel()
+		ctx.BottomUp("begin", cc.BeginMutator).Parallel()
+	})
+
+	ctx.Register()
+
+	bp = bp + `
+		toolchain_library {
+			name: "libcompiler_rt-extras",
+			src: "",
+		}
+
+		toolchain_library {
+			name: "libatomic",
+			src: "",
+		}
+
+		toolchain_library {
+			name: "libgcc",
+			src: "",
+		}
+
+		toolchain_library {
+			name: "libclang_rt.builtins-aarch64-android",
+			src: "",
+		}
+
+		toolchain_library {
+			name: "libclang_rt.builtins-arm-android",
+			src: "",
+		}
+
+		cc_object {
+			name: "crtbegin_so",
+			stl: "none",
+		}
+
+		cc_object {
+			name: "crtend_so",
+			stl: "none",
+		}
+
+	`
+
+	ctx.MockFileSystem(map[string][]byte{
+		"Android.bp":                                []byte(bp),
+		"testkey.avbpubkey":                         nil,
+		"testkey.pem":                               nil,
+		"build/target/product/security":             nil,
+		"apex_manifest.json":                        nil,
+		"system/sepolicy/apex/myapex-file_contexts": nil,
+		"mylib.cpp":                                 nil,
+	})
+	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+	android.FailIfErrored(t, errs)
+	_, errs = ctx.PrepareBuildActions(config)
+	android.FailIfErrored(t, errs)
+
+	return ctx
+}
+
+func setup(t *testing.T) (config android.Config, buildDir string) {
+	buildDir, err := ioutil.TempDir("", "soong_apex_test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	config = android.TestArchConfig(buildDir, nil)
+
+	return
+}
+
+func teardown(buildDir string) {
+	os.RemoveAll(buildDir)
+}
+
+// ensure that 'result' contains 'expected'
+func ensureContains(t *testing.T, result string, expected string) {
+	if !strings.Contains(result, expected) {
+		t.Errorf("%q is not found in %q", expected, result)
+	}
+}
+
+// ensures that 'result' does not contain 'notExpected'
+func ensureNotContains(t *testing.T, result string, notExpected string) {
+	if strings.Contains(result, notExpected) {
+		t.Errorf("%q is found in %q", notExpected, result)
+	}
+}
+
+func ensureListContains(t *testing.T, result []string, expected string) {
+	if !android.InList(expected, result) {
+		t.Errorf("%q is not found in %v", expected, result)
+	}
+}
+
+func ensureListNotContains(t *testing.T, result []string, notExpected string) {
+	if android.InList(notExpected, result) {
+		t.Errorf("%q is found in %v", notExpected, result)
+	}
+}
+
+// Minimal test
+func TestBasicApex(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib2"],
+			system_shared_libs: [],
+			stl: "none",
+		}
+
+		cc_library {
+			name: "mylib2",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+		}
+	`)
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that main rule creates an output
+	ensureContains(t, apexRule.Output.String(), "myapex.apex.unsigned")
+
+	// Ensure that apex variant is created for the direct dep
+	ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_myapex")
+
+	// Ensure that apex variant is created for the indirect dep
+	ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_myapex")
+
+	// Ensure that both direct and indirect deps are copied into apex
+	ensureContains(t, copyCmds, "image/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image/lib64/mylib2.so")
+}
+
+func TestApexWithStubs(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib", "mylib3"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["mylib2", "mylib3"],
+			system_shared_libs: [],
+			stl: "none",
+		}
+
+		cc_library {
+			name: "mylib2",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+		}
+
+		cc_library {
+			name: "mylib3",
+				srcs: ["mylib.cpp"],
+				system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["10", "11", "12"],
+			},
+		}
+	`)
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that direct non-stubs dep is always included
+	ensureContains(t, copyCmds, "image/lib64/mylib.so")
+
+	// Ensure that indirect stubs dep is not included
+	ensureNotContains(t, copyCmds, "image/lib64/mylib2.so")
+
+	// Ensure that direct stubs dep is included
+	ensureContains(t, copyCmds, "image/lib64/mylib3.so")
+
+	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_myapex").Rule("ld").Args["libFlags"]
+
+	// Ensure that mylib is linking with the latest version of stubs for mylib2
+	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_3_myapex/mylib2.so")
+	// ... and not linking to the non-stub (impl) variant of mylib2
+	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_myapex/mylib2.so")
+
+	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because mylib3 is in the same apex)
+	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_myapex/mylib3.so")
+	// .. and not linking to the stubs variant of mylib3
+	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_12_myapex/mylib3.so")
+}
+
+func TestApexWithSystemLibsStubs(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib", "mylib_shared", "libdl", "libm"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libdl#27"],
+			stl: "none",
+		}
+
+		cc_library_shared {
+			name: "mylib_shared",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libdl#27"],
+			stl: "none",
+		}
+
+		cc_library {
+			name: "libc",
+			no_libgcc: true,
+			nocrt: true,
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["27", "28", "29"],
+			},
+		}
+
+		cc_library {
+			name: "libm",
+			no_libgcc: true,
+			nocrt: true,
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["27", "28", "29"],
+			},
+		}
+
+		cc_library {
+			name: "libdl",
+			no_libgcc: true,
+			nocrt: true,
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["27", "28", "29"],
+			},
+		}
+	`)
+
+	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	copyCmds := apexRule.Args["copy_commands"]
+
+	// Ensure that mylib, libm, libdl are included.
+	ensureContains(t, copyCmds, "image/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image/lib64/libm.so")
+	ensureContains(t, copyCmds, "image/lib64/libdl.so")
+
+	// Ensure that libc is not included (since it has stubs and not listed in native_shared_libs)
+	ensureNotContains(t, copyCmds, "image/lib64/libc.so")
+
+	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_myapex").Rule("ld").Args["libFlags"]
+	mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_myapex").Rule("cc").Args["cFlags"]
+	mylibSharedCFlags := ctx.ModuleForTests("mylib_shared", "android_arm64_armv8-a_shared_myapex").Rule("cc").Args["cFlags"]
+
+	// For dependency to libc
+	// Ensure that mylib is linking with the latest version of stubs
+	ensureContains(t, mylibLdFlags, "libc/android_arm64_armv8-a_shared_29_myapex/libc.so")
+	// ... and not linking to the non-stub (impl) variant
+	ensureNotContains(t, mylibLdFlags, "libc/android_arm64_armv8-a_shared_myapex/libc.so")
+	// ... Cflags from stub is correctly exported to mylib
+	ensureContains(t, mylibCFlags, "__LIBC_API__=29")
+	ensureContains(t, mylibSharedCFlags, "__LIBC_API__=29")
+
+	// For dependency to libm
+	// Ensure that mylib is linking with the non-stub (impl) variant
+	ensureContains(t, mylibLdFlags, "libm/android_arm64_armv8-a_shared_myapex/libm.so")
+	// ... and not linking to the stub variant
+	ensureNotContains(t, mylibLdFlags, "libm/android_arm64_armv8-a_shared_29_myapex/libm.so")
+	// ... and is not compiling with the stub
+	ensureNotContains(t, mylibCFlags, "__LIBM_API__=29")
+	ensureNotContains(t, mylibSharedCFlags, "__LIBM_API__=29")
+
+	// For dependency to libdl
+	// Ensure that mylib is linking with the specified version of stubs
+	ensureContains(t, mylibLdFlags, "libdl/android_arm64_armv8-a_shared_27_myapex/libdl.so")
+	// ... and not linking to the other versions of stubs
+	ensureNotContains(t, mylibLdFlags, "libdl/android_arm64_armv8-a_shared_28_myapex/libdl.so")
+	ensureNotContains(t, mylibLdFlags, "libdl/android_arm64_armv8-a_shared_29_myapex/libdl.so")
+	// ... and not linking to the non-stub (impl) variant
+	ensureNotContains(t, mylibLdFlags, "libdl/android_arm64_armv8-a_shared_myapex/libdl.so")
+	// ... Cflags from stub is correctly exported to mylib
+	ensureContains(t, mylibCFlags, "__LIBDL_API__=27")
+	ensureContains(t, mylibSharedCFlags, "__LIBDL_API__=27")
+}
diff --git a/cc/cc.go b/cc/cc.go
index 8a0bf71..b8cbf26 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -39,7 +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("version", VersionMutator).Parallel()
 		ctx.BottomUp("begin", BeginMutator).Parallel()
 	})
 
@@ -248,6 +248,7 @@
 	getVndkExtendsModuleName() string
 	isPgoCompile() bool
 	useClangLld(actx ModuleContext) bool
+	isApex() bool
 }
 
 type ModuleContext interface {
@@ -308,6 +309,8 @@
 	library bool
 
 	reexportFlags bool
+
+	explicitlyVersioned bool
 }
 
 var (
@@ -511,6 +514,20 @@
 	return c.ModuleBase.InstallInRecovery()
 }
 
+func (c *Module) IsStubs() bool {
+	if library, ok := c.linker.(*libraryDecorator); ok {
+		return library.buildStubs()
+	}
+	return false
+}
+
+func (c *Module) HasStubsVariants() bool {
+	if library, ok := c.linker.(*libraryDecorator); ok {
+		return len(library.Properties.Stubs.Versions) > 0
+	}
+	return false
+}
+
 type baseModuleContext struct {
 	android.BaseContext
 	moduleContextImpl
@@ -649,6 +666,11 @@
 	return ctx.mod.getVndkExtendsModuleName()
 }
 
+// Tests if this module is built for APEX
+func (ctx *moduleContextImpl) isApex() bool {
+	return ctx.mod.ApexName() != ""
+}
+
 func newBaseModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
 	return &Module{
 		hod:      hod,
@@ -1081,6 +1103,30 @@
 		{Mutator: "link", Variation: "static"},
 	}, lateStaticDepTag, deps.LateStaticLibs...)
 
+	addSharedLibDependencies := func(depTag dependencyTag, name string, version string) {
+		var variations []blueprint.Variation
+		variations = append(variations, blueprint.Variation{Mutator: "link", Variation: "shared"})
+		versionVariantAvail := ctx.Os() == android.Android && !ctx.useVndk() && !c.inRecovery()
+		if version != "" && versionVariantAvail {
+			// Version is explicitly specified. i.e. libFoo#30
+			variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
+			depTag.explicitlyVersioned = true
+		}
+		actx.AddVariationDependencies(variations, depTag, name)
+
+		// If the version is not specified, add dependency to the latest stubs library.
+		// The stubs library will be used when the depending module is built for APEX and
+		// the dependent module is not in the same APEX.
+		latestVersion := latestStubsVersionFor(actx.Config(), name)
+		if version == "" && latestVersion != "" && versionVariantAvail {
+			actx.AddVariationDependencies([]blueprint.Variation{
+				{Mutator: "link", Variation: "shared"},
+				{Mutator: "version", Variation: latestVersion},
+			}, depTag, name)
+			// Note that depTag.explicitlyVersioned is false in this case.
+		}
+	}
+
 	// shared lib names without the #version suffix
 	var sharedLibNames []string
 
@@ -1091,29 +1137,17 @@
 		if inList(lib, deps.ReexportSharedLibHeaders) {
 			depTag = sharedExportDepTag
 		}
-		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)
+		addSharedLibDependencies(depTag, name, version)
 	}
 
 	for _, lib := range deps.LateSharedLibs {
-		name, version := stubsLibNameAndVersion(lib)
-		if inList(name, sharedLibNames) {
+		if inList(lib, 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)
+		addSharedLibDependencies(lateSharedDepTag, lib, "")
 	}
 
 	actx.AddVariationDependencies([]blueprint.Variation{
@@ -1372,7 +1406,53 @@
 				return
 			}
 		}
+
+		// Extract explicitlyVersioned field from the depTag and reset it inside the struct.
+		// Otherwise, sharedDepTag and lateSharedDepTag with explicitlyVersioned set to true
+		// won't be matched to sharedDepTag and lateSharedDepTag.
+		explicitlyVersioned := false
+		if t, ok := depTag.(dependencyTag); ok {
+			explicitlyVersioned = t.explicitlyVersioned
+			t.explicitlyVersioned = false
+			depTag = t
+		}
+
 		if t, ok := depTag.(dependencyTag); ok && t.library {
+			if dependentLibrary, ok := ccDep.linker.(*libraryDecorator); ok {
+				depIsStubs := dependentLibrary.buildStubs()
+				depHasStubs := ccDep.HasStubsVariants()
+				depNameWithTarget := depName + "-" + ccDep.Target().String()
+				depInSameApex := android.DirectlyInApex(ctx.Config(), c.ApexName(), depNameWithTarget)
+				depInPlatform := !android.DirectlyInAnyApex(ctx.Config(), depNameWithTarget)
+
+				var useThisDep bool
+				if depIsStubs && explicitlyVersioned {
+					// Always respect dependency to the versioned stubs (i.e. libX#10)
+					useThisDep = true
+				} else if !depHasStubs {
+					// Use non-stub variant if that is the only choice
+					// (i.e. depending on a lib without stubs.version property)
+					useThisDep = true
+				} else if c.IsForPlatform() {
+					// If not building for APEX, use stubs only when it is from
+					// an APEX (and not from platform)
+					useThisDep = (depInPlatform != depIsStubs)
+					if c.inRecovery() {
+						// However, for recovery modules, since there is no APEX there,
+						// always link to non-stub variant
+						useThisDep = !depIsStubs
+					}
+				} else {
+					// If building for APEX, use stubs only when it is not from
+					// the same APEX
+					useThisDep = (depInSameApex != depIsStubs)
+				}
+
+				if !useThisDep {
+					return // stop processing this dep
+				}
+			}
+
 			if i, ok := ccDep.linker.(exportedFlagsProducer); ok {
 				flags := i.exportedFlags()
 				deps := i.exportedFlagsDeps()
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 41f633a..e368fb3 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -66,7 +66,7 @@
 		ctx.BottomUp("image", imageMutator).Parallel()
 		ctx.BottomUp("link", LinkageMutator).Parallel()
 		ctx.BottomUp("vndk", vndkMutator).Parallel()
-		ctx.BottomUp("version", versionMutator).Parallel()
+		ctx.BottomUp("version", VersionMutator).Parallel()
 		ctx.BottomUp("begin", BeginMutator).Parallel()
 	})
 	ctx.Register()
diff --git a/cc/library.go b/cc/library.go
index 8b8fe02..822274a 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -16,6 +16,8 @@
 
 import (
 	"regexp"
+	"sort"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -981,30 +983,65 @@
 	}
 }
 
+// maps a module name to the list of stubs versions available for the module
+func stubsVersionsFor(config android.Config) map[string][]string {
+	return config.Once("stubVersions", func() interface{} {
+		return make(map[string][]string)
+	}).(map[string][]string)
+}
+
+var stubsVersionsLock sync.Mutex
+
+func latestStubsVersionFor(config android.Config, name string) string {
+	versions, ok := stubsVersionsFor(config)[name]
+	if ok && len(versions) > 0 {
+		// the versions are alreay sorted in ascending order
+		return versions[len(versions)-1]
+	}
+	return ""
+}
+
 // 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) {
+// (which is unnamed) 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{""}
+		if library, ok := m.linker.(*libraryDecorator); ok && library.buildShared() &&
+			len(library.Properties.Stubs.Versions) > 0 {
+			versions := []string{}
 			for _, v := range library.Properties.Stubs.Versions {
+				if _, err := strconv.Atoi(v); err != nil {
+					mctx.PropertyErrorf("versions", "%q is not a number", v)
+				}
 				versions = append(versions, v)
 			}
+			sort.Slice(versions, func(i, j int) bool {
+				left, _ := strconv.Atoi(versions[i])
+				right, _ := strconv.Atoi(versions[j])
+				return left < right
+			})
+
+			// save the list of versions for later use
+			copiedVersions := make([]string, len(versions))
+			copy(copiedVersions, versions)
+			stubsVersionsLock.Lock()
+			defer stubsVersionsLock.Unlock()
+			stubsVersionsFor(mctx.Config())[mctx.ModuleName()] = copiedVersions
+
+			// "" is for the non-stubs variant
+			versions = append(versions, "")
+
 			modules := mctx.CreateVariations(versions...)
 			for i, m := range modules {
 				l := m.(*Module).linker.(*libraryDecorator)
-				if i == 0 {
-					l.MutatedProperties.BuildStubs = false
-					continue
+				if versions[i] != "" {
+					l.MutatedProperties.BuildStubs = true
+					l.MutatedProperties.StubsVersion = versions[i]
+					m.(*Module).Properties.HideFromMake = true
 				}
-				// Mark that this variant is for stubs.
-				l.MutatedProperties.BuildStubs = true
-				l.MutatedProperties.StubsVersion = versions[i]
-				m.(*Module).Properties.HideFromMake = true
 			}
 		} else {
 			mctx.CreateVariations("")