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