bp2build: cc_library converter for //bionic/libdl:libdl_android

This CL contains the converter for libdl_android, a cc_library that
expands into a cc_shared_library and a regular cc_library.

Test: TH
Test: bp2build; bazel test //build/bazel/tests/...

Change-Id: If70641a538211b0d6b2aac0e4d0d06912318304d
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index cc616f2..d2a8729 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -26,6 +26,7 @@
     testSrcs: [
         "build_conversion_test.go",
         "bzl_conversion_test.go",
+        "cc_library_conversion_test.go",
         "cc_library_headers_conversion_test.go",
         "cc_library_static_conversion_test.go",
         "cc_object_conversion_test.go",
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index dd14c7d..16e7278 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -374,16 +374,9 @@
 		// value>)" to set the default value of unset attributes. In the cases
 		// where the bp2build converter didn't set the default value within the
 		// mutator when creating the BazelTargetModule, this would be a zero
-		// value. For those cases, we return a non-surprising default value so
-		// generated BUILD files are syntactically correct.
-		switch propertyValue.Kind() {
-		case reflect.Slice:
-			return "[]", nil
-		case reflect.Map:
-			return "{}", nil
-		default:
-			return "", nil
-		}
+		// value. For those cases, we return an empty string so we don't
+		// unnecessarily generate empty values.
+		return "", nil
 	}
 
 	var ret string
@@ -397,6 +390,10 @@
 	case reflect.Ptr:
 		return prettyPrint(propertyValue.Elem(), indent)
 	case reflect.Slice:
