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		Unescape_ninja_vars bool
482	}
483}
484
485func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
486	in := PathsForSource(ctx, t.properties.Srcs)
487	implicit := PathForSource(ctx, "implicit")
488	orderOnly := PathForSource(ctx, "orderonly")
489	validation := PathForSource(ctx, "validation")
490	out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
491	outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
492	outDir := PathForModuleOut(ctx, "gen")
493	rspFile := PathForModuleOut(ctx, "rsp")
494	rspFile2 := PathForModuleOut(ctx, "rsp2")
495	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
496	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
497	manifestPath := PathForModuleOut(ctx, "sbox.textproto")
498
499	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, t.properties.Flags,
500		out, outDep, outDir,
501		manifestPath, t.properties.Restat, t.properties.Sbox, t.properties.Sbox_inputs, t.properties.Unescape_ninja_vars,
502		rspFile, rspFileContents, rspFile2, rspFileContents2)
503}
504
505type testRuleBuilderSingleton struct{}
506
507func testRuleBuilderSingletonFactory() Singleton {
508	return &testRuleBuilderSingleton{}
509}
510
511func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
512	in := PathsForSource(ctx, []string{"in"})
513	implicit := PathForSource(ctx, "implicit")
514	orderOnly := PathForSource(ctx, "orderonly")
515	validation := PathForSource(ctx, "validation")
516	out := PathForOutput(ctx, "singleton/gen/baz")
517	outDep := PathForOutput(ctx, "singleton/gen/baz.d")
518	outDir := PathForOutput(ctx, "singleton/gen")
519	rspFile := PathForOutput(ctx, "singleton/rsp")
520	rspFile2 := PathForOutput(ctx, "singleton/rsp2")
521	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
522	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
523	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
524
525	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, nil, out, outDep, outDir,
526		manifestPath, true, false, false, false,
527		rspFile, rspFileContents, rspFile2, rspFileContents2)
528}
529
530func testRuleBuilder_Build(ctx BuilderContext, in Paths, implicit, orderOnly, validation Path,
531	flags []string,
532	out, outDep, outDir, manifestPath WritablePath,
533	restat, sbox, sboxInputs, unescapeNinjaVars bool,
534	rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) {
535
536	rule := NewRuleBuilder(pctx_ruleBuilderTest, ctx)
537
538	if sbox {
539		rule.Sbox(outDir, manifestPath)
540		if sboxInputs {
541			rule.SandboxInputs()
542		}
543	}
544
545	rule.Command().
546		Tool(PathForSource(ctx, "cp")).
547		Flags(flags).
548		Inputs(in).
549		Implicit(implicit).
550		OrderOnly(orderOnly).
551		Validation(validation).
552		Output(out).
553		ImplicitDepFile(outDep).
554		FlagWithRspFileInputList("@", rspFile, rspFileContents).
555		FlagWithRspFileInputList("@", rspFile2, rspFileContents2)
556
557	if restat {
558		rule.Restat()
559	}
560
561	if unescapeNinjaVars {
562		rule.BuildWithUnescapedNinjaVars("rule", "desc")
563	} else {
564		rule.Build("rule", "desc")
565	}
566}
567
568var prepareForRuleBuilderTest = FixtureRegisterWithContext(func(ctx RegistrationContext) {
569	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
570	ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory)
571})
572
573func TestRuleBuilder_Build(t *testing.T) {
574	fs := MockFS{
575		"in": nil,
576		"cp": nil,
577	}
578
579	bp := `
580		rule_builder_test {
581			name: "foo",
582			srcs: ["in"],
583			restat: true,
584		}
585		rule_builder_test {
586			name: "foo_sbox",
587			srcs: ["in"],
588			sbox: true,
589		}
590		rule_builder_test {
591			name: "foo_sbox_inputs",
592			srcs: ["in"],
593			sbox: true,
594			sbox_inputs: true,
595		}
596	`
597
598	result := GroupFixturePreparers(
599		prepareForRuleBuilderTest,
600		FixtureWithRootAndroidBp(bp),
601		fs.AddToFixture(),
602	).RunTest(t)
603
604	check := func(t *testing.T, params TestingBuildParams, rspFile2Params TestingBuildParams,
605		wantCommand, wantOutput, wantDepfile, wantRspFile, wantRspFile2 string,
606		wantRestat bool, extraImplicits, extraCmdDeps []string) {
607
608		t.Helper()
609		command := params.RuleParams.Command
610		re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
611		command = re.ReplaceAllLiteralString(command, "")
612
613		AssertStringEquals(t, "RuleParams.Command", wantCommand, command)
614
615		wantDeps := append([]string{"cp"}, extraCmdDeps...)
616		AssertArrayString(t, "RuleParams.CommandDeps", wantDeps, params.RuleParams.CommandDeps)
617
618		AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat)
619
620		wantInputs := []string{"rsp_in"}
621		AssertArrayString(t, "Inputs", wantInputs, params.Inputs.Strings())
622
623		wantImplicits := append([]string{"implicit", "in"}, extraImplicits...)
624		// The second rsp file and the files listed in it should be in implicits
625		wantImplicits = append(wantImplicits, "rsp_in2", wantRspFile2)
626		AssertPathsRelativeToTopEquals(t, "Implicits", wantImplicits, params.Implicits)
627
628		wantOrderOnlys := []string{"orderonly"}
629		AssertPathsRelativeToTopEquals(t, "OrderOnly", wantOrderOnlys, params.OrderOnly)
630
631		wantValidations := []string{"validation"}
632		AssertPathsRelativeToTopEquals(t, "Validations", wantValidations, params.Validations)
633
634		wantRspFileContent := "$in"
635		AssertStringEquals(t, "RspfileContent", wantRspFileContent, params.RuleParams.RspfileContent)
636
637		AssertStringEquals(t, "Rspfile", wantRspFile, params.RuleParams.Rspfile)
638
639		AssertPathRelativeToTopEquals(t, "Output", wantOutput, params.Output)
640
641		if len(params.ImplicitOutputs) != 0 {
642			t.Errorf("want ImplicitOutputs = [], got %q", params.ImplicitOutputs.Strings())
643		}
644
645		AssertPathRelativeToTopEquals(t, "Depfile", wantDepfile, params.Depfile)
646
647		if params.Deps != blueprint.DepsGCC {
648			t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps)
649		}
650
651		rspFile2Content := ContentFromFileRuleForTests(t, result.TestContext, rspFile2Params)
652		AssertStringEquals(t, "rspFile2 content", "rsp_in2\n", rspFile2Content)
653	}
654
655	t.Run("module", func(t *testing.T) {
656		outFile := "out/soong/.intermediates/foo/gen/foo"
657		rspFile := "out/soong/.intermediates/foo/rsp"
658		rspFile2 := "out/soong/.intermediates/foo/rsp2"
659		module := result.ModuleForTests("foo", "")
660		check(t, module.Rule("rule"), module.Output(rspFile2),
661			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
662			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
663	})
664	t.Run("sbox", func(t *testing.T) {
665		outDir := "out/soong/.intermediates/foo_sbox"
666		sboxOutDir := filepath.Join(outDir, "gen")
667		outFile := filepath.Join(sboxOutDir, "foo_sbox")
668		depFile := filepath.Join(sboxOutDir, "foo_sbox.d")
669		rspFile := filepath.Join(outDir, "rsp")
670		rspFile2 := filepath.Join(outDir, "rsp2")
671		manifest := filepath.Join(outDir, "sbox.textproto")
672		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
673		sandboxPath := shared.TempDirForOutDir("out/soong")
674
675		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
676		module := result.ModuleForTests("foo_sbox", "")
677		check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2),
678			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
679	})
680	t.Run("sbox_inputs", func(t *testing.T) {
681		outDir := "out/soong/.intermediates/foo_sbox_inputs"
682		sboxOutDir := filepath.Join(outDir, "gen")
683		outFile := filepath.Join(sboxOutDir, "foo_sbox_inputs")
684		depFile := filepath.Join(sboxOutDir, "foo_sbox_inputs.d")
685		rspFile := filepath.Join(outDir, "rsp")
686		rspFile2 := filepath.Join(outDir, "rsp2")
687		manifest := filepath.Join(outDir, "sbox.textproto")
688		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
689		sandboxPath := shared.TempDirForOutDir("out/soong")
690
691		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
692
693		module := result.ModuleForTests("foo_sbox_inputs", "")
694		check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2),
695			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
696	})
697	t.Run("singleton", func(t *testing.T) {
698		outFile := filepath.Join("out/soong/singleton/gen/baz")
699		rspFile := filepath.Join("out/soong/singleton/rsp")
700		rspFile2 := filepath.Join("out/soong/singleton/rsp2")
701		singleton := result.SingletonForTests("rule_builder_test")
702		check(t, singleton.Rule("rule"), singleton.Output(rspFile2),
703			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
704			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
705	})
706}
707
708func TestRuleBuilderHashInputs(t *testing.T) {
709	// The basic idea here is to verify that the command (in the case of a
710	// non-sbox rule) or the sbox textproto manifest contain a hash of the
711	// inputs.
712
713	// By including a hash of the inputs, we cause the rule to re-run if
714	// the list of inputs changes because the command line or a dependency
715	// changes.
716
717	hashOf := func(s string) string {
718		sum := sha256.Sum256([]byte(s))
719		return hex.EncodeToString(sum[:])
720	}
721
722	bp := `
723			rule_builder_test {
724				name: "hash0",
725				srcs: ["in1.txt", "in2.txt"],
726			}
727			rule_builder_test {
728				name: "hash0_sbox",
729				srcs: ["in1.txt", "in2.txt"],
730				sbox: true,
731			}
732			rule_builder_test {
733				name: "hash1",
734				srcs: ["in1.txt", "in2.txt", "in3.txt"],
735			}
736			rule_builder_test {
737				name: "hash1_sbox",
738				srcs: ["in1.txt", "in2.txt", "in3.txt"],
739				sbox: true,
740			}
741		`
742	testcases := []struct {
743		name         string
744		expectedHash string
745	}{
746		{
747			name:         "hash0",
748			expectedHash: hashOf("implicit\nin1.txt\nin2.txt"),
749		},
750		{
751			name:         "hash1",
752			expectedHash: hashOf("implicit\nin1.txt\nin2.txt\nin3.txt"),
753		},
754	}
755
756	result := GroupFixturePreparers(
757		prepareForRuleBuilderTest,
758		FixtureWithRootAndroidBp(bp),
759	).RunTest(t)
760
761	for _, test := range testcases {
762		t.Run(test.name, func(t *testing.T) {
763			t.Run("sbox", func(t *testing.T) {
764				gen := result.ModuleForTests(test.name+"_sbox", "")
765				manifest := RuleBuilderSboxProtoForTests(t, result.TestContext, gen.Output("sbox.textproto"))
766				hash := manifest.Commands[0].GetInputHash()
767
768				AssertStringEquals(t, "hash", test.expectedHash, hash)
769			})
770			t.Run("", func(t *testing.T) {
771				gen := result.ModuleForTests(test.name+"", "")
772				command := gen.Output("gen/" + test.name).RuleParams.Command
773				if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
774					t.Errorf("Expected command line to end with %q, got %q", w, g)
775				}
776			})
777		})
778	}
779}
780
781func TestRuleBuilderWithNinjaVarEscaping(t *testing.T) {
782	bp := `
783		rule_builder_test {
784			name: "foo_sbox_escaped",
785			flags: ["${cmdFlags}"],
786			sbox: true,
787			sbox_inputs: true,
788		}
789		rule_builder_test {
790			name: "foo_sbox_unescaped",
791			flags: ["${cmdFlags}"],
792			sbox: true,
793			sbox_inputs: true,
794			unescape_ninja_vars: true,
795		}
796	`
797	result := GroupFixturePreparers(
798		prepareForRuleBuilderTest,
799		FixtureWithRootAndroidBp(bp),
800	).RunTest(t)
801
802	escapedNinjaMod := result.ModuleForTests("foo_sbox_escaped", "").Output("sbox.textproto")
803	AssertStringEquals(t, "expected rule", "android/soong/android.rawFileCopy", escapedNinjaMod.Rule.String())
804	AssertStringDoesContain(
805		t,
806		"",
807		ContentFromFileRuleForTests(t, result.TestContext, escapedNinjaMod),
808		"${cmdFlags}",
809	)
810
811	unescapedNinjaMod := result.ModuleForTests("foo_sbox_unescaped", "").Rule("unescapedWriteFile")
812	AssertStringDoesContain(
813		t,
814		"",
815		unescapedNinjaMod.BuildParams.Args["content"],
816		"${cmdFlags}",
817	)
818	AssertStringDoesNotContain(
819		t,
820		"",
821		unescapedNinjaMod.BuildParams.Args["content"],
822		"$${cmdFlags}",
823	)
824}
825