Implement rust_aconfig_library

rust_aconfig_library generates src/lib.rs and uses it to build library variants (dylib, rlib-rlib_std, and rlib-dylib_std) as of what `rust_library` produces

Test: go test
Change-Id: I6c4603691d4306c463c2e9521f5c11c30765b1e3
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index 6927765..d2ddfdf 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -14,6 +14,7 @@
         "soong-bazel",
         "soong-android",
         "soong-java",
+        "soong-rust",
     ],
     srcs: [
         "aconfig_declarations.go",
@@ -24,6 +25,7 @@
         "init.go",
         "java_aconfig_library.go",
         "testing.go",
+        "rust_aconfig_library.go",
     ],
     testSrcs: [
         "aconfig_declarations_test.go",
diff --git a/aconfig/aconfig_declarations.go b/aconfig/aconfig_declarations.go
index 565d185..d1d1578 100644
--- a/aconfig/aconfig_declarations.go
+++ b/aconfig/aconfig_declarations.go
@@ -17,8 +17,9 @@
 import (
 	"android/soong/android"
 	"fmt"
-	"github.com/google/blueprint"
 	"strings"
+
+	"github.com/google/blueprint"
 )
 
 type DeclarationsModule struct {
diff --git a/aconfig/init.go b/aconfig/init.go
index 7e283ea..58b6b8b 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -70,6 +71,20 @@
 			},
 		}, "gendir")
 
