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