platform_system_sepolicy/build/soong/selinux_contexts.go
Jihoon Kang 8298ae56e6 Cleanup ImageInterface.SetImageVariation
This change modifies the interface method of
ImageInterface.SetImageVariation so that the image variation is set
directly at the caller image variation module, instead of passing the
pointer to set the image variation.

Test: m nothing
Change-Id: Ice92b2496dbe9e342edf5542946620ae409f7d4f
2024-06-13 21:47:41 +00:00

736 lines
22 KiB
Go

// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package selinux
import (
"fmt"
"io"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
"android/soong/android"
"android/soong/sysprop"
)
type selinuxContextsProperties struct {
// Filenames under sepolicy directories, which will be used to generate contexts file.
Srcs []string `android:"path"`
// Output file name. Defaults to module name
Stem *string
Product_variables struct {
Address_sanitize struct {
Srcs []string `android:"path"`
}
}
// Whether the comments in generated contexts file will be removed or not.
Remove_comment *bool
// Whether the result context file is sorted with fc_sort or not.
Fc_sort *bool
// Make this module available when building for recovery
Recovery_available *bool
}
type seappProperties struct {
// Files containing neverallow rules.
Neverallow_files []string `android:"path"`
// Precompiled sepolicy binary file which will be fed to checkseapp.
Sepolicy *string `android:"path"`
}
type selinuxContextsModule struct {
android.ModuleBase
android.DefaultableModuleBase
flaggableModuleBase
properties selinuxContextsProperties
seappProperties seappProperties
build func(ctx android.ModuleContext, inputs android.Paths) android.Path
deps func(ctx android.BottomUpMutatorContext)
outputPath android.Path
installPath android.InstallPath
}
var _ flaggableModule = (*selinuxContextsModule)(nil)
var (
reuseContextsDepTag = dependencyTag{name: "reuseContexts"}
syspropLibraryDepTag = dependencyTag{name: "sysprop_library"}
)
func init() {
pctx.HostBinToolVariable("fc_sort", "fc_sort")
android.RegisterModuleType("contexts_defaults", contextsDefaultsFactory)
android.RegisterModuleType("file_contexts", fileFactory)
android.RegisterModuleType("hwservice_contexts", hwServiceFactory)
android.RegisterModuleType("property_contexts", propertyFactory)
android.RegisterModuleType("service_contexts", serviceFactory)
android.RegisterModuleType("keystore2_key_contexts", keystoreKeyFactory)
android.RegisterModuleType("seapp_contexts", seappFactory)
android.RegisterModuleType("vndservice_contexts", vndServiceFactory)
android.RegisterModuleType("file_contexts_test", fileContextsTestFactory)
android.RegisterModuleType("property_contexts_test", propertyContextsTestFactory)
android.RegisterModuleType("hwservice_contexts_test", hwserviceContextsTestFactory)
android.RegisterModuleType("service_contexts_test", serviceContextsTestFactory)
android.RegisterModuleType("vndservice_contexts_test", vndServiceContextsTestFactory)
}
func (m *selinuxContextsModule) InstallInRoot() bool {
return m.InRecovery()
}
func (m *selinuxContextsModule) InstallInRecovery() bool {
// ModuleBase.InRecovery() checks the image variant
return m.InRecovery()
}
func (m *selinuxContextsModule) onlyInRecovery() bool {
// ModuleBase.InstallInRecovery() checks commonProperties.Recovery property
return m.ModuleBase.InstallInRecovery()
}
func (m *selinuxContextsModule) DepsMutator(ctx android.BottomUpMutatorContext) {
m.flagDeps(ctx)
if m.deps != nil {
m.deps(ctx)
}
if m.InRecovery() && !m.onlyInRecovery() {
ctx.AddFarVariationDependencies([]blueprint.Variation{
{Mutator: "image", Variation: android.CoreVariation},
}, reuseContextsDepTag, ctx.ModuleName())
}
}
func (m *selinuxContextsModule) propertyContextsDeps(ctx android.BottomUpMutatorContext) {
for _, lib := range sysprop.SyspropLibraries(ctx.Config()) {
ctx.AddFarVariationDependencies([]blueprint.Variation{}, syspropLibraryDepTag, lib)
}
}
func (m *selinuxContextsModule) stem() string {
return proptools.StringDefault(m.properties.Stem, m.Name())
}
func (m *selinuxContextsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if m.InRecovery() {
// Installing context files at the root of the recovery partition
m.installPath = android.PathForModuleInstall(ctx)
} else {
m.installPath = android.PathForModuleInstall(ctx, "etc", "selinux")
}
if m.InRecovery() && !m.onlyInRecovery() {
dep := ctx.GetDirectDepWithTag(m.Name(), reuseContextsDepTag)
if reuseDeps, ok := dep.(*selinuxContextsModule); ok {
m.outputPath = reuseDeps.outputPath
ctx.InstallFile(m.installPath, m.stem(), m.outputPath)
return
}
}
m.outputPath = m.build(ctx, android.PathsForModuleSrc(ctx, m.properties.Srcs))
ctx.InstallFile(m.installPath, m.stem(), m.outputPath)
ctx.SetOutputFiles([]android.Path{m.outputPath}, "")
}
func newModule() *selinuxContextsModule {
m := &selinuxContextsModule{}
m.AddProperties(
&m.properties,
&m.seappProperties,
)
initFlaggableModule(m)
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
android.InitDefaultableModule(m)
android.AddLoadHook(m, func(ctx android.LoadHookContext) {
m.selinuxContextsHook(ctx)
})
return m
}
type contextsDefaults struct {
android.ModuleBase
android.DefaultsModuleBase
}
// contexts_defaults provides a set of properties that can be inherited by other contexts modules.
// (file_contexts, property_contexts, seapp_contexts, etc.) A module can use the properties from a
// contexts_defaults using `defaults: ["<:default_module_name>"]`. Properties of both modules are
// erged (when possible) by prepending the default module's values to the depending module's values.
func contextsDefaultsFactory() android.Module {
m := &contextsDefaults{}
m.AddProperties(
&selinuxContextsProperties{},
&seappProperties{},
&flaggableModuleProperties{},
)
android.InitDefaultsModule(m)
return m
}
func (m *selinuxContextsModule) selinuxContextsHook(ctx android.LoadHookContext) {
// TODO: clean this up to use build/soong/android/variable.go after b/79249983
var srcs []string
for _, sanitize := range ctx.Config().SanitizeDevice() {
if sanitize == "address" {
srcs = append(srcs, m.properties.Product_variables.Address_sanitize.Srcs...)
break
}
}
m.properties.Srcs = append(m.properties.Srcs, srcs...)
}
func (m *selinuxContextsModule) AndroidMk() android.AndroidMkData {
nameSuffix := ""
if m.InRecovery() && !m.onlyInRecovery() {
nameSuffix = ".recovery"
}
return android.AndroidMkData{
Class: "ETC",
OutputFile: android.OptionalPathForPath(m.outputPath),
SubName: nameSuffix,
Extra: []android.AndroidMkExtraFunc{
func(w io.Writer, outputFile android.Path) {
fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", m.installPath.String())
fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", m.stem())
},
},
}
}
func (m *selinuxContextsModule) ImageMutatorBegin(ctx android.BaseModuleContext) {
if proptools.Bool(m.properties.Recovery_available) && m.ModuleBase.InstallInRecovery() {
ctx.PropertyErrorf("recovery_available",
"doesn't make sense at the same time as `recovery: true`")
}
}
func (m *selinuxContextsModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
return !m.ModuleBase.InstallInRecovery()
}
func (m *selinuxContextsModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *selinuxContextsModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *selinuxContextsModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *selinuxContextsModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
return m.ModuleBase.InstallInRecovery() || proptools.Bool(m.properties.Recovery_available)
}
func (m *selinuxContextsModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
return nil
}
func (m *selinuxContextsModule) SetImageVariation(ctx android.BaseModuleContext, variation string) {
}
var _ android.ImageInterface = (*selinuxContextsModule)(nil)
func (m *selinuxContextsModule) buildGeneralContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
builtContext := pathForModuleOut(ctx, ctx.ModuleName()+"_m4out")
rule := android.NewRuleBuilder(pctx, ctx)
newlineFile := pathForModuleOut(ctx, "newline")
rule.Command().Text("echo").FlagWithOutput("> ", newlineFile)
rule.Temporary(newlineFile)
var inputsWithNewline android.Paths
for _, input := range inputs {
inputsWithNewline = append(inputsWithNewline, input, newlineFile)
}
flags := m.getBuildFlags(ctx)
rule.Command().
Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
Text("--fatal-warnings -s").
FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
Flags(flagsToM4Macros(flags)).
Inputs(inputsWithNewline).
FlagWithOutput("> ", builtContext)
if proptools.Bool(m.properties.Remove_comment) {
rule.Temporary(builtContext)
remove_comment_output := pathForModuleOut(ctx, ctx.ModuleName()+"_remove_comment")
rule.Command().
Text("sed -e 's/#.*$//' -e '/^$/d'").
Input(builtContext).
FlagWithOutput("> ", remove_comment_output)
builtContext = remove_comment_output
}
if proptools.Bool(m.properties.Fc_sort) {
rule.Temporary(builtContext)
sorted_output := pathForModuleOut(ctx, ctx.ModuleName()+"_sorted")
rule.Command().
Tool(ctx.Config().HostToolPath(ctx, "fc_sort")).
FlagWithInput("-i ", builtContext).
FlagWithOutput("-o ", sorted_output)
builtContext = sorted_output
}
ret := pathForModuleOut(ctx, m.stem())
rule.Temporary(builtContext)
rule.Command().Text("cp").Input(builtContext).Output(ret)
rule.DeleteTemporaryFiles()
rule.Build("selinux_contexts", "building contexts: "+m.Name())
return ret
}
func (m *selinuxContextsModule) buildFileContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
if m.properties.Remove_comment == nil {
m.properties.Remove_comment = proptools.BoolPtr(true)
}
return m.buildGeneralContexts(ctx, inputs)
}
func fileFactory() android.Module {
m := newModule()
m.build = m.buildFileContexts
return m
}
func (m *selinuxContextsModule) buildServiceContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
if m.properties.Remove_comment == nil {
m.properties.Remove_comment = proptools.BoolPtr(true)
}
return m.buildGeneralContexts(ctx, inputs)
}
func (m *selinuxContextsModule) checkVendorPropertyNamespace(ctx android.ModuleContext, input android.Path) android.Path {
shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
ApiLevelR := android.ApiLevelOrPanic(ctx, "R")
rule := android.NewRuleBuilder(pctx, ctx)
// This list is from vts_treble_sys_prop_test.
allowedPropertyPrefixes := []string{
"ctl.odm.",
"ctl.vendor.",
"ctl.start$odm.",
"ctl.start$vendor.",
"ctl.stop$odm.",
"ctl.stop$vendor.",
"init.svc.odm.",
"init.svc.vendor.",
"ro.boot.",
"ro.hardware.",
"ro.odm.",
"ro.vendor.",
"odm.",
"persist.odm.",
"persist.vendor.",
"vendor.",
}
// persist.camera is also allowed for devices launching with R or eariler
if shippingApiLevel.LessThanOrEqualTo(ApiLevelR) {
allowedPropertyPrefixes = append(allowedPropertyPrefixes, "persist.camera.")
}
var allowedContextPrefixes []string
if shippingApiLevel.GreaterThanOrEqualTo(ApiLevelR) {
// This list is from vts_treble_sys_prop_test.
allowedContextPrefixes = []string{
"vendor_",
"odm_",
}
}
cmd := rule.Command().
BuiltTool("check_prop_prefix").
FlagWithInput("--property-contexts ", input).
FlagForEachArg("--allowed-property-prefix ", proptools.ShellEscapeList(allowedPropertyPrefixes)). // contains shell special character '$'
FlagForEachArg("--allowed-context-prefix ", allowedContextPrefixes)
if !ctx.DeviceConfig().BuildBrokenVendorPropertyNamespace() {
cmd.Flag("--strict")
}
out := pathForModuleOut(ctx, ctx.ModuleName()+"_namespace_checked")
rule.Command().Text("cp -f").Input(input).Output(out)
rule.Build("check_namespace", "checking namespace of "+ctx.ModuleName())
return out
}
func (m *selinuxContextsModule) buildPropertyContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
// vendor/odm properties are enforced for devices launching with Android Q or later. So, if
// vendor/odm, make sure that only vendor/odm properties exist.
builtCtxFile := m.buildGeneralContexts(ctx, inputs)
shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
ApiLevelQ := android.ApiLevelOrPanic(ctx, "Q")
if (ctx.SocSpecific() || ctx.DeviceSpecific()) && shippingApiLevel.GreaterThanOrEqualTo(ApiLevelQ) {
builtCtxFile = m.checkVendorPropertyNamespace(ctx, builtCtxFile)
}
var apiFiles android.Paths
ctx.VisitDirectDepsWithTag(syspropLibraryDepTag, func(c android.Module) {
i, ok := c.(interface{ CurrentSyspropApiFile() android.OptionalPath })
if !ok {
panic(fmt.Errorf("unknown dependency %q for %q", ctx.OtherModuleName(c), ctx.ModuleName()))
}
if api := i.CurrentSyspropApiFile(); api.Valid() {
apiFiles = append(apiFiles, api.Path())
}
})
// check compatibility with sysprop_library
if len(apiFiles) > 0 {
out := pathForModuleOut(ctx, ctx.ModuleName()+"_api_checked")
rule := android.NewRuleBuilder(pctx, ctx)
msg := `\n******************************\n` +
`API of sysprop_library doesn't match with property_contexts\n` +
`Please fix the breakage and rebuild.\n` +
`******************************\n`
rule.Command().
Text("( ").
BuiltTool("sysprop_type_checker").
FlagForEachInput("--api ", apiFiles).
FlagWithInput("--context ", builtCtxFile).
Text(" || ( echo").Flag("-e").
Flag(`"` + msg + `"`).
Text("; exit 38) )")
rule.Command().Text("cp -f").Input(builtCtxFile).Output(out)
rule.Build("property_contexts_check_api", "checking API: "+m.Name())
builtCtxFile = out
}
return builtCtxFile
}
func (m *selinuxContextsModule) shouldCheckCoredomain(ctx android.ModuleContext) bool {
if !ctx.SocSpecific() && !ctx.DeviceSpecific() {
return false
}
return ctx.DeviceConfig().CheckVendorSeappViolations()
}
func (m *selinuxContextsModule) buildSeappContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
neverallowFile := pathForModuleOut(ctx, "neverallow")
ret := pathForModuleOut(ctx, "checkseapp", m.stem())
// Step 1. Generate a M4 processed neverallow file
flags := m.getBuildFlags(ctx)
m4NeverallowFile := pathForModuleOut(ctx, "neverallow.m4out")
rule := android.NewRuleBuilder(pctx, ctx)
rule.Command().
Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
Flag("--fatal-warnings").
FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
Flags(flagsToM4Macros(flags)).
Inputs(android.PathsForModuleSrc(ctx, m.seappProperties.Neverallow_files)).
FlagWithOutput("> ", m4NeverallowFile)
rule.Temporary(m4NeverallowFile)
rule.Command().
Text("( grep").
Flag("-ihe").
Text("'^neverallow'").
Input(m4NeverallowFile).
Text(">").
Output(neverallowFile).
Text("|| true )") // to make ninja happy even when result is empty
// Step 2. Generate a M4 processed contexts file
builtCtx := m.buildGeneralContexts(ctx, inputs)
// Step 3. checkseapp
rule.Temporary(neverallowFile)
checkCmd := rule.Command().BuiltTool("checkseapp").
FlagWithInput("-p ", android.PathForModuleSrc(ctx, proptools.String(m.seappProperties.Sepolicy))).
FlagWithOutput("-o ", ret).
Input(builtCtx).
Input(neverallowFile)
if m.shouldCheckCoredomain(ctx) {
checkCmd.Flag("-c") // check coredomain for vendor contexts
}
rule.Build("seapp_contexts", "Building seapp_contexts: "+m.Name())
return ret
}
func hwServiceFactory() android.Module {
m := newModule()
m.build = m.buildServiceContexts
return m
}
func propertyFactory() android.Module {
m := newModule()
m.build = m.buildPropertyContexts
m.deps = m.propertyContextsDeps
return m
}
func serviceFactory() android.Module {
m := newModule()
m.build = m.buildServiceContexts
return m
}
func keystoreKeyFactory() android.Module {
m := newModule()
m.build = m.buildGeneralContexts
return m
}
func seappFactory() android.Module {
m := newModule()
m.build = m.buildSeappContexts
return m
}
func vndServiceFactory() android.Module {
m := newModule()
m.build = m.buildGeneralContexts
android.AddLoadHook(m, func(ctx android.LoadHookContext) {
if !ctx.SocSpecific() {
ctx.ModuleErrorf(m.Name(), "must set vendor: true")
return
}
})
return m
}
type contextsTestProperties struct {
// Contexts files to be tested.
Srcs []string `android:"path"`
// Precompiled sepolicy binary to be tesed together.
Sepolicy *string `android:"path"`
}
type fileContextsTestProperties struct {
// Test data. File passed to `checkfc -t` to validate how contexts are resolved.
Test_data *string `android:"path"`
}
type contextsTestModule struct {
android.ModuleBase
// The type of context.
context contextType
properties contextsTestProperties
fileProperties fileContextsTestProperties
testTimestamp android.OutputPath
}
type contextType int
const (
FileContext contextType = iota
PropertyContext
ServiceContext
HwServiceContext
VndServiceContext
)
// checkfc parses a context file and checks for syntax errors.
// If -s is specified, the service backend is used to verify binder services.
// If -l is specified, the service backend is used to verify hwbinder services.
// Otherwise, context_file is assumed to be a file_contexts file
// If -e is specified, then the context_file is allowed to be empty.
// file_contexts_test tests given file_contexts files with checkfc.
func fileContextsTestFactory() android.Module {
m := &contextsTestModule{context: FileContext}
m.AddProperties(&m.properties)
m.AddProperties(&m.fileProperties)
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
return m
}
// property_contexts_test tests given property_contexts files with property_info_checker.
func propertyContextsTestFactory() android.Module {
m := &contextsTestModule{context: PropertyContext}
m.AddProperties(&m.properties)
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
return m
}
// hwservice_contexts_test tests given hwservice_contexts files with checkfc.
func hwserviceContextsTestFactory() android.Module {
m := &contextsTestModule{context: HwServiceContext}
m.AddProperties(&m.properties)
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
return m
}
// service_contexts_test tests given service_contexts files with checkfc.
func serviceContextsTestFactory() android.Module {
// checkfc -s: service_contexts test
m := &contextsTestModule{context: ServiceContext}
m.AddProperties(&m.properties)
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
return m
}
// vndservice_contexts_test tests given vndservice_contexts files with checkfc.
func vndServiceContextsTestFactory() android.Module {
m := &contextsTestModule{context: VndServiceContext}
m.AddProperties(&m.properties)
android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
return m
}
func (m *contextsTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
tool := "checkfc"
if m.context == PropertyContext {
tool = "property_info_checker"
}
if len(m.properties.Srcs) == 0 {
ctx.PropertyErrorf("srcs", "can't be empty")
return
}
validateWithPolicy := true
if proptools.String(m.properties.Sepolicy) == "" {
if m.context == FileContext {
if proptools.String(m.fileProperties.Test_data) == "" {
ctx.PropertyErrorf("test_data", "Either test_data or sepolicy should be provided")
return
}
validateWithPolicy = false
} else {
ctx.PropertyErrorf("sepolicy", "can't be empty")
return
}
}
flags := []string(nil)
switch m.context {
case FileContext:
if !validateWithPolicy {
flags = []string{"-t"}
}
case ServiceContext:
flags = []string{"-s" /* binder services */}
case HwServiceContext:
flags = []string{"-e" /* allow empty */, "-l" /* hwbinder services */}
case VndServiceContext:
flags = []string{"-e" /* allow empty */, "-v" /* vnd service */}
}
srcs := android.PathsForModuleSrc(ctx, m.properties.Srcs)
rule := android.NewRuleBuilder(pctx, ctx)
if validateWithPolicy {
sepolicy := android.PathForModuleSrc(ctx, proptools.String(m.properties.Sepolicy))
rule.Command().BuiltTool(tool).
Flags(flags).
Input(sepolicy).
Inputs(srcs)
} else {
test_data := android.PathForModuleSrc(ctx, proptools.String(m.fileProperties.Test_data))
rule.Command().BuiltTool(tool).
Flags(flags).
Inputs(srcs).
Input(test_data)
}
m.testTimestamp = pathForModuleOut(ctx, "timestamp")
rule.Command().Text("touch").Output(m.testTimestamp)
rule.Build("contexts_test", "running contexts test: "+ctx.ModuleName())
}
func (m *contextsTestModule) 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(m.testTimestamp),
Include: "$(BUILD_PHONY_PACKAGE)",
ExtraEntries: []android.AndroidMkExtraEntriesFunc{
func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", m.testTimestamp.String())
},
},
}}
}
// contextsTestModule implements ImageInterface to be able to include recovery_available contexts
// modules as its sources.
func (m *contextsTestModule) ImageMutatorBegin(ctx android.BaseModuleContext) {
}
func (m *contextsTestModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
return true
}
func (m *contextsTestModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *contextsTestModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *contextsTestModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *contextsTestModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (m *contextsTestModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
return nil
}
func (m *contextsTestModule) SetImageVariation(ctx android.BaseModuleContext, variation string) {
}
var _ android.ImageInterface = (*contextsTestModule)(nil)