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