xref: /aosp_15_r20/build/soong/android/rule_builder_test.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
18	"crypto/sha256"
19	"encoding/hex"
20	"fmt"
21	"path/filepath"
22	"regexp"
23	"strings"
24	"testing"
25
26	"github.com/google/blueprint"
27
28	"android/soong/shared"
29)
30
31var (
32	pctx_ruleBuilderTest           = NewPackageContext("android/soong/rule_builder")
33	pctx_ruleBuilderTestSubContext = NewPackageContext("android/soong/rule_builder/config")
34)
35
36func init() {
37	pctx_ruleBuilderTest.Import("android/soong/rule_builder/config")
38	pctx_ruleBuilderTest.StaticVariable("cmdFlags", "${config.ConfigFlags}")
39	pctx_ruleBuilderTestSubContext.StaticVariable("ConfigFlags", "--some-clang-flag")
40}
41
42func builderContext() BuilderContext {
43	return BuilderContextForTesting(TestConfig("out", nil, "", map[string][]byte{
44		"ld":      nil,
45		"a.o":     nil,
46		"b.o":     nil,
47		"cp":      nil,
48		"a":       nil,
49		"b":       nil,
50		"ls":      nil,
51		"turbine": nil,
52		"java":    nil,
53		"javac":   nil,
54	}))
55}
56
57func ExampleRuleBuilder() {
58	ctx := builderContext()
59
60	rule := NewRuleBuilder(pctx, ctx)
61
62	rule.Command().
63		Tool(PathForSource(ctx, "ld")).
64		Inputs(PathsForTesting("a.o", "b.o")).
65		FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
66	rule.Command().Text("echo success")
67
68	// To add the command to the build graph:
69	// rule.Build("link", "link")
70
71	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
72	fmt.Printf("tools: %q\n", rule.Tools())
73	fmt.Printf("inputs: %q\n", rule.Inputs())
74	fmt.Printf("outputs: %q\n", rule.Outputs())
75
76	// Output:
77	// commands: "ld a.o b.o -o out/soong/linked && echo success"
78	// tools: ["ld"]
79	// inputs: ["a.o" "b.o"]
80	// outputs: ["out/soong/linked"]
81}
82
83func ExampleRuleBuilder_Temporary() {
84	ctx := builderContext()
85
86	rule := NewRuleBuilder(pctx, ctx)
87
88	rule.Command().
89		Tool(PathForSource(ctx, "cp")).
90		Input(PathForSource(ctx, "a")).
91		Output(PathForOutput(ctx, "b"))
92	rule.Command().
93		Tool(PathForSource(ctx, "cp")).
94		Input(PathForOutput(ctx, "b")).
95		Output(PathForOutput(ctx, "c"))
96	rule.Temporary(PathForOutput(ctx, "b"))
97
98	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
99	fmt.Printf("tools: %q\n", rule.Tools())
100	fmt.Printf("inputs: %q\n", rule.Inputs())
101	fmt.Printf("outputs: %q\n", rule.Outputs())
102
103	// Output:
104	// commands: "cp a out/soong/b && cp out/soong/b out/soong/c"
105	// tools: ["cp"]
106	// inputs: ["a"]
107	// outputs: ["out/soong/c"]
108}
109
110func ExampleRuleBuilder_DeleteTemporaryFiles() {
111	ctx := builderContext()
112
113	rule := NewRuleBuilder(pctx, ctx)
114
115	rule.Command().
116		Tool(PathForSource(ctx, "cp")).
117		Input(PathForSource(ctx, "a")).
118		Output(PathForOutput(ctx, "b"))
119	rule.Command().
120		Tool(PathForSource(ctx, "cp")).
121		Input(PathForOutput(ctx, "b")).
122		Output(PathForOutput(ctx, "c"))
123	rule.Temporary(PathForOutput(ctx, "b"))
124	rule.DeleteTemporaryFiles()
125
126	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
127	fmt.Printf("tools: %q\n", rule.Tools())
128	fmt.Printf("inputs: %q\n", rule.Inputs())
129	fmt.Printf("outputs: %q\n", rule.Outputs())
130
131	// Output:
132	// commands: "cp a out/soong/b && cp out/soong/b out/soong/c && rm -f out/soong/b"
133	// tools: ["cp"]
134	// inputs: ["a"]
135	// outputs: ["out/soong/c"]
136}
137
138func ExampleRuleBuilder_Installs() {
139	ctx := builderContext()
140
141	rule := NewRuleBuilder(pctx, ctx)
142
143	out := PathForOutput(ctx, "linked")
144
145	rule.Command().
146		Tool(PathForSource(ctx, "ld")).
147		Inputs(PathsForTesting("a.o", "b.o")).
148		FlagWithOutput("-o ", out)
149	rule.Install(out, "/bin/linked")
150	rule.Install(out, "/sbin/linked")
151
152	fmt.Printf("rule.Installs().String() = %q\n", rule.Installs().String())
153
154	// Output:
155	// rule.Installs().String() = "out/soong/linked:/bin/linked out/soong/linked:/sbin/linked"
156}
157
158func ExampleRuleBuilderCommand() {
159	ctx := builderContext()
160
161	rule := NewRuleBuilder(pctx, ctx)
162
163	// chained
164	rule.Command().
165		Tool(PathForSource(ctx, "ld")).
166		Inputs(PathsForTesting("a.o", "b.o")).
167		FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
168
169	// unchained
170	cmd := rule.Command()
171	cmd.Tool(PathForSource(ctx, "ld"))
172	cmd.Inputs(PathsForTesting("a.o", "b.o"))
173	cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
174
175	// mixed:
176	cmd = rule.Command().Tool(PathForSource(ctx, "ld"))
177	cmd.Inputs(PathsForTesting("a.o", "b.o"))
178	cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
179}
180
181func ExampleRuleBuilderCommand_Flag() {
182	ctx := builderContext()
183	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
184		Tool(PathForSource(ctx, "ls")).Flag("-l"))
185	// Output:
186	// ls -l
187}
188
189func ExampleRuleBuilderCommand_Flags() {
190	ctx := builderContext()
191	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
192		Tool(PathForSource(ctx, "ls")).Flags([]string{"-l", "-a"}))
193	// Output:
194	// ls -l -a
195}
196
197func ExampleRuleBuilderCommand_FlagWithArg() {
198	ctx := builderContext()
199	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
200		Tool(PathForSource(ctx, "ls")).
201		FlagWithArg("--sort=", "time"))
202	// Output:
203	// ls --sort=time
204}
205
206func ExampleRuleBuilderCommand_FlagForEachArg() {
207	ctx := builderContext()
208	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
209		Tool(PathForSource(ctx, "ls")).
210		FlagForEachArg("--sort=", []string{"time", "size"}))
211	// Output:
212	// ls --sort=time --sort=size
213}
214
215func ExampleRuleBuilderCommand_FlagForEachInput() {
216	ctx := builderContext()
217	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
218		Tool(PathForSource(ctx, "turbine")).
219		FlagForEachInput("--classpath ", PathsForTesting("a.jar", "b.jar")))
220	// Output:
221	// turbine --classpath a.jar --classpath b.jar
222}
223
224func ExampleRuleBuilderCommand_FlagWithInputList() {
225	ctx := builderContext()
226	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
227		Tool(PathForSource(ctx, "java")).
228		FlagWithInputList("-classpath=", PathsForTesting("a.jar", "b.jar"), ":"))
229	// Output:
230	// java -classpath=a.jar:b.jar
231}
232
233func ExampleRuleBuilderCommand_FlagWithInput() {
234	ctx := builderContext()
235	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
236		Tool(PathForSource(ctx, "java")).
237		FlagWithInput("-classpath=", PathForSource(ctx, "a")))
238	// Output:
239	// java -classpath=a
240}
241
242func ExampleRuleBuilderCommand_FlagWithList() {
243	ctx := builderContext()
244	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
245		Tool(PathForSource(ctx, "ls")).
246		FlagWithList("--sort=", []string{"time", "size"}, ","))
247	// Output:
248	// ls --sort=time,size
249}
250
251func ExampleRuleBuilderCommand_FlagWithRspFileInputList() {
252	ctx := builderContext()
253	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
254		Tool(PathForSource(ctx, "javac")).
255		FlagWithRspFileInputList("@", PathForOutput(ctx, "foo.rsp"), PathsForTesting("a.java", "b.java")).
256		String())
257	// Output:
258	// javac @out/soong/foo.rsp
259}
260
261func ExampleRuleBuilderCommand_String() {
262	ctx := builderContext()
263	fmt.Println(NewRuleBuilder(pctx, ctx).Command().
264		Text("FOO=foo").
265		Text("echo $FOO").
266		String())
267	// Output:
268	// FOO=foo echo $FOO
269}
270
271func TestRuleBuilder(t *testing.T) {
272	fs := map[string][]byte{
273		"dep_fixer":  nil,
274		"input":      nil,
275		"Implicit":   nil,
276		"Input":      nil,
277		"OrderOnly":  nil,
278		"OrderOnlys": nil,
279		"Tool":       nil,
280		"input2":     nil,
281		"tool2":      nil,
282		"input3":     nil,
283	}
284
285	pathCtx := PathContextForTesting(TestConfig("out_local", nil, "", fs))
286	ctx := builderContextForTests{
287		PathContext: pathCtx,
288	}
289
290	addCommands := func(rule *RuleBuilder) {
291		cmd := rule.Command().
292			DepFile(PathForOutput(ctx, "module/DepFile")).
293			Flag("Flag").
294			FlagWithArg("FlagWithArg=", "arg").
295			FlagWithDepFile("FlagWithDepFile=", PathForOutput(ctx, "module/depfile")).
296			FlagWithInput("FlagWithInput=", PathForSource(ctx, "input")).
297			FlagWithOutput("FlagWithOutput=", PathForOutput(ctx, "module/output")).
298			FlagWithRspFileInputList("FlagWithRspFileInputList=", PathForOutput(ctx, "rsp"),
299				Paths{
300					PathForSource(ctx, "RspInput"),
301					PathForOutput(ctx, "other/RspOutput2"),
302				}).
303			Implicit(PathForSource(ctx, "Implicit")).
304			ImplicitDepFile(PathForOutput(ctx, "module/ImplicitDepFile")).
305			ImplicitOutput(PathForOutput(ctx, "module/ImplicitOutput")).
306			Input(PathForSource(ctx, "Input")).
307			Output(PathForOutput(ctx, "module/Output")).
308			OrderOnly(PathForSource(ctx, "OrderOnly")).
309			Validation(PathForSource(ctx, "Validation")).
310			Text("Text").
311			Tool(PathForSource(ctx, "Tool"))
312
313		rule.Command().
314			Text("command2").
315			DepFile(PathForOutput(ctx, "module/depfile2")).
316			Input(PathForSource(ctx, "input2")).
317			Output(PathForOutput(ctx, "module/output2")).
318			OrderOnlys(PathsForSource(ctx, []string{"OrderOnlys"})).
319			Validations(PathsForSource(ctx, []string{"Validations"})).
320			Tool(PathForSource(ctx, "tool2"))
321
322		// Test updates to the first command after the second command has been started
323		cmd.Text("after command2")
324		// Test updating a command when the previous update did not replace the cmd variable
325		cmd.Text("old cmd")
326
327		// Test a command that uses the output of a previous command as an input
328		rule.Command().
329			Text("command3").
330			Input(PathForSource(ctx, "input3")).
331			Input(PathForOutput(ctx, "module/output2")).
332			Output(PathForOutput(ctx, "module/output3")).
333			Text(cmd.PathForInput(PathForSource(ctx, "input3"))).
334			Text(cmd.PathForOutput(PathForOutput(ctx, "module/output2")))
335	}
336
337	wantInputs := PathsForSource(ctx, []string{"Implicit", "Input", "input", "input2", "input3"})
338	wantRspFileInputs := Paths{PathForSource(ctx, "RspInput"),
339		PathForOutput(ctx, "other/RspOutput2")}
340	wantOutputs := PathsForOutput(ctx, []string{
341		"module/ImplicitOutput", "module/Output", "module/output", "module/output2",
342		"module/output3"})
343	wantDepFiles := PathsForOutput(ctx, []string{
344		"module/DepFile", "module/depfile", "module/ImplicitDepFile", "module/depfile2"})
345	wantTools := PathsForSource(ctx, []string{"Tool", "tool2"})
346	wantOrderOnlys := PathsForSource(ctx, []string{"OrderOnly", "OrderOnlys"})
347	wantValidations := PathsForSource(ctx, []string{"Validation", "Validations"})
348
349	t.Run("normal", func(t *testing.T) {
350		rule := NewRuleBuilder(pctx, ctx)
351		addCommands(rule)
352
353		wantCommands := []string{
354			"out_local/soong/module/DepFile Flag FlagWithArg=arg FlagWithDepFile=out_local/soong/module/depfile " +
355				"FlagWithInput=input FlagWithOutput=out_local/soong/module/output FlagWithRspFileInputList=out_local/soong/rsp " +
356				"Input out_local/soong/module/Output Text Tool after command2 old cmd",
357			"command2 out_local/soong/module/depfile2 input2 out_local/soong/module/output2 tool2",
358			"command3 input3 out_local/soong/module/output2 out_local/soong/module/output3 input3 out_local/soong/module/output2",
359		}
360
361		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
362			"out_local/soong/module/DepFile out_local/soong/module/depfile out_local/soong/module/ImplicitDepFile out_local/soong/module/depfile2"
363
364		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
365
366		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
367		AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
368		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
369		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
370		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
371		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
372		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
373
374		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
375	})
376
377	t.Run("sbox", func(t *testing.T) {
378		rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"),
379			PathForOutput(ctx, "sbox.textproto"))
380		addCommands(rule)
381
382		wantCommands := []string{
383			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
384				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
385				"FlagWithRspFileInputList=out_local/soong/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
386				"Text Tool after command2 old cmd",
387			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2",
388			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
389		}
390
391		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
392
393		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
394
395		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
396		AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
397		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
398		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
399		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
400		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
401		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
402
403		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
404	})
405
406	t.Run("sbox tools", func(t *testing.T) {
407		rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"),
408			PathForOutput(ctx, "sbox.textproto")).SandboxTools()
409		addCommands(rule)
410
411		wantCommands := []string{
412			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
413				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
414				"FlagWithRspFileInputList=out_local/soong/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
415				"Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd",
416			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2",
417			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
418		}
419
420		wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
421
422		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
423
424		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
425		AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
426		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
427		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
428		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
429		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
430		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
431
432		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
433	})
434
435	t.Run("sbox inputs", func(t *testing.T) {
436		rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"),
437			PathForOutput(ctx, "sbox.textproto")).SandboxInputs()
438		addCommands(rule)
439
440		wantCommands := []string{
441			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
442				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
443				"FlagWithRspFileInputList=__SBOX_SANDBOX_DIR__/out/soong/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
444				"Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd",
445			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2",
446			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
447		}
448
449		wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
450
451		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
452
453		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
454		AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
455		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
456		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
457		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
458		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
459		AssertDeepEquals(t, "rule.Validations()", wantValidations, rule.Validations())
460
461		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
462	})
463}
464
465func testRuleBuilderFactory() Module {
466	module := &testRuleBuilderModule{}
467	module.AddProperties(&module.properties)
468	InitAndroidModule(module)
469	return module
470}
471
472type testRuleBuilderModule struct {
473	ModuleBase
474	properties struct {
475		Srcs  []string
476		Flags []string
477
478		Restat      bool
479		Sbox        bool
480		Sbox_inputs bool
481	}
482}
483
484func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
485	in := PathsForSource(ctx, t.properties.Srcs)
486	implicit := PathForSource(ctx, "implicit")
487	orderOnly := PathForSource(ctx, "orderonly")
488	validation := PathForSource(ctx, "validation")
489	out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
490	outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
491	outDir := PathForModuleOut(ctx, "gen")
492	rspFile := PathForModuleOut(ctx, "rsp")
493	rspFile2 := PathForModuleOut(ctx, "rsp2")
494	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
495	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
496	manifestPath := PathForModuleOut(ctx, "sbox.textproto")
497
498	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, t.properties.Flags,
499		out, outDep, outDir,
500		manifestPath, t.properties.Restat, t.properties.Sbox, t.properties.Sbox_inputs,
501		rspFile, rspFileContents, rspFile2, rspFileContents2)
502}
503
504type testRuleBuilderSingleton struct{}
505
506func testRuleBuilderSingletonFactory() Singleton {
507	return &testRuleBuilderSingleton{}
508}
509
510func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
511	in := PathsForSource(ctx, []string{"in"})
512	implicit := PathForSource(ctx, "implicit")
513	orderOnly := PathForSource(ctx, "orderonly")
514	validation := PathForSource(ctx, "validation")
515	out := PathForOutput(ctx, "singleton/gen/baz")
516	outDep := PathForOutput(ctx, "singleton/gen/baz.d")
517	outDir := PathForOutput(ctx, "singleton/gen")
518	rspFile := PathForOutput(ctx, "singleton/rsp")
519	rspFile2 := PathForOutput(ctx, "singleton/rsp2")
520	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
521	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
522	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
523
524	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, nil, out, outDep, outDir,
525		manifestPath, true, false, false,
526		rspFile, rspFileContents, rspFile2, rspFileContents2)
527}
528
529func testRuleBuilder_Build(ctx BuilderContext, in Paths, implicit, orderOnly, validation Path,
530	flags []string,
531	out, outDep, outDir, manifestPath WritablePath,
532	restat, sbox, sboxInputs bool,
533	rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) {
534
535	rule := NewRuleBuilder(pctx_ruleBuilderTest, ctx)
536
537	if sbox {
538		rule.Sbox(outDir, manifestPath)
539		if sboxInputs {
540			rule.SandboxInputs()
541		}
542	}
543
544	rule.Command().
545		Tool(PathForSource(ctx, "cp")).
546		Flags(flags).
547		Inputs(in).
548		Implicit(implicit).
549		OrderOnly(orderOnly).
550		Validation(validation).
551		Output(out).
552		ImplicitDepFile(outDep).
553		FlagWithRspFileInputList("@", rspFile, rspFileContents).
554		FlagWithRspFileInputList("@", rspFile2, rspFileContents2)
555
556	if restat {
557		rule.Restat()
558	}
559
560	rule.Build("rule", "desc")
561}
562
563var prepareForRuleBuilderTest = FixtureRegisterWithContext(func(ctx RegistrationContext) {
564	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
565	ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory)
566})
567
568func TestRuleBuilder_Build(t *testing.T) {
569	fs := MockFS{
570		"in": nil,
571		"cp": nil,
572	}
573
574	bp := `
575		rule_builder_test {
576			name: "foo",
577			srcs: ["in"],
578			restat: true,
579		}
580		rule_builder_test {
581			name: "foo_sbox",
582			srcs: ["in"],
583			sbox: true,
584		}
585		rule_builder_test {
586			name: "foo_sbox_inputs",
587			srcs: ["in"],
588			sbox: true,
589			sbox_inputs: true,
590		}
591	`
592
593	result := GroupFixturePreparers(
594		prepareForRuleBuilderTest,
595		FixtureWithRootAndroidBp(bp),
596		fs.AddToFixture(),
597	).RunTest(t)
598
599	check := func(t *testing.T, params TestingBuildParams, rspFile2Params TestingBuildParams,
600		wantCommand, wantOutput, wantDepfile, wantRspFile, wantRspFile2 string,
601		wantRestat bool, extraImplicits, extraCmdDeps []string) {
602
603		t.Helper()
604		command := params.RuleParams.Command
605		re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
606		command = re.ReplaceAllLiteralString(command, "")
607
608		AssertStringEquals(t, "RuleParams.Command", wantCommand, command)
609
610		wantDeps := append([]string{"cp"}, extraCmdDeps...)
611		AssertArrayString(t, "RuleParams.CommandDeps", wantDeps, params.RuleParams.CommandDeps)
612
613		AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat)
614
615		wantInputs := []string{"rsp_in"}
616		AssertArrayString(t, "Inputs", wantInputs, params.Inputs.Strings())
617
618		wantImplicits := append([]string{"implicit", "in"}, extraImplicits...)
619		// The second rsp file and the files listed in it should be in implicits
620		wantImplicits = append(wantImplicits, "rsp_in2", wantRspFile2)
621		AssertPathsRelativeToTopEquals(t, "Implicits", wantImplicits, params.Implicits)
622
623		wantOrderOnlys := []string{"orderonly"}
624		AssertPathsRelativeToTopEquals(t, "OrderOnly", wantOrderOnlys, params.OrderOnly)
625
626		wantValidations := []string{"validation"}
627		AssertPathsRelativeToTopEquals(t, "Validations", wantValidations, params.Validations)
628
629		wantRspFileContent := "$in"
630		AssertStringEquals(t, "RspfileContent", wantRspFileContent, params.RuleParams.RspfileContent)
631
632		AssertStringEquals(t, "Rspfile", wantRspFile, params.RuleParams.Rspfile)
633
634		AssertPathRelativeToTopEquals(t, "Output", wantOutput, params.Output)
635
636		if len(params.ImplicitOutputs) != 0 {
637			t.Errorf("want ImplicitOutputs = [], got %q", params.ImplicitOutputs.Strings())
638		}
639
640		AssertPathRelativeToTopEquals(t, "Depfile", wantDepfile, params.Depfile)
641
642		if params.Deps != blueprint.DepsGCC {
643			t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps)
644		}
645
646		rspFile2Content := ContentFromFileRuleForTests(t, result.TestContext, rspFile2Params)
647		AssertStringEquals(t, "rspFile2 content", "rsp_in2\n", rspFile2Content)
648	}
649
650	t.Run("module", func(t *testing.T) {
651		outFile := "out/soong/.intermediates/foo/gen/foo"
652		rspFile := "out/soong/.intermediates/foo/rsp"
653		rspFile2 := "out/soong/.intermediates/foo/rsp2"
654		module := result.ModuleForTests("foo", "")
655		check(t, module.Rule("rule"), module.Output(rspFile2),
656			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
657			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
658	})
659	t.Run("sbox", func(t *testing.T) {
660		outDir := "out/soong/.intermediates/foo_sbox"
661		sboxOutDir := filepath.Join(outDir, "gen")
662		outFile := filepath.Join(sboxOutDir, "foo_sbox")
663		depFile := filepath.Join(sboxOutDir, "foo_sbox.d")
664		rspFile := filepath.Join(outDir, "rsp")
665		rspFile2 := filepath.Join(outDir, "rsp2")
666		manifest := filepath.Join(outDir, "sbox.textproto")
667		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
668		sandboxPath := shared.TempDirForOutDir("out/soong")
669
670		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
671		module := result.ModuleForTests("foo_sbox", "")
672		check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2),
673			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
674	})
675	t.Run("sbox_inputs", func(t *testing.T) {
676		outDir := "out/soong/.intermediates/foo_sbox_inputs"
677		sboxOutDir := filepath.Join(outDir, "gen")
678		outFile := filepath.Join(sboxOutDir, "foo_sbox_inputs")
679		depFile := filepath.Join(sboxOutDir, "foo_sbox_inputs.d")
680		rspFile := filepath.Join(outDir, "rsp")
681		rspFile2 := filepath.Join(outDir, "rsp2")
682		manifest := filepath.Join(outDir, "sbox.textproto")
683		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
684		sandboxPath := shared.TempDirForOutDir("out/soong")
685
686		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
687
688		module := result.ModuleForTests("foo_sbox_inputs", "")
689		check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2),
690			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
691	})
692	t.Run("singleton", func(t *testing.T) {
693		outFile := filepath.Join("out/soong/singleton/gen/baz")
694		rspFile := filepath.Join("out/soong/singleton/rsp")
695		rspFile2 := filepath.Join("out/soong/singleton/rsp2")
696		singleton := result.SingletonForTests("rule_builder_test")
697		check(t, singleton.Rule("rule"), singleton.Output(rspFile2),
698			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
699			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
700	})
701}
702
703func TestRuleBuilderHashInputs(t *testing.T) {
704	// The basic idea here is to verify that the command (in the case of a
705	// non-sbox rule) or the sbox textproto manifest contain a hash of the
706	// inputs.
707
708	// By including a hash of the inputs, we cause the rule to re-run if
709	// the list of inputs changes because the command line or a dependency
710	// changes.
711
712	hashOf := func(s string) string {
713		sum := sha256.Sum256([]byte(s))
714		return hex.EncodeToString(sum[:])
715	}
716
717	bp := `
718			rule_builder_test {
719				name: "hash0",
720				srcs: ["in1.txt", "in2.txt"],
721			}
722			rule_builder_test {
723				name: "hash0_sbox",
724				srcs: ["in1.txt", "in2.txt"],
725				sbox: true,
726			}
727			rule_builder_test {
728				name: "hash1",
729				srcs: ["in1.txt", "in2.txt", "in3.txt"],
730			}
731			rule_builder_test {
732				name: "hash1_sbox",
733				srcs: ["in1.txt", "in2.txt", "in3.txt"],
734				sbox: true,
735			}
736		`
737	testcases := []struct {
738		name         string
739		expectedHash string
740	}{
741		{
742			name:         "hash0",
743			expectedHash: hashOf("implicit\nin1.txt\nin2.txt"),
744		},
745		{
746			name:         "hash1",
747			expectedHash: hashOf("implicit\nin1.txt\nin2.txt\nin3.txt"),
748		},
749	}
750
751	result := GroupFixturePreparers(
752		prepareForRuleBuilderTest,
753		FixtureWithRootAndroidBp(bp),
754	).RunTest(t)
755
756	for _, test := range testcases {
757		t.Run(test.name, func(t *testing.T) {
758			t.Run("sbox", func(t *testing.T) {
759				gen := result.ModuleForTests(test.name+"_sbox", "")
760				manifest := RuleBuilderSboxProtoForTests(t, result.TestContext, gen.Output("sbox.textproto"))
761				hash := manifest.Commands[0].GetInputHash()
762
763				AssertStringEquals(t, "hash", test.expectedHash, hash)
764			})
765			t.Run("", func(t *testing.T) {
766				gen := result.ModuleForTests(test.name+"", "")
767				command := gen.Output("gen/" + test.name).RuleParams.Command
768				if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
769					t.Errorf("Expected command line to end with %q, got %q", w, g)
770				}
771			})
772		})
773	}
774}
775