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