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.
5 #include "tools/gn/ninja_build_writer.h"
10 #include "base/command_line.h"
11 #include "base/files/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/err.h"
19 #include "tools/gn/escape.h"
20 #include "tools/gn/filesystem_utils.h"
21 #include "tools/gn/input_file_manager.h"
22 #include "tools/gn/ninja_utils.h"
23 #include "tools/gn/scheduler.h"
24 #include "tools/gn/switches.h"
25 #include "tools/gn/target.h"
26 #include "tools/gn/trace.h"
34 std::string
GetSelfInvocationCommand(const BuildSettings
* build_settings
) {
35 base::FilePath executable
;
36 PathService::Get(base::FILE_EXE
, &executable
);
38 base::CommandLine
cmdline(executable
.NormalizePathSeparatorsTo('/'));
39 cmdline
.AppendArg("gen");
40 cmdline
.AppendArg(build_settings
->build_dir().value());
41 cmdline
.AppendSwitchPath(std::string("--") + switches::kRoot
,
42 build_settings
->root_path());
43 // Successful automatic invocations shouldn't print output.
44 cmdline
.AppendSwitch(std::string("-") + switches::kQuiet
);
46 EscapeOptions escape_shell
;
47 escape_shell
.mode
= ESCAPE_NINJA_COMMAND
;
49 // The command line code quoting varies by platform. We have one string,
50 // possibly with spaces, that we want to quote. The Windows command line
51 // quotes again, so we don't want quoting. The Posix one doesn't.
52 escape_shell
.inhibit_quoting
= true;
55 const base::CommandLine
& our_cmdline
=
56 *base::CommandLine::ForCurrentProcess();
57 const base::CommandLine::SwitchMap
& switches
= our_cmdline
.GetSwitches();
58 for (base::CommandLine::SwitchMap::const_iterator i
= switches
.begin();
59 i
!= switches
.end(); ++i
) {
60 // Only write arguments we haven't already written. Always skip "args"
61 // since those will have been written to the file and will be used
62 // implicitly in the future. Keeping --args would mean changes to the file
64 if (i
->first
!= switches::kQuiet
&&
65 i
->first
!= switches::kRoot
&&
66 i
->first
!= switches::kArgs
) {
67 std::string escaped_value
=
68 EscapeString(FilePathToUTF8(i
->second
), escape_shell
, nullptr);
69 cmdline
.AppendSwitchASCII(i
->first
, escaped_value
);
74 return base::WideToUTF8(cmdline
.GetCommandLineString());
76 return cmdline
.GetCommandLineString();
81 NinjaBuildWriter::NinjaBuildWriter(
82 const BuildSettings
* build_settings
,
83 const std::vector
<const Settings
*>& all_settings
,
84 const Toolchain
* default_toolchain
,
85 const std::vector
<const Target
*>& default_toolchain_targets
,
87 std::ostream
& dep_out
)
88 : build_settings_(build_settings
),
89 all_settings_(all_settings
),
90 default_toolchain_(default_toolchain
),
91 default_toolchain_targets_(default_toolchain_targets
),
94 path_output_(build_settings
->build_dir(),
95 build_settings
->root_path_utf8(), ESCAPE_NINJA
) {
98 NinjaBuildWriter::~NinjaBuildWriter() {
101 bool NinjaBuildWriter::Run(Err
* err
) {
105 return WritePhonyAndAllRules(err
);
109 bool NinjaBuildWriter::RunAndWriteFile(
110 const BuildSettings
* build_settings
,
111 const std::vector
<const Settings
*>& all_settings
,
112 const Toolchain
* default_toolchain
,
113 const std::vector
<const Target
*>& default_toolchain_targets
,
115 ScopedTrace
trace(TraceItem::TRACE_FILE_WRITE
, "build.ninja");
117 base::FilePath
ninja_file(build_settings
->GetFullPath(
118 SourceFile(build_settings
->build_dir().value() + "build.ninja")));
119 base::CreateDirectory(ninja_file
.DirName());
122 file
.open(FilePathToUTF8(ninja_file
).c_str(),
123 std::ios_base::out
| std::ios_base::binary
);
125 *err
= Err(Location(), "Couldn't open build.ninja for writing");
129 std::ofstream depfile
;
130 depfile
.open((FilePathToUTF8(ninja_file
) + ".d").c_str(),
131 std::ios_base::out
| std::ios_base::binary
);
132 if (depfile
.fail()) {
133 *err
= Err(Location(), "Couldn't open depfile for writing");
137 NinjaBuildWriter
gen(build_settings
, all_settings
, default_toolchain
,
138 default_toolchain_targets
, file
, depfile
);
142 void NinjaBuildWriter::WriteNinjaRules() {
144 out_
<< " command = " << GetSelfInvocationCommand(build_settings_
) << "\n";
145 out_
<< " description = Regenerating ninja files\n";
146 out_
<< " restat = 1\n\n";
148 // This rule will regenerate the ninja files when any input file has changed,
150 out_
<< "build build.ninja";
152 // Other files read by the build.
153 EscapeOptions path_escaping
;
154 path_escaping
.mode
= ESCAPE_NINJA_COMMAND
;
155 std::vector
<SourceFile
> written_files
= g_scheduler
->GetWrittenFiles();
156 for (const auto& written_file
: written_files
)
157 out_
<< " " << EscapeString(RebasePath(written_file
.value(),
158 build_settings_
->build_dir(), build_settings_
->root_path_utf8()),
159 path_escaping
, nullptr);
162 << " generator = 1\n"
163 << " depfile = build.ninja.d\n";
165 // Input build files. These go in the ".d" file. If we write them as
166 // dependencies in the .ninja file itself, ninja will expect the files to
167 // exist and will error if they don't. When files are listed in a depfile,
168 // missing files are ignored.
169 dep_out_
<< "build.ninja:";
170 std::vector
<base::FilePath
> input_files
;
171 g_scheduler
->input_file_manager()->GetAllPhysicalInputFileNames(&input_files
);
172 for (const auto& input_file
: input_files
)
173 dep_out_
<< " " << FilePathToUTF8(input_file
);
175 // Other files read by the build.
176 std::vector
<base::FilePath
> other_files
= g_scheduler
->GetGenDependencies();
177 for (const auto& other_file
: other_files
)
178 dep_out_
<< " " << FilePathToUTF8(other_file
);
183 void NinjaBuildWriter::WriteLinkPool() {
184 out_
<< "pool link_pool\n"
185 << " depth = " << default_toolchain_
->concurrent_links() << std::endl
189 void NinjaBuildWriter::WriteSubninjas() {
190 for (const auto& elem
: all_settings_
) {
192 path_output_
.WriteFile(out_
, GetNinjaFileForToolchain(elem
));
198 bool NinjaBuildWriter::WritePhonyAndAllRules(Err
* err
) {
199 std::string all_rules
;
201 // Track rules as we generate them so we don't accidentally write a phony
202 // rule that collides with something else.
203 // GN internally generates an "all" target, so don't duplicate it.
204 std::set
<std::string
> written_rules
;
205 written_rules
.insert("all");
207 // Write phony rules for all uniquely-named targets in the default toolchain.
208 // Don't do other toolchains or we'll get naming conflicts, and if the name
209 // isn't unique, also skip it. The exception is for the toplevel targets
210 // which we also find.
211 std::map
<std::string
, int> small_name_count
;
212 std::map
<std::string
, int> exe_count
;
213 std::vector
<const Target
*> toplevel_targets
;
214 base::hash_set
<std::string
> target_files
;
215 for (const auto& target
: default_toolchain_targets_
) {
216 const Label
& label
= target
->label();
217 small_name_count
[label
.name()]++;
219 // Look for targets with a name of the form
220 // dir = "//foo/", name = "foo"
221 // i.e. where the target name matches the top level directory. We will
222 // always write phony rules for these even if there is another target with
223 // the same short name.
224 const std::string
& dir_string
= label
.dir().value();
225 if (dir_string
.size() == label
.name().size() + 3 && // Size matches.
226 dir_string
[0] == '/' && dir_string
[1] == '/' && // "//" at beginning.
227 dir_string
[dir_string
.size() - 1] == '/' && // "/" at end.
228 dir_string
.compare(2, label
.name().size(), label
.name()) == 0)
229 toplevel_targets
.push_back(target
);
231 // Look for executables; later we will generate phony rules for them
232 // even if there are non-executable targets with the same name.
233 if (target
->output_type() == Target::EXECUTABLE
)
234 exe_count
[label
.name()]++;
236 // Add the files to the list of generated targets so we don't write phony
237 // rules that collide.
238 std::string
target_file(target
->dependency_output_file().value());
239 NormalizePath(&target_file
);
240 written_rules
.insert(target_file
);
243 for (const auto& target
: default_toolchain_targets_
) {
244 const Label
& label
= target
->label();
245 OutputFile
target_file(target
->dependency_output_file());
246 // The output files may have leading "./" so normalize those away.
247 NormalizePath(&target_file
.value());
248 if (!target_files
.insert(target_file
.value()).second
) {
249 *err
= Err(Location(), "Duplicate rules for " + target_file
.value());
253 // Write the long name "foo/bar:baz" for the target "//foo/bar:baz".
254 std::string long_name
= label
.GetUserVisibleName(false);
255 base::TrimString(long_name
, "/", &long_name
);
256 WritePhonyRule(target
, target_file
, long_name
, &written_rules
);
258 // Write the directory name with no target name if they match
259 // (e.g. "//foo/bar:bar" -> "foo/bar").
260 if (FindLastDirComponent(label
.dir()) == label
.name()) {
261 std::string medium_name
= DirectoryWithNoLastSlash(label
.dir());
262 base::TrimString(medium_name
, "/", &medium_name
);
263 // That may have generated a name the same as the short name of the
264 // target which we already wrote.
265 if (medium_name
!= label
.name())
266 WritePhonyRule(target
, target_file
, medium_name
, &written_rules
);
269 // Write short names for ones which are either completely unique or there
270 // at least only one of them in the default toolchain that is an exe.
271 if (small_name_count
[label
.name()] == 1 ||
272 (target
->output_type() == Target::EXECUTABLE
&&
273 exe_count
[label
.name()] == 1)) {
274 WritePhonyRule(target
, target_file
, label
.name(), &written_rules
);
277 if (!all_rules
.empty())
278 all_rules
.append(" $\n ");
279 all_rules
.append(target_file
.value());
282 // Pick up phony rules for the toplevel targets with non-unique names (which
283 // would have been skipped in the above loop).
284 for (const auto& toplevel_target
: toplevel_targets
) {
285 if (small_name_count
[toplevel_target
->label().name()] > 1) {
286 WritePhonyRule(toplevel_target
, toplevel_target
->dependency_output_file(),
287 toplevel_target
->label().name(), &written_rules
);
291 // Figure out if the BUILD file wants to declare a custom "default"
292 // target (rather than building 'all' by default). By convention
293 // we use group("default") but it doesn't have to be a group.
294 bool default_target_exists
= false;
295 for (const auto& target
: default_toolchain_targets_
) {
296 const Label
& label
= target
->label();
297 if (label
.dir().value() == "//" && label
.name() == "default")
298 default_target_exists
= true;
301 if (!all_rules
.empty()) {
302 out_
<< "\nbuild all: phony " << all_rules
<< std::endl
;
305 if (default_target_exists
) {
306 out_
<< "default default" << std::endl
;
307 } else if (!all_rules
.empty()) {
308 out_
<< "default all" << std::endl
;
314 void NinjaBuildWriter::WritePhonyRule(const Target
* target
,
315 const OutputFile
& target_file
,
316 const std::string
& phony_name
,
317 std::set
<std::string
>* written_rules
) {
318 if (target_file
.value() == phony_name
)
319 return; // No need for a phony rule.
321 if (written_rules
->find(phony_name
) != written_rules
->end())
322 return; // Already exists.
323 written_rules
->insert(phony_name
);
325 EscapeOptions ninja_escape
;
326 ninja_escape
.mode
= ESCAPE_NINJA
;
328 // Escape for special chars Ninja will handle.
329 std::string escaped
= EscapeString(phony_name
, ninja_escape
, nullptr);
331 out_
<< "build " << escaped
<< ": phony ";
332 path_output_
.WriteFile(out_
, target_file
);