xref: /aosp_15_r20/build/soong/android/androidmk_test.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2019 Google Inc. All rights reserved.
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 android
16
17import (
18	"fmt"
19	"io"
20	"reflect"
21	"runtime"
22	"strings"
23	"testing"
24
25	"github.com/google/blueprint/proptools"
26)
27
28type customModule struct {
29	ModuleBase
30
31	properties struct {
32		Default_dist_files *string
33		Dist_output_file   *bool
34	}
35
36	data       AndroidMkData
37	distFiles  TaggedDistFiles
38	outputFile OptionalPath
39}
40
41const (
42	defaultDistFiles_None    = "none"
43	defaultDistFiles_Default = "default"
44	defaultDistFiles_Tagged  = "tagged"
45)
46
47func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
48
49	var defaultDistPaths Paths
50
51	// If the dist_output_file: true then create an output file that is stored in
52	// the OutputFile property of the AndroidMkEntry.
53	if proptools.BoolDefault(m.properties.Dist_output_file, true) {
54		path := PathForTesting("dist-output-file.out")
55		m.outputFile = OptionalPathForPath(path)
56
57		// Previous code would prioritize the DistFiles property over the OutputFile
58		// property in AndroidMkEntry when determining the default dist paths.
59		// Setting this first allows it to be overridden based on the
60		// default_dist_files setting replicating that previous behavior.
61		defaultDistPaths = Paths{path}
62	}
63
64	// Based on the setting of the default_dist_files property possibly create a
65	// TaggedDistFiles structure that will be stored in the DistFiles property of
66	// the AndroidMkEntry.
67	defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged)
68	switch defaultDistFiles {
69	case defaultDistFiles_None:
70		m.setOutputFiles(ctx, defaultDistPaths)
71
72	case defaultDistFiles_Default:
73		path := PathForTesting("default-dist.out")
74		defaultDistPaths = Paths{path}
75		m.setOutputFiles(ctx, defaultDistPaths)
76		m.distFiles = MakeDefaultDistFiles(path)
77
78	case defaultDistFiles_Tagged:
79		// Module types that set AndroidMkEntry.DistFiles to the result of calling
80		// GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which
81		// meant that the default dist paths would be the same as empty-string-tag
82		// output files. In order to preserve that behavior when treating no tag
83		// as being equal to DefaultDistTag this ensures that DefaultDistTag output
84		// will be the same as empty-string-tag output.
85		defaultDistPaths = PathsForTesting("one.out")
86		m.setOutputFiles(ctx, defaultDistPaths)
87
88		// This must be called after setting defaultDistPaths/outputFile as
89		// GenerateTaggedDistFiles calls into outputFiles property which may use
90		// those fields.
91		m.distFiles = m.GenerateTaggedDistFiles(ctx)
92	}
93}
94
95func (m *customModule) setOutputFiles(ctx ModuleContext, defaultDistPaths Paths) {
96	ctx.SetOutputFiles(PathsForTesting("one.out"), "")
97	ctx.SetOutputFiles(PathsForTesting("two.out", "three/four.out"), ".multiple")
98	ctx.SetOutputFiles(PathsForTesting("another.out"), ".another-tag")
99	if defaultDistPaths != nil {
100		ctx.SetOutputFiles(defaultDistPaths, DefaultDistTag)
101	}
102}
103
104func (m *customModule) AndroidMk() AndroidMkData {
105	return AndroidMkData{
106		Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) {
107			m.data = data
108		},
109	}
110}
111
112func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
113	return []AndroidMkEntries{
114		{
115			Class:      "CUSTOM_MODULE",
116			DistFiles:  m.distFiles,
117			OutputFile: m.outputFile,
118		},
119	}
120}
121
122func customModuleFactory() Module {
123	module := &customModule{}
124
125	module.AddProperties(&module.properties)
126
127	InitAndroidModule(module)
128	return module
129}
130
131// buildContextAndCustomModuleFoo creates a config object, processes the supplied
132// bp module and then returns the config and the custom module called "foo".
133func buildContextAndCustomModuleFoo(t *testing.T, bp string) (*TestContext, *customModule) {
134	t.Helper()
135	result := GroupFixturePreparers(
136		// Enable androidmk Singleton
137		PrepareForTestWithAndroidMk,
138		FixtureRegisterWithContext(func(ctx RegistrationContext) {
139			ctx.RegisterModuleType("custom", customModuleFactory)
140		}),
141		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
142			variables.DeviceProduct = proptools.StringPtr("bar")
143		}),
144		FixtureWithRootAndroidBp(bp),
145	).RunTest(t)
146
147	module := result.ModuleForTests("foo", "").Module().(*customModule)
148	return result.TestContext, module
149}
150
151func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
152	if runtime.GOOS == "darwin" {
153		// Device modules are not exported on Mac, so this test doesn't work.
154		t.SkipNow()
155	}
156
157	bp := `
158	custom {
159		name: "foo",
160		required: ["bar"],
161		host_required: ["baz"],
162		target_required: ["qux"],
163	}
164	`
165
166	_, m := buildContextAndCustomModuleFoo(t, bp)
167
168	assertEqual := func(expected interface{}, actual interface{}) {
169		if !reflect.DeepEqual(expected, actual) {
170			t.Errorf("%q expected, but got %q", expected, actual)
171		}
172	}
173	assertEqual([]string{"bar"}, m.data.Required)
174	assertEqual([]string{"baz"}, m.data.Host_required)
175	assertEqual([]string{"qux"}, m.data.Target_required)
176}
177
178func TestGenerateDistContributionsForMake(t *testing.T) {
179	dc := &distContributions{
180		copiesForGoals: []*copiesForGoals{
181			{
182				goals: "my_goal",
183				copies: []distCopy{
184					distCopyForTest("one.out", "one.out"),
185					distCopyForTest("two.out", "other.out"),
186				},
187			},
188		},
189	}
190
191	dc.licenseMetadataFile = PathForTesting("meta_lic")
192	makeOutput := generateDistContributionsForMake(dc)
193
194	assertStringEquals(t, `.PHONY: my_goal
195$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))
196$(call dist-for-goals,my_goal,one.out:one.out)
197$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))
198$(call dist-for-goals,my_goal,two.out:other.out)`, strings.Join(makeOutput, "\n"))
199}
200
201func TestGetDistForGoals(t *testing.T) {
202	bp := `
203			custom {
204				name: "foo",
205				dist: {
206					targets: ["my_goal", "my_other_goal"],
207					tag: ".multiple",
208				},
209				dists: [
210					{
211						targets: ["my_second_goal"],
212						tag: ".multiple",
213					},
214					{
215						targets: ["my_third_goal"],
216						dir: "test/dir",
217					},
218					{
219						targets: ["my_fourth_goal"],
220						suffix: ".suffix",
221					},
222					{
223						targets: ["my_fifth_goal"],
224						dest: "new-name",
225					},
226					{
227						targets: ["my_sixth_goal"],
228						dest: "new-name",
229						dir: "some/dir",
230						suffix: ".suffix",
231					},
232				],
233			}
234			`
235
236	expectedAndroidMkLines := []string{
237		".PHONY: my_second_goal",
238		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))",
239		"$(call dist-for-goals,my_second_goal,two.out:two.out)",
240		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))",
241		"$(call dist-for-goals,my_second_goal,three/four.out:four.out)",
242		".PHONY: my_third_goal",
243		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
244		"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)",
245		".PHONY: my_fourth_goal",
246		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
247		"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)",
248		".PHONY: my_fifth_goal",
249		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
250		"$(call dist-for-goals,my_fifth_goal,one.out:new-name)",
251		".PHONY: my_sixth_goal",
252		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
253		"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)",
254		".PHONY: my_goal my_other_goal",
255		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))",
256		"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)",
257		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))",
258		"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)",
259	}
260
261	ctx, module := buildContextAndCustomModuleFoo(t, bp)
262	entries := AndroidMkEntriesForTest(t, ctx, module)
263	if len(entries) != 1 {
264		t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
265	}
266	androidMkLines := entries[0].GetDistForGoals(module)
267
268	if len(androidMkLines) != len(expectedAndroidMkLines) {
269		t.Errorf(
270			"Expected %d AndroidMk lines, got %d:\n%v",
271			len(expectedAndroidMkLines),
272			len(androidMkLines),
273			androidMkLines,
274		)
275	}
276	for idx, line := range androidMkLines {
277		expectedLine := strings.ReplaceAll(expectedAndroidMkLines[idx], "meta_lic",
278			OtherModuleProviderOrDefault(ctx, module, InstallFilesProvider).LicenseMetadataFile.String())
279		if line != expectedLine {
280			t.Errorf(
281				"Expected AndroidMk line to be '%s', got '%s'",
282				expectedLine,
283				line,
284			)
285		}
286	}
287}
288
289func distCopyForTest(from, to string) distCopy {
290	return distCopy{PathForTesting(from), to}
291}
292
293func TestGetDistContributions(t *testing.T) {
294	compareContributions := func(d1 *distContributions, d2 *distContributions) error {
295		if d1 == nil || d2 == nil {
296			if d1 != d2 {
297				return fmt.Errorf("pointer mismatch, expected both to be nil but they were %p and %p", d1, d2)
298			} else {
299				return nil
300			}
301		}
302		if expected, actual := len(d1.copiesForGoals), len(d2.copiesForGoals); expected != actual {
303			return fmt.Errorf("length mismatch, expected %d found %d", expected, actual)
304		}
305
306		for i, copies1 := range d1.copiesForGoals {
307			copies2 := d2.copiesForGoals[i]
308			if expected, actual := copies1.goals, copies2.goals; expected != actual {
309				return fmt.Errorf("goals mismatch at position %d: expected %q found %q", i, expected, actual)
310			}
311
312			if expected, actual := len(copies1.copies), len(copies2.copies); expected != actual {
313				return fmt.Errorf("length mismatch in copy instructions at position %d, expected %d found %d", i, expected, actual)
314			}
315
316			for j, c1 := range copies1.copies {
317				c2 := copies2.copies[j]
318				if expected, actual := NormalizePathForTesting(c1.from), NormalizePathForTesting(c2.from); expected != actual {
319					return fmt.Errorf("paths mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
320				}
321
322				if expected, actual := c1.dest, c2.dest; expected != actual {
323					return fmt.Errorf("dest mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
324				}
325			}
326		}
327
328		return nil
329	}
330
331	formatContributions := func(d *distContributions) string {
332		buf := &strings.Builder{}
333		if d == nil {
334			fmt.Fprint(buf, "nil")
335		} else {
336			for _, copiesForGoals := range d.copiesForGoals {
337				fmt.Fprintf(buf, "    Goals: %q {\n", copiesForGoals.goals)
338				for _, c := range copiesForGoals.copies {
339					fmt.Fprintf(buf, "        %s -> %s\n", NormalizePathForTesting(c.from), c.dest)
340				}
341				fmt.Fprint(buf, "    }\n")
342			}
343		}
344		return buf.String()
345	}
346
347	testHelper := func(t *testing.T, name, bp string, expectedContributions *distContributions) {
348		t.Helper()
349		t.Run(name, func(t *testing.T) {
350			t.Helper()
351
352			ctx, module := buildContextAndCustomModuleFoo(t, bp)
353			entries := AndroidMkEntriesForTest(t, ctx, module)
354			if len(entries) != 1 {
355				t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
356			}
357			distContributions := entries[0].getDistContributions(module)
358
359			if err := compareContributions(expectedContributions, distContributions); err != nil {
360				t.Errorf("%s\nExpected Contributions\n%sActualContributions\n%s",
361					err,
362					formatContributions(expectedContributions),
363					formatContributions(distContributions))
364			}
365		})
366	}
367
368	testHelper(t, "dist-without-tag", `
369			custom {
370				name: "foo",
371				dist: {
372					targets: ["my_goal"]
373				}
374			}
375`,
376		&distContributions{
377			copiesForGoals: []*copiesForGoals{
378				{
379					goals: "my_goal",
380					copies: []distCopy{
381						distCopyForTest("one.out", "one.out"),
382					},
383				},
384			},
385		})
386
387	testHelper(t, "dist-with-tag", `
388			custom {
389				name: "foo",
390				dist: {
391					targets: ["my_goal"],
392					tag: ".another-tag",
393				}
394			}
395`,
396		&distContributions{
397			copiesForGoals: []*copiesForGoals{
398				{
399					goals: "my_goal",
400					copies: []distCopy{
401						distCopyForTest("another.out", "another.out"),
402					},
403				},
404			},
405		})
406
407	testHelper(t, "append-artifact-with-product", `
408			custom {
409				name: "foo",
410				dist: {
411					targets: ["my_goal"],
412					append_artifact_with_product: true,
413				}
414			}
415`, &distContributions{
416		copiesForGoals: []*copiesForGoals{
417			{
418				goals: "my_goal",
419				copies: []distCopy{
420					distCopyForTest("one.out", "one_bar.out"),
421				},
422			},
423		},
424	})
425
426	testHelper(t, "dists-with-tag", `
427			custom {
428				name: "foo",
429				dists: [
430					{
431						targets: ["my_goal"],
432						tag: ".another-tag",
433					},
434				],
435			}
436`,
437		&distContributions{
438			copiesForGoals: []*copiesForGoals{
439				{
440					goals: "my_goal",
441					copies: []distCopy{
442						distCopyForTest("another.out", "another.out"),
443					},
444				},
445			},
446		})
447
448	testHelper(t, "multiple-dists-with-and-without-tag", `
449			custom {
450				name: "foo",
451				dists: [
452					{
453						targets: ["my_goal"],
454					},
455					{
456						targets: ["my_second_goal", "my_third_goal"],
457					},
458				],
459			}
460`,
461		&distContributions{
462			copiesForGoals: []*copiesForGoals{
463				{
464					goals: "my_goal",
465					copies: []distCopy{
466						distCopyForTest("one.out", "one.out"),
467					},
468				},
469				{
470					goals: "my_second_goal my_third_goal",
471					copies: []distCopy{
472						distCopyForTest("one.out", "one.out"),
473					},
474				},
475			},
476		})
477
478	testHelper(t, "dist-plus-dists-without-tags", `
479			custom {
480				name: "foo",
481				dist: {
482					targets: ["my_goal"],
483				},
484				dists: [
485					{
486						targets: ["my_second_goal", "my_third_goal"],
487					},
488				],
489			}
490`,
491		&distContributions{
492			copiesForGoals: []*copiesForGoals{
493				{
494					goals: "my_second_goal my_third_goal",
495					copies: []distCopy{
496						distCopyForTest("one.out", "one.out"),
497					},
498				},
499				{
500					goals: "my_goal",
501					copies: []distCopy{
502						distCopyForTest("one.out", "one.out"),
503					},
504				},
505			},
506		})
507
508	testHelper(t, "dist-plus-dists-with-tags", `
509			custom {
510				name: "foo",
511				dist: {
512					targets: ["my_goal", "my_other_goal"],
513					tag: ".multiple",
514				},
515				dists: [
516					{
517						targets: ["my_second_goal"],
518						tag: ".multiple",
519					},
520					{
521						targets: ["my_third_goal"],
522						dir: "test/dir",
523					},
524					{
525						targets: ["my_fourth_goal"],
526						suffix: ".suffix",
527					},
528					{
529						targets: ["my_fifth_goal"],
530						dest: "new-name",
531					},
532					{
533						targets: ["my_sixth_goal"],
534						dest: "new-name",
535						dir: "some/dir",
536						suffix: ".suffix",
537					},
538				],
539			}
540`,
541		&distContributions{
542			copiesForGoals: []*copiesForGoals{
543				{
544					goals: "my_second_goal",
545					copies: []distCopy{
546						distCopyForTest("two.out", "two.out"),
547						distCopyForTest("three/four.out", "four.out"),
548					},
549				},
550				{
551					goals: "my_third_goal",
552					copies: []distCopy{
553						distCopyForTest("one.out", "test/dir/one.out"),
554					},
555				},
556				{
557					goals: "my_fourth_goal",
558					copies: []distCopy{
559						distCopyForTest("one.out", "one.suffix.out"),
560					},
561				},
562				{
563					goals: "my_fifth_goal",
564					copies: []distCopy{
565						distCopyForTest("one.out", "new-name"),
566					},
567				},
568				{
569					goals: "my_sixth_goal",
570					copies: []distCopy{
571						distCopyForTest("one.out", "some/dir/new-name.suffix"),
572					},
573				},
574				{
575					goals: "my_goal my_other_goal",
576					copies: []distCopy{
577						distCopyForTest("two.out", "two.out"),
578						distCopyForTest("three/four.out", "four.out"),
579					},
580				},
581			},
582		})
583
584	// The above test the default values of default_dist_files and use_output_file.
585
586	// The following tests explicitly test the different combinations of those settings.
587	testHelper(t, "tagged-dist-files-no-output", `
588			custom {
589				name: "foo",
590				default_dist_files: "tagged",
591				dist_output_file: false,
592				dists: [
593					{
594						targets: ["my_goal"],
595					},
596					{
597						targets: ["my_goal"],
598						tag: ".multiple",
599					},
600				],
601			}
602`, &distContributions{
603		copiesForGoals: []*copiesForGoals{
604			{
605				goals: "my_goal",
606				copies: []distCopy{
607					distCopyForTest("one.out", "one.out"),
608				},
609			},
610			{
611				goals: "my_goal",
612				copies: []distCopy{
613					distCopyForTest("two.out", "two.out"),
614					distCopyForTest("three/four.out", "four.out"),
615				},
616			},
617		},
618	})
619
620	testHelper(t, "default-dist-files-no-output", `
621			custom {
622				name: "foo",
623				default_dist_files: "default",
624				dist_output_file: false,
625				dists: [
626					{
627						targets: ["my_goal"],
628					},
629					{
630						targets: ["my_goal"],
631						tag: ".multiple",
632					},
633				],
634			}
635`, &distContributions{
636		copiesForGoals: []*copiesForGoals{
637			{
638				goals: "my_goal",
639				copies: []distCopy{
640					distCopyForTest("default-dist.out", "default-dist.out"),
641				},
642			},
643			{
644				goals: "my_goal",
645				copies: []distCopy{
646					distCopyForTest("two.out", "two.out"),
647					distCopyForTest("three/four.out", "four.out"),
648				},
649			},
650		},
651	})
652
653	testHelper(t, "no-dist-files-no-output", `
654			custom {
655				name: "foo",
656				default_dist_files: "none",
657				dist_output_file: false,
658				dists: [
659					// The following is silently ignored because there is not default file
660					// in either the dist files or the output file.
661					{
662						targets: ["my_goal"],
663					},
664					{
665						targets: ["my_goal"],
666						tag: ".multiple",
667					},
668				],
669			}
670`, &distContributions{
671		copiesForGoals: []*copiesForGoals{
672			{
673				goals: "my_goal",
674				copies: []distCopy{
675					distCopyForTest("two.out", "two.out"),
676					distCopyForTest("three/four.out", "four.out"),
677				},
678			},
679		},
680	})
681
682	testHelper(t, "tagged-dist-files-default-output", `
683			custom {
684				name: "foo",
685				default_dist_files: "tagged",
686				dist_output_file: true,
687				dists: [
688					{
689						targets: ["my_goal"],
690					},
691					{
692						targets: ["my_goal"],
693						tag: ".multiple",
694					},
695				],
696			}
697`, &distContributions{
698		copiesForGoals: []*copiesForGoals{
699			{
700				goals: "my_goal",
701				copies: []distCopy{
702					distCopyForTest("one.out", "one.out"),
703				},
704			},
705			{
706				goals: "my_goal",
707				copies: []distCopy{
708					distCopyForTest("two.out", "two.out"),
709					distCopyForTest("three/four.out", "four.out"),
710				},
711			},
712		},
713	})
714
715	testHelper(t, "default-dist-files-default-output", `
716			custom {
717				name: "foo",
718				default_dist_files: "default",
719				dist_output_file: true,
720				dists: [
721					{
722						targets: ["my_goal"],
723					},
724					{
725						targets: ["my_goal"],
726						tag: ".multiple",
727					},
728				],
729			}
730`, &distContributions{
731		copiesForGoals: []*copiesForGoals{
732			{
733				goals: "my_goal",
734				copies: []distCopy{
735					distCopyForTest("default-dist.out", "default-dist.out"),
736				},
737			},
738			{
739				goals: "my_goal",
740				copies: []distCopy{
741					distCopyForTest("two.out", "two.out"),
742					distCopyForTest("three/four.out", "four.out"),
743				},
744			},
745		},
746	})
747
748	testHelper(t, "no-dist-files-default-output", `
749			custom {
750				name: "foo",
751				default_dist_files: "none",
752				dist_output_file: true,
753				dists: [
754					{
755						targets: ["my_goal"],
756					},
757					{
758						targets: ["my_goal"],
759						tag: ".multiple",
760					},
761				],
762			}
763`, &distContributions{
764		copiesForGoals: []*copiesForGoals{
765			{
766				goals: "my_goal",
767				copies: []distCopy{
768					distCopyForTest("dist-output-file.out", "dist-output-file.out"),
769				},
770			},
771			{
772				goals: "my_goal",
773				copies: []distCopy{
774					distCopyForTest("two.out", "two.out"),
775					distCopyForTest("three/four.out", "four.out"),
776				},
777			},
778		},
779	})
780}
781