Migrate sepolicy compat test to Android.bp

compat_test tests whether {ver}.compat.cil is compatible to current
policy or not. This commit migrates all tests into a single module named
"sepolicy_compat_tests".

A minor issue is also resolved with this migration. Suppose that the
vendor's speolicy version is {VER}. Then the following cil files are
compiled in runtime.

- system/etc/selinux/plat_sepolicy.cil
- system/etc/selinux/mapping/{VER}.cil
- system/etc/selinux/mapping/{VER}.compat.cil (optional)
- system_ext/etc/selinux/system_ext_sepolicy.cil (optional)
- system_ext/etc/selinux/mapping/{VER}.cil (optional)
- system_ext/etc/selinux/mapping/{VER}.compat.cil (optional)
- product/etc/selinux/product_sepolicy.cil (optional)
- product/etc/selinux/mapping/{VER}.cil (optional)
- product/etc/selinux/mapping/{VER}.compat.cil (optional)
- vendor/etc/selinux/vendor_sepolicy.cil
- vendor/etc/selinux/plat_pub_versioned.cil
- odm/etc/selinux/odm_sepolicy.cil (optional)

That is, the vendor policy of version {VER} (vendor_sepolicy.cil,
plat_pub_versioned.cil, and odm_sepolicy.cil) is required to be
compatible only to {VER}.compat.cil. So, the vendor policy is included
only to $(BOARD_SEPOLICY_VERS)_compat_test. The other tests will be
built only with platform side policies.

Bug: 33691272
Test: boot
Test: manually edit {ver}.compat.cil files and try build
Change-Id: I16b30a9171f10ee8f08fc03b7bd7c047eec12b19
diff --git a/build/soong/build_files.go b/build/soong/build_files.go
index 865dbb4..0909f70 100644
--- a/build/soong/build_files.go
+++ b/build/soong/build_files.go
@@ -124,4 +124,9 @@
 		b.srcs[".product_public_for_vendor"] = b.findSrcsInDirs(ctx, ctx.DeviceConfig().BoardProductPublicPrebuiltDirs()...)
 		b.srcs[".product_private_for_vendor"] = b.findSrcsInDirs(ctx, ctx.DeviceConfig().BoardProductPrivatePrebuiltDirs()...)
 	}
+
+	for _, ver := range ctx.DeviceConfig().PlatformSepolicyCompatVersions() {
+		b.srcs[".plat_public_"+ver] = b.findSrcsInDirs(ctx, filepath.Join(ctx.ModuleDir(), "prebuilts", "api", ver, "public"))
+		b.srcs[".plat_private_"+ver] = b.findSrcsInDirs(ctx, filepath.Join(ctx.ModuleDir(), "prebuilts", "api", ver, "private"))
+	}
 }
diff --git a/build/soong/compat_cil.go b/build/soong/compat_cil.go
index 46b0f71..3044425 100644
--- a/build/soong/compat_cil.go
+++ b/build/soong/compat_cil.go
@@ -15,13 +15,21 @@
 package selinux
 
 import (
+	"fmt"
+
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
 )
 
+var (
+	compatTestDepTag = dependencyTag{name: "compat_test"}
+)
+
 func init() {
-	android.RegisterModuleType("se_compat_cil", compatCilFactory)
+	ctx := android.InitRegistrationContext
+	ctx.RegisterModuleType("se_compat_cil", compatCilFactory)
+	ctx.RegisterSingletonModuleType("se_compat_test", compatTestFactory)
 }
 
 // se_compat_cil collects and installs backwards compatibility cil files.
@@ -107,3 +115,154 @@
 		},
 	}}
 }
