Make 'file_contexts' prop as `android:"path"`

For platform APEXes, file_contexts should point a file under
/system/sepolicy.

Bug: 144732805
Test: m
Change-Id: Ib2d5db715bbebc80a6178d1c42e387b268cc4a0d
diff --git a/apex/apex.go b/apex/apex.go
index b831333..c19c9eb 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -118,16 +119,11 @@
 func apexMutator(mctx android.BottomUpMutatorContext) {
 	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
 		am.CreateApexVariations(mctx)
-	} else if a, ok := mctx.Module().(*apexBundle); ok {
+	} else if _, ok := mctx.Module().(*apexBundle); ok {
 		// apex bundle itself is mutated so that it and its modules have same
 		// apex variant.
 		apexBundleName := mctx.ModuleName()
 		mctx.CreateVariations(apexBundleName)
-
-		// collects APEX list
-		if mctx.Device() && a.installable() {
-			addApexFileContextsInfos(mctx, a)
-		}
 	} else if o, ok := mctx.Module().(*OverrideApex); ok {
 		apexBundleName := o.GetOverriddenModuleName()
 		if apexBundleName == "" {
@@ -150,14 +146,11 @@
 	}).(*[]string)
 }
 
-func addApexFileContextsInfos(ctx android.BaseModuleContext, a *apexBundle) {
-	apexName := proptools.StringDefault(a.properties.Apex_name, ctx.ModuleName())
-	fileContextsName := proptools.StringDefault(a.properties.File_contexts, ctx.ModuleName())
-
+func addFlattenedFileContextsInfos(ctx android.BaseModuleContext, fileContextsInfo string) {
 	apexFileContextsInfosMutex.Lock()
 	defer apexFileContextsInfosMutex.Unlock()
 	apexFileContextsInfos := apexFileContextsInfos(ctx.Config())
-	*apexFileContextsInfos = append(*apexFileContextsInfos, apexName+":"+fileContextsName)
+	*apexFileContextsInfos = append(*apexFileContextsInfos, fileContextsInfo)
 }
 
 func apexFlattenedMutator(mctx android.BottomUpMutatorContext) {
@@ -272,10 +265,9 @@
 	Apex_name *string
 
 	// Determines the file contexts file for setting security context to each file in this APEX bundle.
-	// Specifically, when this is set to <value>, /system/sepolicy/apex/<value>_file_contexts file is
-	// used.
-	// Default: <name_of_this_module>
-	File_contexts *string
+	// For platform APEXes, this should points to a file under /system/sepolicy
+	// Default: /system/sepolicy/apex/<module_name>_file_contexts.
+	File_contexts *string `android:"path"`
 
 	// List of native shared libs that are embedded inside this APEX bundle
 	Native_shared_libs []string
@@ -481,6 +473,8 @@
 	container_certificate_file android.Path
 	container_private_key_file android.Path
 
+	fileContexts android.Path
+
 	// list of files to be included in this apex
 	filesInfo []apexFile
 
@@ -1162,6 +1156,23 @@
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = filesInfo
 
+	if a.properties.ApexType != zipApex {
+		if a.properties.File_contexts == nil {
+			a.fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
+		} else {
+			a.fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts)
+			if a.Platform() {
+				if matched, err := path.Match("system/sepolicy/**/*", a.fileContexts.String()); err != nil || !matched {
+					ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", a.fileContexts)
+				}
+			}
+		}
+		if !android.ExistentPathForSource(ctx, a.fileContexts.String()).Valid() {
+			ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", a.fileContexts)
+			return
+		}
+	}
+
 	// prepare apex_manifest.json
 	a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
 
diff --git a/apex/apex_test.go b/apex/apex_test.go
index f4b8e35..8d62d26 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -263,50 +263,57 @@
 			symbol_file: "",
 			native_bridge_supported: true,
 		}
