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