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	"fmt"
19	"path/filepath"
20	"reflect"
21	"strings"
22	"testing"
23
24	"github.com/google/blueprint"
25
26	"android/soong/shared"
27)
28
29func pathContext() PathContext {
30	return PathContextForTesting(TestConfig("out", nil, "", map[string][]byte{
31		"ld":      nil,
32		"a.o":     nil,
33		"b.o":     nil,
34		"cp":      nil,
35		"a":       nil,
36		"b":       nil,
37		"ls":      nil,
38		"turbine": nil,
39		"java":    nil,
40		"javac":   nil,
41	}))
42}
43
44func ExampleRuleBuilder() {
45	rule := NewRuleBuilder()
46
47	ctx := pathContext()
48
49	rule.Command().
50		Tool(PathForSource(ctx, "ld")).
51		Inputs(PathsForTesting("a.o", "b.o")).
52		FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
53	rule.Command().Text("echo success")
54
55	// To add the command to the build graph:
56	// rule.Build(pctx, ctx, "link", "link")
57
58	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
59	fmt.Printf("tools: %q\n", rule.Tools())
60	fmt.Printf("inputs: %q\n", rule.Inputs())
61	fmt.Printf("outputs: %q\n", rule.Outputs())
62
63	// Output:
64	// commands: "ld a.o b.o -o out/linked && echo success"
65	// tools: ["ld"]
66	// inputs: ["a.o" "b.o"]
67	// outputs: ["out/linked"]
68}
69
70func ExampleRuleBuilder_Temporary() {
71	rule := NewRuleBuilder()
72
73	ctx := pathContext()
74
75	rule.Command().
76		Tool(PathForSource(ctx, "cp")).
77		Input(PathForSource(ctx, "a")).
78		Output(PathForOutput(ctx, "b"))
79	rule.Command().
80		Tool(PathForSource(ctx, "cp")).
81		Input(PathForOutput(ctx, "b")).
82		Output(PathForOutput(ctx, "c"))
83	rule.Temporary(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
90	// Output:
91	// commands: "cp a out/b && cp out/b out/c"
92	// tools: ["cp"]
93	// inputs: ["a"]
94	// outputs: ["out/c"]
95}
96
97func ExampleRuleBuilder_DeleteTemporaryFiles() {
98	rule := NewRuleBuilder()
99
100	ctx := pathContext()
101
102	rule.Command().
103		Tool(PathForSource(ctx, "cp")).
104		Input(PathForSource(ctx, "a")).
105		Output(PathForOutput(ctx, "b"))
106	rule.Command().
107		Tool(PathForSource(ctx, "cp")).
108		Input(PathForOutput(ctx, "b")).
109		Output(PathForOutput(ctx, "c"))
110	rule.Temporary(PathForOutput(ctx, "b"))
111	rule.DeleteTemporaryFiles()
112
113	fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && "))
114	fmt.Printf("tools: %q\n", rule.Tools())
115	fmt.Printf("inputs: %q\n", rule.Inputs())
116	fmt.Printf("outputs: %q\n", rule.Outputs())
117
118	// Output:
119	// commands: "cp a out/b && cp out/b out/c && rm -f out/b"
120	// tools: ["cp"]
121	// inputs: ["a"]
122	// outputs: ["out/c"]
123}
124
125func ExampleRuleBuilder_Installs() {
126	rule := NewRuleBuilder()
127
128	ctx := pathContext()
129
130	out := PathForOutput(ctx, "linked")
131
132	rule.Command().
133		Tool(PathForSource(ctx, "ld")).
134		Inputs(PathsForTesting("a.o", "b.o")).
135		FlagWithOutput("-o ", out)
136	rule.Install(out, "/bin/linked")
137	rule.Install(out, "/sbin/linked")
138
139	fmt.Printf("rule.Installs().String() = %q\n", rule.Installs().String())
140
141	// Output:
142	// rule.Installs().String() = "out/linked:/bin/linked out/linked:/sbin/linked"
143}
144
145func ExampleRuleBuilderCommand() {
146	rule := NewRuleBuilder()
147
148	ctx := pathContext()
149
150	// chained
151	rule.Command().
152		Tool(PathForSource(ctx, "ld")).
153		Inputs(PathsForTesting("a.o", "b.o")).
154		FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
155
156	// unchained
157	cmd := rule.Command()
158	cmd.Tool(PathForSource(ctx, "ld"))
159	cmd.Inputs(PathsForTesting("a.o", "b.o"))
160	cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
161
162	// mixed:
163	cmd = rule.Command().Tool(PathForSource(ctx, "ld"))
164	cmd.Inputs(PathsForTesting("a.o", "b.o"))
165	cmd.FlagWithOutput("-o ", PathForOutput(ctx, "linked"))
166}
167
168func ExampleRuleBuilderCommand_Flag() {
169	ctx := pathContext()
170	fmt.Println(NewRuleBuilder().Command().
171		Tool(PathForSource(ctx, "ls")).Flag("-l"))
172	// Output:
173	// ls -l
174}
175
176func ExampleRuleBuilderCommand_Flags() {
177	ctx := pathContext()
178	fmt.Println(NewRuleBuilder().Command().
179		Tool(PathForSource(ctx, "ls")).Flags([]string{"-l", "-a"}))
180	// Output:
181	// ls -l -a
182}
183
184func ExampleRuleBuilderCommand_FlagWithArg() {
185	ctx := pathContext()
186	fmt.Println(NewRuleBuilder().Command().
187		Tool(PathForSource(ctx, "ls")).
188		FlagWithArg("--sort=", "time"))
189	// Output:
190	// ls --sort=time
191}
192
193func ExampleRuleBuilderCommand_FlagForEachArg() {
194	ctx := pathContext()
195	fmt.Println(NewRuleBuilder().Command().
196		Tool(PathForSource(ctx, "ls")).
197		FlagForEachArg("--sort=", []string{"time", "size"}))
198	// Output:
199	// ls --sort=time --sort=size
200}
201
202func ExampleRuleBuilderCommand_FlagForEachInput() {
203	ctx := pathContext()
204	fmt.Println(NewRuleBuilder().Command().
205		Tool(PathForSource(ctx, "turbine")).
206		FlagForEachInput("--classpath ", PathsForTesting("a.jar", "b.jar")))
207	// Output:
208	// turbine --classpath a.jar --classpath b.jar
209}
210
211func ExampleRuleBuilderCommand_FlagWithInputList() {
212	ctx := pathContext()
213	fmt.Println(NewRuleBuilder().Command().
214		Tool(PathForSource(ctx, "java")).
215		FlagWithInputList("-classpath=", PathsForTesting("a.jar", "b.jar"), ":"))
216	// Output:
217	// java -classpath=a.jar:b.jar
218}
219
220func ExampleRuleBuilderCommand_FlagWithInput() {
221	ctx := pathContext()
222	fmt.Println(NewRuleBuilder().Command().
223		Tool(PathForSource(ctx, "java")).
224		FlagWithInput("-classpath=", PathForSource(ctx, "a")))
225	// Output:
226	// java -classpath=a
227}
228
229func ExampleRuleBuilderCommand_FlagWithList() {
230	ctx := pathContext()
231	fmt.Println(NewRuleBuilder().Command().
232		Tool(PathForSource(ctx, "ls")).
233		FlagWithList("--sort=", []string{"time", "size"}, ","))
234	// Output:
235	// ls --sort=time,size
236}
237
238func ExampleRuleBuilderCommand_FlagWithRspFileInputList() {
239	ctx := pathContext()
240	fmt.Println(NewRuleBuilder().Command().
241		Tool(PathForSource(ctx, "javac")).
242		FlagWithRspFileInputList("@", PathsForTesting("a.java", "b.java")).
243		NinjaEscapedString())
244	// Output:
245	// javac @$out.rsp
246}
247
248func ExampleRuleBuilderCommand_String() {
249	fmt.Println(NewRuleBuilder().Command().
250		Text("FOO=foo").
251		Text("echo $FOO").
252		String())
253	// Output:
254	// FOO=foo echo $FOO
255}
256
257func ExampleRuleBuilderCommand_NinjaEscapedString() {
258	fmt.Println(NewRuleBuilder().Command().
259		Text("FOO=foo").
260		Text("echo $FOO").
261		NinjaEscapedString())
262	// Output:
263	// FOO=foo echo $$FOO
264}
265
266func TestRuleBuilder(t *testing.T) {
267	fs := map[string][]byte{
268		"dep_fixer":  nil,
269		"input":      nil,
270		"Implicit":   nil,
271		"Input":      nil,
272		"OrderOnly":  nil,
273		"OrderOnlys": nil,
274		"Tool":       nil,
275		"input2":     nil,
276		"tool2":      nil,
277		"input3":     nil,
278	}
279
280	ctx := PathContextForTesting(TestConfig("out", nil, "", fs))
281
282	addCommands := func(rule *RuleBuilder) {
283		cmd := rule.Command().
284			DepFile(PathForOutput(ctx, "DepFile")).
285			Flag("Flag").
286			FlagWithArg("FlagWithArg=", "arg").
287			FlagWithDepFile("FlagWithDepFile=", PathForOutput(ctx, "depfile")).
288			FlagWithInput("FlagWithInput=", PathForSource(ctx, "input")).
289			FlagWithOutput("FlagWithOutput=", PathForOutput(ctx, "output")).
290			Implicit(PathForSource(ctx, "Implicit")).
291			ImplicitDepFile(PathForOutput(ctx, "ImplicitDepFile")).
292			ImplicitOutput(PathForOutput(ctx, "ImplicitOutput")).
293			Input(PathForSource(ctx, "Input")).
294			Output(PathForOutput(ctx, "Output")).
295			OrderOnly(PathForSource(ctx, "OrderOnly")).
296			Text("Text").
297			Tool(PathForSource(ctx, "Tool"))
298
299		rule.Command().
300			Text("command2").
301			DepFile(PathForOutput(ctx, "depfile2")).
302			Input(PathForSource(ctx, "input2")).
303			Output(PathForOutput(ctx, "output2")).
304			OrderOnlys(PathsForSource(ctx, []string{"OrderOnlys"})).
305			Tool(PathForSource(ctx, "tool2"))
306
307		// Test updates to the first command after the second command has been started
308		cmd.Text("after command2")
309		// Test updating a command when the previous update did not replace the cmd variable
310		cmd.Text("old cmd")
311
312		// Test a command that uses the output of a previous command as an input
313		rule.Command().
314			Text("command3").
315			Input(PathForSource(ctx, "input3")).
316			Input(PathForOutput(ctx, "output2")).
317			Output(PathForOutput(ctx, "output3"))
318	}
319
320	wantInputs := PathsForSource(ctx, []string{"Implicit", "Input", "input", "input2", "input3"})
321	wantOutputs := PathsForOutput(ctx, []string{"ImplicitOutput", "Output", "output", "output2", "output3"})
322	wantDepFiles := PathsForOutput(ctx, []string{"DepFile", "depfile", "ImplicitDepFile", "depfile2"})
323	wantTools := PathsForSource(ctx, []string{"Tool", "tool2"})
324	wantOrderOnlys := PathsForSource(ctx, []string{"OrderOnly", "OrderOnlys"})
325
326	t.Run("normal", func(t *testing.T) {
327		rule := NewRuleBuilder()
328		addCommands(rule)
329
330		wantCommands := []string{
331			"out/DepFile Flag FlagWithArg=arg FlagWithDepFile=out/depfile FlagWithInput=input FlagWithOutput=out/output Input out/Output Text Tool after command2 old cmd",
332			"command2 out/depfile2 input2 out/output2 tool2",
333			"command3 input3 out/output2 out/output3",
334		}
335
336		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer out/DepFile out/depfile out/ImplicitDepFile out/depfile2"
337
338		if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
339			t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", w, g)
340		}
341
342		if g, w := rule.Inputs(), wantInputs; !reflect.DeepEqual(w, g) {
343			t.Errorf("\nwant rule.Inputs() = %#v\n                 got %#v", w, g)
344		}
345		if g, w := rule.Outputs(), wantOutputs; !reflect.DeepEqual(w, g) {
346			t.Errorf("\nwant rule.Outputs() = %#v\n                  got %#v", w, g)
347		}
348		if g, w := rule.DepFiles(), wantDepFiles; !reflect.DeepEqual(w, g) {
349			t.Errorf("\nwant rule.DepFiles() = %#v\n                  got %#v", w, g)
350		}
351		if g, w := rule.Tools(), wantTools; !reflect.DeepEqual(w, g) {
352			t.Errorf("\nwant rule.Tools() = %#v\n                got %#v", w, g)
353		}
354		if g, w := rule.OrderOnlys(), wantOrderOnlys; !reflect.DeepEqual(w, g) {
355			t.Errorf("\nwant rule.OrderOnlys() = %#v\n                got %#v", w, g)
356		}
357
358		if g, w := rule.depFileMergerCmd(ctx, rule.DepFiles()).String(), wantDepMergerCommand; g != w {
359			t.Errorf("\nwant rule.depFileMergerCmd() = %#v\n                   got %#v", w, g)
360		}
361	})
362
363	t.Run("sbox", func(t *testing.T) {
364		rule := NewRuleBuilder().Sbox(PathForOutput(ctx))
365		addCommands(rule)
366
367		wantCommands := []string{
368			"__SBOX_OUT_DIR__/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_OUT_DIR__/depfile FlagWithInput=input FlagWithOutput=__SBOX_OUT_DIR__/output Input __SBOX_OUT_DIR__/Output Text Tool after command2 old cmd",
369			"command2 __SBOX_OUT_DIR__/depfile2 input2 __SBOX_OUT_DIR__/output2 tool2",
370			"command3 input3 __SBOX_OUT_DIR__/output2 __SBOX_OUT_DIR__/output3",
371		}
372
373		wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_OUT_DIR__/DepFile __SBOX_OUT_DIR__/depfile __SBOX_OUT_DIR__/ImplicitDepFile __SBOX_OUT_DIR__/depfile2"
374
375		if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
376			t.Errorf("\nwant rule.Commands() = %#v\n                   got %#v", w, g)
377		}
378
379		if g, w := rule.Inputs(), wantInputs; !reflect.DeepEqual(w, g) {
380			t.Errorf("\nwant rule.Inputs() = %#v\n                 got %#v", w, g)
381		}
382		if g, w := rule.Outputs(), wantOutputs; !reflect.DeepEqual(w, g) {
383			t.Errorf("\nwant rule.Outputs() = %#v\n                  got %#v", w, g)
384		}
385		if g, w := rule.DepFiles(), wantDepFiles; !reflect.DeepEqual(w, g) {
386			t.Errorf("\nwant rule.DepFiles() = %#v\n                  got %#v", w, g)
387		}
388		if g, w := rule.Tools(), wantTools; !reflect.DeepEqual(w, g) {
389			t.Errorf("\nwant rule.Tools() = %#v\n                got %#v", w, g)
390		}
391		if g, w := rule.OrderOnlys(), wantOrderOnlys; !reflect.DeepEqual(w, g) {
392			t.Errorf("\nwant rule.OrderOnlys() = %#v\n                got %#v", w, g)
393		}
394
395		if g, w := rule.depFileMergerCmd(ctx, rule.DepFiles()).String(), wantDepMergerCommand; g != w {
396			t.Errorf("\nwant rule.depFileMergerCmd() = %#v\n                   got %#v", w, g)
397		}
398	})
399}
400
401func testRuleBuilderFactory() Module {
402	module := &testRuleBuilderModule{}
403	module.AddProperties(&module.properties)
404	InitAndroidModule(module)
405	return module
406}
407
408type testRuleBuilderModule struct {
409	ModuleBase
410	properties struct {
411		Src string
412
413		Restat bool
414		Sbox   bool
415	}
416}
417
418func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
419	in := PathForSource(ctx, t.properties.Src)
420	out := PathForModuleOut(ctx, ctx.ModuleName())
421	outDep := PathForModuleOut(ctx, ctx.ModuleName()+".d")
422	outDir := PathForModuleOut(ctx)
423
424	testRuleBuilder_Build(ctx, in, out, outDep, outDir, t.properties.Restat, t.properties.Sbox)
425}
426
427type testRuleBuilderSingleton struct{}
428
429func testRuleBuilderSingletonFactory() Singleton {
430	return &testRuleBuilderSingleton{}
431}
432
433func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
434	in := PathForSource(ctx, "bar")
435	out := PathForOutput(ctx, "baz")
436	outDep := PathForOutput(ctx, "baz.d")
437	outDir := PathForOutput(ctx)
438	testRuleBuilder_Build(ctx, in, out, outDep, outDir, true, false)
439}
440
441func testRuleBuilder_Build(ctx BuilderContext, in Path, out, outDep, outDir WritablePath, restat, sbox bool) {
442	rule := NewRuleBuilder()
443
444	if sbox {
445		rule.Sbox(outDir)
446	}
447
448	rule.Command().Tool(PathForSource(ctx, "cp")).Input(in).Output(out).ImplicitDepFile(outDep)
449
450	if restat {
451		rule.Restat()
452	}
453
454	rule.Build(pctx, ctx, "rule", "desc")
455}
456
457func TestRuleBuilder_Build(t *testing.T) {
458	fs := map[string][]byte{
459		"bar": nil,
460		"cp":  nil,
461	}
462
463	bp := `
464		rule_builder_test {
465			name: "foo",
466			src: "bar",
467			restat: true,
468		}
469		rule_builder_test {
470			name: "foo_sbox",
471			src: "bar",
472			sbox: true,
473		}
474	`
475
476	config := TestConfig(buildDir, nil, bp, fs)
477	ctx := NewTestContext()
478	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
479	ctx.RegisterSingletonType("rule_builder_test", testRuleBuilderSingletonFactory)
480	ctx.Register(config)
481
482	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
483	FailIfErrored(t, errs)
484	_, errs = ctx.PrepareBuildActions(config)
485	FailIfErrored(t, errs)
486
487	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraCmdDeps []string) {
488		t.Helper()
489		if params.RuleParams.Command != wantCommand {
490			t.Errorf("\nwant RuleParams.Command = %q\n                      got %q", wantCommand, params.RuleParams.Command)
491		}
492
493		wantDeps := append([]string{"cp"}, extraCmdDeps...)
494		if !reflect.DeepEqual(params.RuleParams.CommandDeps, wantDeps) {
495			t.Errorf("\nwant RuleParams.CommandDeps = %q\n                          got %q", wantDeps, params.RuleParams.CommandDeps)
496		}
497
498		if params.RuleParams.Restat != wantRestat {
499			t.Errorf("want RuleParams.Restat = %v, got %v", wantRestat, params.RuleParams.Restat)
500		}
501
502		if len(params.Implicits) != 1 || params.Implicits[0].String() != "bar" {
503			t.Errorf("want Implicits = [%q], got %q", "bar", params.Implicits.Strings())
504		}
505
506		if params.Output.String() != wantOutput {
507			t.Errorf("want Output = %q, got %q", wantOutput, params.Output)
508		}
509
510		if len(params.ImplicitOutputs) != 0 {
511			t.Errorf("want ImplicitOutputs = [], got %q", params.ImplicitOutputs.Strings())
512		}
513
514		if params.Depfile.String() != wantDepfile {
515			t.Errorf("want Depfile = %q, got %q", wantDepfile, params.Depfile)
516		}
517
518		if params.Deps != blueprint.DepsGCC {
519			t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps)
520		}
521	}
522
523	t.Run("module", func(t *testing.T) {
524		outFile := filepath.Join(buildDir, ".intermediates", "foo", "foo")
525		check(t, ctx.ModuleForTests("foo", "").Rule("rule"),
526			"cp bar "+outFile,
527			outFile, outFile+".d", true, nil)
528	})
529	t.Run("sbox", func(t *testing.T) {
530		outDir := filepath.Join(buildDir, ".intermediates", "foo_sbox")
531		outFile := filepath.Join(outDir, "foo_sbox")
532		depFile := filepath.Join(outDir, "foo_sbox.d")
533		sbox := filepath.Join(buildDir, "host", config.PrebuiltOS(), "bin/sbox")
534		sandboxPath := shared.TempDirForOutDir(buildDir)
535
536		cmd := sbox + ` -c 'cp bar __SBOX_OUT_DIR__/foo_sbox' --sandbox-path ` + sandboxPath + " --output-root " + outDir + " --depfile-out " + depFile + " __SBOX_OUT_DIR__/foo_sbox"
537
538		check(t, ctx.ModuleForTests("foo_sbox", "").Rule("rule"),
539			cmd, outFile, depFile, false, []string{sbox})
540	})
541	t.Run("singleton", func(t *testing.T) {
542		outFile := filepath.Join(buildDir, "baz")
543		check(t, ctx.SingletonForTests("rule_builder_test").Rule("rule"),
544			"cp bar "+outFile, outFile, outFile+".d", true, nil)
545	})
546}
547
548func Test_ninjaEscapeExceptForSpans(t *testing.T) {
549	type args struct {
550		s     string
551		spans [][2]int
552	}
553	tests := []struct {
554		name string
555		args args
556		want string
557	}{
558		{
559			name: "empty",
560			args: args{
561				s: "",
562			},
563			want: "",
564		},
565		{
566			name: "unescape none",
567			args: args{
568				s: "$abc",
569			},
570			want: "$$abc",
571		},
572		{
573			name: "unescape all",
574			args: args{
575				s:     "$abc",
576				spans: [][2]int{{0, 4}},
577			},
578			want: "$abc",
579		},
580		{
581			name: "unescape first",
582			args: args{
583				s:     "$abc$",
584				spans: [][2]int{{0, 1}},
585			},
586			want: "$abc$$",
587		},
588		{
589			name: "unescape last",
590			args: args{
591				s:     "$abc$",
592				spans: [][2]int{{4, 5}},
593			},
594			want: "$$abc$",
595		},
596		{
597			name: "unescape middle",
598			args: args{
599				s:     "$a$b$c$",
600				spans: [][2]int{{2, 5}},
601			},
602			want: "$$a$b$c$$",
603		},
604		{
605			name: "unescape multiple",
606			args: args{
607				s:     "$a$b$c$",
608				spans: [][2]int{{2, 3}, {4, 5}},
609			},
610			want: "$$a$b$c$$",
611		},
612	}
613	for _, tt := range tests {
614		t.Run(tt.name, func(t *testing.T) {
615			if got := ninjaEscapeExceptForSpans(tt.args.s, tt.args.spans); got != tt.want {
616				t.Errorf("ninjaEscapeExceptForSpans() = %v, want %v", got, tt.want)
617			}
618		})
619	}
620}
621