1 // Copyright 2016 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 <fstream>
6 #include <sstream>
7
8 #include "base/command_line.h"
9 #include "base/files/file_util.h"
10 #include "gn/ninja_build_writer.h"
11 #include "gn/pool.h"
12 #include "gn/scheduler.h"
13 #include "gn/switches.h"
14 #include "gn/target.h"
15 #include "gn/test_with_scheduler.h"
16 #include "gn/test_with_scope.h"
17 #include "util/test/test.h"
18
19 using NinjaBuildWriterTest = TestWithScheduler;
20
21 class ScopedDotGNFile {
22 public:
ScopedDotGNFile(const base::FilePath & path)23 ScopedDotGNFile(const base::FilePath& path)
24 : path_(path),
25 file_(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE) {
26 EXPECT_TRUE(file_.IsValid());
27 }
~ScopedDotGNFile()28 ~ScopedDotGNFile() {
29 file_.Close();
30 base::DeleteFile(path_, false);
31 }
32
33 private:
34 base::FilePath path_;
35 base::File file_;
36 };
37
TEST_F(NinjaBuildWriterTest,GetSelfInvocationCommandLine)38 TEST_F(NinjaBuildWriterTest, GetSelfInvocationCommandLine) {
39 // TestWithScope sets up a config with a build dir of //out/Debug.
40 TestWithScope setup;
41 base::CommandLine cmd_out(base::CommandLine::NO_PROGRAM);
42
43 // Setup sets the default root dir to ".".
44 base::FilePath root(FILE_PATH_LITERAL("."));
45 base::FilePath root_realpath = base::MakeAbsoluteFilePath(root);
46
47 base::FilePath gn(FILE_PATH_LITERAL("testdot.gn"));
48
49 // The file must exist on disk for MakeAbsoluteFilePath() to work.
50 ScopedDotGNFile dot_gn(gn);
51 base::FilePath gn_realpath = base::MakeAbsoluteFilePath(gn);
52
53 // Without any parameters the self invocation should pass --root=../..
54 // (from //out/Debug to //).
55 setup.build_settings()->SetRootPath(root_realpath);
56 cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
57 EXPECT_EQ("../..", cmd_out.GetSwitchValueString(switches::kRoot));
58 EXPECT_FALSE(cmd_out.HasSwitch(switches::kDotfile));
59
60 // If --root is . and --dotfile is foo/.gn, then --dotfile also needs
61 // to to become ../../foo/.gn.
62 setup.build_settings()->SetRootPath(root_realpath);
63 setup.build_settings()->set_dotfile_name(gn_realpath);
64 cmd_out = GetSelfInvocationCommandLine(setup.build_settings());
65 EXPECT_EQ("../..", cmd_out.GetSwitchValueString(switches::kRoot));
66 EXPECT_EQ("../../testdot.gn",
67 cmd_out.GetSwitchValueString(switches::kDotfile));
68 }
69
TEST_F(NinjaBuildWriterTest,TwoTargets)70 TEST_F(NinjaBuildWriterTest, TwoTargets) {
71 TestWithScope setup;
72 Err err;
73
74 Target target_foo(setup.settings(), Label(SourceDir("//foo/"), "bar"));
75 target_foo.set_output_type(Target::ACTION);
76 target_foo.action_values().set_script(SourceFile("//foo/script.py"));
77 target_foo.action_values().outputs() = SubstitutionList::MakeForTest(
78 "//out/Debug/out1.out", "//out/Debug/out2.out");
79 target_foo.SetToolchain(setup.toolchain());
80 ASSERT_TRUE(target_foo.OnResolved(&err));
81
82 Target target_bar(setup.settings(), Label(SourceDir("//bar/"), "bar"));
83 target_bar.set_output_type(Target::ACTION);
84 target_bar.action_values().set_script(SourceFile("//bar/script.py"));
85 target_bar.action_values().outputs() = SubstitutionList::MakeForTest(
86 "//out/Debug/out3.out", "//out/Debug/out4.out");
87 target_bar.SetToolchain(setup.toolchain());
88 ASSERT_TRUE(target_bar.OnResolved(&err));
89
90 // Make a secondary toolchain that references two pools.
91 Label other_toolchain_label(SourceDir("//other/"), "toolchain");
92 Toolchain other_toolchain(setup.settings(), other_toolchain_label);
93 TestWithScope::SetupToolchain(&other_toolchain);
94
95 Pool other_regular_pool(
96 setup.settings(),
97 Label(SourceDir("//other/"), "depth_pool", other_toolchain_label.dir(),
98 other_toolchain_label.name()));
99 other_regular_pool.set_depth(42);
100 other_toolchain.GetTool(CTool::kCToolLink)
101 ->set_pool(LabelPtrPair<Pool>(&other_regular_pool));
102
103 // Make another target that uses its own pool
104
105 Pool another_regular_pool(
106 setup.settings(),
107 Label(SourceDir("//another/"), "depth_pool", other_toolchain_label.dir(),
108 other_toolchain_label.name()));
109 another_regular_pool.set_depth(7);
110
111 Target target_baz(setup.settings(), Label(SourceDir("//baz/"), "baz"));
112 target_baz.set_output_type(Target::ACTION);
113 target_baz.action_values().set_script(SourceFile("//baz/script.py"));
114 target_baz.action_values().outputs() = SubstitutionList::MakeForTest(
115 "//out/Debug/out5.out", "//out/Debug/out6.out");
116 target_baz.SetToolchain(&other_toolchain);
117 target_baz.set_pool(LabelPtrPair<Pool>(&another_regular_pool));
118 ASSERT_TRUE(target_baz.OnResolved(&err));
119
120 // The console pool must be in the default toolchain.
121 Pool console_pool(setup.settings(), Label(SourceDir("//"), "console",
122 setup.toolchain()->label().dir(),
123 setup.toolchain()->label().name()));
124 console_pool.set_depth(1);
125 other_toolchain.GetTool(GeneralTool::kGeneralToolStamp)
126 ->set_pool(LabelPtrPair<Pool>(&console_pool));
127
128 // Settings to go with the other toolchain.
129 Settings other_settings(setup.build_settings(), "toolchain/");
130 other_settings.set_toolchain_label(other_toolchain_label);
131
132 std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
133 used_toolchains[setup.settings()] = setup.toolchain();
134 used_toolchains[&other_settings] = &other_toolchain;
135
136 std::vector<const Target*> targets = {&target_foo, &target_bar, &target_baz};
137
138 std::ostringstream ninja_out;
139 std::ostringstream depfile_out;
140
141 NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
142 setup.toolchain(), targets, ninja_out, depfile_out);
143 ASSERT_TRUE(writer.Run(&err));
144
145 const char expected_rule_gn[] = "rule gn\n";
146 const char expected_build_ninja_stamp[] =
147 "build build.ninja.stamp: gn\n"
148 " generator = 1\n"
149 " depfile = build.ninja.d\n";
150 const char expected_build_ninja[] =
151 "build build.ninja: phony build.ninja.stamp\n"
152 " generator = 1\n";
153 const char expected_other_pool[] =
154 "pool other_toolchain_another_depth_pool\n"
155 " depth = 7\n"
156 "\n"
157 "pool other_toolchain_other_depth_pool\n"
158 " depth = 42\n";
159 const char expected_toolchain[] = "subninja toolchain.ninja\n";
160 const char expected_targets[] =
161 "build bar: phony obj/bar/bar.stamp\n"
162 "build baz: phony obj/baz/baz.stamp\n"
163 "build foo$:bar: phony obj/foo/bar.stamp\n"
164 "build bar$:bar: phony obj/bar/bar.stamp\n"
165 "build baz$:baz: phony obj/baz/baz.stamp\n";
166 const char expected_root_target[] =
167 "build all: phony $\n"
168 " obj/foo/bar.stamp $\n"
169 " obj/bar/bar.stamp $\n"
170 " obj/baz/baz.stamp\n";
171 const char expected_default[] = "default all\n";
172 std::string out_str = ninja_out.str();
173 #define EXPECT_SNIPPET(expected) \
174 EXPECT_NE(std::string::npos, out_str.find(expected)) \
175 << "Expected to find: " << expected << "\n" \
176 << "Within: " << out_str
177 EXPECT_SNIPPET(expected_rule_gn);
178 EXPECT_SNIPPET(expected_build_ninja_stamp);
179 EXPECT_SNIPPET(expected_build_ninja);
180 EXPECT_SNIPPET(expected_other_pool);
181 EXPECT_SNIPPET(expected_toolchain);
182 EXPECT_SNIPPET(expected_targets);
183 EXPECT_SNIPPET(expected_root_target);
184 EXPECT_SNIPPET(expected_default);
185 #undef EXPECT_SNIPPET
186
187 // A pool definition for ninja's built-in console pool must not be written.
188 EXPECT_EQ(std::string::npos, out_str.find("pool console"));
189 }
190
TEST_F(NinjaBuildWriterTest,ExtractRegenerationCommands)191 TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands) {
192 TestWithScope setup;
193 Err err;
194
195 Target target_foo(setup.settings(), Label(SourceDir("//foo/"), "bar"));
196 target_foo.set_output_type(Target::ACTION);
197 target_foo.action_values().set_script(SourceFile("//foo/script.py"));
198 target_foo.action_values().outputs() = SubstitutionList::MakeForTest(
199 "//out/Debug/out1.out", "//out/Debug/out2.out");
200 target_foo.SetToolchain(setup.toolchain());
201 ASSERT_TRUE(target_foo.OnResolved(&err));
202
203 // The console pool must be in the default toolchain.
204 Pool console_pool(setup.settings(), Label(SourceDir("//"), "console",
205 setup.toolchain()->label().dir(),
206 setup.toolchain()->label().name()));
207 console_pool.set_depth(1);
208
209 std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
210 used_toolchains[setup.settings()] = setup.toolchain();
211
212 std::vector<const Target*> targets = {&target_foo};
213
214 std::stringstream ninja_out;
215 std::ostringstream depfile_out;
216
217 NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
218 setup.toolchain(), targets, ninja_out, depfile_out);
219 ASSERT_TRUE(writer.Run(&err));
220
221 const char expected_rule_gn[] = "rule gn\n";
222 const char expected_build_ninja_stamp[] = "build build.ninja.stamp: gn\n";
223 const char expected_build_ninja[] =
224 "build build.ninja: phony build.ninja.stamp\n";
225 const char expected_target[] = "build bar:";
226 const char expected_root_target[] = "build all: phony $\n";
227 const char expected_default[] = "default all\n";
228 std::string ninja_out_str = ninja_out.str();
229 #define EXPECT_SNIPPET(str, expected) \
230 EXPECT_NE(std::string::npos, str.find(expected)) \
231 << "Expected to find: " << expected << "\n" \
232 << "Within: " << str
233 #define EXPECT_NO_SNIPPET(str, unexpected) \
234 EXPECT_EQ(std::string::npos, str.find(unexpected)) \
235 << "Found unexpected: " << unexpected << "\n" \
236 << "Within: " << str
237 EXPECT_SNIPPET(ninja_out_str, expected_rule_gn);
238 EXPECT_SNIPPET(ninja_out_str, expected_build_ninja_stamp);
239 EXPECT_SNIPPET(ninja_out_str, expected_build_ninja);
240 EXPECT_SNIPPET(ninja_out_str, expected_target);
241 EXPECT_SNIPPET(ninja_out_str, expected_root_target);
242 EXPECT_SNIPPET(ninja_out_str, expected_default);
243
244 std::string commands =
245 NinjaBuildWriter::ExtractRegenerationCommands(ninja_out);
246 EXPECT_SNIPPET(commands, expected_rule_gn);
247 EXPECT_SNIPPET(commands, expected_build_ninja_stamp);
248 EXPECT_SNIPPET(commands, expected_build_ninja);
249 EXPECT_NO_SNIPPET(commands, expected_target);
250 EXPECT_NO_SNIPPET(commands, expected_root_target);
251 EXPECT_NO_SNIPPET(commands, expected_default);
252
253 #undef EXPECT_SNIPPET
254 #undef EXPECT_NO_SNIPPET
255 }
256
TEST_F(NinjaBuildWriterTest,ExtractRegenerationCommands_DefaultStream)257 TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands_DefaultStream) {
258 std::ifstream ninja_in;
259 EXPECT_EQ(NinjaBuildWriter::ExtractRegenerationCommands(ninja_in), "");
260 }
261
TEST_F(NinjaBuildWriterTest,ExtractRegenerationCommands_StreamError)262 TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands_StreamError) {
263 std::ifstream ninja_in("/does/not/exist");
264 EXPECT_EQ(NinjaBuildWriter::ExtractRegenerationCommands(ninja_in), "");
265 }
266
TEST_F(NinjaBuildWriterTest,ExtractRegenerationCommands_IncompleteNinja)267 TEST_F(NinjaBuildWriterTest, ExtractRegenerationCommands_IncompleteNinja) {
268 std::stringstream ninja_in;
269 ninja_in << "foo\nbar\nbaz\nbif\n";
270 EXPECT_EQ(NinjaBuildWriter::ExtractRegenerationCommands(ninja_in), "");
271 }
272
TEST_F(NinjaBuildWriterTest,SpaceInDepfile)273 TEST_F(NinjaBuildWriterTest, SpaceInDepfile) {
274 TestWithScope setup;
275 Err err;
276
277 // Setup sets the default root dir to ".".
278 base::FilePath root(FILE_PATH_LITERAL("."));
279 base::FilePath root_realpath = base::MakeAbsoluteFilePath(root);
280 setup.build_settings()->SetRootPath(root_realpath);
281
282 // Cannot use MakeAbsoluteFilePath for non-existed paths
283 base::FilePath dependency =
284 root_realpath.Append(FILE_PATH_LITERAL("path with space/BUILD.gn"));
285 g_scheduler->AddGenDependency(dependency);
286
287 std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
288 used_toolchains[setup.settings()] = setup.toolchain();
289 std::vector<const Target*> targets;
290 std::ostringstream ninja_out;
291 std::ostringstream depfile_out;
292 NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
293 setup.toolchain(), targets, ninja_out, depfile_out);
294 ASSERT_TRUE(writer.Run(&err));
295
296 EXPECT_EQ(depfile_out.str(),
297 "build.ninja.stamp: ../../path\\ with\\ space/BUILD.gn");
298 }
299
TEST_F(NinjaBuildWriterTest,DuplicateOutputs)300 TEST_F(NinjaBuildWriterTest, DuplicateOutputs) {
301 TestWithScope setup;
302 Err err;
303
304 Target target_foo(setup.settings(), Label(SourceDir("//foo/"), "bar"));
305 target_foo.set_output_type(Target::ACTION);
306 target_foo.action_values().set_script(SourceFile("//foo/script.py"));
307 target_foo.action_values().outputs() = SubstitutionList::MakeForTest(
308 "//out/Debug/out1.out", "//out/Debug/out2.out");
309 target_foo.SetToolchain(setup.toolchain());
310 ASSERT_TRUE(target_foo.OnResolved(&err));
311
312 Target target_bar(setup.settings(), Label(SourceDir("//bar/"), "bar"));
313 target_bar.set_output_type(Target::ACTION);
314 target_bar.action_values().set_script(SourceFile("//bar/script.py"));
315 target_bar.action_values().outputs() = SubstitutionList::MakeForTest(
316 "//out/Debug/out3.out", "//out/Debug/out2.out");
317 target_bar.SetToolchain(setup.toolchain());
318 ASSERT_TRUE(target_bar.OnResolved(&err));
319
320 std::unordered_map<const Settings*, const Toolchain*> used_toolchains;
321 used_toolchains[setup.settings()] = setup.toolchain();
322 std::vector<const Target*> targets = {&target_foo, &target_bar};
323 std::ostringstream ninja_out;
324 std::ostringstream depfile_out;
325 NinjaBuildWriter writer(setup.build_settings(), used_toolchains, targets,
326 setup.toolchain(), targets, ninja_out, depfile_out);
327 ASSERT_FALSE(writer.Run(&err));
328
329 const char expected_help_test[] =
330 "Two or more targets generate the same output:\n"
331 " out2.out\n"
332 "\n"
333 "This is can often be fixed by changing one of the target names, or by \n"
334 "setting an output_name on one of them.\n"
335 "\n"
336 "Collisions:\n"
337 " //foo:bar()\n"
338 " //bar:bar()\n";
339
340 EXPECT_EQ(expected_help_test, err.help_text());
341 }
342