Handle .proto files that end up in a different package

Bazel poses a strict requirement that .proto files and proto_library
must be in the same package. This CL handles this automatically by
creating the proto_library in a separate dir/package if necessary

Implementation details
- Partition the `srcs` by package. `srcs` has been computed using
  `transformSubpackagePath`, so the information about packages is
  available at this point
- Create a proto_library in each package by using
  `CommonAttributes.Dir`. Collect all these additional libraries
  and put them in `info.Proto_libraries` so that they get added as deps
  of (cc|python|...)_proto_library
- Add an import_prefix to the proto_library in subpackages relative to
  the current directory. This relies on the assumption that every src is
  beneath the current directory (Soong will complain if a path in
  Android.bp contains ../)

filegroup module type uses a separate code-path to create proto_library.
This will be handled in the next CL in stack.

Test: bp2build unit tests
Test: TH
Test: Built the failing internal module mentioned in
b/292583584#comment1

Bug: 292583584

Change-Id: I437fc89092321b26c5f0511387cde9e84084d6f9
diff --git a/android/proto.go b/android/proto.go
index cebbd59..aad521b 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"path/filepath"
 	"strings"
 
 	"android/soong/bazel"
@@ -156,12 +157,12 @@
 // Bp2buildProtoInfo contains information necessary to pass on to language specific conversion.
 type Bp2buildProtoInfo struct {
 	Type       *string
-	Name       string
 	Proto_libs bazel.LabelList
 }
 
 type ProtoAttrs struct {
 	Srcs                bazel.LabelListAttribute
+	Import_prefix       *string
 	Strip_import_prefix *string
 	Deps                bazel.LabelListAttribute
 }
@@ -172,6 +173,35 @@
 	"external/protobuf/src": "//external/protobuf:libprotobuf-proto",
 }
 
+// Partitions srcs by the pkg it is in
+// srcs has been created using `TransformSubpackagePaths`
+// This function uses existence of Android.bp/BUILD files to create a label that is compatible with the package structure of bp2build workspace
+func partitionSrcsByPackage(currentDir string, srcs bazel.LabelList) map[string]bazel.LabelList {
+	getPackageFromLabel := func(label string) string {
+		// Remove any preceding //
+		label = strings.TrimPrefix(label, "//")
+		split := strings.Split(label, ":")
+		if len(split) == 1 {
+			// e.g. foo.proto
+			return currentDir
+		} else if split[0] == "" {
+			// e.g. :foo.proto
+			return currentDir
+		} else {
+			return split[0]
+		}
+	}
+
+	pkgToSrcs := map[string]bazel.LabelList{}
+	for _, src := range srcs.Includes {
+		pkg := getPackageFromLabel(src.Label)
+		list := pkgToSrcs[pkg]
+		list.Add(&src)
+		pkgToSrcs[pkg] = list
+	}
+	return pkgToSrcs
+}
+
 // Bp2buildProtoProperties converts proto properties, creating a proto_library and returning the
 // information necessary for language-specific handling.
 func Bp2buildProtoProperties(ctx Bp2buildMutatorContext, m *ModuleBase, srcs bazel.LabelListAttribute) (Bp2buildProtoInfo, bool) {
@@ -197,54 +227,73 @@
 		}
 	}
 
