Convert libprotobuf to Bazel

Since this is a one-off just for building libprotobuf that will be removed when we handle cargo output more generically (b/297364081), I didn't write a unit test for this CL.

Test: b build //external/rust/crates/protobuf:libprotobuf
Bug: 295925256
Change-Id: I00cf44d54be27a09c184a96c13b250a2e54e2d10
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index dda125a..7163328 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -513,6 +513,7 @@
 		"libprotobuf_support",
 		"libtinytemplate",
 		"libserde_json",
+		"libprotobuf",
 
 		// ext
 		"tagsoup",
diff --git a/bp2build/rust_library_conversion_test.go b/bp2build/rust_library_conversion_test.go
index 296f57e..b860b76 100644
--- a/bp2build/rust_library_conversion_test.go
+++ b/bp2build/rust_library_conversion_test.go
@@ -16,6 +16,36 @@
 	ctx.RegisterModuleType("rust_library_host", rust.RustLibraryHostFactory)
 }
 
+func TestLibProtobuf(t *testing.T) {
+	runRustLibraryTestCase(t, Bp2buildTestCase{
+		Dir:       "external/rust/crates/foo",
+		Blueprint: "",
+		Filesystem: map[string]string{
+			"external/rust/crates/foo/src/lib.rs": "",
+			"external/rust/crates/foo/Android.bp": `
+rust_library_host {
+	name: "libprotobuf",
+	crate_name: "protobuf",
+	srcs: ["src/lib.rs"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+		},
+		ExpectedBazelTargets: []string{
+			// TODO(b/290790800): Remove the restriction when rust toolchain for android is implemented
+			makeBazelTargetHostOrDevice("rust_library", "libprotobuf", AttrNameToString{
+				"crate_name": `"protobuf"`,
+				"srcs":       `["src/lib.rs"]`,
+				"deps":       `[":libprotobuf_build_script"]`,
+			}, android.HostSupported),
+			makeBazelTargetHostOrDevice("cargo_build_script", "libprotobuf_build_script", AttrNameToString{
+				"srcs": `["build.rs"]`,
+			}, android.HostSupported),
+		},
+	},
+	)
+}
+
 func TestRustLibrary(t *testing.T) {
 	expectedAttrs := AttrNameToString{
 		"crate_name": `"foo"`,
diff --git a/rust/library.go b/rust/library.go
index da2209a..3f031c1 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -812,11 +812,21 @@
 
 func libraryBp2build(ctx android.TopDownMutatorContext, m *Module) {
 	lib := m.compiler.(*libraryDecorator)
+
 	srcs, compileData := srcsAndCompileDataAttrs(ctx, *lib.baseCompiler)
+
 	deps := android.BazelLabelForModuleDeps(ctx, append(
 		lib.baseCompiler.Properties.Rustlibs,
 		lib.baseCompiler.Properties.Rlibs...,
 	))
+
+	cargoBuildScript := cargoBuildScriptBp2build(ctx, m)
+	if cargoBuildScript != nil {
+		deps.Add(&bazel.Label{
+			Label: ":" + *cargoBuildScript,
+		})
+	}
+
 	procMacroDeps := android.BazelLabelForModuleDeps(ctx, lib.baseCompiler.Properties.Proc_macros)
 
 	var rustcFLags []string
@@ -870,3 +880,73 @@
 		restriction,
 	)
 }
+
+type cargoBuildScriptAttributes struct {
+	Srcs    bazel.LabelListAttribute
+	Edition bazel.StringAttribute
+	Version bazel.StringAttribute
+}
+
+func cargoBuildScriptBp2build(ctx android.TopDownMutatorContext, m *Module) *string {
+	// Soong treats some crates like libprotobuf as special in that they have
+	// cargo build script ran to produce an out folder and check it into AOSP
+	// For example, https://cs.android.com/android/platform/superproject/main/+/main:external/rust/crates/protobuf/out/
+	// is produced by cargo build script https://cs.android.com/android/platform/superproject/main/+/main:external/rust/crates/protobuf/build.rs
+	// The out folder is then fed into `rust_library` by a genrule
+	// https://cs.android.com/android/platform/superproject/main/+/main:external/rust/crates/protobuf/Android.bp;l=22
+	// This allows Soong to decouple from cargo completely.
+
+	// Soong decouples from cargo so that it has control over cc compilation.
+	// https://cs.android.com/android/platform/superproject/main/+/main:development/scripts/cargo2android.py;l=1033-1041;drc=8449944a50a0445a5ecaf9b7aed12608c81bf3f1
+	// generates a `cc_library_static` module to have custom cc flags.
+	// Since bp2build will convert the cc modules to cc targets which include the cflags,
+	// Bazel does not need to have this optimization.
+
+	// Performance-wise: rust_library -> cargo_build_script vs rust_library -> genrule (like Soong)
+	// don't have any major difference in build time in Bazel. So using cargo_build_script does not slow
+	// down the build.
+
+	// The benefit of using `cargo_build_script` here is that it would take care of setting correct
+	// `OUT_DIR` for us - similar to what Soong does here
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/soong/rust/builder.go;l=202-218;drc=f29ca58e88c5846bbe8955e5192135e5ab4f14a1
+
+	// TODO(b/297364081): cargo2android.py has logic for when generate/not cc_library_static and out directory
+	// bp2build might be able use the same logic for when to use `cargo_build_script`.
+	// For now, we're building libprotobuf_build_script as a one-off until we have a more principled solution
+	if m.Name() != "libprotobuf" {
+		return nil
+	}
+
+	lib := m.compiler.(*libraryDecorator)
+
+	name := m.Name() + "_build_script"
+	attrs := &cargoBuildScriptAttributes{
+		Srcs: bazel.MakeLabelListAttribute(
+			android.BazelLabelForModuleSrc(ctx, []string{"build.rs"}),
+		),
+		Edition: bazel.StringAttribute{
+			Value: lib.baseCompiler.Properties.Edition,
+		},
+		Version: bazel.StringAttribute{
+			Value: lib.baseCompiler.Properties.Cargo_pkg_version,
+		},
+	}
+
+	// TODO(b/290790800): Remove the restriction when rust toolchain for android is implemented
+	var restriction bazel.BoolAttribute
+	restriction.SetSelectValue(bazel.OsConfigurationAxis, "android", proptools.BoolPtr(false))
+
+	ctx.CreateBazelTargetModuleWithRestrictions(
+		bazel.BazelTargetModuleProperties{
+			Rule_class:        "cargo_build_script",
+			Bzl_load_location: "@rules_rust//cargo:cargo_build_script.bzl",
+		},
+		android.CommonAttributes{
+			Name: name,
+		},
+		attrs,
+		restriction,
+	)
+
+	return &name
+}