Add support for cc_binary in module_exports

Bug: 142920869
Test: m nothing
Change-Id: Ib7e16cb323806782c2186e73e13642e4485fc149
diff --git a/Android.bp b/Android.bp
index f425c3f..2a4653a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -204,6 +204,7 @@
         "cc/linker.go",
 
         "cc/binary.go",
+        "cc/binary_sdk_member.go",
         "cc/fuzz.go",
         "cc/library.go",
         "cc/library_sdk_member.go",
diff --git a/cc/binary.go b/cc/binary.go
index 280d17b..661264e 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -200,6 +200,11 @@
 	module.compiler = NewBaseCompiler()
 	module.linker = binary
 	module.installer = binary
+
+	// Allow module to be added as member of an sdk/module_exports.
+	module.sdkMemberTypes = []android.SdkMemberType{
+		ccBinarySdkMemberType,
+	}
 	return module, binary
 }
 
diff --git a/cc/binary_sdk_member.go b/cc/binary_sdk_member.go
new file mode 100644
index 0000000..53bc065
--- /dev/null
+++ b/cc/binary_sdk_member.go
@@ -0,0 +1,143 @@
+// Copyright 2020 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 cc
+
+import (
+	"path/filepath"
+
+	"android/soong/android"
+	"github.com/google/blueprint"
+)
+
+func init() {
+	android.RegisterSdkMemberType(ccBinarySdkMemberType)
+}
+
+var ccBinarySdkMemberType = &binarySdkMemberType{
+	SdkMemberTypeBase: android.SdkMemberTypeBase{
+		PropertyName: "native_binaries",
+	},
+}
+
+type binarySdkMemberType struct {
+	android.SdkMemberTypeBase
+}
+
+func (mt *binarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
+	targets := mctx.MultiTargets()
+	for _, lib := range names {
+		for _, target := range targets {
+			name, version := StubsLibNameAndVersion(lib)
+			if version == "" {
+				version = LatestStubsVersionFor(mctx.Config(), name)
+			}
+			mctx.AddFarVariationDependencies(append(target.Variations(), []blueprint.Variation{
+				{Mutator: "version", Variation: version},
+			}...), dependencyTag, name)
+		}
+	}
+}
+
+func (mt *binarySdkMemberType) IsInstance(module android.Module) bool {
+	// Check the module to see if it can be used with this module type.
+	if m, ok := module.(*Module); ok {
+		for _, allowableMemberType := range m.sdkMemberTypes {
+			if allowableMemberType == mt {
+				return true
+			}
+		}
+	}
+
+	return false
+}
+
+func (mt *binarySdkMemberType) BuildSnapshot(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, member android.SdkMember) {
+	info := mt.organizeVariants(member)
+	buildSharedNativeBinarySnapshot(info, builder, member)
+}
+
+// Organize the variants by architecture.
+func (mt *binarySdkMemberType) organizeVariants(member android.SdkMember) *nativeBinaryInfo {
+	memberName := member.Name()
+	info := &nativeBinaryInfo{
+		name:       memberName,
+		memberType: mt,
+	}
+
+	for _, variant := range member.Variants() {
+		ccModule := variant.(*Module)
+
+		info.archVariantProperties = append(info.archVariantProperties, nativeBinaryInfoProperties{
+			name:       memberName,
+			archType:   ccModule.Target().Arch.ArchType.String(),
+			outputFile: ccModule.OutputFile().Path(),
+		})
+	}
+
+	// Initialize the unexported properties that will not be set during the
+	// extraction process.
+	info.commonProperties.name = memberName
+
+	// Extract common properties from the arch specific properties.
+	extractCommonProperties(&info.commonProperties, info.archVariantProperties)
+
+	return info
+}
+
+func buildSharedNativeBinarySnapshot(info *nativeBinaryInfo, builder android.SnapshotBuilder, member android.SdkMember) {
+	pbm := builder.AddPrebuiltModule(member, "cc_prebuilt_binary")
+	pbm.AddProperty("compile_multilib", "both")
+	archProperties := pbm.AddPropertySet("arch")
+	for _, av := range info.archVariantProperties {
+		archTypeProperties := archProperties.AddPropertySet(av.archType)
+		archTypeProperties.AddProperty("srcs", []string{nativeBinaryPathFor(av)})
+
+		builder.CopyToSnapshot(av.outputFile, nativeBinaryPathFor(av))
+	}
+}
+
+const (
+	nativeBinaryDir = "bin"
+)
+
+// path to the native binary. Relative to <sdk_root>/<api_dir>
+func nativeBinaryPathFor(lib nativeBinaryInfoProperties) string {
+	return filepath.Join(lib.archType,
+		nativeBinaryDir, lib.outputFile.Base())
+}
+
+// nativeBinaryInfoProperties represents properties of a native binary
+//
+// The exported (capitalized) fields will be examined and may be changed during common value extraction.
+// The unexported fields will be left untouched.
+type nativeBinaryInfoProperties struct {
+	// The name of the library, is not exported as this must not be changed during optimization.
+	name string
+
+	// archType is not exported as if set (to a non default value) it is always arch specific.
+	// This is "" for common properties.
+	archType string
+
+	// outputFile is not exported as it is always arch specific.
+	outputFile android.Path
+}
+
+// nativeBinaryInfo represents a collection of arch-specific modules having the same name
+type nativeBinaryInfo struct {
+	name                  string
+	memberType            *binarySdkMemberType
+	archVariantProperties []nativeBinaryInfoProperties
+	commonProperties      nativeBinaryInfoProperties
+}
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 9a75610..8c32d8c 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -337,6 +337,68 @@
 	)
 }
 
+func TestSnapshotWithCcBinary(t *testing.T) {
+	result := testSdkWithCc(t, `
+		module_exports {
+			name: "mymodule_exports",
+			native_binaries: ["mynativebinary"],
+		}
+
+		cc_binary {
+			name: "mynativebinary",
+			srcs: [
+				"Test.cpp",
+			],
+			compile_multilib: "both",
+			system_shared_libs: [],
+			stl: "none",
+		}
+	`)
+
+	result.CheckSnapshot("mymodule_exports", "android_common", "",
+		checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+cc_prebuilt_binary {
+    name: "mymodule_exports_mynativebinary@current",
+    sdk_member_name: "mynativebinary",
+    compile_multilib: "both",
+    arch: {
+        arm64: {
+            srcs: ["arm64/bin/mynativebinary"],
+        },
+        arm: {
+            srcs: ["arm/bin/mynativebinary"],
+        },
+    },
+}
+
+cc_prebuilt_binary {
+    name: "mynativebinary",
+    prefer: false,
+    compile_multilib: "both",
+    arch: {
+        arm64: {
+            srcs: ["arm64/bin/mynativebinary"],
+        },
+        arm: {
+            srcs: ["arm/bin/mynativebinary"],
+        },
+    },
+}
+
+module_exports_snapshot {
+    name: "mymodule_exports@current",
+    native_binaries: ["mymodule_exports_mynativebinary@current"],
+}
+`),
+		checkAllCopyRules(`
+.intermediates/mynativebinary/android_arm64_armv8-a/mynativebinary -> arm64/bin/mynativebinary
+.intermediates/mynativebinary/android_arm_armv7-a-neon/mynativebinary -> arm/bin/mynativebinary
+`),
+	)
+}
+
 func TestSnapshotWithCcSharedLibrary(t *testing.T) {
 	result := testSdkWithCc(t, `
 		sdk {