-	info.Name = m.Name() + "_proto"
+	name := m.Name() + "_proto"
+
+	depsFromFilegroup := protoLibraries
 
 	if len(directProtoSrcs.Includes) > 0 {
-		attrs := ProtoAttrs{
-			Srcs: bazel.MakeLabelListAttribute(directProtoSrcs),
-		}
-		attrs.Deps.Append(bazel.MakeLabelListAttribute(protoLibraries))
+		pkgToSrcs := partitionSrcsByPackage(ctx.ModuleDir(), directProtoSrcs)
+		for _, pkg := range SortedStringKeys(pkgToSrcs) {
+			srcs := pkgToSrcs[pkg]
+			attrs := ProtoAttrs{
+				Srcs: bazel.MakeLabelListAttribute(srcs),
+			}
+			attrs.Deps.Append(bazel.MakeLabelListAttribute(depsFromFilegroup))
 
-		for axis, configToProps := range m.GetArchVariantProperties(ctx, &ProtoProperties{}) {
-			for _, rawProps := range configToProps {
-				var props *ProtoProperties
-				var ok bool
-				if props, ok = rawProps.(*ProtoProperties); !ok {
-					ctx.ModuleErrorf("Could not cast ProtoProperties to expected type")
-				}
-				if axis == bazel.NoConfigAxis {
-					info.Type = props.Proto.Type
-
-					if !proptools.BoolDefault(props.Proto.Canonical_path_from_root, canonicalPathFromRootDefault) {
-						// an empty string indicates to strips the package path
-						path := ""
-						attrs.Strip_import_prefix = &path
+			for axis, configToProps := range m.GetArchVariantProperties(ctx, &ProtoProperties{}) {
+				for _, rawProps := range configToProps {
+					var props *ProtoProperties
+					var ok bool
+					if props, ok = rawProps.(*ProtoProperties); !ok {
+						ctx.ModuleErrorf("Could not cast ProtoProperties to expected type")
 					}
+					if axis == bazel.NoConfigAxis {
+						info.Type = props.Proto.Type
 
-					for _, dir := range props.Proto.Include_dirs {
-						if dep, ok := includeDirsToProtoDeps[dir]; ok {
-							attrs.Deps.Add(bazel.MakeLabelAttribute(dep))
-						} else {
-							ctx.PropertyErrorf("Could not find the proto_library target for include dir", dir)
+						if !proptools.BoolDefault(props.Proto.Canonical_path_from_root, canonicalPathFromRootDefault) {
+							// an empty string indicates to strips the package path
+							path := ""
+							attrs.Strip_import_prefix = &path
 						}
+
+						for _, dir := range props.Proto.Include_dirs {
+							if dep, ok := includeDirsToProtoDeps[dir]; ok {
+								attrs.Deps.Add(bazel.MakeLabelAttribute(dep))
+							} else {
+								ctx.PropertyErrorf("Could not find the proto_library target for include dir", dir)
+							}
+						}
+					} else if props.Proto.Type != info.Type && props.Proto.Type != nil {
+						ctx.ModuleErrorf("Cannot handle arch-variant types for protos at this time.")
 					}
-				} else if props.Proto.Type != info.Type && props.Proto.Type != nil {
-					ctx.ModuleErrorf("Cannot handle arch-variant types for protos at this time.")
 				}
 			}
+
+			tags := ApexAvailableTagsWithoutTestApexes(ctx.(TopDownMutatorContext), ctx.Module())
+
+			// Since we are creating the proto_library in a subpackage, create an import_prefix relative to the current package
+			if rel, err := filepath.Rel(ctx.ModuleDir(), pkg); err != nil {
+				ctx.ModuleErrorf("Could not get relative path for %v %v", pkg, err)
+			} else if rel != "." {
+				attrs.Import_prefix = &rel
+			}
+
+			ctx.CreateBazelTargetModule(
+				bazel.BazelTargetModuleProperties{Rule_class: "proto_library"},
+				CommonAttributes{Name: name, Dir: proptools.StringPtr(pkg), Tags: tags},
+				&attrs,
+			)
+
+			l := ""
+			if pkg == ctx.ModuleDir() { // same package that the original module lives in
+				l = ":" + name
+			} else {
+				l = "//" + pkg + ":" + name
+			}
+			protoLibraries.Add(&bazel.Label{
+				Label: l,
+			})
 		}
-
-		tags := ApexAvailableTagsWithoutTestApexes(ctx.(TopDownMutatorContext), ctx.Module())
-
-		ctx.CreateBazelTargetModule(
-			bazel.BazelTargetModuleProperties{Rule_class: "proto_library"},
-			CommonAttributes{Name: info.Name, Tags: tags},
-			&attrs,
-		)
-
-		protoLibraries.Add(&bazel.Label{
-			Label: ":" + info.Name,
-		})
 	}
 
 	info.Proto_libs = protoLibraries
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 490cd91..496a482 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -4903,3 +4903,67 @@
 		},
 	})
 }
+
+// Bazel enforces that proto_library and the .proto file are in the same bazel package
+func TestGenerateProtoLibraryInSamePackage(t *testing.T) {
+	tc := Bp2buildTestCase{
+		Description:                "cc_library depends on .proto files from multiple packages",
+		ModuleTypeUnderTest:        "cc_library",
+		ModuleTypeUnderTestFactory: cc.LibraryFactory,
+		Blueprint: `
+cc_library_static {
+	name: "foo",
+	srcs: [
+	   "foo.proto",
+	   "bar/bar.proto", // Different package because there is a bar/Android.bp
+	   "baz/subbaz/baz.proto", // Different package because there is baz/subbaz/Android.bp
+	],
+}
+` + simpleModuleDoNotConvertBp2build("cc_library", "libprotobuf-cpp-lite"),
+		Filesystem: map[string]string{
+			"bar/Android.bp":        "",
+			"baz/subbaz/Android.bp": "",
+		},
+	}
+
+	// We will run the test 3 times and check in the root, bar and baz/subbaz directories
+	// Root dir
+	tc.ExpectedBazelTargets = []string{
+		MakeBazelTarget("cc_library_static", "foo", AttrNameToString{
+			"local_includes":                    `["."]`,
+			"deps":                              `[":libprotobuf-cpp-lite"]`,
+			"implementation_whole_archive_deps": `[":foo_cc_proto_lite"]`,
+		}),
+		MakeBazelTarget("proto_library", "foo_proto", AttrNameToString{
+			"srcs": `["foo.proto"]`,
+		}),
+		MakeBazelTarget("cc_lite_proto_library", "foo_cc_proto_lite", AttrNameToString{
+			"deps": `[
+        ":foo_proto",
+        "//bar:foo_proto",
+        "//baz/subbaz:foo_proto",
+    ]`,
+		}),
+	}
+	runCcLibraryTestCase(t, tc)
+
+	// bar dir
+	tc.Dir = "bar"
+	tc.ExpectedBazelTargets = []string{
+		MakeBazelTarget("proto_library", "foo_proto", AttrNameToString{
+			"srcs":          `["//bar:bar.proto"]`,
+			"import_prefix": `"bar"`,
+		}),
+	}
+	runCcLibraryTestCase(t, tc)
+
+	// baz/subbaz dir
+	tc.Dir = "baz/subbaz"
+	tc.ExpectedBazelTargets = []string{
+		MakeBazelTarget("proto_library", "foo_proto", AttrNameToString{
+			"srcs":          `["//baz/subbaz:baz.proto"]`,
+			"import_prefix": `"baz/subbaz"`,
+		}),
+	}
+	runCcLibraryTestCase(t, tc)
+}
diff --git a/python/bp2build.go b/python/bp2build.go
index cd3f2a1..7c28da3 100644
--- a/python/bp2build.go
+++ b/python/bp2build.go
@@ -73,7 +73,6 @@
 
 	if !partitionedSrcs["proto"].IsEmpty() {
 		protoInfo, _ := android.Bp2buildProtoProperties(ctx, &m.ModuleBase, partitionedSrcs["proto"])
-		protoLabel := bazel.Label{Label: ":" + protoInfo.Name}
 
 		pyProtoLibraryName := m.Name() + "_py_proto"
 		ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
@@ -82,7 +81,7 @@
 		}, android.CommonAttributes{
 			Name: pyProtoLibraryName,
 		}, &bazelPythonProtoLibraryAttributes{
-			Deps: bazel.MakeSingleLabelListAttribute(protoLabel),
+			Deps: bazel.MakeLabelListAttribute(protoInfo.Proto_libs),
 		})
 
 		attrs.Deps.Add(bazel.MakeLabelAttribute(":" + pyProtoLibraryName))