+	rustRule = pctx.AndroidStaticRule("rust_aconfig_library",
+		blueprint.RuleParams{
+			Command: `rm -rf ${gendir}` +
+				` && mkdir -p ${gendir}` +
+				` && ${aconfig} create-rust-lib` +
+				`    --mode ${mode}` +
+				`    --cache ${in}` +
+				`    --out ${gendir}`,
+			CommandDeps: []string{
+				"$aconfig",
+				"$soong_zip",
+			},
+		}, "gendir", "mode")
+
 	// For all_aconfig_declarations
 	allDeclarationsRule = pctx.AndroidStaticRule("all_aconfig_declarations_dump",
 		blueprint.RuleParams{
@@ -92,5 +107,6 @@
 	ctx.RegisterModuleType("aconfig_value_set", ValueSetFactory)
 	ctx.RegisterModuleType("cc_aconfig_library", CcAconfigLibraryFactory)
 	ctx.RegisterModuleType("java_aconfig_library", JavaDeclarationsLibraryFactory)
+	ctx.RegisterModuleType("rust_aconfig_library", RustAconfigLibraryFactory)
 	ctx.RegisterParallelSingletonType("all_aconfig_declarations", AllAconfigDeclarationsFactory)
 }
diff --git a/aconfig/rust_aconfig_library.go b/aconfig/rust_aconfig_library.go
new file mode 100644
index 0000000..8b16372
--- /dev/null
+++ b/aconfig/rust_aconfig_library.go
@@ -0,0 +1,83 @@
+package aconfig
+
+import (
+	"android/soong/android"
+	"android/soong/rust"
+	"fmt"
+
+	"github.com/google/blueprint"
+)
+
+type rustDeclarationsTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+var rustDeclarationsTag = rustDeclarationsTagType{}
+
+type RustAconfigLibraryProperties struct {
+	// name of the aconfig_declarations module to generate a library for
+	Aconfig_declarations string
+}
+
+type aconfigDecorator struct {
+	*rust.BaseSourceProvider
+
+	Properties RustAconfigLibraryProperties
+}
+
+func NewRustAconfigLibrary(hod android.HostOrDeviceSupported) (*rust.Module, *aconfigDecorator) {
+	aconfig := &aconfigDecorator{
+		BaseSourceProvider: rust.NewSourceProvider(),
+		Properties:         RustAconfigLibraryProperties{},
+	}
+
+	module := rust.NewSourceProviderModule(android.HostAndDeviceSupported, aconfig, false, false)
+	return module, aconfig
+}
+
+// rust_aconfig_library generates aconfig rust code from the provided aconfig declaration. This module type will
+// create library variants that can be used as a crate dependency by adding it to the rlibs, dylibs, and rustlibs
+// properties of other modules.
+func RustAconfigLibraryFactory() android.Module {
+	module, _ := NewRustAconfigLibrary(android.HostAndDeviceSupported)
+	return module.Init()
+}
+
+func (a *aconfigDecorator) SourceProviderProps() []interface{} {
+	return append(a.BaseSourceProvider.SourceProviderProps(), &a.Properties)
+}
+
+func (a *aconfigDecorator) GenerateSource(ctx rust.ModuleContext, deps rust.PathDeps) android.Path {
+	generatedDir := android.PathForModuleGen(ctx)
+	generatedSource := android.PathForModuleGen(ctx, "src", "lib.rs")
+
+	declarationsModules := ctx.GetDirectDepsWithTag(rustDeclarationsTag)
+
+	if len(declarationsModules) != 1 {
+		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
+	}
+	declarations := ctx.OtherModuleProvider(declarationsModules[0], declarationsProviderKey).(declarationsProviderData)
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:  rustRule,
+		Input: declarations.IntermediatePath,
+		Outputs: []android.WritablePath{
+			generatedSource,
+		},
+		Description: "rust_aconfig_library",
+		Args: map[string]string{
+			"gendir": generatedDir.String(),
+			// TODO: Add test mode
+			"mode": "production",
+		},
+	})
+	a.BaseSourceProvider.OutputFiles = android.Paths{generatedSource}
+	return generatedSource
+}
+
+func (a *aconfigDecorator) SourceProviderDeps(ctx rust.DepsContext, deps rust.Deps) rust.Deps {
+	deps = a.BaseSourceProvider.SourceProviderDeps(ctx, deps)
+	deps.Rustlibs = append(deps.Rustlibs, "libflags_rust")
+	ctx.AddDependency(ctx.Module(), rustDeclarationsTag, a.Properties.Aconfig_declarations)
+	return deps
+}
diff --git a/aconfig/rust_aconfig_library_test.go b/aconfig/rust_aconfig_library_test.go
new file mode 100644
index 0000000..17385c3
--- /dev/null
+++ b/aconfig/rust_aconfig_library_test.go
@@ -0,0 +1,60 @@
+package aconfig
+
+import (
+	"android/soong/android"
+	"android/soong/rust"
+	"fmt"
+	"testing"
+)
+
+func TestRustAconfigLibrary(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithAconfigBuildComponents,
+		rust.PrepareForTestWithRustIncludeVndk,
+		android.PrepareForTestWithArchMutator,
+		android.PrepareForTestWithDefaults,
+		android.PrepareForTestWithPrebuilts,
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
+		RunTestWithBp(t, fmt.Sprintf(`
+			rust_library {
+				name: "libflags_rust", // test mock
+				crate_name: "flags_rust",
+				srcs: ["lib.rs"],
+			}
+			aconfig_declarations {
+				name: "my_aconfig_declarations",
+				package: "com.example.package",
+				srcs: ["foo.aconfig"],
+			}
+
+			rust_aconfig_library {
+				name: "libmy_rust_aconfig_library",
+				crate_name: "my_rust_aconfig_library",
+				aconfig_declarations: "my_aconfig_declarations",
+			}
+		`))
+
+	sourceVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_source")
+	rule := sourceVariant.Rule("rust_aconfig_library")
+	android.AssertStringEquals(t, "rule must contain production mode", rule.Args["mode"], "production")
+
+	dylibVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_dylib")
+	rlibRlibStdVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_rlib-std")
+	rlibDylibStdVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_dylib-std")
+
+	variants := []android.TestingModule{
+		dylibVariant,
+		rlibDylibStdVariant,
+		rlibRlibStdVariant,
+	}
+
+	for _, variant := range variants {
+		android.AssertStringEquals(
+			t,
+			"dylib variant builds from generated rust code",
+			"out/soong/.intermediates/libmy_rust_aconfig_library/android_arm64_armv8-a_source/gen/src/lib.rs",
+			variant.Rule("rustc").Inputs[0].RelativeToTop().String(),
+		)
+	}
+}