• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 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 genrule
16
17import (
18	"fmt"
19	"os"
20	"regexp"
21	"testing"
22
23	"android/soong/android"
24
25	"github.com/google/blueprint/proptools"
26)
27
28func TestMain(m *testing.M) {
29	os.Exit(m.Run())
30}
31
32var prepareForGenRuleTest = android.GroupFixturePreparers(
33	android.PrepareForTestWithArchMutator,
34	android.PrepareForTestWithDefaults,
35	android.PrepareForTestWithFilegroup,
36	PrepareForTestWithGenRuleBuildComponents,
37	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
38		android.RegisterPrebuiltMutators(ctx)
39		ctx.RegisterModuleType("tool", toolFactory)
40		ctx.RegisterModuleType("prebuilt_tool", prebuiltToolFactory)
41		ctx.RegisterModuleType("output", outputProducerFactory)
42		ctx.RegisterModuleType("use_source", useSourceFactory)
43	}),
44	android.FixtureMergeMockFs(android.MockFS{
45		"tool":       nil,
46		"tool_file1": nil,
47		"tool_file2": nil,
48		"in1":        nil,
49		"in2":        nil,
50		"in1.txt":    nil,
51		"in2.txt":    nil,
52		"in3.txt":    nil,
53	}),
54)
55
56func testGenruleBp() string {
57	return `
58		tool {
59			name: "tool",
60		}
61
62		filegroup {
63			name: "tool_files",
64			srcs: [
65				"tool_file1",
66				"tool_file2",
67			],
68		}
69
70		filegroup {
71			name: "1tool_file",
72			srcs: [
73				"tool_file1",
74			],
75		}
76
77		filegroup {
78			name: "ins",
79			srcs: [
80				"in1",
81				"in2",
82			],
83		}
84
85		filegroup {
86			name: "1in",
87			srcs: [
88				"in1",
89			],
90		}
91
92		filegroup {
93			name: "empty",
94		}
95	`
96}
97
98func TestGenruleCmd(t *testing.T) {
99	testcases := []struct {
100		name string
101		prop string
102
103		allowMissingDependencies bool
104
105		err    string
106		expect string
107	}{
108		{
109			name: "empty location tool",
110			prop: `
111				tools: ["tool"],
112				out: ["out"],
113				cmd: "$(location) > $(out)",
114			`,
115			expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out",
116		},
117		{
118			name: "empty location tool2",
119			prop: `
120				tools: [":tool"],
121				out: ["out"],
122				cmd: "$(location) > $(out)",
123			`,
124			expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out",
125		},
126		{
127			name: "empty location tool file",
128			prop: `
129				tool_files: ["tool_file1"],
130				out: ["out"],
131				cmd: "$(location) > $(out)",
132			`,
133			expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
134		},
135		{
136			name: "empty location tool file fg",
137			prop: `
138				tool_files: [":1tool_file"],
139				out: ["out"],
140				cmd: "$(location) > $(out)",
141			`,
142			expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
143		},
144		{
145			name: "empty location tool and tool file",
146			prop: `
147				tools: ["tool"],
148				tool_files: ["tool_file1"],
149				out: ["out"],
150				cmd: "$(location) > $(out)",
151			`,
152			expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out",
153		},
154		{
155			name: "tool",
156			prop: `
157				tools: ["tool"],
158				out: ["out"],
159				cmd: "$(location tool) > $(out)",
160			`,
161			expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out",
162		},
163		{
164			name: "tool2",
165			prop: `
166				tools: [":tool"],
167				out: ["out"],
168				cmd: "$(location :tool) > $(out)",
169			`,
170			expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out",
171		},
172		{
173			name: "tool file",
174			prop: `
175				tool_files: ["tool_file1"],
176				out: ["out"],
177				cmd: "$(location tool_file1) > $(out)",
178			`,
179			expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
180		},
181		{
182			name: "tool file fg",
183			prop: `
184				tool_files: [":1tool_file"],
185				out: ["out"],
186				cmd: "$(location :1tool_file) > $(out)",
187			`,
188			expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
189		},
190		{
191			name: "tool files",
192			prop: `
193				tool_files: [":tool_files"],
194				out: ["out"],
195				cmd: "$(locations :tool_files) > $(out)",
196			`,
197			expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 __SBOX_SANDBOX_DIR__/tools/src/tool_file2 > __SBOX_SANDBOX_DIR__/out/out",
198		},
199		{
200			name: "in1",
201			prop: `
202				srcs: ["in1"],
203				out: ["out"],
204				cmd: "cat $(in) > $(out)",
205			`,
206			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
207		},
208		{
209			name: "in1 fg",
210			prop: `
211				srcs: [":1in"],
212				out: ["out"],
213				cmd: "cat $(in) > $(out)",
214			`,
215			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
216		},
217		{
218			name: "ins",
219			prop: `
220				srcs: ["in1", "in2"],
221				out: ["out"],
222				cmd: "cat $(in) > $(out)",
223			`,
224			expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
225		},
226		{
227			name: "ins fg",
228			prop: `
229				srcs: [":ins"],
230				out: ["out"],
231				cmd: "cat $(in) > $(out)",
232			`,
233			expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
234		},
235		{
236			name: "location in1",
237			prop: `
238				srcs: ["in1"],
239				out: ["out"],
240				cmd: "cat $(location in1) > $(out)",
241			`,
242			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
243		},
244		{
245			name: "location in1 fg",
246			prop: `
247				srcs: [":1in"],
248				out: ["out"],
249				cmd: "cat $(location :1in) > $(out)",
250			`,
251			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
252		},
253		{
254			name: "location ins",
255			prop: `
256				srcs: ["in1", "in2"],
257				out: ["out"],
258				cmd: "cat $(location in1) > $(out)",
259			`,
260			expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
261		},
262		{
263			name: "location ins fg",
264			prop: `
265				srcs: [":ins"],
266				out: ["out"],
267				cmd: "cat $(locations :ins) > $(out)",
268			`,
269			expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
270		},
271		{
272			name: "outs",
273			prop: `
274				out: ["out", "out2"],
275				cmd: "echo foo > $(out)",
276			`,
277			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out __SBOX_SANDBOX_DIR__/out/out2",
278		},
279		{
280			name: "location out",
281			prop: `
282				out: ["out", "out2"],
283				cmd: "echo foo > $(location out2)",
284			`,
285			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out2",
286		},
287		{
288			name: "depfile",
289			prop: `
290				out: ["out"],
291				depfile: true,
292				cmd: "echo foo > $(out) && touch $(depfile)",
293			`,
294			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out && touch __SBOX_DEPFILE__",
295		},
296		{
297			name: "gendir",
298			prop: `
299				out: ["out"],
300				cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)",
301			`,
302			expect: "echo foo > __SBOX_SANDBOX_DIR__/out/foo && cp __SBOX_SANDBOX_DIR__/out/foo __SBOX_SANDBOX_DIR__/out/out",
303		},
304		{
305			name: "$",
306			prop: `
307				out: ["out"],
308				cmd: "echo $$ > $(out)",
309			`,
310			expect: "echo $ > __SBOX_SANDBOX_DIR__/out/out",
311		},
312
313		{
314			name: "error empty location",
315			prop: `
316				out: ["out"],
317				cmd: "$(location) > $(out)",
318			`,
319			err: "at least one `tools` or `tool_files` is required if $(location) is used",
320		},
321		{
322			name: "error empty location no files",
323			prop: `
324				tool_files: [":empty"],
325				out: ["out"],
326				cmd: "$(location) > $(out)",
327			`,
328			err: `default label ":empty" has no files`,
329		},
330		{
331			name: "error empty location multiple files",
332			prop: `
333				tool_files: [":tool_files"],
334				out: ["out"],
335				cmd: "$(location) > $(out)",
336			`,
337			err: `default label ":tool_files" has multiple files`,
338		},
339		{
340			name: "error location",
341			prop: `
342				out: ["out"],
343				cmd: "echo foo > $(location missing)",
344			`,
345			err: `unknown location label "missing" is not in srcs, out, tools or tool_files.`,
346		},
347		{
348			name: "error locations",
349			prop: `
350					out: ["out"],
351					cmd: "echo foo > $(locations missing)",
352			`,
353			err: `unknown locations label "missing" is not in srcs, out, tools or tool_files`,
354		},
355		{
356			name: "error location no files",
357			prop: `
358					out: ["out"],
359					srcs: [":empty"],
360					cmd: "echo $(location :empty) > $(out)",
361			`,
362			err: `label ":empty" has no files`,
363		},
364		{
365			name: "error locations no files",
366			prop: `
367					out: ["out"],
368					srcs: [":empty"],
369					cmd: "echo $(locations :empty) > $(out)",
370			`,
371			err: `label ":empty" has no files`,
372		},
373		{
374			name: "error location multiple files",
375			prop: `
376					out: ["out"],
377					srcs: [":ins"],
378					cmd: "echo $(location :ins) > $(out)",
379			`,
380			err: `label ":ins" has multiple files`,
381		},
382		{
383			name: "error variable",
384			prop: `
385					out: ["out"],
386					srcs: ["in1"],
387					cmd: "echo $(foo) > $(out)",
388			`,
389			err: `unknown variable '$(foo)'`,
390		},
391		{
392			name: "error depfile",
393			prop: `
394				out: ["out"],
395				cmd: "echo foo > $(out) && touch $(depfile)",
396			`,
397			err: "$(depfile) used without depfile property",
398		},
399		{
400			name: "error no depfile",
401			prop: `
402				out: ["out"],
403				depfile: true,
404				cmd: "echo foo > $(out)",
405			`,
406			err: "specified depfile=true but did not include a reference to '${depfile}' in cmd",
407		},
408		{
409			name: "error no out",
410			prop: `
411				cmd: "echo foo > $(out)",
412			`,
413			err: "must have at least one output file",
414		},
415		{
416			name: "srcs allow missing dependencies",
417			prop: `
418				srcs: [":missing"],
419				out: ["out"],
420				cmd: "cat $(location :missing) > $(out)",
421			`,
422
423			allowMissingDependencies: true,
424
425			expect: "cat '***missing srcs :missing***' > __SBOX_SANDBOX_DIR__/out/out",
426		},
427		{
428			name: "tool allow missing dependencies",
429			prop: `
430				tools: [":missing"],
431				out: ["out"],
432				cmd: "$(location :missing) > $(out)",
433			`,
434
435			allowMissingDependencies: true,
436
437			expect: "'***missing tool :missing***' > __SBOX_SANDBOX_DIR__/out/out",
438		},
439	}
440
441	for _, test := range testcases {
442		t.Run(test.name, func(t *testing.T) {
443			bp := "genrule {\n"
444			bp += "name: \"gen\",\n"
445			bp += test.prop
446			bp += "}\n"
447
448			var expectedErrors []string
449			if test.err != "" {
450				expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err))
451			}
452
453			result := android.GroupFixturePreparers(
454				prepareForGenRuleTest,
455				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
456					variables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies)
457				}),
458				android.FixtureModifyContext(func(ctx *android.TestContext) {
459					ctx.SetAllowMissingDependencies(test.allowMissingDependencies)
460				}),
461			).
462				ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
463				RunTestWithBp(t, testGenruleBp()+bp)
464
465			if expectedErrors != nil {
466				return
467			}
468
469			gen := result.Module("gen", "").(*Module)
470			android.AssertStringEquals(t, "raw commands", test.expect, gen.rawCommands[0])
471		})
472	}
473}
474
475func TestGenruleHashInputs(t *testing.T) {
476
477	// The basic idea here is to verify that the sbox command (which is
478	// in the Command field of the generate rule) contains a hash of the
479	// inputs, but only if $(in) is not referenced in the genrule cmd
480	// property.
481
482	// By including a hash of the inputs, we cause the rule to re-run if
483	// the list of inputs changes (because the sbox command changes).
484
485	// However, if the genrule cmd property already contains $(in), then
486	// the dependency is already expressed, so we don't need to include the
487	// hash in that case.
488
489	bp := `
490			genrule {
491				name: "hash0",
492				srcs: ["in1.txt", "in2.txt"],
493				out: ["out"],
494				cmd: "echo foo > $(out)",
495			}
496			genrule {
497				name: "hash1",
498				srcs: ["*.txt"],
499				out: ["out"],
500				cmd: "echo bar > $(out)",
501			}
502			genrule {
503				name: "hash2",
504				srcs: ["*.txt"],
505				out: ["out"],
506				cmd: "echo $(in) > $(out)",
507			}
508		`
509	testcases := []struct {
510		name         string
511		expectedHash string
512	}{
513		{
514			name: "hash0",
515			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
516			expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
517		},
518		{
519			name: "hash1",
520			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
521			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
522		},
523		{
524			name: "hash2",
525			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
526			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
527		},
528	}
529
530	result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp)
531
532	for _, test := range testcases {
533		t.Run(test.name, func(t *testing.T) {
534			gen := result.ModuleForTests(test.name, "")
535			manifest := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
536			hash := manifest.Commands[0].GetInputHash()
537
538			android.AssertStringEquals(t, "hash", test.expectedHash, hash)
539		})
540	}
541}
542
543func TestGenSrcs(t *testing.T) {
544	testcases := []struct {
545		name string
546		prop string
547
548		allowMissingDependencies bool
549
550		err   string
551		cmds  []string
552		deps  []string
553		files []string
554	}{
555		{
556			name: "gensrcs",
557			prop: `
558				tools: ["tool"],
559				srcs: ["in1.txt", "in2.txt"],
560				cmd: "$(location) $(in) > $(out)",
561			`,
562			cmds: []string{
563				"bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
564			},
565			deps: []string{
566				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
567				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
568			},
569			files: []string{
570				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
571				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
572			},
573		},
574		{
575			name: "shards",
576			prop: `
577				tools: ["tool"],
578				srcs: ["in1.txt", "in2.txt", "in3.txt"],
579				cmd: "$(location) $(in) > $(out)",
580				shard_size: 2,
581			`,
582			cmds: []string{
583				"bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
584				"bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in3.txt > __SBOX_SANDBOX_DIR__/out/in3.h'",
585			},
586			deps: []string{
587				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
588				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
589				"out/soong/.intermediates/gen/gen/gensrcs/in3.h",
590			},
591			files: []string{
592				"out/soong/.intermediates/gen/gen/gensrcs/in1.h",
593				"out/soong/.intermediates/gen/gen/gensrcs/in2.h",
594				"out/soong/.intermediates/gen/gen/gensrcs/in3.h",
595			},
596		},
597	}
598
599	for _, test := range testcases {
600		t.Run(test.name, func(t *testing.T) {
601			bp := "gensrcs {\n"
602			bp += `name: "gen",` + "\n"
603			bp += `output_extension: "h",` + "\n"
604			bp += test.prop
605			bp += "}\n"
606
607			var expectedErrors []string
608			if test.err != "" {
609				expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err))
610			}
611
612			result := prepareForGenRuleTest.
613				ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
614				RunTestWithBp(t, testGenruleBp()+bp)
615
616			if expectedErrors != nil {
617				return
618			}
619
620			gen := result.Module("gen", "").(*Module)
621			android.AssertDeepEquals(t, "cmd", test.cmds, gen.rawCommands)
622
623			android.AssertPathsRelativeToTopEquals(t, "deps", test.deps, gen.outputDeps)
624
625			android.AssertPathsRelativeToTopEquals(t, "files", test.files, gen.outputFiles)
626		})
627	}
628}
629
630func TestGensrcsBuildBrokenDepfile(t *testing.T) {
631	tests := []struct {
632		name               string
633		prop               string
634		BuildBrokenDepfile *bool
635		err                string
636	}{
637		{
638			name: `error when BuildBrokenDepfile is set to false`,
639			prop: `
640				depfile: true,
641				cmd: "cat $(in) > $(out) && cat $(depfile)",
642			`,
643			BuildBrokenDepfile: proptools.BoolPtr(false),
644			err:                "depfile: Deprecated to ensure the module type is convertible to Bazel",
645		},
646		{
647			name: `error when BuildBrokenDepfile is not set`,
648			prop: `
649				depfile: true,
650				cmd: "cat $(in) > $(out) && cat $(depfile)",
651			`,
652			err: "depfile: Deprecated to ensure the module type is convertible to Bazel.",
653		},
654		{
655			name: `no error when BuildBrokenDepfile is explicitly set to true`,
656			prop: `
657				depfile: true,
658				cmd: "cat $(in) > $(out) && cat $(depfile)",
659			`,
660			BuildBrokenDepfile: proptools.BoolPtr(true),
661		},
662		{
663			name: `no error if depfile is not set`,
664			prop: `
665				cmd: "cat $(in) > $(out)",
666			`,
667		},
668	}
669	for _, test := range tests {
670		t.Run(test.name, func(t *testing.T) {
671			bp := fmt.Sprintf(`
672			gensrcs {
673			   name: "foo",
674			   srcs: ["data.txt"],
675			   %s
676			}`, test.prop)
677
678			var expectedErrors []string
679			if test.err != "" {
680				expectedErrors = append(expectedErrors, test.err)
681			}
682			android.GroupFixturePreparers(
683				prepareForGenRuleTest,
684				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
685					if test.BuildBrokenDepfile != nil {
686						variables.BuildBrokenDepfile = test.BuildBrokenDepfile
687					}
688				}),
689			).
690				ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
691				RunTestWithBp(t, bp)
692		})
693
694	}
695}
696
697func TestGenruleDefaults(t *testing.T) {
698	bp := `
699				genrule_defaults {
700					name: "gen_defaults1",
701					cmd: "cp $(in) $(out)",
702				}
703
704				genrule_defaults {
705					name: "gen_defaults2",
706					srcs: ["in1"],
707				}
708
709				genrule {
710					name: "gen",
711					out: ["out"],
712					defaults: ["gen_defaults1", "gen_defaults2"],
713				}
714			`
715
716	result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp)
717
718	gen := result.Module("gen", "").(*Module)
719
720	expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out"
721	android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0])
722
723	expectedSrcs := []string{"in1"}
724	android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs)
725}
726
727func TestGenruleAllowMissingDependencies(t *testing.T) {
728	bp := `
729		output {
730			name: "disabled",
731			enabled: false,
732		}
733
734		genrule {
735			name: "gen",
736			srcs: [
737				":disabled",
738			],
739			out: ["out"],
740			cmd: "cat $(in) > $(out)",
741		}
742       `
743	result := android.GroupFixturePreparers(
744		prepareForGenRuleTest,
745		android.FixtureModifyConfigAndContext(
746			func(config android.Config, ctx *android.TestContext) {
747				config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
748				ctx.SetAllowMissingDependencies(true)
749			})).RunTestWithBp(t, bp)
750
751	gen := result.ModuleForTests("gen", "").Output("out")
752	if gen.Rule != android.ErrorRule {
753		t.Errorf("Expected missing dependency error rule for gen, got %q", gen.Rule.String())
754	}
755}
756
757func TestGenruleOutputFiles(t *testing.T) {
758	bp := `
759				genrule {
760					name: "gen",
761					out: ["foo", "sub/bar"],
762					cmd: "echo foo > $(location foo) && echo bar > $(location sub/bar)",
763				}
764				use_source {
765					name: "gen_foo",
766					srcs: [":gen{foo}"],
767				}
768				use_source {
769					name: "gen_bar",
770					srcs: [":gen{sub/bar}"],
771				}
772				use_source {
773					name: "gen_all",
774					srcs: [":gen"],
775				}
776			`
777
778	result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp)
779	android.AssertPathsRelativeToTopEquals(t,
780		"genrule.tag with output",
781		[]string{"out/soong/.intermediates/gen/gen/foo"},
782		result.ModuleForTests("gen_foo", "").Module().(*useSource).srcs)
783	android.AssertPathsRelativeToTopEquals(t,
784		"genrule.tag with output in subdir",
785		[]string{"out/soong/.intermediates/gen/gen/sub/bar"},
786		result.ModuleForTests("gen_bar", "").Module().(*useSource).srcs)
787	android.AssertPathsRelativeToTopEquals(t,
788		"genrule.tag with all",
789		[]string{"out/soong/.intermediates/gen/gen/foo", "out/soong/.intermediates/gen/gen/sub/bar"},
790		result.ModuleForTests("gen_all", "").Module().(*useSource).srcs)
791}
792
793func TestGenSrcsWithNonRootAndroidBpOutputFiles(t *testing.T) {
794	result := android.GroupFixturePreparers(
795		prepareForGenRuleTest,
796		android.FixtureMergeMockFs(android.MockFS{
797			"external-protos/path/Android.bp": []byte(`
798				filegroup {
799					name: "external-protos",
800					srcs: ["baz/baz.proto", "bar.proto"],
801				}
802			`),
803			"package-dir/Android.bp": []byte(`
804				gensrcs {
805					name: "module-name",
806					cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)",
807					srcs: [
808						"src/foo.proto",
809						":external-protos",
810					],
811					output_extension: "proto.h",
812				}
813			`),
814		}),
815	).RunTest(t)
816
817	exportedIncludeDir := "out/soong/.intermediates/package-dir/module-name/gen/gensrcs"
818	gen := result.Module("module-name", "").(*Module)
819
820	android.AssertPathsRelativeToTopEquals(
821		t,
822		"include path",
823		[]string{exportedIncludeDir},
824		gen.exportedIncludeDirs,
825	)
826	android.AssertPathsRelativeToTopEquals(
827		t,
828		"files",
829		[]string{
830			exportedIncludeDir + "/package-dir/src/foo.proto.h",
831			exportedIncludeDir + "/external-protos/path/baz/baz.proto.h",
832			exportedIncludeDir + "/external-protos/path/bar.proto.h",
833		},
834		gen.outputFiles,
835	)
836}
837
838func TestGenSrcsWithSrcsFromExternalPackage(t *testing.T) {
839	bp := `
840		gensrcs {
841			name: "module-name",
842			cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)",
843			srcs: [
844				":external-protos",
845			],
846			output_extension: "proto.h",
847		}
848	`
849	result := android.GroupFixturePreparers(
850		prepareForGenRuleTest,
851		android.FixtureMergeMockFs(android.MockFS{
852			"external-protos/path/Android.bp": []byte(`
853				filegroup {
854					name: "external-protos",
855					srcs: ["foo/foo.proto", "bar.proto"],
856				}
857			`),
858		}),
859	).RunTestWithBp(t, bp)
860
861	exportedIncludeDir := "out/soong/.intermediates/module-name/gen/gensrcs"
862	gen := result.Module("module-name", "").(*Module)
863
864	android.AssertPathsRelativeToTopEquals(
865		t,
866		"include path",
867		[]string{exportedIncludeDir},
868		gen.exportedIncludeDirs,
869	)
870	android.AssertPathsRelativeToTopEquals(
871		t,
872		"files",
873		[]string{
874			exportedIncludeDir + "/external-protos/path/foo/foo.proto.h",
875			exportedIncludeDir + "/external-protos/path/bar.proto.h",
876		},
877		gen.outputFiles,
878	)
879}
880
881func TestPrebuiltTool(t *testing.T) {
882	testcases := []struct {
883		name             string
884		bp               string
885		expectedToolName string
886	}{
887		{
888			name: "source only",
889			bp: `
890				tool { name: "tool" }
891			`,
892			expectedToolName: "bin/tool",
893		},
894		{
895			name: "prebuilt only",
896			bp: `
897				prebuilt_tool { name: "tool" }
898			`,
899			expectedToolName: "prebuilt_bin/tool",
900		},
901		{
902			name: "source preferred",
903			bp: `
904				tool { name: "tool" }
905				prebuilt_tool { name: "tool" }
906			`,
907			expectedToolName: "bin/tool",
908		},
909		{
910			name: "prebuilt preferred",
911			bp: `
912				tool { name: "tool" }
913				prebuilt_tool { name: "tool", prefer: true }
914			`,
915			expectedToolName: "prebuilt_bin/prebuilt_tool",
916		},
917		{
918			name: "source disabled",
919			bp: `
920				tool { name: "tool", enabled: false }
921				prebuilt_tool { name: "tool" }
922      `,
923			expectedToolName: "prebuilt_bin/prebuilt_tool",
924		},
925	}
926
927	for _, test := range testcases {
928		t.Run(test.name, func(t *testing.T) {
929			result := prepareForGenRuleTest.RunTestWithBp(t, test.bp+`
930				genrule {
931					name: "gen",
932					tools: ["tool"],
933					out: ["foo"],
934					cmd: "$(location tool)",
935				}
936			`)
937			gen := result.Module("gen", "").(*Module)
938			expectedCmd := "__SBOX_SANDBOX_DIR__/tools/out/" + test.expectedToolName
939			android.AssertStringEquals(t, "command", expectedCmd, gen.rawCommands[0])
940		})
941	}
942}
943
944func TestGenruleWithBazel(t *testing.T) {
945	bp := `
946		genrule {
947				name: "foo",
948				out: ["one.txt", "two.txt"],
949				bazel_module: { label: "//foo/bar:bar" },
950		}
951	`
952
953	result := android.GroupFixturePreparers(
954		prepareForGenRuleTest, android.FixtureModifyConfig(func(config android.Config) {
955			config.BazelContext = android.MockBazelContext{
956				OutputBaseDir: "outputbase",
957				LabelToOutputFiles: map[string][]string{
958					"//foo/bar:bar": []string{"bazelone.txt", "bazeltwo.txt"}}}
959		})).RunTestWithBp(t, testGenruleBp()+bp)
960
961	gen := result.Module("foo", "").(*Module)
962
963	expectedOutputFiles := []string{"outputbase/execroot/__main__/bazelone.txt",
964		"outputbase/execroot/__main__/bazeltwo.txt"}
965	android.AssertDeepEquals(t, "output files", expectedOutputFiles, gen.outputFiles.Strings())
966	android.AssertDeepEquals(t, "output deps", expectedOutputFiles, gen.outputDeps.Strings())
967}
968
969func TestGenruleWithGlobPaths(t *testing.T) {
970	testcases := []struct {
971		name            string
972		bp              string
973		additionalFiles android.MockFS
974		expectedCmd     string
975	}{
976		{
977			name: "single file in directory with $ sign",
978			bp: `
979				genrule {
980					name: "gen",
981					srcs: ["inn*.txt"],
982					out: ["out.txt"],
983					cmd: "cp $(in) $(out)",
984				}
985				`,
986			additionalFiles: android.MockFS{"inn$1.txt": nil},
987			expectedCmd:     "cp 'inn$1.txt' __SBOX_SANDBOX_DIR__/out/out.txt",
988		},
989		{
990			name: "multiple file in directory with $ sign",
991			bp: `
992				genrule {
993					name: "gen",
994					srcs: ["inn*.txt"],
995					out: ["."],
996					cmd: "cp $(in) $(out)",
997				}
998				`,
999			additionalFiles: android.MockFS{"inn$1.txt": nil, "inn$2.txt": nil},
1000			expectedCmd:     "cp 'inn$1.txt' 'inn$2.txt' __SBOX_SANDBOX_DIR__/out",
1001		},
1002		{
1003			name: "file in directory with other shell unsafe character",
1004			bp: `
1005				genrule {
1006					name: "gen",
1007					srcs: ["inn*.txt"],
1008					out: ["out.txt"],
1009					cmd: "cp $(in) $(out)",
1010				}
1011				`,
1012			additionalFiles: android.MockFS{"inn@1.txt": nil},
1013			expectedCmd:     "cp 'inn@1.txt' __SBOX_SANDBOX_DIR__/out/out.txt",
1014		},
1015		{
1016			name: "glob location param with filepath containing $",
1017			bp: `
1018				genrule {
1019					name: "gen",
1020					srcs: ["**/inn*"],
1021					out: ["."],
1022					cmd: "cp $(in) $(location **/inn*)",
1023				}
1024				`,
1025			additionalFiles: android.MockFS{"a/inn$1.txt": nil},
1026			expectedCmd:     "cp 'a/inn$1.txt' 'a/inn$1.txt'",
1027		},
1028		{
1029			name: "glob locations param with filepath containing $",
1030			bp: `
1031				genrule {
1032					name: "gen",
1033					tool_files: ["**/inn*"],
1034					out: ["out.txt"],
1035					cmd: "cp $(locations  **/inn*) $(out)",
1036				}
1037				`,
1038			additionalFiles: android.MockFS{"a/inn$1.txt": nil},
1039			expectedCmd:     "cp '__SBOX_SANDBOX_DIR__/tools/src/a/inn$1.txt' __SBOX_SANDBOX_DIR__/out/out.txt",
1040		},
1041	}
1042
1043	for _, test := range testcases {
1044		t.Run(test.name, func(t *testing.T) {
1045			result := android.GroupFixturePreparers(
1046				prepareForGenRuleTest,
1047				android.FixtureMergeMockFs(test.additionalFiles),
1048			).RunTestWithBp(t, test.bp)
1049			gen := result.Module("gen", "").(*Module)
1050			android.AssertStringEquals(t, "command", test.expectedCmd, gen.rawCommands[0])
1051		})
1052	}
1053}
1054
1055type testTool struct {
1056	android.ModuleBase
1057	outputFile android.Path
1058}
1059
1060func toolFactory() android.Module {
1061	module := &testTool{}
1062	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
1063	return module
1064}
1065
1066func (t *testTool) GenerateAndroidBuildActions(ctx android.ModuleContext) {
1067	t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName()))
1068}
1069
1070func (t *testTool) HostToolPath() android.OptionalPath {
1071	return android.OptionalPathForPath(t.outputFile)
1072}
1073
1074type prebuiltTestTool struct {
1075	android.ModuleBase
1076	prebuilt android.Prebuilt
1077	testTool
1078}
1079
1080func (p *prebuiltTestTool) Name() string {
1081	return p.prebuilt.Name(p.ModuleBase.Name())
1082}
1083
1084func (p *prebuiltTestTool) Prebuilt() *android.Prebuilt {
1085	return &p.prebuilt
1086}
1087
1088func (t *prebuiltTestTool) GenerateAndroidBuildActions(ctx android.ModuleContext) {
1089	t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "prebuilt_bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName()))
1090}
1091
1092func prebuiltToolFactory() android.Module {
1093	module := &prebuiltTestTool{}
1094	android.InitPrebuiltModuleWithoutSrcs(module)
1095	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
1096	return module
1097}
1098
1099var _ android.HostToolProvider = (*testTool)(nil)
1100var _ android.HostToolProvider = (*prebuiltTestTool)(nil)
1101
1102type testOutputProducer struct {
1103	android.ModuleBase
1104	outputFile android.Path
1105}
1106
1107func outputProducerFactory() android.Module {
1108	module := &testOutputProducer{}
1109	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
1110	return module
1111}
1112
1113func (t *testOutputProducer) GenerateAndroidBuildActions(ctx android.ModuleContext) {
1114	t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName()))
1115}
1116
1117func (t *testOutputProducer) OutputFiles(tag string) (android.Paths, error) {
1118	return android.Paths{t.outputFile}, nil
1119}
1120
1121var _ android.OutputFileProducer = (*testOutputProducer)(nil)
1122
1123type useSource struct {
1124	android.ModuleBase
1125	props struct {
1126		Srcs []string `android:"path"`
1127	}
1128	srcs android.Paths
1129}
1130
1131func (s *useSource) GenerateAndroidBuildActions(ctx android.ModuleContext) {
1132	s.srcs = android.PathsForModuleSrc(ctx, s.props.Srcs)
1133}
1134
1135func useSourceFactory() android.Module {
1136	module := &useSource{}
1137	module.AddProperties(&module.props)
1138	android.InitAndroidModule(module)
1139	return module
1140}
1141