+
+func (c *compatCil) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{c.installSource}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+var _ android.OutputFileProducer = (*compatCil)(nil)
+
+// se_compat_test checks if compat files ({ver}.cil, {ver}.compat.cil) files are compatible with
+// current policy.
+func compatTestFactory() android.SingletonModule {
+	f := &compatTestModule{}
+	android.InitAndroidModule(f)
+	android.AddLoadHook(f, func(ctx android.LoadHookContext) {
+		f.loadHook(ctx)
+	})
+	return f
+}
+
+type compatTestModule struct {
+	android.SingletonModuleBase
+
+	compatTestTimestamp android.ModuleOutPath
+}
+
+func (f *compatTestModule) createPlatPubVersionedModule(ctx android.LoadHookContext, ver string) {
+	confName := fmt.Sprintf("pub_policy_%s.conf", ver)
+	cilName := fmt.Sprintf("pub_policy_%s.cil", ver)
+	platPubVersionedName := fmt.Sprintf("plat_pub_versioned_%s.cil", ver)
+
+	ctx.CreateModule(policyConfFactory, &nameProperties{
+		Name: proptools.StringPtr(confName),
+	}, &policyConfProperties{
+		Srcs: []string{
+			fmt.Sprintf(":se_build_files{.plat_public_%s}", ver),
+			":se_build_files{.reqd_mask}",
+		},
+		Installable: proptools.BoolPtr(false),
+	})
+
+	ctx.CreateModule(policyCilFactory, &nameProperties{
+		Name: proptools.StringPtr(cilName),
+	}, &policyCilProperties{
+		Src:          proptools.StringPtr(":" + confName),
+		Filter_out:   []string{":reqd_policy_mask.cil"},
+		Secilc_check: proptools.BoolPtr(false),
+		Installable:  proptools.BoolPtr(false),
+	})
+
+	ctx.CreateModule(versionedPolicyFactory, &nameProperties{
+		Name: proptools.StringPtr(platPubVersionedName),
+	}, &versionedPolicyProperties{
+		Base:          proptools.StringPtr(":" + cilName),
+		Target_policy: proptools.StringPtr(":" + cilName),
+		Version:       proptools.StringPtr(ver),
+		Installable:   proptools.BoolPtr(false),
+	})
+}
+
+func (f *compatTestModule) createCompatTestModule(ctx android.LoadHookContext, ver string) {
+	srcs := []string{
+		":plat_sepolicy.cil",
+		":system_ext_sepolicy.cil",
+		":product_sepolicy.cil",
+		fmt.Sprintf(":plat_%s.cil", ver),
+		fmt.Sprintf(":%s.compat.cil", ver),
+		fmt.Sprintf(":system_ext_%s.cil", ver),
+		fmt.Sprintf(":system_ext_%s.compat.cil", ver),
+		fmt.Sprintf(":product_%s.cil", ver),
+	}
+
+	if ver == ctx.DeviceConfig().BoardSepolicyVers() {
+		srcs = append(srcs,
+			":plat_pub_versioned.cil",
+			":vendor_sepolicy.cil",
+			":odm_sepolicy.cil",
+		)
+	} else {
+		srcs = append(srcs, fmt.Sprintf(":plat_pub_versioned_%s.cil", ver))
+	}
+
+	compatTestName := fmt.Sprintf("%s_compat_test", ver)
+	ctx.CreateModule(policyBinaryFactory, &nameProperties{
+		Name: proptools.StringPtr(compatTestName),
+	}, &policyBinaryProperties{
+		Srcs:              srcs,
+		Ignore_neverallow: proptools.BoolPtr(true),
+		Installable:       proptools.BoolPtr(false),
+	})
+}
+
+func (f *compatTestModule) loadHook(ctx android.LoadHookContext) {
+	for _, ver := range ctx.DeviceConfig().PlatformSepolicyCompatVersions() {
+		f.createPlatPubVersionedModule(ctx, ver)
+		f.createCompatTestModule(ctx, ver)
+	}
+}
+
+func (f *compatTestModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	for _, ver := range ctx.DeviceConfig().PlatformSepolicyCompatVersions() {
+		ctx.AddDependency(f, compatTestDepTag, fmt.Sprintf("%s_compat_test", ver))
+	}
+}
+
+func (f *compatTestModule) GenerateSingletonBuildActions(ctx android.SingletonContext) {
+	// does nothing; se_compat_test is a singeton because two compat test modules don't make sense.
+}
+
+func (f *compatTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var inputs android.Paths
+	ctx.VisitDirectDepsWithTag(compatTestDepTag, func(child android.Module) {
+		o, ok := child.(android.OutputFileProducer)
+		if !ok {
+			panic(fmt.Errorf("Module %q should be an OutputFileProducer but it isn't", ctx.OtherModuleName(child)))
+		}
+
+		outputs, err := o.OutputFiles("")
+		if err != nil {
+			panic(fmt.Errorf("Module %q error while producing output: %v", ctx.OtherModuleName(child), err))
+		}
+		if len(outputs) != 1 {
+			panic(fmt.Errorf("Module %q should produce exactly one output, but did %q", ctx.OtherModuleName(child), outputs.Strings()))
+		}
+
+		inputs = append(inputs, outputs[0])
+	})
+
+	f.compatTestTimestamp = android.PathForModuleOut(ctx, "timestamp")
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().Text("touch").Output(f.compatTestTimestamp).Implicits(inputs)
+	rule.Build("compat", "compat test timestamp for: "+f.Name())
+}
+
+func (f *compatTestModule) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class: "FAKE",
+		// OutputFile is needed, even though BUILD_PHONY_PACKAGE doesn't use it.
+		// Without OutputFile this module won't be exported to Makefile.
+		OutputFile: android.OptionalPathForPath(f.compatTestTimestamp),
+		Include:    "$(BUILD_PHONY_PACKAGE)",
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", f.compatTestTimestamp.String())
+			},
+		},
+	}}
+}