OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "tools/gn/ninja_build_writer.h" | 5 #include "tools/gn/ninja_build_writer.h" |
6 | 6 |
7 #include <stddef.h> | 7 #include <stddef.h> |
8 | 8 |
9 #include <fstream> | 9 #include <fstream> |
10 #include <map> | 10 #include <map> |
| 11 #include <sstream> |
11 | 12 |
12 #include "base/command_line.h" | 13 #include "base/command_line.h" |
13 #include "base/files/file_util.h" | 14 #include "base/files/file_util.h" |
14 #include "base/path_service.h" | 15 #include "base/path_service.h" |
15 #include "base/process/process_handle.h" | 16 #include "base/process/process_handle.h" |
16 #include "base/strings/string_util.h" | 17 #include "base/strings/string_util.h" |
17 #include "base/strings/utf_string_conversions.h" | 18 #include "base/strings/utf_string_conversions.h" |
18 #include "build/build_config.h" | 19 #include "build/build_config.h" |
19 #include "tools/gn/build_settings.h" | 20 #include "tools/gn/build_settings.h" |
20 #include "tools/gn/err.h" | 21 #include "tools/gn/err.h" |
21 #include "tools/gn/escape.h" | 22 #include "tools/gn/escape.h" |
22 #include "tools/gn/filesystem_utils.h" | 23 #include "tools/gn/filesystem_utils.h" |
23 #include "tools/gn/input_file_manager.h" | 24 #include "tools/gn/input_file_manager.h" |
24 #include "tools/gn/ninja_utils.h" | 25 #include "tools/gn/ninja_utils.h" |
25 #include "tools/gn/scheduler.h" | 26 #include "tools/gn/scheduler.h" |
26 #include "tools/gn/switches.h" | 27 #include "tools/gn/switches.h" |
27 #include "tools/gn/target.h" | 28 #include "tools/gn/target.h" |
28 #include "tools/gn/trace.h" | 29 #include "tools/gn/trace.h" |
29 | 30 |
30 #if defined(OS_WIN) | 31 #if defined(OS_WIN) |
31 #include <windows.h> | 32 #include <windows.h> |
32 #endif | 33 #endif |
33 | 34 |
34 namespace { | 35 namespace { |
35 | 36 |
| 37 struct Counts { |
| 38 Counts() : count(0), last_seen(nullptr) {} |
| 39 |
| 40 // Number of targets of this type. |
| 41 int count; |
| 42 |
| 43 // The last one we encountered. |
| 44 const Target* last_seen; |
| 45 }; |
| 46 |
36 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) { | 47 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) { |
37 base::FilePath executable; | 48 base::FilePath executable; |
38 PathService::Get(base::FILE_EXE, &executable); | 49 PathService::Get(base::FILE_EXE, &executable); |
39 | 50 |
40 base::CommandLine cmdline(executable.NormalizePathSeparatorsTo('/')); | 51 base::CommandLine cmdline(executable.NormalizePathSeparatorsTo('/')); |
41 cmdline.AppendArg("gen"); | 52 cmdline.AppendArg("gen"); |
42 cmdline.AppendArg(build_settings->build_dir().value()); | 53 cmdline.AppendArg(build_settings->build_dir().value()); |
43 cmdline.AppendSwitchPath(std::string("--") + switches::kRoot, | 54 cmdline.AppendSwitchPath(std::string("--") + switches::kRoot, |
44 build_settings->root_path()); | 55 build_settings->root_path()); |
45 // Successful automatic invocations shouldn't print output. | 56 // Successful automatic invocations shouldn't print output. |
(...skipping 26 matching lines...) Expand all Loading... |
72 } | 83 } |
73 } | 84 } |
74 | 85 |
75 #if defined(OS_WIN) | 86 #if defined(OS_WIN) |
76 return base::WideToUTF8(cmdline.GetCommandLineString()); | 87 return base::WideToUTF8(cmdline.GetCommandLineString()); |
77 #else | 88 #else |
78 return cmdline.GetCommandLineString(); | 89 return cmdline.GetCommandLineString(); |
79 #endif | 90 #endif |
80 } | 91 } |
81 | 92 |
82 OutputFile GetTargetOutputFile(const Target* target) { | |
83 OutputFile result(target->dependency_output_file()); | |
84 | |
85 // The output files may have leading "./" so normalize those away. | |
86 NormalizePath(&result.value()); | |
87 return result; | |
88 } | |
89 | |
90 bool HasOutputIdenticalToLabel(const Target* target, | |
91 const std::string& short_name) { | |
92 if (target->output_type() != Target::ACTION && | |
93 target->output_type() != Target::ACTION_FOREACH) | |
94 return false; | |
95 | |
96 // Rather than convert all outputs to be relative to the build directory | |
97 // and then compare to the short name, convert the short name to look like a | |
98 // file in the output directory since this is only one conversion. | |
99 SourceFile short_name_as_source_file( | |
100 target->settings()->build_settings()->build_dir().value() + short_name); | |
101 | |
102 std::vector<SourceFile> outputs_as_source; | |
103 target->action_values().GetOutputsAsSourceFiles(target, &outputs_as_source); | |
104 for (const SourceFile& output_as_source : outputs_as_source) { | |
105 if (output_as_source == short_name_as_source_file) | |
106 return true; | |
107 } | |
108 return false; | |
109 } | |
110 | |
111 // Given an output that appears more than once, generates an error message | 93 // Given an output that appears more than once, generates an error message |
112 // that describes the problem and which targets generate it. | 94 // that describes the problem and which targets generate it. |
113 Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets, | 95 Err GetDuplicateOutputError(const std::vector<const Target*>& all_targets, |
114 const OutputFile& bad_output) { | 96 const OutputFile& bad_output) { |
115 std::vector<const Target*> matches; | 97 std::vector<const Target*> matches; |
116 for (const Target* target : all_targets) { | 98 for (const Target* target : all_targets) { |
117 for (const auto& output : target->computed_outputs()) { | 99 for (const auto& output : target->computed_outputs()) { |
118 if (output == bad_output) { | 100 if (output == bad_output) { |
119 matches.push_back(target); | 101 matches.push_back(target); |
120 break; | 102 break; |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
171 | 153 |
172 // static | 154 // static |
173 bool NinjaBuildWriter::RunAndWriteFile( | 155 bool NinjaBuildWriter::RunAndWriteFile( |
174 const BuildSettings* build_settings, | 156 const BuildSettings* build_settings, |
175 const std::vector<const Settings*>& all_settings, | 157 const std::vector<const Settings*>& all_settings, |
176 const Toolchain* default_toolchain, | 158 const Toolchain* default_toolchain, |
177 const std::vector<const Target*>& default_toolchain_targets, | 159 const std::vector<const Target*>& default_toolchain_targets, |
178 Err* err) { | 160 Err* err) { |
179 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja"); | 161 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja"); |
180 | 162 |
181 base::FilePath ninja_file(build_settings->GetFullPath( | 163 std::stringstream file; |
182 SourceFile(build_settings->build_dir().value() + "build.ninja"))); | 164 std::stringstream depfile; |
183 base::CreateDirectory(ninja_file.DirName()); | |
184 | |
185 std::ofstream file; | |
186 file.open(FilePathToUTF8(ninja_file).c_str(), | |
187 std::ios_base::out | std::ios_base::binary); | |
188 if (file.fail()) { | |
189 *err = Err(Location(), "Couldn't open build.ninja for writing"); | |
190 return false; | |
191 } | |
192 | |
193 std::ofstream depfile; | |
194 depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(), | |
195 std::ios_base::out | std::ios_base::binary); | |
196 if (depfile.fail()) { | |
197 *err = Err(Location(), "Couldn't open depfile for writing"); | |
198 return false; | |
199 } | |
200 | |
201 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain, | 165 NinjaBuildWriter gen(build_settings, all_settings, default_toolchain, |
202 default_toolchain_targets, file, depfile); | 166 default_toolchain_targets, file, depfile); |
203 return gen.Run(err); | 167 if (!gen.Run(err)) |
| 168 return false; |
| 169 |
| 170 base::FilePath ninja_file_name(build_settings->GetFullPath( |
| 171 SourceFile(build_settings->build_dir().value() + "build.ninja"))); |
| 172 base::FilePath dep_file_name(build_settings->GetFullPath( |
| 173 SourceFile(build_settings->build_dir().value() + "build.ninja.d"))); |
| 174 base::CreateDirectory(ninja_file_name.DirName()); |
| 175 |
| 176 if (!WriteFileIfChanged(ninja_file_name, file.str(), err) || |
| 177 !WriteFileIfChanged(dep_file_name, depfile.str(), err)) |
| 178 return false; |
| 179 |
| 180 return true; |
204 } | 181 } |
205 | 182 |
206 void NinjaBuildWriter::WriteNinjaRules() { | 183 void NinjaBuildWriter::WriteNinjaRules() { |
207 out_ << "rule gn\n"; | 184 out_ << "rule gn\n"; |
208 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; | 185 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n"; |
209 out_ << " description = Regenerating ninja files\n\n"; | 186 out_ << " description = Regenerating ninja files\n\n"; |
210 | 187 |
211 // This rule will regenerate the ninja files when any input file has changed. | 188 // This rule will regenerate the ninja files when any input file has changed. |
212 out_ << "build build.ninja: gn\n" | 189 out_ << "build build.ninja: gn\n" |
213 << " generator = 1\n" | 190 << " generator = 1\n" |
(...skipping 30 matching lines...) Expand all Loading... |
244 void NinjaBuildWriter::WriteSubninjas() { | 221 void NinjaBuildWriter::WriteSubninjas() { |
245 for (const auto& elem : all_settings_) { | 222 for (const auto& elem : all_settings_) { |
246 out_ << "subninja "; | 223 out_ << "subninja "; |
247 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem)); | 224 path_output_.WriteFile(out_, GetNinjaFileForToolchain(elem)); |
248 out_ << std::endl; | 225 out_ << std::endl; |
249 } | 226 } |
250 out_ << std::endl; | 227 out_ << std::endl; |
251 } | 228 } |
252 | 229 |
253 bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) { | 230 bool NinjaBuildWriter::WritePhonyAndAllRules(Err* err) { |
254 std::string all_rules; | |
255 | |
256 // Track rules as we generate them so we don't accidentally write a phony | 231 // Track rules as we generate them so we don't accidentally write a phony |
257 // rule that collides with something else. | 232 // rule that collides with something else. |
258 // GN internally generates an "all" target, so don't duplicate it. | 233 // GN internally generates an "all" target, so don't duplicate it. |
259 std::set<std::string> written_rules; | 234 base::hash_set<std::string> written_rules; |
260 written_rules.insert("all"); | 235 written_rules.insert("all"); |
261 | 236 |
262 // Write phony rules for all uniquely-named targets in the default toolchain. | 237 // Set if we encounter a target named "//:default". |
263 // Don't do other toolchains or we'll get naming conflicts, and if the name | 238 bool default_target_exists = false; |
264 // isn't unique, also skip it. The exception is for the toplevel targets | 239 |
265 // which we also find. | 240 // Targets in the root build file. |
266 std::map<std::string, int> small_name_count; | |
267 std::map<std::string, int> exe_count; | |
268 std::vector<const Target*> toplevel_targets; | 241 std::vector<const Target*> toplevel_targets; |
269 base::hash_set<std::string> target_files; | 242 |
270 for (const auto& target : default_toolchain_targets_) { | 243 // Targets with names matching their toplevel directories. For example |
| 244 // "//foo:foo". Expect this is the naming scheme for "big components." |
| 245 std::vector<const Target*> toplevel_dir_targets; |
| 246 |
| 247 // Tracks the number of each target with the given short name, as well |
| 248 // as the short names of executables (which will be a subset of short_names). |
| 249 std::map<std::string, Counts> short_names; |
| 250 std::map<std::string, Counts> exes; |
| 251 |
| 252 for (const Target* target : default_toolchain_targets_) { |
271 const Label& label = target->label(); | 253 const Label& label = target->label(); |
272 small_name_count[label.name()]++; | 254 const std::string& short_name = label.name(); |
273 | 255 |
274 // Look for targets with a name of the form | 256 if (label.dir().value() == "//" && label.name() == "default") |
275 // dir = "//foo/", name = "foo" | 257 default_target_exists = true; |
276 // i.e. where the target name matches the top level directory. We will | 258 |
277 // always write phony rules for these even if there is another target with | 259 // Count the number of targets with the given short name. |
278 // the same short name. | 260 Counts& short_names_counts = short_names[short_name]; |
| 261 short_names_counts.count++; |
| 262 short_names_counts.last_seen = target; |
| 263 |
| 264 // Count executables with the given short name. |
| 265 if (target->output_type() == Target::EXECUTABLE) { |
| 266 Counts& exes_counts = exes[short_name]; |
| 267 exes_counts.count++; |
| 268 exes_counts.last_seen = target; |
| 269 } |
| 270 |
| 271 // Find targets in "important" directories. |
279 const std::string& dir_string = label.dir().value(); | 272 const std::string& dir_string = label.dir().value(); |
280 if (dir_string.size() == label.name().size() + 3 && // Size matches. | 273 if (dir_string.size() == 2 && |
| 274 dir_string[0] == '/' && dir_string[1] == '/') { |
| 275 toplevel_targets.push_back(target); |
| 276 } else if ( |
| 277 dir_string.size() == label.name().size() + 3 && // Size matches. |
281 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning. | 278 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning. |
282 dir_string[dir_string.size() - 1] == '/' && // "/" at end. | 279 dir_string[dir_string.size() - 1] == '/' && // "/" at end. |
283 dir_string.compare(2, label.name().size(), label.name()) == 0) | 280 dir_string.compare(2, label.name().size(), label.name()) == 0) { |
284 toplevel_targets.push_back(target); | 281 toplevel_dir_targets.push_back(target); |
| 282 } |
285 | 283 |
286 // Look for executables; later we will generate phony rules for them | 284 // Add the output files from each target to the written rules so that |
287 // even if there are non-executable targets with the same name. | 285 // we don't write phony rules that collide with anything generated by the |
288 if (target->output_type() == Target::EXECUTABLE) | 286 // build. |
289 exe_count[label.name()]++; | 287 // |
290 | 288 // If at this point there is a collision (no phony rules have been |
291 // Add the files to the list of generated targets so we don't write phony | 289 // generated yet), two targets make the same output so throw an error. |
292 // rules that collide. | |
293 std::string target_file(target->dependency_output_file().value()); | |
294 NormalizePath(&target_file); | |
295 written_rules.insert(target_file); | |
296 } | |
297 | |
298 for (const auto& target : default_toolchain_targets_) { | |
299 const Label& label = target->label(); | |
300 for (const auto& output : target->computed_outputs()) { | 290 for (const auto& output : target->computed_outputs()) { |
301 if (!target_files.insert(output.value()).second) { | 291 // Need to normalize because many toolchain outputs will be preceeded |
| 292 // with "./". |
| 293 std::string output_string(output.value()); |
| 294 NormalizePath(&output_string); |
| 295 if (!written_rules.insert(output_string).second) { |
302 *err = GetDuplicateOutputError(default_toolchain_targets_, output); | 296 *err = GetDuplicateOutputError(default_toolchain_targets_, output); |
303 return false; | 297 return false; |
304 } | 298 } |
305 } | 299 } |
| 300 } |
306 | 301 |
307 OutputFile target_file = GetTargetOutputFile(target); | 302 // First prefer the short names of toplevel targets. |
| 303 for (const Target* target : toplevel_targets) { |
| 304 if (written_rules.insert(target->label().name()).second) |
| 305 WritePhonyRule(target, target->label().name()); |
| 306 } |
| 307 |
| 308 // Next prefer short names of toplevel dir targets. |
| 309 for (const Target* target : toplevel_dir_targets) { |
| 310 if (written_rules.insert(target->label().name()).second) |
| 311 WritePhonyRule(target, target->label().name()); |
| 312 } |
| 313 |
| 314 // Write out the names labels of executables. Many toolchains will produce |
| 315 // executables in the root build directory with no extensions, so the names |
| 316 // will already exist and this will be a no-op. But on Windows such programs |
| 317 // will have extensions, and executables may override the output directory to |
| 318 // go into some other place. |
| 319 // |
| 320 // Putting this after the "toplevel" rules above also means that you can |
| 321 // steal the short name from an executable by outputting the executable to |
| 322 // a different directory or using a different output name, and writing a |
| 323 // toplevel build rule. |
| 324 for (const auto& pair : exes) { |
| 325 const Counts& counts = pair.second; |
| 326 const std::string& short_name = counts.last_seen->label().name(); |
| 327 if (counts.count == 1 && written_rules.insert(short_name).second) |
| 328 WritePhonyRule(counts.last_seen, short_name); |
| 329 } |
| 330 |
| 331 // Write short names when those names are unique and not already taken. |
| 332 for (const auto& pair : short_names) { |
| 333 const Counts& counts = pair.second; |
| 334 const std::string& short_name = counts.last_seen->label().name(); |
| 335 if (counts.count == 1 && written_rules.insert(short_name).second) |
| 336 WritePhonyRule(counts.last_seen, short_name); |
| 337 } |
| 338 |
| 339 // Write the label variants of the target name. |
| 340 for (const Target* target : default_toolchain_targets_) { |
| 341 const Label& label = target->label(); |
| 342 |
308 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz". | 343 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz". |
309 std::string long_name = label.GetUserVisibleName(false); | 344 std::string long_name = label.GetUserVisibleName(false); |
310 base::TrimString(long_name, "/", &long_name); | 345 base::TrimString(long_name, "/", &long_name); |
311 WritePhonyRule(target, target_file, long_name, &written_rules); | 346 if (written_rules.insert(long_name).second) |
| 347 WritePhonyRule(target, long_name); |
312 | 348 |
313 // Write the directory name with no target name if they match | 349 // Write the directory name with no target name if they match |
314 // (e.g. "//foo/bar:bar" -> "foo/bar"). | 350 // (e.g. "//foo/bar:bar" -> "foo/bar"). |
315 if (FindLastDirComponent(label.dir()) == label.name()) { | 351 if (FindLastDirComponent(label.dir()) == label.name()) { |
316 std::string medium_name = DirectoryWithNoLastSlash(label.dir()); | 352 std::string medium_name = DirectoryWithNoLastSlash(label.dir()); |
317 base::TrimString(medium_name, "/", &medium_name); | 353 base::TrimString(medium_name, "/", &medium_name); |
318 // That may have generated a name the same as the short name of the | 354 // That may have generated a name the same as the short name of the |
319 // target which we already wrote. | 355 // target which we already wrote. |
320 if (medium_name != label.name()) | 356 if (medium_name != label.name() && |
321 WritePhonyRule(target, target_file, medium_name, &written_rules); | 357 written_rules.insert(medium_name).second) |
| 358 WritePhonyRule(target, medium_name); |
322 } | 359 } |
323 | 360 |
324 // Write short names for ones which are either completely unique or there | 361 // Write the short name if no other target shares that short name and |
325 // at least only one of them in the default toolchain that is an exe. | 362 // non of the higher-priority rules above claimed it. |
326 if (small_name_count[label.name()] == 1 || | 363 if (short_names[label.name()].count == 1 && |
327 (target->output_type() == Target::EXECUTABLE && | 364 written_rules.insert(label.name()).second) |
328 exe_count[label.name()] == 1)) { | 365 WritePhonyRule(target, label.name()); |
329 // It's reasonable to generate output files in the root build directory | |
330 // with the same name as the target. Don't generate phony rules for | |
331 // these cases. | |
332 // | |
333 // All of this does not do the general checking of all target's outputs | |
334 // which may theoretically collide. But it's not very reasonable for | |
335 // a script target named "foo" to generate a file named "bar" with no | |
336 // extension in the root build directory while another target is named | |
337 // "bar". If this does occur, the user is likely to be confused when | |
338 // building "bar" that is builds foo anyway, so you probably just | |
339 // shouldn't do that. | |
340 // | |
341 // We should fix this however, and build up all generated script outputs | |
342 // and check everything against that. There are other edge cases that the | |
343 // current phony rule generator doesn't check. We may need to make a big | |
344 // set of every possible generated file in the build for this purpose. | |
345 if (!HasOutputIdenticalToLabel(target, label.name())) | |
346 WritePhonyRule(target, target_file, label.name(), &written_rules); | |
347 } | |
348 | |
349 if (!all_rules.empty()) | |
350 all_rules.append(" $\n "); | |
351 all_rules.append(target_file.value()); | |
352 } | 366 } |
353 | 367 |
354 // Pick up phony rules for the toplevel targets with non-unique names (which | 368 // Write the autogenerated "all" rule. |
355 // would have been skipped in the above loop). | 369 if (!default_toolchain_targets_.empty()) { |
356 for (const auto& toplevel_target : toplevel_targets) { | 370 out_ << "\nbuild all: phony"; |
357 if (small_name_count[toplevel_target->label().name()] > 1) { | 371 |
358 WritePhonyRule(toplevel_target, toplevel_target->dependency_output_file(), | 372 EscapeOptions ninja_escape; |
359 toplevel_target->label().name(), &written_rules); | 373 ninja_escape.mode = ESCAPE_NINJA; |
| 374 for (const Target* target : default_toolchain_targets_) { |
| 375 out_ << " $\n "; |
| 376 path_output_.WriteFile(out_, target->dependency_output_file()); |
360 } | 377 } |
361 } | 378 } |
| 379 out_ << std::endl; |
362 | 380 |
363 // Figure out if the BUILD file wants to declare a custom "default" | 381 if (default_target_exists) |
364 // target (rather than building 'all' by default). By convention | 382 out_ << "\ndefault default" << std::endl; |
365 // we use group("default") but it doesn't have to be a group. | 383 else if (!default_toolchain_targets_.empty()) |
366 bool default_target_exists = false; | 384 out_ << "\ndefault all" << std::endl; |
367 for (const auto& target : default_toolchain_targets_) { | |
368 const Label& label = target->label(); | |
369 if (label.dir().value() == "//" && label.name() == "default") | |
370 default_target_exists = true; | |
371 } | |
372 | |
373 if (!all_rules.empty()) { | |
374 out_ << "\nbuild all: phony " << all_rules << std::endl; | |
375 } | |
376 | |
377 if (default_target_exists) { | |
378 out_ << "default default" << std::endl; | |
379 } else if (!all_rules.empty()) { | |
380 out_ << "default all" << std::endl; | |
381 } | |
382 | 385 |
383 return true; | 386 return true; |
384 } | 387 } |
385 | 388 |
386 void NinjaBuildWriter::WritePhonyRule(const Target* target, | 389 void NinjaBuildWriter::WritePhonyRule(const Target* target, |
387 const OutputFile& target_file, | 390 const std::string& phony_name) { |
388 const std::string& phony_name, | |
389 std::set<std::string>* written_rules) { | |
390 if (target_file.value() == phony_name) | |
391 return; // No need for a phony rule. | |
392 | |
393 if (written_rules->find(phony_name) != written_rules->end()) | |
394 return; // Already exists. | |
395 written_rules->insert(phony_name); | |
396 | |
397 EscapeOptions ninja_escape; | 391 EscapeOptions ninja_escape; |
398 ninja_escape.mode = ESCAPE_NINJA; | 392 ninja_escape.mode = ESCAPE_NINJA; |
399 | 393 |
400 // Escape for special chars Ninja will handle. | 394 // Escape for special chars Ninja will handle. |
401 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr); | 395 std::string escaped = EscapeString(phony_name, ninja_escape, nullptr); |
402 | 396 |
403 out_ << "build " << escaped << ": phony "; | 397 out_ << "build " << escaped << ": phony "; |
404 path_output_.WriteFile(out_, target_file); | 398 path_output_.WriteFile(out_, target->dependency_output_file()); |
405 out_ << std::endl; | 399 out_ << std::endl; |
406 } | 400 } |
OLD | NEW |