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 "tools/gn/ninja_build_writer.h"
6
7 #include <fstream>
8 #include <map>
9
10 #include "base/command_line.h"
11 #include "base/file_util.h"
12 #include "base/path_service.h"
13 #include "base/process/process_handle.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "build/build_config.h"
17 #include "tools/gn/build_settings.h"
18 #include "tools/gn/escape.h"
19 #include "tools/gn/filesystem_utils.h"
20 #include "tools/gn/input_file_manager.h"
21 #include "tools/gn/scheduler.h"
22 #include "tools/gn/target.h"
23 #include "tools/gn/trace.h"
24
25 #if defined(OS_WIN)
26 #include <windows.h>
27 #endif
28
29 namespace {
30
GetSelfInvocationCommand(const BuildSettings * build_settings)31 std::string GetSelfInvocationCommand(const BuildSettings* build_settings) {
32 base::FilePath executable;
33 PathService::Get(base::FILE_EXE, &executable);
34
35 CommandLine cmdline(executable.NormalizePathSeparatorsTo('/'));
36 cmdline.AppendArg("gen");
37 cmdline.AppendArg(build_settings->build_dir().value());
38 cmdline.AppendSwitchPath("--root", build_settings->root_path());
39 cmdline.AppendSwitch("-q"); // Don't write output.
40
41 EscapeOptions escape_shell;
42 escape_shell.mode = ESCAPE_NINJA_COMMAND;
43 #if defined(OS_WIN)
44 // The command line code quoting varies by platform. We have one string,
45 // possibly with spaces, that we want to quote. The Windows command line
46 // quotes again, so we don't want quoting. The Posix one doesn't.
47 escape_shell.inhibit_quoting = true;
48 #endif
49
50 const CommandLine& our_cmdline = *CommandLine::ForCurrentProcess();
51 const CommandLine::SwitchMap& switches = our_cmdline.GetSwitches();
52 for (CommandLine::SwitchMap::const_iterator i = switches.begin();
53 i != switches.end(); ++i) {
54 // Only write arguments we haven't already written. Always skip "args"
55 // since those will have been written to the file and will be used
56 // implicitly in the future. Keeping --args would mean changes to the file
57 // would be ignored.
58 if (i->first != "q" && i->first != "root" && i->first != "args") {
59 std::string escaped_value =
60 EscapeString(FilePathToUTF8(i->second), escape_shell, NULL);
61 cmdline.AppendSwitchASCII(i->first, escaped_value);
62 }
63 }
64
65 #if defined(OS_WIN)
66 return base::WideToUTF8(cmdline.GetCommandLineString());
67 #else
68 return cmdline.GetCommandLineString();
69 #endif
70 }
71
72 } // namespace
73
NinjaBuildWriter(const BuildSettings * build_settings,const std::vector<const Settings * > & all_settings,const std::vector<const Target * > & default_toolchain_targets,std::ostream & out,std::ostream & dep_out)74 NinjaBuildWriter::NinjaBuildWriter(
75 const BuildSettings* build_settings,
76 const std::vector<const Settings*>& all_settings,
77 const std::vector<const Target*>& default_toolchain_targets,
78 std::ostream& out,
79 std::ostream& dep_out)
80 : build_settings_(build_settings),
81 all_settings_(all_settings),
82 default_toolchain_targets_(default_toolchain_targets),
83 out_(out),
84 dep_out_(dep_out),
85 path_output_(build_settings->build_dir(), ESCAPE_NINJA),
86 helper_(build_settings) {
87 }
88
~NinjaBuildWriter()89 NinjaBuildWriter::~NinjaBuildWriter() {
90 }
91
Run()92 void NinjaBuildWriter::Run() {
93 WriteNinjaRules();
94 WriteSubninjas();
95 WritePhonyAndAllRules();
96 }
97
98 // static
RunAndWriteFile(const BuildSettings * build_settings,const std::vector<const Settings * > & all_settings,const std::vector<const Target * > & default_toolchain_targets)99 bool NinjaBuildWriter::RunAndWriteFile(
100 const BuildSettings* build_settings,
101 const std::vector<const Settings*>& all_settings,
102 const std::vector<const Target*>& default_toolchain_targets) {
103 ScopedTrace trace(TraceItem::TRACE_FILE_WRITE, "build.ninja");
104
105 base::FilePath ninja_file(build_settings->GetFullPath(
106 SourceFile(build_settings->build_dir().value() + "build.ninja")));
107 base::CreateDirectory(ninja_file.DirName());
108
109 std::ofstream file;
110 file.open(FilePathToUTF8(ninja_file).c_str(),
111 std::ios_base::out | std::ios_base::binary);
112 if (file.fail())
113 return false;
114
115 std::ofstream depfile;
116 depfile.open((FilePathToUTF8(ninja_file) + ".d").c_str(),
117 std::ios_base::out | std::ios_base::binary);
118 if (depfile.fail())
119 return false;
120
121 NinjaBuildWriter gen(build_settings, all_settings,
122 default_toolchain_targets, file, depfile);
123 gen.Run();
124 return true;
125 }
126
WriteNinjaRules()127 void NinjaBuildWriter::WriteNinjaRules() {
128 out_ << "rule gn\n";
129 out_ << " command = " << GetSelfInvocationCommand(build_settings_) << "\n";
130 out_ << " description = Regenerating ninja files\n\n";
131
132 // This rule will regenerate the ninja files when any input file has changed.
133 out_ << "build build.ninja: gn\n"
134 << " generator = 1\n"
135 << " depfile = build.ninja.d\n";
136
137 // Input build files. These go in the ".d" file. If we write them as
138 // dependencies in the .ninja file itself, ninja will expect the files to
139 // exist and will error if they don't. When files are listed in a depfile,
140 // missing files are ignored.
141 dep_out_ << "build.ninja:";
142 std::vector<base::FilePath> input_files;
143 g_scheduler->input_file_manager()->GetAllPhysicalInputFileNames(&input_files);
144 for (size_t i = 0; i < input_files.size(); i++)
145 dep_out_ << " " << FilePathToUTF8(input_files[i]);
146
147 // Other files read by the build.
148 std::vector<base::FilePath> other_files = g_scheduler->GetGenDependencies();
149 for (size_t i = 0; i < other_files.size(); i++)
150 dep_out_ << " " << FilePathToUTF8(other_files[i]);
151
152 out_ << std::endl;
153 }
154
WriteSubninjas()155 void NinjaBuildWriter::WriteSubninjas() {
156 for (size_t i = 0; i < all_settings_.size(); i++) {
157 out_ << "subninja ";
158 path_output_.WriteFile(out_,
159 helper_.GetNinjaFileForToolchain(all_settings_[i]));
160 out_ << std::endl;
161 }
162 out_ << std::endl;
163 }
164
WritePhonyAndAllRules()165 void NinjaBuildWriter::WritePhonyAndAllRules() {
166 std::string all_rules;
167
168 // Write phony rules for all uniquely-named targets in the default toolchain.
169 // Don't do other toolchains or we'll get naming conflicts, and if the name
170 // isn't unique, also skip it. The exception is for the toplevel targets
171 // which we also find.
172 std::map<std::string, int> small_name_count;
173 std::vector<const Target*> toplevel_targets;
174 for (size_t i = 0; i < default_toolchain_targets_.size(); i++) {
175 const Target* target = default_toolchain_targets_[i];
176 const Label& label = target->label();
177 small_name_count[label.name()]++;
178
179 // Look for targets with a name of the form
180 // dir = "//foo/", name = "foo"
181 // i.e. where the target name matches the top level directory. We will
182 // always write phony rules for these even if there is another target with
183 // the same short name.
184 const std::string& dir_string = label.dir().value();
185 if (dir_string.size() == label.name().size() + 3 && // Size matches.
186 dir_string[0] == '/' && dir_string[1] == '/' && // "//" at beginning.
187 dir_string[dir_string.size() - 1] == '/' && // "/" at end.
188 dir_string.compare(2, label.name().size(), label.name()) == 0)
189 toplevel_targets.push_back(target);
190 }
191
192 for (size_t i = 0; i < default_toolchain_targets_.size(); i++) {
193 const Target* target = default_toolchain_targets_[i];
194 const Label& label = target->label();
195 OutputFile target_file = helper_.GetTargetOutputFile(target);
196
197 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
198 std::string long_name = label.GetUserVisibleName(false);
199 base::TrimString(long_name, "/", &long_name);
200 WritePhonyRule(target, target_file, long_name);
201
202 // Write the directory name with no target name if they match
203 // (e.g. "//foo/bar:bar" -> "foo/bar").
204 if (FindLastDirComponent(label.dir()) == label.name()) {
205 std::string medium_name = DirectoryWithNoLastSlash(label.dir());
206 base::TrimString(medium_name, "/", &medium_name);
207 // That may have generated a name the same as the short name of the
208 // target which we already wrote.
209 if (medium_name != label.name())
210 WritePhonyRule(target, target_file, medium_name);
211 }
212
213 // Write short names for ones which are unique.
214 if (small_name_count[label.name()] == 1)
215 WritePhonyRule(target, target_file, label.name());
216
217 if (!all_rules.empty())
218 all_rules.append(" $\n ");
219 all_rules.append(target_file.value());
220 }
221
222 // Pick up phony rules for the toplevel targets with non-unique names (which
223 // would have been skipped in the above loop).
224 for (size_t i = 0; i < toplevel_targets.size(); i++) {
225 if (small_name_count[toplevel_targets[i]->label().name()] > 1) {
226 const Target* target = toplevel_targets[i];
227 WritePhonyRule(target, helper_.GetTargetOutputFile(target),
228 target->label().name());
229 }
230 }
231
232 if (!all_rules.empty()) {
233 out_ << "\nbuild all: phony " << all_rules << std::endl;
234 out_ << "default all" << std::endl;
235 }
236 }
237
WritePhonyRule(const Target * target,const OutputFile & target_file,const std::string & phony_name)238 void NinjaBuildWriter::WritePhonyRule(const Target* target,
239 const OutputFile& target_file,
240 const std::string& phony_name) {
241 if (target_file.value() == phony_name)
242 return; // No need for a phony rule.
243
244 EscapeOptions ninja_escape;
245 ninja_escape.mode = ESCAPE_NINJA;
246
247 // Escape for special chars Ninja will handle.
248 std::string escaped = EscapeString(phony_name, ninja_escape, NULL);
249
250 out_ << "build " << escaped << ": phony ";
251 path_output_.WriteFile(out_, target_file);
252 out_ << std::endl;
253 }
254