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