• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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