1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <algorithm>
6 #include <sstream>
7
8 #include "gn/ninja_action_target_writer.h"
9 #include "gn/pool.h"
10 #include "gn/substitution_list.h"
11 #include "gn/target.h"
12 #include "gn/test_with_scope.h"
13 #include "util/build_config.h"
14 #include "util/test/test.h"
15
TEST(NinjaActionTargetWriter,WriteOutputFilesForBuildLine)16 TEST(NinjaActionTargetWriter, WriteOutputFilesForBuildLine) {
17 Err err;
18 TestWithScope setup;
19
20 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
21 target.set_output_type(Target::ACTION_FOREACH);
22 target.action_values().outputs() =
23 SubstitutionList::MakeForTest("//out/Debug/gen/a b{{source_name_part}}.h",
24 "//out/Debug/gen/{{source_name_part}}.cc");
25
26 target.SetToolchain(setup.toolchain());
27 ASSERT_TRUE(target.OnResolved(&err));
28
29 std::ostringstream out;
30 NinjaActionTargetWriter writer(&target, out);
31
32 SourceFile source("//foo/bar.in");
33 std::vector<OutputFile> output_files;
34 writer.WriteOutputFilesForBuildLine(source, &output_files);
35
36 EXPECT_EQ(" gen/a$ bbar.h gen/bar.cc", out.str());
37 }
38
39 // Tests an action with no sources.
TEST(NinjaActionTargetWriter,ActionNoSources)40 TEST(NinjaActionTargetWriter, ActionNoSources) {
41 Err err;
42 TestWithScope setup;
43
44 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
45 target.set_output_type(Target::ACTION);
46
47 target.action_values().set_script(SourceFile("//foo/script.py"));
48 target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
49
50 target.action_values().outputs() =
51 SubstitutionList::MakeForTest("//out/Debug/foo.out");
52
53 target.SetToolchain(setup.toolchain());
54 ASSERT_TRUE(target.OnResolved(&err));
55
56 setup.build_settings()->set_python_path(
57 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
58
59 std::ostringstream out;
60 NinjaActionTargetWriter writer(&target, out);
61 writer.Run();
62
63 const char* expected = R"(rule __foo_bar___rule
64 command = /usr/bin/python ../../foo/script.py
65 description = ACTION //foo:bar()
66 restat = 1
67
68 build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
69
70 build obj/foo/bar.stamp: stamp foo.out
71 )";
72 EXPECT_EQ(expected, out.str()) << expected << "--" << out.str();
73 }
74
75 // Tests an action with no sources and pool
TEST(NinjaActionTargetWriter,ActionNoSourcesConsole)76 TEST(NinjaActionTargetWriter, ActionNoSourcesConsole) {
77 Err err;
78 TestWithScope setup;
79
80 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
81 target.set_output_type(Target::ACTION);
82
83 target.action_values().set_script(SourceFile("//foo/script.py"));
84 target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
85
86 target.action_values().outputs() =
87 SubstitutionList::MakeForTest("//out/Debug/foo.out");
88
89 Pool pool(setup.settings(),
90 Label(SourceDir("//"), "console", setup.toolchain()->label().dir(),
91 setup.toolchain()->label().name()));
92 pool.set_depth(1);
93 target.action_values().set_pool(LabelPtrPair<Pool>(&pool));
94
95 target.SetToolchain(setup.toolchain());
96 ASSERT_TRUE(target.OnResolved(&err));
97
98 setup.build_settings()->set_python_path(
99 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
100
101 std::ostringstream out;
102 NinjaActionTargetWriter writer(&target, out);
103 writer.Run();
104
105 // The console pool's name must be mapped exactly to the string "console"
106 // which is a special pre-defined pool name in ninja.
107 const char* expected = R"(rule __foo_bar___rule
108 command = /usr/bin/python ../../foo/script.py
109 description = ACTION //foo:bar()
110 restat = 1
111
112 build foo.out: __foo_bar___rule | ../../foo/script.py ../../foo/included.txt
113 pool = console
114
115 build obj/foo/bar.stamp: stamp foo.out
116 )";
117 EXPECT_EQ(expected, out.str());
118 }
119
120 // Makes sure that we write sources as input dependencies for actions with
121 // both sources and inputs (ACTION_FOREACH treats the sources differently).
TEST(NinjaActionTargetWriter,ActionWithSources)122 TEST(NinjaActionTargetWriter, ActionWithSources) {
123 Err err;
124 TestWithScope setup;
125
126 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
127 target.set_output_type(Target::ACTION);
128
129 target.action_values().set_script(SourceFile("//foo/script.py"));
130
131 target.sources().push_back(SourceFile("//foo/source.txt"));
132 target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
133
134 target.action_values().outputs() =
135 SubstitutionList::MakeForTest("//out/Debug/foo.out");
136
137 target.SetToolchain(setup.toolchain());
138 ASSERT_TRUE(target.OnResolved(&err));
139
140 setup.build_settings()->set_python_path(
141 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
142
143 std::ostringstream out;
144 NinjaActionTargetWriter writer(&target, out);
145 writer.Run();
146
147 const char expected_linux[] =
148 "rule __foo_bar___rule\n"
149 " command = /usr/bin/python ../../foo/script.py\n"
150 " description = ACTION //foo:bar()\n"
151 " restat = 1\n"
152 "\n"
153 "build foo.out: __foo_bar___rule | ../../foo/script.py "
154 "../../foo/included.txt ../../foo/source.txt\n"
155 "\n"
156 "build obj/foo/bar.stamp: stamp foo.out\n";
157 EXPECT_EQ(expected_linux, out.str());
158 }
159
TEST(NinjaActionTargetWriter,ForEach)160 TEST(NinjaActionTargetWriter, ForEach) {
161 Err err;
162 TestWithScope setup;
163
164 // Some dependencies that the action can depend on. Use actions for these
165 // so they have a nice platform-independent stamp file that can appear in the
166 // output (rather than having to worry about how the current platform names
167 // binaries).
168 Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
169 dep.set_output_type(Target::ACTION);
170 dep.visibility().SetPublic();
171 dep.SetToolchain(setup.toolchain());
172 ASSERT_TRUE(dep.OnResolved(&err));
173
174 Target datadep(setup.settings(), Label(SourceDir("//foo/"), "datadep"));
175 datadep.set_output_type(Target::ACTION);
176 datadep.visibility().SetPublic();
177 datadep.SetToolchain(setup.toolchain());
178 ASSERT_TRUE(datadep.OnResolved(&err));
179
180 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
181 target.set_output_type(Target::ACTION_FOREACH);
182 target.private_deps().push_back(LabelTargetPair(&dep));
183 target.data_deps().push_back(LabelTargetPair(&datadep));
184
185 target.sources().push_back(SourceFile("//foo/input1.txt"));
186 target.sources().push_back(SourceFile("//foo/input2.txt"));
187
188 target.action_values().set_script(SourceFile("//foo/script.py"));
189
190 target.action_values().args() = SubstitutionList::MakeForTest(
191 "-i", "{{source}}", "--out=foo bar{{source_name_part}}.o");
192 target.action_values().outputs() =
193 SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
194
195 target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
196
197 target.SetToolchain(setup.toolchain());
198 ASSERT_TRUE(target.OnResolved(&err));
199
200 setup.build_settings()->set_python_path(
201 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
202
203 std::ostringstream out;
204 NinjaActionTargetWriter writer(&target, out);
205 writer.Run();
206
207 const char expected_linux[] =
208 "rule __foo_bar___rule\n"
209 " command = /usr/bin/python ../../foo/script.py -i ${in} "
210 // Escaping is different between Windows and Posix.
211 #if defined(OS_WIN)
212 "\"--out=foo$ bar${source_name_part}.o\"\n"
213 #else
214 "--out=foo\\$ bar${source_name_part}.o\n"
215 #endif
216 " description = ACTION //foo:bar()\n"
217 " restat = 1\n"
218 "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
219 "../../foo/included.txt obj/foo/dep.stamp\n"
220 "\n"
221 "build input1.out: __foo_bar___rule ../../foo/input1.txt | "
222 "obj/foo/bar.inputdeps.stamp\n"
223 " source_name_part = input1\n"
224 "build input2.out: __foo_bar___rule ../../foo/input2.txt | "
225 "obj/foo/bar.inputdeps.stamp\n"
226 " source_name_part = input2\n"
227 "\n"
228 "build obj/foo/bar.stamp: "
229 "stamp input1.out input2.out || obj/foo/datadep.stamp\n";
230
231 std::string out_str = out.str();
232 #if defined(OS_WIN)
233 std::replace(out_str.begin(), out_str.end(), '\\', '/');
234 #endif
235 EXPECT_EQ(expected_linux, out_str);
236 }
237
TEST(NinjaActionTargetWriter,ForEachWithDepfile)238 TEST(NinjaActionTargetWriter, ForEachWithDepfile) {
239 Err err;
240 TestWithScope setup;
241
242 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
243 target.set_output_type(Target::ACTION_FOREACH);
244
245 target.sources().push_back(SourceFile("//foo/input1.txt"));
246 target.sources().push_back(SourceFile("//foo/input2.txt"));
247
248 target.action_values().set_script(SourceFile("//foo/script.py"));
249
250 target.SetToolchain(setup.toolchain());
251 ASSERT_TRUE(target.OnResolved(&err));
252
253 SubstitutionPattern depfile;
254 ASSERT_TRUE(
255 depfile.Parse("//out/Debug/gen/{{source_name_part}}.d", nullptr, &err));
256 target.action_values().set_depfile(depfile);
257
258 target.action_values().args() = SubstitutionList::MakeForTest(
259 "-i", "{{source}}", "--out=foo bar{{source_name_part}}.o");
260 target.action_values().outputs() =
261 SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
262
263 target.config_values().inputs().push_back(SourceFile("//foo/included.txt"));
264
265 setup.build_settings()->set_python_path(
266 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
267
268 std::ostringstream out;
269 NinjaActionTargetWriter writer(&target, out);
270 writer.Run();
271
272 const char expected_linux[] =
273 "rule __foo_bar___rule\n"
274 " command = /usr/bin/python ../../foo/script.py -i ${in} "
275 #if defined(OS_WIN)
276 "\"--out=foo$ bar${source_name_part}.o\"\n"
277 #else
278 "--out=foo\\$ bar${source_name_part}.o\n"
279 #endif
280 " description = ACTION //foo:bar()\n"
281 " restat = 1\n"
282 "build obj/foo/bar.inputdeps.stamp: stamp ../../foo/script.py "
283 "../../foo/included.txt\n"
284 "\n"
285 "build input1.out: __foo_bar___rule ../../foo/input1.txt"
286 " | obj/foo/bar.inputdeps.stamp\n"
287 " source_name_part = input1\n"
288 " depfile = gen/input1.d\n"
289 "build input2.out: __foo_bar___rule ../../foo/input2.txt"
290 " | obj/foo/bar.inputdeps.stamp\n"
291 " source_name_part = input2\n"
292 " depfile = gen/input2.d\n"
293 "\n"
294 "build obj/foo/bar.stamp: stamp input1.out input2.out\n";
295 EXPECT_EQ(expected_linux, out.str());
296 }
297
TEST(NinjaActionTargetWriter,ForEachWithResponseFile)298 TEST(NinjaActionTargetWriter, ForEachWithResponseFile) {
299 Err err;
300 TestWithScope setup;
301
302 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
303 target.set_output_type(Target::ACTION_FOREACH);
304
305 target.sources().push_back(SourceFile("//foo/input1.txt"));
306 target.action_values().set_script(SourceFile("//foo/script.py"));
307
308 target.SetToolchain(setup.toolchain());
309 ASSERT_TRUE(target.OnResolved(&err));
310
311 // Make sure we get interesting substitutions for both the args and the
312 // response file contents.
313 target.action_values().args() = SubstitutionList::MakeForTest(
314 "{{source}}", "{{source_file_part}}", "{{response_file_name}}");
315 target.action_values().rsp_file_contents() =
316 SubstitutionList::MakeForTest("-j", "{{source_name_part}}");
317 target.action_values().outputs() =
318 SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
319
320 setup.build_settings()->set_python_path(
321 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
322
323 std::ostringstream out;
324 NinjaActionTargetWriter writer(&target, out);
325 writer.Run();
326
327 const char expected_linux[] =
328 "rule __foo_bar___rule\n"
329 // This name is autogenerated from the target rule name.
330 " rspfile = __foo_bar___rule.$unique_name.rsp\n"
331 // These come from rsp_file_contents above.
332 " rspfile_content = -j ${source_name_part}\n"
333 // These come from the args.
334 " command = /usr/bin/python ../../foo/script.py ${in} "
335 "${source_file_part} ${rspfile}\n"
336 " description = ACTION //foo:bar()\n"
337 " restat = 1\n"
338 "\n"
339 "build input1.out: __foo_bar___rule ../../foo/input1.txt"
340 " | ../../foo/script.py\n"
341 // Necessary for the rspfile defined in the rule.
342 " unique_name = 0\n"
343 // Substitution for the args.
344 " source_file_part = input1.txt\n"
345 // Substitution for the rspfile contents.
346 " source_name_part = input1\n"
347 "\n"
348 "build obj/foo/bar.stamp: stamp input1.out\n";
349 EXPECT_EQ(expected_linux, out.str());
350 }
351
TEST(NinjaActionTargetWriter,ForEachWithPool)352 TEST(NinjaActionTargetWriter, ForEachWithPool) {
353 Err err;
354 TestWithScope setup;
355
356 Target target(setup.settings(), Label(SourceDir("//foo/"), "bar"));
357 target.set_output_type(Target::ACTION_FOREACH);
358
359 target.sources().push_back(SourceFile("//foo/input1.txt"));
360 target.action_values().set_script(SourceFile("//foo/script.py"));
361
362 Pool pool(setup.settings(),
363 Label(SourceDir("//foo/"), "pool", setup.toolchain()->label().dir(),
364 setup.toolchain()->label().name()));
365 pool.set_depth(5);
366 target.action_values().set_pool(LabelPtrPair<Pool>(&pool));
367
368 target.SetToolchain(setup.toolchain());
369 ASSERT_TRUE(target.OnResolved(&err));
370
371 // Make sure we get interesting substitutions for both the args and the
372 // response file contents.
373 target.action_values().args() =
374 SubstitutionList::MakeForTest("{{source}}", "{{source_file_part}}");
375 target.action_values().outputs() =
376 SubstitutionList::MakeForTest("//out/Debug/{{source_name_part}}.out");
377
378 setup.build_settings()->set_python_path(
379 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
380
381 std::ostringstream out;
382 NinjaActionTargetWriter writer(&target, out);
383 writer.Run();
384
385 const char expected_linux[] =
386 "rule __foo_bar___rule\n"
387 // These come from the args.
388 " command = /usr/bin/python ../../foo/script.py ${in} "
389 "${source_file_part}\n"
390 " description = ACTION //foo:bar()\n"
391 " restat = 1\n"
392 "\n"
393 "build input1.out: __foo_bar___rule ../../foo/input1.txt"
394 " | ../../foo/script.py\n"
395 // Substitution for the args.
396 " source_file_part = input1.txt\n"
397 " pool = foo_pool\n"
398 "\n"
399 "build obj/foo/bar.stamp: stamp input1.out\n";
400 EXPECT_EQ(expected_linux, out.str());
401 }
402
TEST(NinjaActionTargetWriter,NoTransitiveHardDeps)403 TEST(NinjaActionTargetWriter, NoTransitiveHardDeps) {
404 Err err;
405 TestWithScope setup;
406
407 setup.build_settings()->set_python_path(
408 base::FilePath(FILE_PATH_LITERAL("/usr/bin/python")));
409
410 Target dep(setup.settings(), Label(SourceDir("//foo/"), "dep"));
411 dep.set_output_type(Target::ACTION);
412 dep.visibility().SetPublic();
413 dep.SetToolchain(setup.toolchain());
414 ASSERT_TRUE(dep.OnResolved(&err));
415
416 Target foo(setup.settings(), Label(SourceDir("//foo/"), "foo"));
417 foo.set_output_type(Target::ACTION);
418 foo.visibility().SetPublic();
419 foo.sources().push_back(SourceFile("//foo/input1.txt"));
420 foo.action_values().set_script(SourceFile("//foo/script.py"));
421 foo.private_deps().push_back(LabelTargetPair(&dep));
422 foo.SetToolchain(setup.toolchain());
423 foo.action_values().outputs() =
424 SubstitutionList::MakeForTest("//out/Debug/foo.out");
425 ASSERT_TRUE(foo.OnResolved(&err));
426
427 {
428 std::ostringstream out;
429 NinjaActionTargetWriter writer(&foo, out);
430 writer.Run();
431
432 const char expected_linux[] =
433 "rule __foo_foo___rule\n"
434 // These come from the args.
435 " command = /usr/bin/python ../../foo/script.py\n"
436 " description = ACTION //foo:foo()\n"
437 " restat = 1\n"
438 "\n"
439 "build foo.out: __foo_foo___rule | ../../foo/script.py"
440 " ../../foo/input1.txt obj/foo/dep.stamp\n"
441 "\n"
442 "build obj/foo/foo.stamp: stamp foo.out\n";
443 EXPECT_EQ(expected_linux, out.str());
444 }
445
446 Target bar(setup.settings(), Label(SourceDir("//bar/"), "bar"));
447 bar.set_output_type(Target::ACTION);
448 bar.sources().push_back(SourceFile("//bar/input1.txt"));
449 bar.action_values().set_script(SourceFile("//bar/script.py"));
450 bar.private_deps().push_back(LabelTargetPair(&foo));
451 bar.SetToolchain(setup.toolchain());
452 bar.action_values().outputs() =
453 SubstitutionList::MakeForTest("//out/Debug/bar.out");
454 ASSERT_TRUE(bar.OnResolved(&err)) << err.message();
455
456 {
457 std::ostringstream out;
458 NinjaActionTargetWriter writer(&bar, out);
459 writer.Run();
460
461 const char expected_linux[] =
462 "rule __bar_bar___rule\n"
463 // These come from the args.
464 " command = /usr/bin/python ../../bar/script.py\n"
465 " description = ACTION //bar:bar()\n"
466 " restat = 1\n"
467 "\n"
468 // Do not have obj/foo/dep.stamp as dependency.
469 "build bar.out: __bar_bar___rule | ../../bar/script.py"
470 " ../../bar/input1.txt obj/foo/foo.stamp\n"
471 "\n"
472 "build obj/bar/bar.stamp: stamp bar.out\n";
473 EXPECT_EQ(expected_linux, out.str());
474 }
475 }
476