+
+		filegroup {
+			name: "myapex-file_contexts",
+			srcs: [
+				"system/sepolicy/apex/myapex-file_contexts",
+			],
+		}
 	`
 	bp = bp + java.GatherRequiredDepsForTest()
 
 	fs := map[string][]byte{
-		"Android.bp":                                []byte(bp),
-		"a.java":                                    nil,
-		"PrebuiltAppFoo.apk":                        nil,
-		"PrebuiltAppFooPriv.apk":                    nil,
-		"build/make/target/product/security":        nil,
-		"apex_manifest.json":                        nil,
-		"AndroidManifest.xml":                       nil,
-		"system/sepolicy/apex/myapex-file_contexts": nil,
-		"system/sepolicy/apex/myapex_keytest-file_contexts": nil,
-		"system/sepolicy/apex/otherapex-file_contexts":      nil,
-		"system/sepolicy/apex/commonapex-file_contexts":     nil,
-		"mylib.cpp":                                  nil,
-		"mylib_common.cpp":                           nil,
-		"mytest.cpp":                                 nil,
-		"mytest1.cpp":                                nil,
-		"mytest2.cpp":                                nil,
-		"mytest3.cpp":                                nil,
-		"myprebuilt":                                 nil,
-		"my_include":                                 nil,
-		"foo/bar/MyClass.java":                       nil,
-		"prebuilt.jar":                               nil,
-		"vendor/foo/devkeys/test.x509.pem":           nil,
-		"vendor/foo/devkeys/test.pk8":                nil,
-		"testkey.x509.pem":                           nil,
-		"testkey.pk8":                                nil,
-		"testkey.override.x509.pem":                  nil,
-		"testkey.override.pk8":                       nil,
-		"vendor/foo/devkeys/testkey.avbpubkey":       nil,
-		"vendor/foo/devkeys/testkey.pem":             nil,
-		"NOTICE":                                     nil,
-		"custom_notice":                              nil,
-		"testkey2.avbpubkey":                         nil,
-		"testkey2.pem":                               nil,
-		"myapex-arm64.apex":                          nil,
-		"myapex-arm.apex":                            nil,
-		"frameworks/base/api/current.txt":            nil,
-		"framework/aidl/a.aidl":                      nil,
-		"build/make/core/proguard.flags":             nil,
-		"build/make/core/proguard_basic_keeps.flags": nil,
-		"dummy.txt":                                  nil,
+		"Android.bp":                                          []byte(bp),
+		"a.java":                                              nil,
+		"PrebuiltAppFoo.apk":                                  nil,
+		"PrebuiltAppFooPriv.apk":                              nil,
+		"build/make/target/product/security":                  nil,
+		"apex_manifest.json":                                  nil,
+		"AndroidManifest.xml":                                 nil,
+		"system/sepolicy/apex/myapex-file_contexts":           nil,
+		"system/sepolicy/apex/otherapex-file_contexts":        nil,
+		"system/sepolicy/apex/commonapex-file_contexts":       nil,
+		"system/sepolicy/apex/com.android.vndk-file_contexts": nil,
+		"mylib.cpp":                                           nil,
+		"mylib_common.cpp":                                    nil,
+		"mytest.cpp":                                          nil,
+		"mytest1.cpp":                                         nil,
+		"mytest2.cpp":                                         nil,
+		"mytest3.cpp":                                         nil,
+		"myprebuilt":                                          nil,
+		"my_include":                                          nil,
+		"foo/bar/MyClass.java":                                nil,
+		"prebuilt.jar":                                        nil,
+		"vendor/foo/devkeys/test.x509.pem":                    nil,
+		"vendor/foo/devkeys/test.pk8":                         nil,
+		"testkey.x509.pem":                                    nil,
+		"testkey.pk8":                                         nil,
+		"testkey.override.x509.pem":                           nil,
+		"testkey.override.pk8":                                nil,
+		"vendor/foo/devkeys/testkey.avbpubkey":                nil,
+		"vendor/foo/devkeys/testkey.pem":                      nil,
+		"NOTICE":                                              nil,
+		"custom_notice":                                       nil,
+		"testkey2.avbpubkey":                                  nil,
+		"testkey2.pem":                                        nil,
+		"myapex-arm64.apex":                                   nil,
+		"myapex-arm.apex":                                     nil,
+		"frameworks/base/api/current.txt":                     nil,
+		"framework/aidl/a.aidl":                               nil,
+		"build/make/core/proguard.flags":                      nil,
+		"build/make/core/proguard_basic_keeps.flags":          nil,
+		"dummy.txt":                                           nil,
 	}
 
 	for _, handler := range handlers {
@@ -1201,6 +1208,7 @@
 			key: "myapex.key",
 			certificate: ":myapex.certificate",
 			native_shared_libs: ["mylib"],
+			file_contexts: ":myapex-file_contexts",
 		}
 
 		cc_library {
@@ -1411,7 +1419,6 @@
 		apex_vndk {
 			name: "myapex",
 			key: "myapex.key",
-			file_contexts: "myapex",
 		}
 
 		apex_key {
@@ -1462,7 +1469,6 @@
 		apex_vndk {
 			name: "myapex",
 			key: "myapex.key",
-			file_contexts: "myapex",
 		}
 
 		apex_key {
@@ -1541,7 +1547,7 @@
 		apex_vndk {
 			name: "myapex_v27",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 			vndk_version: "27",
 		}
 
@@ -1606,13 +1612,13 @@
 		apex_vndk {
 			name: "myapex_v27",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 			vndk_version: "27",
 		}
 		apex_vndk {
 			name: "myapex_v27_other",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 			vndk_version: "27",
 		}
 
@@ -1652,12 +1658,12 @@
 		apex_vndk {
 			name: "myapex",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 		}
 		apex_vndk {
 			name: "myapex_v28",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 			vndk_version: "28",
 		}
 		apex_key {
@@ -1683,7 +1689,7 @@
 		apex_vndk {
 			name: "myapex",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 		}
 
 		apex_key {
@@ -1726,7 +1732,7 @@
 		apex_vndk {
 			name: "myapex",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 			native_bridge_supported: true,
 		}
 
@@ -1756,7 +1762,7 @@
 		apex_vndk {
 			name: "myapex_v27",
 			key: "myapex.key",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 			vndk_version: "27",
 		}
 
@@ -1822,7 +1828,7 @@
 			key: "myapex.key",
 			native_shared_libs: ["lib_nodep"],
 			compile_multilib: "both",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 		}
 
 		apex {
@@ -1830,7 +1836,7 @@
 			key: "myapex.key",
 			native_shared_libs: ["lib_dep"],
 			compile_multilib: "both",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 		}
 
 		apex {
@@ -1838,7 +1844,7 @@
 			key: "myapex.key",
 			native_shared_libs: ["libfoo"],
 			compile_multilib: "both",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 		}
 
 		apex {
@@ -1846,7 +1852,7 @@
 			key: "myapex.key",
 			native_shared_libs: ["lib_dep", "libfoo"],
 			compile_multilib: "both",
-			file_contexts: "myapex",
+			file_contexts: ":myapex-file_contexts",
 		}
 
 		apex_key {
@@ -2145,6 +2151,7 @@
 			key: "myapex.key",
 			native_shared_libs: ["mylib"],
 			product_specific: true,
+			file_contexts: "myapex_file_contexts",
 		}
 
 		apex_key {
@@ -2160,7 +2167,9 @@
 			system_shared_libs: [],
 			stl: "none",
 		}
-	`)
+	`, withFiles(map[string][]byte{
+		"myapex_file_contexts": nil,
+	}))
 
 	apex := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
 	expected := buildDir + "/target/product/test_device/product/apex"
@@ -2170,6 +2179,112 @@
 	}
 }
 
+func TestFileContexts(t *testing.T) {
+	ctx, _ := testApex(t, `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+	`)
+	module := ctx.ModuleForTests("myapex", "android_common_myapex_image")
+	apexRule := module.Rule("apexRule")
+	actual := apexRule.Args["file_contexts"]
+	expected := "system/sepolicy/apex/myapex-file_contexts"
+	if actual != expected {
+		t.Errorf("wrong file_contexts. expected %q. actual %q", expected, actual)
+	}
+
+	testApexError(t, `"myapex" .*: file_contexts: should be under system/sepolicy`, `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		file_contexts: "my_own_file_contexts",
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+	`, withFiles(map[string][]byte{
+		"my_own_file_contexts": nil,
+	}))
+
+	testApexError(t, `"myapex" .*: file_contexts: cannot find`, `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		product_specific: true,
+		file_contexts: "product_specific_file_contexts",
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+	`)
+
+	ctx, _ = testApex(t, `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		product_specific: true,
+		file_contexts: "product_specific_file_contexts",
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+	`, withFiles(map[string][]byte{
+		"product_specific_file_contexts": nil,
+	}))
+	module = ctx.ModuleForTests("myapex", "android_common_myapex_image")
+	apexRule = module.Rule("apexRule")
+	actual = apexRule.Args["file_contexts"]
+	expected = "product_specific_file_contexts"
+	if actual != expected {
+		t.Errorf("wrong file_contexts. expected %q. actual %q", expected, actual)
+	}
+
+	ctx, _ = testApex(t, `
+	apex {
+		name: "myapex",
+		key: "myapex.key",
+		product_specific: true,
+		file_contexts: ":my-file-contexts",
+	}
+
+	apex_key {
+		name: "myapex.key",
+		public_key: "testkey.avbpubkey",
+		private_key: "testkey.pem",
+	}
+
+	filegroup {
+		name: "my-file-contexts",
+		srcs: ["product_specific_file_contexts"],
+	}
+	`, withFiles(map[string][]byte{
+		"product_specific_file_contexts": nil,
+	}))
+	module = ctx.ModuleForTests("myapex", "android_common_myapex_image")
+	apexRule = module.Rule("apexRule")
+	actual = apexRule.Args["file_contexts"]
+	expected = "product_specific_file_contexts"
+	if actual != expected {
+		t.Errorf("wrong file_contexts. expected %q. actual %q", expected, actual)
+	}
+}
+
 func TestApexKeyFromOtherModule(t *testing.T) {
 	ctx, _ := testApex(t, `
 		apex_key {
diff --git a/apex/builder.go b/apex/builder.go
index b29bc2c..c834e3d 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -340,19 +340,10 @@
 			},
 		})
 
