libclang_rt_prebuilt_library_shared mixed builds

Also fix nil deref in setting $LOCAL_SOONG_UNSTRIPPED_BINARY

Bug: 201802518
Test: request_type_test.go
Test: prebuilt_test.go:TestPrebuiltLibrarySharedWithBazel
Test: mixed_{libc,droid}.sh
Change-Id: I22afb56c4b42d3412c2b2e1f079f1bcf8f3129a7
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index 41f9886..4dc0e4c 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -14,6 +14,7 @@
 type CcInfo struct {
 	OutputFiles          []string
 	CcObjectFiles        []string
+	CcSharedLibraryFiles []string
 	CcStaticLibraryFiles []string
 	Includes             []string
 	SystemIncludes       []string
@@ -128,28 +129,43 @@
         if linker_input.owner == target.label:
           rootStaticArchives.append(library.static_library.path)
 
-rootDynamicLibraries = []
+sharedLibraries = []
+rootSharedLibraries = []
 
 shared_info_tag = "@rules_cc//examples:experimental_cc_shared_library.bzl%CcSharedLibraryInfo"
 if shared_info_tag in providers(target):
   shared_info = providers(target)[shared_info_tag]
   for lib in shared_info.linker_input.libraries:
-    rootDynamicLibraries += [lib.dynamic_library.path]
+    path = lib.dynamic_library.path
+    rootSharedLibraries += [path]
+    sharedLibraries.append(path)
+else:
+  for linker_input in linker_inputs:
+    for library in linker_input.libraries:
+      if library.dynamic_library:
+        path = library.dynamic_library.path
+        sharedLibraries.append(path)
+        if linker_input.owner == target.label:
+          rootSharedLibraries.append(path)
 
 toc_file = ""
 toc_file_tag = "//build/bazel/rules:generate_toc.bzl%CcTocInfo"
 if toc_file_tag in providers(target):
   toc_file = providers(target)[toc_file_tag].toc.path
+else:
+  # NOTE: It's OK if there's no ToC, as Soong just uses it for optimization
+  pass
 
 returns = [
   outputFiles,
-  staticLibraries,
   ccObjectFiles,
+  sharedLibraries,
+  staticLibraries,
   includes,
   system_includes,
   headers,
   rootStaticArchives,
-  rootDynamicLibraries,
+  rootSharedLibraries,
   [toc_file]
 ]
 
@@ -160,28 +176,35 @@
 // The given rawString must correspond to the string output which was created by evaluating the
 // Starlark given in StarlarkFunctionBody.
 func (g getCcInfoType) ParseResult(rawString string) (CcInfo, error) {
-	var outputFiles []string
-	var ccObjects []string
-
+	const expectedLen = 10
 	splitString := strings.Split(rawString, "|")
-	if expectedLen := 9; len(splitString) != expectedLen {
+	if len(splitString) != expectedLen {
 		return CcInfo{}, fmt.Errorf("Expected %d items, got %q", expectedLen, splitString)
 	}
 	outputFilesString := splitString[0]
-	ccStaticLibrariesString := splitString[1]
-	ccObjectsString := splitString[2]
-	outputFiles = splitOrEmpty(outputFilesString, ", ")
+	ccObjectsString := splitString[1]
+	ccSharedLibrariesString := splitString[2]
+	ccStaticLibrariesString := splitString[3]
+	includesString := splitString[4]
+	systemIncludesString := splitString[5]
+	headersString := splitString[6]
+	rootStaticArchivesString := splitString[7]
+	rootDynamicLibrariesString := splitString[8]
+	tocFile := splitString[9] // NOTE: Will be the empty string if there wasn't
+
+	outputFiles := splitOrEmpty(outputFilesString, ", ")
+	ccObjects := splitOrEmpty(ccObjectsString, ", ")
+	ccSharedLibraries := splitOrEmpty(ccSharedLibrariesString, ", ")
 	ccStaticLibraries := splitOrEmpty(ccStaticLibrariesString, ", ")
-	ccObjects = splitOrEmpty(ccObjectsString, ", ")
-	includes := splitOrEmpty(splitString[3], ", ")
-	systemIncludes := splitOrEmpty(splitString[4], ", ")
-	headers := splitOrEmpty(splitString[5], ", ")
-	rootStaticArchives := splitOrEmpty(splitString[6], ", ")
-	rootDynamicLibraries := splitOrEmpty(splitString[7], ", ")
-	tocFile := splitString[8] // NOTE: Will be the empty string if there wasn't
+	includes := splitOrEmpty(includesString, ", ")
+	systemIncludes := splitOrEmpty(systemIncludesString, ", ")
+	headers := splitOrEmpty(headersString, ", ")
+	rootStaticArchives := splitOrEmpty(rootStaticArchivesString, ", ")
+	rootDynamicLibraries := splitOrEmpty(rootDynamicLibrariesString, ", ")
 	return CcInfo{
 		OutputFiles:          outputFiles,
 		CcObjectFiles:        ccObjects,
+		CcSharedLibraryFiles: ccSharedLibraries,
 		CcStaticLibraryFiles: ccStaticLibraries,
 		Includes:             includes,
 		SystemIncludes:       systemIncludes,
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index d3bcb45..606e285 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -63,6 +63,8 @@
 }
 
 func TestGetCcInfoParseResults(t *testing.T) {
+	const expectedSplits = 10
+	noResult := strings.Repeat("|", expectedSplits-1)
 	testCases := []struct {
 		description          string
 		input                string
@@ -71,10 +73,11 @@
 	}{
 		{
 			description: "no result",
-			input:       "||||||||",
+			input:       noResult,
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{},
 				CcObjectFiles:        []string{},
+				CcSharedLibraryFiles: []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
@@ -86,10 +89,11 @@
 		},
 		{
 			description: "only output",
-			input:       "test||||||||",
+			input:       "test" + noResult,
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"test"},
 				CcObjectFiles:        []string{},
+				CcSharedLibraryFiles: []string{},
 				CcStaticLibraryFiles: []string{},
 				Includes:             []string{},
 				SystemIncludes:       []string{},
@@ -100,11 +104,37 @@
 			},
 		},
 		{
+			description: "only ToC",
+			input:       noResult + "test",
+			expectedOutput: CcInfo{
+				OutputFiles:          []string{},
+				CcObjectFiles:        []string{},
+				CcSharedLibraryFiles: []string{},
+				CcStaticLibraryFiles: []string{},
+				Includes:             []string{},
+				SystemIncludes:       []string{},
+				Headers:              []string{},
+				RootStaticArchives:   []string{},
+				RootDynamicLibraries: []string{},
+				TocFile:              "test",
+			},
+		},
+		{
 			description: "all items set",
-			input:       "out1, out2|static_lib1, static_lib2|object1, object2|., dir/subdir|system/dir, system/other/dir|dir/subdir/hdr.h|rootstaticarchive1|rootdynamiclibrary1|lib.so.toc",
+			input: "out1, out2" +
+				"|object1, object2" +
+				"|shared_lib1, shared_lib2" +
+				"|static_lib1, static_lib2" +
+				"|., dir/subdir" +
+				"|system/dir, system/other/dir" +
+				"|dir/subdir/hdr.h" +
+				"|rootstaticarchive1" +
+				"|rootdynamiclibrary1" +
+				"|lib.so.toc",
 			expectedOutput: CcInfo{
 				OutputFiles:          []string{"out1", "out2"},
 				CcObjectFiles:        []string{"object1", "object2"},
+				CcSharedLibraryFiles: []string{"shared_lib1", "shared_lib2"},
 				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
 				Includes:             []string{".", "dir/subdir"},
 				SystemIncludes:       []string{"system/dir", "system/other/dir"},
@@ -118,22 +148,22 @@
 			description:          "too few result splits",
 			input:                "|",
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 9, []string{"", ""}),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", expectedSplits, []string{"", ""}),
 		},
 		{
 			description:          "too many result splits",
-			input:                strings.Repeat("|", 50),
+			input:                strings.Repeat("|", expectedSplits+1), // 2 too many
 			expectedOutput:       CcInfo{},
-			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", 9, make([]string, 51)),
+			expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", expectedSplits, make([]string, expectedSplits+2)),
 		},
 	}
 	for _, tc := range testCases {
 		actualOutput, err := GetCcInfo.ParseResult(tc.input)
 		if (err == nil && tc.expectedErrorMessage != "") ||
 			(err != nil && err.Error() != tc.expectedErrorMessage) {
-			t.Errorf("%q: expected Error %s, got %s", tc.description, tc.expectedErrorMessage, err)
+			t.Errorf("%q:\nexpected Error %s\n, got %s", tc.description, tc.expectedErrorMessage, err)
 		} else if err == nil && !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-			t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput)
+			t.Errorf("%q:\n expected %#v\n!= actual %#v", tc.description, tc.expectedOutput, actualOutput)
 		}
 	}
 }
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 800b58f..f6fe6f7 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -241,7 +241,7 @@
 		entries.Class = "SHARED_LIBRARIES"
 		entries.ExtraEntries = append(entries.ExtraEntries, func(_ android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 			entries.SetString("LOCAL_SOONG_TOC", library.toc().String())
-			if !library.buildStubs() {
+			if !library.buildStubs() && library.unstrippedOutputFile != nil {
 				entries.SetString("LOCAL_SOONG_UNSTRIPPED_BINARY", library.unstrippedOutputFile.String())
 			}
 			if len(library.Properties.Overrides) > 0 {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index feae812..c928ed9 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -298,6 +298,7 @@
 	module, library := NewPrebuiltLibrary(hod, "srcs")
 	library.BuildOnlyShared()
 	module.bazelable = true
+	module.bazelHandler = &prebuiltSharedLibraryBazelHandler{module: module, library: library}
 
 	// Prebuilt shared libraries can be included in APEXes
 	android.InitApexModule(module)
@@ -426,6 +427,69 @@
 	return true
 }
 
+type prebuiltSharedLibraryBazelHandler struct {
+	android.BazelHandler
+
+	module  *Module
+	library *libraryDecorator
+}
+
+func (h *prebuiltSharedLibraryBazelHandler) GenerateBazelBuildActions(ctx android.ModuleContext, label string) bool {
+	bazelCtx := ctx.Config().BazelContext
+	ccInfo, ok, err := bazelCtx.GetCcInfo(label, android.GetConfigKey(ctx))
+	if err != nil {
+		ctx.ModuleErrorf("Error getting Bazel CcInfo for %s: %s", label, err)
+	}
+	if !ok {
+		return false
+	}
+	sharedLibs := ccInfo.CcSharedLibraryFiles
+	if len(sharedLibs) != 1 {
+		ctx.ModuleErrorf("expected 1 shared library from bazel target %s, got %q", label, sharedLibs)
+		return false
+	}
+
+	// TODO(b/184543518): cc_prebuilt_library_shared may have properties for re-exporting flags
+
+	// TODO(eakammer):Add stub-related flags if this library is a stub library.
+	// h.library.exportVersioningMacroIfNeeded(ctx)
+
+	// Dependencies on this library will expect collectedSnapshotHeaders to be set, otherwise
+	// validation will fail. For now, set this to an empty list.
+	// TODO(cparsons): More closely mirror the collectHeadersForSnapshot implementation.
+	h.library.collectedSnapshotHeaders = android.Paths{}
+
+	if len(sharedLibs) == 0 {
+		h.module.outputFile = android.OptionalPath{}
+		return true
+	}
+
+	out := android.PathForBazelOut(ctx, sharedLibs[0])
+	h.module.outputFile = android.OptionalPathForPath(out)
+
+	// FIXME(b/214600441): We don't yet strip prebuilt shared libraries
+	h.library.unstrippedOutputFile = out
+
+	var toc android.Path
+	if len(ccInfo.TocFile) > 0 {
+		toc = android.PathForBazelOut(ctx, ccInfo.TocFile)
+	} else {
+		toc = out // Just reuse `out` so ninja still gets an input but won't matter
+	}
+
+	info := SharedLibraryInfo{
+		SharedLibrary:   out,
+		TableOfContents: android.OptionalPathForPath(toc),
+		Target:          ctx.Target(),
+	}
+	ctx.SetProvider(SharedLibraryInfoProvider, info)
+
+	h.library.setFlagExporterInfoFromCcInfo(ctx, ccInfo)
+	h.module.maybeUnhideFromMake()
+
+	return true
+}
+
 func (p *prebuiltObjectLinker) prebuilt() *android.Prebuilt {
 	return &p.Prebuilt
 }
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index c8f8103..94f75fe 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -19,7 +19,7 @@
 	"testing"
 
 	"android/soong/android"
-
+	"android/soong/bazel/cquery"
 	"github.com/google/blueprint"
 )
 
@@ -378,3 +378,74 @@
 	static2 = ctx.ModuleForTests("libtest_static", "android_arm64_armv8-a_static_hwasan").Module().(*Module)
 	assertString(t, static2.OutputFile().Path().Base(), "libf.hwasan.a")
 }
+
+func TestPrebuiltLibrarySharedWithBazelWithoutToc(t *testing.T) {
+	const bp = `
+cc_prebuilt_library_shared {
+	name: "foo",
+	srcs: ["foo.so"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	outBaseDir := "outputbase"
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: outBaseDir,
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcSharedLibraryFiles: []string{"foo.so"},
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	pathPrefix := outBaseDir + "/execroot/__main__/"
+
+	info := ctx.ModuleProvider(sharedFoo, SharedLibraryInfoProvider).(SharedLibraryInfo)
+	android.AssertPathRelativeToTopEquals(t, "prebuilt shared library",
+		pathPrefix+"foo.so", info.SharedLibrary)
+	android.AssertPathRelativeToTopEquals(t, "prebuilt's 'nullary' ToC",
+		pathPrefix+"foo.so", info.TableOfContents.Path())
+
+	outputFiles, err := sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{pathPrefix + "foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+}
+
+func TestPrebuiltLibrarySharedWithBazelWithToc(t *testing.T) {
+	const bp = `
+cc_prebuilt_library_shared {
+	name: "foo",
+	srcs: ["foo.so"],
+	bazel_module: { label: "//foo/bar:bar" },
+}`
+	outBaseDir := "outputbase"
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: outBaseDir,
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcSharedLibraryFiles: []string{"foo.so"},
+				TocFile:              "toc",
+			},
+		},
+	}
+	ctx := testCcWithConfig(t, config)
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	pathPrefix := outBaseDir + "/execroot/__main__/"
+
+	info := ctx.ModuleProvider(sharedFoo, SharedLibraryInfoProvider).(SharedLibraryInfo)
+	android.AssertPathRelativeToTopEquals(t, "prebuilt shared library's ToC",
+		pathPrefix+"toc", info.TableOfContents.Path())
+	android.AssertPathRelativeToTopEquals(t, "prebuilt shared library",
+		pathPrefix+"foo.so", info.SharedLibrary)
+
+	outputFiles, err := sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{pathPrefix + "foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+}