• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2019 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package selinux
16
17import (
18	"fmt"
19	"io"
20	"os"
21	"strings"
22
23	"github.com/google/blueprint"
24	"github.com/google/blueprint/proptools"
25
26	"android/soong/android"
27	"android/soong/sysprop"
28)
29
30type selinuxContextsProperties struct {
31	// Filenames under sepolicy directories, which will be used to generate contexts file.
32	Srcs []string `android:"path"`
33
34	// Output file name. Defaults to module name
35	Stem *string
36
37	Product_variables struct {
38		Address_sanitize struct {
39			Srcs []string `android:"path"`
40		}
41	}
42
43	// Whether the comments in generated contexts file will be removed or not.
44	Remove_comment *bool
45
46	// Whether the result context file is sorted with fc_sort or not.
47	Fc_sort *bool
48
49	// Make this module available when building for recovery
50	Recovery_available *bool
51}
52
53type fileContextsProperties struct {
54	// flatten_apex can be used to specify additional sources of file_contexts.
55	// Apex paths, /system/apex/{apex_name}, will be amended to the paths of file_contexts
56	// entries.
57	Flatten_apex struct {
58		Srcs []string `android:"path"`
59	}
60}
61
62type seappProperties struct {
63	// Files containing neverallow rules.
64	Neverallow_files []string `android:"path"`
65
66	// Precompiled sepolicy binary file which will be fed to checkseapp.
67	Sepolicy *string `android:"path"`
68}
69
70type selinuxContextsModule struct {
71	android.ModuleBase
72
73	properties             selinuxContextsProperties
74	fileContextsProperties fileContextsProperties
75	seappProperties        seappProperties
76	build                  func(ctx android.ModuleContext, inputs android.Paths) android.Path
77	deps                   func(ctx android.BottomUpMutatorContext)
78	outputPath             android.Path
79	installPath            android.InstallPath
80}
81
82var (
83	reuseContextsDepTag  = dependencyTag{name: "reuseContexts"}
84	syspropLibraryDepTag = dependencyTag{name: "sysprop_library"}
85)
86
87func init() {
88	pctx.HostBinToolVariable("fc_sort", "fc_sort")
89
90	android.RegisterModuleType("file_contexts", fileFactory)
91	android.RegisterModuleType("hwservice_contexts", hwServiceFactory)
92	android.RegisterModuleType("property_contexts", propertyFactory)
93	android.RegisterModuleType("service_contexts", serviceFactory)
94	android.RegisterModuleType("keystore2_key_contexts", keystoreKeyFactory)
95	android.RegisterModuleType("seapp_contexts", seappFactory)
96	android.RegisterModuleType("vndservice_contexts", vndServiceFactory)
97
98	android.RegisterModuleType("file_contexts_test", fileContextsTestFactory)
99	android.RegisterModuleType("property_contexts_test", propertyContextsTestFactory)
100	android.RegisterModuleType("hwservice_contexts_test", hwserviceContextsTestFactory)
101	android.RegisterModuleType("service_contexts_test", serviceContextsTestFactory)
102	android.RegisterModuleType("vndservice_contexts_test", vndServiceContextsTestFactory)
103}
104
105func (m *selinuxContextsModule) InstallInRoot() bool {
106	return m.InRecovery()
107}
108
109func (m *selinuxContextsModule) InstallInRecovery() bool {
110	// ModuleBase.InRecovery() checks the image variant
111	return m.InRecovery()
112}
113
114func (m *selinuxContextsModule) onlyInRecovery() bool {
115	// ModuleBase.InstallInRecovery() checks commonProperties.Recovery property
116	return m.ModuleBase.InstallInRecovery()
117}
118
119func (m *selinuxContextsModule) DepsMutator(ctx android.BottomUpMutatorContext) {
120	if m.deps != nil {
121		m.deps(ctx)
122	}
123
124	if m.InRecovery() && !m.onlyInRecovery() {
125		ctx.AddFarVariationDependencies([]blueprint.Variation{
126			{Mutator: "image", Variation: android.CoreVariation},
127		}, reuseContextsDepTag, ctx.ModuleName())
128	}
129}
130
131func (m *selinuxContextsModule) propertyContextsDeps(ctx android.BottomUpMutatorContext) {
132	for _, lib := range sysprop.SyspropLibraries(ctx.Config()) {
133		ctx.AddFarVariationDependencies([]blueprint.Variation{}, syspropLibraryDepTag, lib)
134	}
135}
136
137func (m *selinuxContextsModule) stem() string {
138	return proptools.StringDefault(m.properties.Stem, m.Name())
139}
140
141func (m *selinuxContextsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
142	if m.InRecovery() {
143		// Installing context files at the root of the recovery partition
144		m.installPath = android.PathForModuleInstall(ctx)
145	} else {
146		m.installPath = android.PathForModuleInstall(ctx, "etc", "selinux")
147	}
148
149	if m.InRecovery() && !m.onlyInRecovery() {
150		dep := ctx.GetDirectDepWithTag(m.Name(), reuseContextsDepTag)
151
152		if reuseDeps, ok := dep.(*selinuxContextsModule); ok {
153			m.outputPath = reuseDeps.outputPath
154			ctx.InstallFile(m.installPath, m.stem(), m.outputPath)
155			return
156		}
157	}
158
159	m.outputPath = m.build(ctx, android.PathsForModuleSrc(ctx, m.properties.Srcs))
160	ctx.InstallFile(m.installPath, m.stem(), m.outputPath)
161}
162
163func newModule() *selinuxContextsModule {
164	m := &selinuxContextsModule{}
165	m.AddProperties(
166		&m.properties,
167		&m.fileContextsProperties,
168		&m.seappProperties,
169	)
170	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
171	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
172		m.selinuxContextsHook(ctx)
173	})
174	return m
175}
176
177func (m *selinuxContextsModule) selinuxContextsHook(ctx android.LoadHookContext) {
178	// TODO: clean this up to use build/soong/android/variable.go after b/79249983
179	var srcs []string
180
181	for _, sanitize := range ctx.Config().SanitizeDevice() {
182		if sanitize == "address" {
183			srcs = append(srcs, m.properties.Product_variables.Address_sanitize.Srcs...)
184			break
185		}
186	}
187
188	m.properties.Srcs = append(m.properties.Srcs, srcs...)
189}
190
191func (m *selinuxContextsModule) AndroidMk() android.AndroidMkData {
192	nameSuffix := ""
193	if m.InRecovery() && !m.onlyInRecovery() {
194		nameSuffix = ".recovery"
195	}
196	return android.AndroidMkData{
197		Class:      "ETC",
198		OutputFile: android.OptionalPathForPath(m.outputPath),
199		SubName:    nameSuffix,
200		Extra: []android.AndroidMkExtraFunc{
201			func(w io.Writer, outputFile android.Path) {
202				fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", m.installPath.String())
203				fmt.Fprintln(w, "LOCAL_INSTALLED_MODULE_STEM :=", m.stem())
204			},
205		},
206	}
207}
208
209func (m *selinuxContextsModule) ImageMutatorBegin(ctx android.BaseModuleContext) {
210	if proptools.Bool(m.properties.Recovery_available) && m.ModuleBase.InstallInRecovery() {
211		ctx.PropertyErrorf("recovery_available",
212			"doesn't make sense at the same time as `recovery: true`")
213	}
214}
215
216func (m *selinuxContextsModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
217	return !m.ModuleBase.InstallInRecovery()
218}
219
220func (m *selinuxContextsModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
221	return false
222}
223
224func (m *selinuxContextsModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
225	return false
226}
227
228func (m *selinuxContextsModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
229	return false
230}
231
232func (m *selinuxContextsModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
233	return m.ModuleBase.InstallInRecovery() || proptools.Bool(m.properties.Recovery_available)
234}
235
236func (m *selinuxContextsModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
237	return nil
238}
239
240func (m *selinuxContextsModule) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
241}
242
243var _ android.ImageInterface = (*selinuxContextsModule)(nil)
244
245func (m *selinuxContextsModule) buildGeneralContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
246	builtContext := android.PathForModuleGen(ctx, ctx.ModuleName()+"_m4out")
247
248	rule := android.NewRuleBuilder(pctx, ctx)
249
250	newlineFile := android.PathForModuleGen(ctx, "newline")
251
252	rule.Command().Text("echo").FlagWithOutput("> ", newlineFile)
253	rule.Temporary(newlineFile)
254
255	var inputsWithNewline android.Paths
256	for _, input := range inputs {
257		inputsWithNewline = append(inputsWithNewline, input, newlineFile)
258	}
259
260	rule.Command().
261		Tool(ctx.Config().PrebuiltBuildTool(ctx, "m4")).
262		Text("--fatal-warnings -s").
263		FlagForEachArg("-D", ctx.DeviceConfig().SepolicyM4Defs()).
264		Inputs(inputsWithNewline).
265		FlagWithOutput("> ", builtContext)
266
267	if proptools.Bool(m.properties.Remove_comment) {
268		rule.Temporary(builtContext)
269
270		remove_comment_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_remove_comment")
271
272		rule.Command().
273			Text("sed -e 's/#.*$//' -e '/^$/d'").
274			Input(builtContext).
275			FlagWithOutput("> ", remove_comment_output)
276
277		builtContext = remove_comment_output
278	}
279
280	if proptools.Bool(m.properties.Fc_sort) {
281		rule.Temporary(builtContext)
282
283		sorted_output := android.PathForModuleGen(ctx, ctx.ModuleName()+"_sorted")
284
285		rule.Command().
286			Tool(ctx.Config().HostToolPath(ctx, "fc_sort")).
287			FlagWithInput("-i ", builtContext).
288			FlagWithOutput("-o ", sorted_output)
289
290		builtContext = sorted_output
291	}
292
293	ret := android.PathForModuleGen(ctx, m.stem())
294	rule.Temporary(builtContext)
295	rule.Command().Text("cp").Input(builtContext).Output(ret)
296
297	rule.DeleteTemporaryFiles()
298	rule.Build("selinux_contexts", "building contexts: "+m.Name())
299
300	return ret
301}
302
303func (m *selinuxContextsModule) buildFileContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
304	if m.properties.Fc_sort == nil {
305		m.properties.Fc_sort = proptools.BoolPtr(true)
306	}
307
308	rule := android.NewRuleBuilder(pctx, ctx)
309
310	if ctx.Config().FlattenApex() {
311		for _, path := range android.PathsForModuleSrc(ctx, m.fileContextsProperties.Flatten_apex.Srcs) {
312			out := android.PathForModuleGen(ctx, "flattened_apex", path.Rel())
313			apex_path := "/system/apex/" + strings.Replace(
314				strings.TrimSuffix(path.Base(), "-file_contexts"),
315				".", "\\\\.", -1)
316
317			rule.Command().
318				Text("awk '/object_r/{printf(\""+apex_path+"%s\\n\",$0)}'").
319				Input(path).
320				FlagWithOutput("> ", out)
321
322			inputs = append(inputs, out)
323		}
324	}
325
326	rule.Build(m.Name(), "flattened_apex_file_contexts")
327	return m.buildGeneralContexts(ctx, inputs)
328}
329
330func fileFactory() android.Module {
331	m := newModule()
332	m.build = m.buildFileContexts
333	return m
334}
335
336func (m *selinuxContextsModule) buildServiceContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
337	if m.properties.Remove_comment == nil {
338		m.properties.Remove_comment = proptools.BoolPtr(true)
339	}
340
341	return m.buildGeneralContexts(ctx, inputs)
342}
343
344func (m *selinuxContextsModule) checkVendorPropertyNamespace(ctx android.ModuleContext, inputs android.Paths) android.Paths {
345	shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
346	ApiLevelR := android.ApiLevelOrPanic(ctx, "R")
347
348	rule := android.NewRuleBuilder(pctx, ctx)
349
350	// This list is from vts_treble_sys_prop_test.
351	allowedPropertyPrefixes := []string{
352		"ctl.odm.",
353		"ctl.vendor.",
354		"ctl.start$odm.",
355		"ctl.start$vendor.",
356		"ctl.stop$odm.",
357		"ctl.stop$vendor.",
358		"init.svc.odm.",
359		"init.svc.vendor.",
360		"ro.boot.",
361		"ro.hardware.",
362		"ro.odm.",
363		"ro.vendor.",
364		"odm.",
365		"persist.odm.",
366		"persist.vendor.",
367		"vendor.",
368	}
369
370	// persist.camera is also allowed for devices launching with R or eariler
371	if shippingApiLevel.LessThanOrEqualTo(ApiLevelR) {
372		allowedPropertyPrefixes = append(allowedPropertyPrefixes, "persist.camera.")
373	}
374
375	var allowedContextPrefixes []string
376
377	if shippingApiLevel.GreaterThanOrEqualTo(ApiLevelR) {
378		// This list is from vts_treble_sys_prop_test.
379		allowedContextPrefixes = []string{
380			"vendor_",
381			"odm_",
382		}
383	}
384
385	var ret android.Paths
386	for _, input := range inputs {
387		cmd := rule.Command().
388			BuiltTool("check_prop_prefix").
389			FlagWithInput("--property-contexts ", input).
390			FlagForEachArg("--allowed-property-prefix ", proptools.ShellEscapeList(allowedPropertyPrefixes)). // contains shell special character '$'
391			FlagForEachArg("--allowed-context-prefix ", allowedContextPrefixes)
392
393		if !ctx.DeviceConfig().BuildBrokenVendorPropertyNamespace() {
394			cmd.Flag("--strict")
395		}
396
397		out := android.PathForModuleGen(ctx, "namespace_checked").Join(ctx, input.String())
398		rule.Command().Text("cp -f").Input(input).Output(out)
399		ret = append(ret, out)
400	}
401	rule.Build("check_namespace", "checking namespace of "+ctx.ModuleName())
402	return ret
403}
404
405func (m *selinuxContextsModule) buildPropertyContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
406	// vendor/odm properties are enforced for devices launching with Android Q or later. So, if
407	// vendor/odm, make sure that only vendor/odm properties exist.
408	shippingApiLevel := ctx.DeviceConfig().ShippingApiLevel()
409	ApiLevelQ := android.ApiLevelOrPanic(ctx, "Q")
410	if (ctx.SocSpecific() || ctx.DeviceSpecific()) && shippingApiLevel.GreaterThanOrEqualTo(ApiLevelQ) {
411		inputs = m.checkVendorPropertyNamespace(ctx, inputs)
412	}
413
414	builtCtxFile := m.buildGeneralContexts(ctx, inputs)
415
416	var apiFiles android.Paths
417	ctx.VisitDirectDepsWithTag(syspropLibraryDepTag, func(c android.Module) {
418		i, ok := c.(interface{ CurrentSyspropApiFile() android.OptionalPath })
419		if !ok {
420			panic(fmt.Errorf("unknown dependency %q for %q", ctx.OtherModuleName(c), ctx.ModuleName()))
421		}
422		if api := i.CurrentSyspropApiFile(); api.Valid() {
423			apiFiles = append(apiFiles, api.Path())
424		}
425	})
426
427	// check compatibility with sysprop_library
428	if len(apiFiles) > 0 {
429		out := android.PathForModuleGen(ctx, ctx.ModuleName()+"_api_checked")
430		rule := android.NewRuleBuilder(pctx, ctx)
431
432		msg := `\n******************************\n` +
433			`API of sysprop_library doesn't match with property_contexts\n` +
434			`Please fix the breakage and rebuild.\n` +
435			`******************************\n`
436
437		rule.Command().
438			Text("( ").
439			BuiltTool("sysprop_type_checker").
440			FlagForEachInput("--api ", apiFiles).
441			FlagWithInput("--context ", builtCtxFile).
442			Text(" || ( echo").Flag("-e").
443			Flag(`"` + msg + `"`).
444			Text("; exit 38) )")
445
446		rule.Command().Text("cp -f").Input(builtCtxFile).Output(out)
447		rule.Build("property_contexts_check_api", "checking API: "+m.Name())
448		builtCtxFile = out
449	}
450
451	return builtCtxFile
452}
453
454func (m *selinuxContextsModule) buildSeappContexts(ctx android.ModuleContext, inputs android.Paths) android.Path {
455	neverallowFile := android.PathForModuleGen(ctx, "neverallow")
456	ret := android.PathForModuleGen(ctx, m.stem())
457
458	rule := android.NewRuleBuilder(pctx, ctx)
459	rule.Command().Text("(grep").
460		Flag("-ihe").
461		Text("'^neverallow'").
462		Inputs(android.PathsForModuleSrc(ctx, m.seappProperties.Neverallow_files)).
463		Text(os.DevNull). // to make grep happy even when Neverallow_files is empty
464		Text(">").
465		Output(neverallowFile).
466		Text("|| true)") // to make ninja happy even when result is empty
467
468	rule.Temporary(neverallowFile)
469	rule.Command().BuiltTool("checkseapp").
470		FlagWithInput("-p ", android.PathForModuleSrc(ctx, proptools.String(m.seappProperties.Sepolicy))).
471		FlagWithOutput("-o ", ret).
472		Inputs(inputs).
473		Input(neverallowFile)
474
475	rule.Build("seapp_contexts", "Building seapp_contexts: "+m.Name())
476	return ret
477}
478
479func hwServiceFactory() android.Module {
480	m := newModule()
481	m.build = m.buildServiceContexts
482	return m
483}
484
485func propertyFactory() android.Module {
486	m := newModule()
487	m.build = m.buildPropertyContexts
488	m.deps = m.propertyContextsDeps
489	return m
490}
491
492func serviceFactory() android.Module {
493	m := newModule()
494	m.build = m.buildServiceContexts
495	return m
496}
497
498func keystoreKeyFactory() android.Module {
499	m := newModule()
500	m.build = m.buildGeneralContexts
501	return m
502}
503
504func seappFactory() android.Module {
505	m := newModule()
506	m.build = m.buildSeappContexts
507	return m
508}
509
510func vndServiceFactory() android.Module {
511	m := newModule()
512	m.build = m.buildGeneralContexts
513	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
514		if !ctx.SocSpecific() {
515			ctx.ModuleErrorf(m.Name(), "must set vendor: true")
516			return
517		}
518	})
519	return m
520}
521
522var _ android.OutputFileProducer = (*selinuxContextsModule)(nil)
523
524// Implements android.OutputFileProducer
525func (m *selinuxContextsModule) OutputFiles(tag string) (android.Paths, error) {
526	if tag == "" {
527		return []android.Path{m.outputPath}, nil
528	}
529	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
530}
531
532type contextsTestProperties struct {
533	// Contexts files to be tested.
534	Srcs []string `android:"path"`
535
536	// Precompiled sepolicy binary to be tesed together.
537	Sepolicy *string `android:"path"`
538}
539
540type contextsTestModule struct {
541	android.ModuleBase
542
543	// Name of the test tool. "checkfc" or "property_info_checker"
544	tool string
545
546	// Additional flags to be passed to the tool.
547	flags []string
548
549	properties    contextsTestProperties
550	testTimestamp android.ModuleOutPath
551}
552
553// checkfc parses a context file and checks for syntax errors.
554// If -s is specified, the service backend is used to verify binder services.
555// If -l is specified, the service backend is used to verify hwbinder services.
556// Otherwise, context_file is assumed to be a file_contexts file
557// If -e is specified, then the context_file is allowed to be empty.
558
559// file_contexts_test tests given file_contexts files with checkfc.
560func fileContextsTestFactory() android.Module {
561	m := &contextsTestModule{tool: "checkfc" /* no flags: file_contexts file check */}
562	m.AddProperties(&m.properties)
563	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
564	return m
565}
566
567// property_contexts_test tests given property_contexts files with property_info_checker.
568func propertyContextsTestFactory() android.Module {
569	m := &contextsTestModule{tool: "property_info_checker"}
570	m.AddProperties(&m.properties)
571	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
572	return m
573}
574
575// hwservice_contexts_test tests given hwservice_contexts files with checkfc.
576func hwserviceContextsTestFactory() android.Module {
577	m := &contextsTestModule{tool: "checkfc", flags: []string{"-e" /* allow empty */, "-l" /* hwbinder services */}}
578	m.AddProperties(&m.properties)
579	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
580	return m
581}
582
583// service_contexts_test tests given service_contexts files with checkfc.
584func serviceContextsTestFactory() android.Module {
585	// checkfc -s: service_contexts test
586	m := &contextsTestModule{tool: "checkfc", flags: []string{"-s" /* binder services */}}
587	m.AddProperties(&m.properties)
588	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
589	return m
590}
591
592// vndservice_contexts_test tests given vndservice_contexts files with checkfc.
593func vndServiceContextsTestFactory() android.Module {
594	m := &contextsTestModule{tool: "checkfc", flags: []string{"-e" /* allow empty */, "-v" /* vnd service */}}
595	m.AddProperties(&m.properties)
596	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
597	return m
598}
599
600func (m *contextsTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
601	tool := m.tool
602	if tool != "checkfc" && tool != "property_info_checker" {
603		panic(fmt.Errorf("%q: unknown tool name: %q", ctx.ModuleName(), tool))
604	}
605
606	if len(m.properties.Srcs) == 0 {
607		ctx.PropertyErrorf("srcs", "can't be empty")
608		return
609	}
610
611	if proptools.String(m.properties.Sepolicy) == "" {
612		ctx.PropertyErrorf("sepolicy", "can't be empty")
613		return
614	}
615
616	srcs := android.PathsForModuleSrc(ctx, m.properties.Srcs)
617	sepolicy := android.PathForModuleSrc(ctx, proptools.String(m.properties.Sepolicy))
618
619	rule := android.NewRuleBuilder(pctx, ctx)
620	rule.Command().BuiltTool(tool).
621		Flags(m.flags).
622		Input(sepolicy).
623		Inputs(srcs)
624
625	m.testTimestamp = android.PathForModuleOut(ctx, "timestamp")
626	rule.Command().Text("touch").Output(m.testTimestamp)
627	rule.Build("contexts_test", "running contexts test: "+ctx.ModuleName())
628}
629
630func (m *contextsTestModule) AndroidMkEntries() []android.AndroidMkEntries {
631	return []android.AndroidMkEntries{android.AndroidMkEntries{
632		Class: "FAKE",
633		// OutputFile is needed, even though BUILD_PHONY_PACKAGE doesn't use it.
634		// Without OutputFile this module won't be exported to Makefile.
635		OutputFile: android.OptionalPathForPath(m.testTimestamp),
636		Include:    "$(BUILD_PHONY_PACKAGE)",
637		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
638			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
639				entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", m.testTimestamp.String())
640			},
641		},
642	}}
643}
644
645// contextsTestModule implements ImageInterface to be able to include recovery_available contexts
646// modules as its sources.
647func (m *contextsTestModule) ImageMutatorBegin(ctx android.BaseModuleContext) {
648}
649
650func (m *contextsTestModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
651	return true
652}
653
654func (m *contextsTestModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
655	return false
656}
657
658func (m *contextsTestModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
659	return false
660}
661
662func (m *contextsTestModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
663	return false
664}
665
666func (m *contextsTestModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
667	return false
668}
669
670func (m *contextsTestModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
671	return nil
672}
673
674func (m *contextsTestModule) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
675}
676
677var _ android.ImageInterface = (*contextsTestModule)(nil)
678