-		fcName := proptools.StringDefault(a.properties.File_contexts, ctx.ModuleName())
-		fileContextsPath := "system/sepolicy/apex/" + fcName + "-file_contexts"
-		fileContextsOptionalPath := android.ExistentPathForSource(ctx, fileContextsPath)
-		if !fileContextsOptionalPath.Valid() {
-			ctx.ModuleErrorf("Cannot find file_contexts file: %q", fileContextsPath)
-			return
-		}
-		fileContexts := fileContextsOptionalPath.Path()
-
 		optFlags := []string{}
 
 		// Additional implicit inputs.
-		implicitInputs = append(implicitInputs, cannedFsConfig, fileContexts, a.private_key_file, a.public_key_file)
+		implicitInputs = append(implicitInputs, cannedFsConfig, a.fileContexts, a.private_key_file, a.public_key_file)
 		optFlags = append(optFlags, "--pubkey "+a.public_key_file.String())
 
 		manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
@@ -409,7 +400,7 @@
 				"manifest_json_full": a.manifestJsonFullOut.String(),
 				"manifest_json":      a.manifestJsonOut.String(),
 				"manifest":           a.manifestPbOut.String(),
-				"file_contexts":      fileContexts.String(),
+				"file_contexts":      a.fileContexts.String(),
 				"canned_fs_config":   cannedFsConfig.String(),
 				"key":                a.private_key_file.String(),
 				"opt_flags":          strings.Join(optFlags, " "),
@@ -485,6 +476,11 @@
 	apexName := proptools.StringDefault(a.properties.Apex_name, ctx.ModuleName())
 	a.outputFile = android.PathForModuleInstall(&factx, "apex", apexName)
 
+	if a.installable() {
+		installPath := android.PathForModuleInstall(ctx, "apex", apexName)
+		devicePath := android.InstallPathToOnDevicePath(ctx, installPath)
+		addFlattenedFileContextsInfos(ctx, apexName+":"+devicePath+":"+a.fileContexts.String())
+	}
 	a.buildFilesInfo(ctx)
 }