+		if propertyValue.Len() == 0 {
+			return "", nil
+		}
+
 		ret = "[\n"
 		for i := 0; i < propertyValue.Len(); i++ {
 			indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
new file mode 100644
index 0000000..6a148a8
--- /dev/null
+++ b/bp2build/cc_library_conversion_test.go
@@ -0,0 +1,303 @@
+// Copyright 2021 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 bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"strings"
+	"testing"
+)
+
+const (
+	// See cc/testing.go for more context
+	soongCcLibraryPreamble = `
+cc_defaults {
+	name: "linux_bionic_supported",
+}
+
+toolchain_library {
+	name: "libclang_rt.builtins-x86_64-android",
+	defaults: ["linux_bionic_supported"],
+	vendor_available: true,
+	vendor_ramdisk_available: true,
+	product_available: true,
+	recovery_available: true,
+	native_bridge_supported: true,
+	src: "",
+}`
+)
+
+func TestCcLibraryBp2Build(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		bp                                 string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+		dir                                string
+	}{
+		{
+			description:                        "cc_library - simple example",
+			moduleTypeUnderTest:                "cc_library",
+			moduleTypeUnderTestFactory:         cc.LibraryFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+			filesystem: map[string]string{
+				"android.cpp": "",
+				"darwin.cpp":  "",
+				// Refer to cc.headerExts for the supported header extensions in Soong.
+				"header.h":         "",
+				"header.hh":        "",
+				"header.hpp":       "",
+				"header.hxx":       "",
+				"header.h++":       "",
+				"header.inl":       "",
+				"header.inc":       "",
+				"header.ipp":       "",
+				"header.h.generic": "",
+				"impl.cpp":         "",
+				"linux.cpp":        "",
+				"x86.cpp":          "",
+				"x86_64.cpp":       "",
+				"foo-dir/a.h":      "",
+			},
+			bp: soongCcLibraryPreamble + `
+cc_library_headers { name: "some-headers" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["impl.cpp"],
+    cflags: ["-Wall"],
+    header_libs: ["some-headers"],
+    export_include_dirs: ["foo-dir"],
+    ldflags: ["-Wl,--exclude-libs=bar.a"],
+    arch: {
+        x86: {
+            ldflags: ["-Wl,--exclude-libs=baz.a"],
+            srcs: ["x86.cpp"],
+        },
+        x86_64: {
+            ldflags: ["-Wl,--exclude-libs=qux.a"],
+            srcs: ["x86_64.cpp"],
+        },
+    },
+    target: {
+        android: {
+            srcs: ["android.cpp"],
+        },
+        linux_glibc: {
+            srcs: ["linux.cpp"],
+        },
+        darwin: {
+            srcs: ["darwin.cpp"],
+        },
+    },
+}
+`,
+			expectedBazelTargets: []string{`cc_library(
+    name = "foo-lib",
+    copts = [
+        "-Wall",
+    ],
+    deps = [
+        ":some-headers",
+    ],
+    hdrs = [
+        "header.h",
+        "header.hh",
+        "header.hpp",
+        "header.hxx",
+        "header.h++",
+        "header.inl",
+        "header.inc",
+        "header.ipp",
+        "header.h.generic",
+        "foo-dir/a.h",
+    ],
+    includes = [
+        "foo-dir",
+    ],
+    linkopts = [
+        "-Wl,--exclude-libs=bar.a",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": [
+            "-Wl,--exclude-libs=baz.a",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "-Wl,--exclude-libs=qux.a",
+        ],
+        "//conditions:default": [],
+    }),
+    srcs = [
+        "impl.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": [
+            "x86.cpp",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "x86_64.cpp",
+        ],
+        "//conditions:default": [],
+    }) + select({
+        "//build/bazel/platforms/os:android": [
+            "android.cpp",
+        ],
+        "//build/bazel/platforms/os:darwin": [
+            "darwin.cpp",
+        ],
+        "//build/bazel/platforms/os:linux": [
+            "linux.cpp",
+        ],
+        "//conditions:default": [],
+    }),
+)`},
+		},
+		{
+			description:                        "cc_library - trimmed example of //bionic/linker:ld-android",
+			moduleTypeUnderTest:                "cc_library",
+			moduleTypeUnderTestFactory:         cc.LibraryFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+			filesystem: map[string]string{
+				"ld-android.cpp":           "",
+				"linked_list.h":            "",
+				"linker.h":                 "",
+				"linker_block_allocator.h": "",
+				"linker_cfi.h":             "",
+			},
+			bp: soongCcLibraryPreamble + `
+cc_library_headers { name: "libc_headers" }
+cc_library {
+    name: "fake-ld-android",
+    srcs: ["ld_android.cpp"],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Wunused",
+        "-Werror",
+    ],
+    header_libs: ["libc_headers"],
+    ldflags: [
+        "-Wl,--exclude-libs=libgcc.a",
+        "-Wl,--exclude-libs=libgcc_stripped.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-aarch64-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-i686-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-x86_64-android.a",
+    ],
+    arch: {
+        x86: {
+            ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
+        },
+        x86_64: {
+            ldflags: ["-Wl,--exclude-libs=libgcc_eh.a"],
+        },
+    },
+}
+`,
+			expectedBazelTargets: []string{`cc_library(
+    name = "fake-ld-android",
+    copts = [
+        "-Wall",
+        "-Wextra",
+        "-Wunused",
+        "-Werror",
+    ],
+    deps = [
+        ":libc_headers",
+    ],
+    hdrs = [
+        "linked_list.h",
+        "linker.h",
+        "linker_block_allocator.h",
+        "linker_cfi.h",
+    ],
+    linkopts = [
+        "-Wl,--exclude-libs=libgcc.a",
+        "-Wl,--exclude-libs=libgcc_stripped.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-arm-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-aarch64-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-i686-android.a",
+        "-Wl,--exclude-libs=libclang_rt.builtins-x86_64-android.a",
+    ] + select({
+        "//build/bazel/platforms/arch:x86": [
+            "-Wl,--exclude-libs=libgcc_eh.a",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "-Wl,--exclude-libs=libgcc_eh.a",
+        ],
+        "//conditions:default": [],
+    }),
+    srcs = [
+        "ld_android.cpp",
+    ],
+)`},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		filesystem := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.filesystem {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			filesystem[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
+		ctx := android.NewTestContext(config)
+
+		cc.RegisterCCBuildComponents(ctx)
+		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+		ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterBp2BuildConfig(bp2buildConfig) // TODO(jingwen): make this the default for all tests
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		checkDir := dir
+		if testCase.dir != "" {
+			checkDir = testCase.dir
+		}
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index 74226ae..655218d 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -23,7 +23,7 @@
 
 const (
 	// See cc/testing.go for more context
-	soongCcLibraryPreamble = `
+	soongCcLibraryHeadersPreamble = `
 cc_defaults {
 	name: "linux_bionic_supported",
 }
@@ -98,7 +98,7 @@
 				"arch_x86_exported_include_dir/b.h":    "",
 				"arch_x86_64_exported_include_dir/c.h": "",
 			},
-			bp: soongCcLibraryPreamble + `
+			bp: soongCcLibraryHeadersPreamble + `
 cc_library_headers {
     name: "lib-1",
     export_include_dirs: ["lib-1"],
@@ -278,7 +278,7 @@
     name = "exported-lib",
 )`, `cc_library_headers(
     name = "foo_headers",
-    deps = [] + select({
+    deps = select({
         "//build/bazel/platforms/os:android": [
             ":android-lib",
             ":exported-lib",
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index ef528e9..a6a0028 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -194,6 +194,8 @@
         ":whole_static_lib_2",
     ],
     hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
         "export_include_dir_1/export_include_dir_1_a.h",
         "export_include_dir_1/export_include_dir_1_b.h",
         "export_include_dir_2/export_include_dir_2_a.h",
@@ -212,8 +214,6 @@
     srcs = [
         "foo_static1.cc",
         "foo_static2.cc",
-        "implicit_include_1.h",
-        "implicit_include_2.h",
         "include_dir_1/include_dir_1_a.h",
         "include_dir_1/include_dir_1_b.h",
         "include_dir_2/include_dir_2_a.h",
@@ -222,50 +222,68 @@
         "local_include_dir_1/local_include_dir_1_b.h",
         "local_include_dir_2/local_include_dir_2_a.h",
         "local_include_dir_2/local_include_dir_2_b.h",
+        "implicit_include_1.h",
+        "implicit_include_2.h",
     ],
 )`, `cc_library_static(
     name = "static_lib_1",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
     includes = [
         ".",
     ],
     linkstatic = True,
     srcs = [
+        "static_lib_1.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
-        "static_lib_1.cc",
     ],
 )`, `cc_library_static(
     name = "static_lib_2",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
     includes = [
         ".",
     ],
     linkstatic = True,
     srcs = [
+        "static_lib_2.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
-        "static_lib_2.cc",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_1",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
     includes = [
         ".",
     ],
     linkstatic = True,
     srcs = [
+        "whole_static_lib_1.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
-        "whole_static_lib_1.cc",
     ],
 )`, `cc_library_static(
     name = "whole_static_lib_2",
+    hdrs = [
+        "implicit_include_1.h",
+        "implicit_include_2.h",
+    ],
     includes = [
         ".",
     ],
     linkstatic = True,
     srcs = [
+        "whole_static_lib_2.cc",
         "implicit_include_1.h",
         "implicit_include_2.h",
-        "whole_static_lib_2.cc",
     ],
 )`},
 		},
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 4f3babe..840bf42 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -52,7 +52,6 @@
         "-Werror",
     ],
     srcs: [
-        "a/b/*.h",
         "a/b/*.c"
     ],
     exclude_srcs: ["a/b/exclude.c"],
@@ -68,14 +67,16 @@
         "-Wall",
         "-Werror",
     ],
+    hdrs = [
+        "a/b/bar.h",
+        "a/b/foo.h",
+    ],
     local_include_dirs = [
         "include",
         ".",
     ],
     srcs = [
-        "a/b/bar.h",
         "a/b/c.c",
-        "a/b/foo.h",
     ],
 )`,
 			},
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index b2b3379..97729df 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -69,20 +69,26 @@
 		return ret, err
 	}
 
-	// Create the selects for arch specific values.
-	selectMap, err := prettyPrintSelectMap(archSelects, "[]", indent)
+	// Convenience function to append selects components to an attribute value.
+	appendSelects := func(selectsData selects, defaultValue, s string) (string, error) {
+		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent)
+		if err != nil {
+			return "", err
+		}
+		if s != "" && selectMap != "" {
+			s += " + "
+		}
+		s += selectMap
+
+		return s, nil
+	}
+
+	ret, err = appendSelects(archSelects, "[]", ret)
 	if err != nil {
 		return "", err
 	}
-	ret += selectMap
 
-	// Create the selects for target os specific values.
-	selectMap, err = prettyPrintSelectMap(osSelects, "[]", indent)
-	if err != nil {
-		return "", err
-	}
-	ret += selectMap
-
+	ret, err = appendSelects(osSelects, "[]", ret)
 	return ret, err
 }
 
@@ -113,7 +119,7 @@
 	}
 
 	// Create the map.
-	ret := " + select({\n"
+	ret := "select({\n"
 	ret += selects
 	// default condition comes last.
 	ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), "//conditions:default", defaultValue)