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