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 "gn/ninja_binary_target_writer.h"
6
7 #include <sstream>
8
9 #include "base/strings/string_util.h"
10 #include "gn/config_values_extractors.h"
11 #include "gn/deps_iterator.h"
12 #include "gn/filesystem_utils.h"
13 #include "gn/general_tool.h"
14 #include "gn/ninja_c_binary_target_writer.h"
15 #include "gn/ninja_rust_binary_target_writer.h"
16 #include "gn/ninja_target_command_util.h"
17 #include "gn/ninja_utils.h"
18 #include "gn/settings.h"
19 #include "gn/string_utils.h"
20 #include "gn/substitution_writer.h"
21 #include "gn/target.h"
22 #include "gn/variables.h"
23
24 namespace {
25
26 // Returns the proper escape options for writing compiler and linker flags.
GetFlagOptions()27 EscapeOptions GetFlagOptions() {
28 EscapeOptions opts;
29 opts.mode = ESCAPE_NINJA_COMMAND;
30 return opts;
31 }
32
33 } // namespace
34
NinjaBinaryTargetWriter(const Target * target,std::ostream & out)35 NinjaBinaryTargetWriter::NinjaBinaryTargetWriter(const Target* target,
36 std::ostream& out)
37 : NinjaTargetWriter(target, out),
38 rule_prefix_(GetNinjaRulePrefixForToolchain(settings_)) {}
39
40 NinjaBinaryTargetWriter::~NinjaBinaryTargetWriter() = default;
41
Run()42 void NinjaBinaryTargetWriter::Run() {
43 if (target_->source_types_used().RustSourceUsed()) {
44 NinjaRustBinaryTargetWriter writer(target_, out_);
45 writer.Run();
46 return;
47 }
48
49 NinjaCBinaryTargetWriter writer(target_, out_);
50 writer.Run();
51 }
52
WriteInputsStampAndGetDep(size_t num_stamp_uses) const53 std::vector<OutputFile> NinjaBinaryTargetWriter::WriteInputsStampAndGetDep(
54 size_t num_stamp_uses) const {
55 CHECK(target_->toolchain()) << "Toolchain not set on target "
56 << target_->label().GetUserVisibleName(true);
57
58 UniqueVector<const SourceFile*> inputs;
59 for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
60 for (const auto& input : iter.cur().inputs()) {
61 inputs.push_back(&input);
62 }
63 }
64
65 if (inputs.size() == 0)
66 return std::vector<OutputFile>(); // No inputs
67
68 // If we only have one input, return it directly instead of writing a stamp
69 // file for it.
70 if (inputs.size() == 1) {
71 return std::vector<OutputFile>{
72 OutputFile(settings_->build_settings(), *inputs[0])};
73 }
74
75 std::vector<OutputFile> outs;
76 for (const SourceFile* source : inputs)
77 outs.push_back(OutputFile(settings_->build_settings(), *source));
78
79 // If there are multiple inputs, but the stamp file would be referenced only
80 // once, don't write it but depend on the inputs directly.
81 if (num_stamp_uses == 1u)
82 return outs;
83
84 // Make a stamp file.
85 OutputFile stamp_file =
86 GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
87 stamp_file.value().append(target_->label().name());
88 stamp_file.value().append(".inputs.stamp");
89
90 out_ << "build ";
91 path_output_.WriteFile(out_, stamp_file);
92 out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
93 << GeneralTool::kGeneralToolStamp;
94
95 // File inputs.
96 for (const auto* input : inputs) {
97 out_ << " ";
98 path_output_.WriteFile(out_, *input);
99 }
100
101 out_ << std::endl;
102 return {stamp_file};
103 }
104
WriteSourceSetStamp(const std::vector<OutputFile> & object_files)105 void NinjaBinaryTargetWriter::WriteSourceSetStamp(
106 const std::vector<OutputFile>& object_files) {
107 // The stamp rule for source sets is generally not used, since targets that
108 // depend on this will reference the object files directly. However, writing
109 // this rule allows the user to type the name of the target and get a build
110 // which can be convenient for development.
111 ClassifiedDeps classified_deps = GetClassifiedDeps();
112
113 // The classifier should never put extra object files in a source sets: any
114 // source sets that we depend on should appear in our non-linkable deps
115 // instead.
116 DCHECK(classified_deps.extra_object_files.empty());
117
118 std::vector<OutputFile> order_only_deps;
119 for (auto* dep : classified_deps.non_linkable_deps)
120 order_only_deps.push_back(dep->dependency_output_file());
121
122 WriteStampForTarget(object_files, order_only_deps);
123 }
124
125 NinjaBinaryTargetWriter::ClassifiedDeps
GetClassifiedDeps() const126 NinjaBinaryTargetWriter::GetClassifiedDeps() const {
127 ClassifiedDeps classified_deps;
128
129 // Normal public/private deps.
130 for (const auto& pair : target_->GetDeps(Target::DEPS_LINKED)) {
131 ClassifyDependency(pair.ptr, &classified_deps);
132 }
133
134 // Inherited libraries.
135 for (auto* inherited_target : target_->inherited_libraries().GetOrdered()) {
136 ClassifyDependency(inherited_target, &classified_deps);
137 }
138
139 // Data deps.
140 for (const auto& data_dep_pair : target_->data_deps())
141 classified_deps.non_linkable_deps.push_back(data_dep_pair.ptr);
142
143 return classified_deps;
144 }
145
ClassifyDependency(const Target * dep,ClassifiedDeps * classified_deps) const146 void NinjaBinaryTargetWriter::ClassifyDependency(
147 const Target* dep,
148 ClassifiedDeps* classified_deps) const {
149 // Only the following types of outputs have libraries linked into them:
150 // EXECUTABLE
151 // SHARED_LIBRARY
152 // _complete_ STATIC_LIBRARY
153 //
154 // Child deps of intermediate static libraries get pushed up the
155 // dependency tree until one of these is reached, and source sets
156 // don't link at all.
157 bool can_link_libs = target_->IsFinal();
158
159 if (can_link_libs && dep->builds_swift_module())
160 classified_deps->swiftmodule_deps.push_back(dep);
161
162 if (target_->source_types_used().RustSourceUsed() &&
163 (target_->output_type() == Target::RUST_LIBRARY ||
164 target_->output_type() == Target::STATIC_LIBRARY) &&
165 dep->IsLinkable()) {
166 // Rust libraries and static libraries aren't final, but need to have the
167 // link lines of all transitive deps specified.
168 classified_deps->linkable_deps.push_back(dep);
169 } else if (dep->output_type() == Target::SOURCE_SET ||
170 // If a complete static library depends on an incomplete static
171 // library, manually link in the object files of the dependent
172 // library as if it were a source set. This avoids problems with
173 // braindead tools such as ar which don't properly link dependent
174 // static libraries.
175 (target_->complete_static_lib() &&
176 (dep->output_type() == Target::STATIC_LIBRARY &&
177 !dep->complete_static_lib()))) {
178 // Source sets have their object files linked into final targets
179 // (shared libraries, executables, loadable modules, and complete static
180 // libraries). Intermediate static libraries and other source sets
181 // just forward the dependency, otherwise the files in the source
182 // set can easily get linked more than once which will cause
183 // multiple definition errors.
184 if (can_link_libs)
185 AddSourceSetFiles(dep, &classified_deps->extra_object_files);
186
187 // Add the source set itself as a non-linkable dependency on the current
188 // target. This will make sure that anything the source set's stamp file
189 // depends on (like data deps) are also built before the current target
190 // can be complete. Otherwise, these will be skipped since this target
191 // will depend only on the source set's object files.
192 classified_deps->non_linkable_deps.push_back(dep);
193 } else if (target_->complete_static_lib() && dep->IsFinal()) {
194 classified_deps->non_linkable_deps.push_back(dep);
195 } else if (can_link_libs && dep->IsLinkable()) {
196 classified_deps->linkable_deps.push_back(dep);
197 } else if (dep->output_type() == Target::CREATE_BUNDLE &&
198 dep->bundle_data().is_framework()) {
199 classified_deps->framework_deps.push_back(dep);
200 } else {
201 classified_deps->non_linkable_deps.push_back(dep);
202 }
203 }
204
AddSourceSetFiles(const Target * source_set,UniqueVector<OutputFile> * obj_files) const205 void NinjaBinaryTargetWriter::AddSourceSetFiles(
206 const Target* source_set,
207 UniqueVector<OutputFile>* obj_files) const {
208 std::vector<OutputFile> tool_outputs; // Prevent allocation in loop.
209
210 // Compute object files for all sources. Only link the first output from
211 // the tool if there are more than one.
212 for (const auto& source : source_set->sources()) {
213 const char* tool_name = Tool::kToolNone;
214 if (source_set->GetOutputFilesForSource(source, &tool_name, &tool_outputs))
215 obj_files->push_back(tool_outputs[0]);
216 }
217
218 // Swift files may generate one object file per module or one per source file
219 // depending on how the compiler is invoked (whole module optimization).
220 if (source_set->source_types_used().SwiftSourceUsed()) {
221 const Tool* tool = source_set->toolchain()->GetToolForSourceTypeAsC(
222 SourceFile::SOURCE_SWIFT);
223
224 std::vector<OutputFile> outputs;
225 SubstitutionWriter::ApplyListToLinkerAsOutputFile(
226 source_set, tool, tool->outputs(), &outputs);
227
228 for (const OutputFile& output : outputs) {
229 SourceFile output_as_source =
230 output.AsSourceFile(source_set->settings()->build_settings());
231 if (output_as_source.IsObjectType()) {
232 obj_files->push_back(output);
233 }
234 }
235 }
236
237 // Add MSVC precompiled header object files. GCC .gch files are not object
238 // files so they are omitted.
239 if (source_set->config_values().has_precompiled_headers()) {
240 if (source_set->source_types_used().Get(SourceFile::SOURCE_C)) {
241 const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCc);
242 if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
243 GetPCHOutputFiles(source_set, CTool::kCToolCc, &tool_outputs);
244 obj_files->Append(tool_outputs.begin(), tool_outputs.end());
245 }
246 }
247 if (source_set->source_types_used().Get(SourceFile::SOURCE_CPP)) {
248 const CTool* tool = source_set->toolchain()->GetToolAsC(CTool::kCToolCxx);
249 if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
250 GetPCHOutputFiles(source_set, CTool::kCToolCxx, &tool_outputs);
251 obj_files->Append(tool_outputs.begin(), tool_outputs.end());
252 }
253 }
254 if (source_set->source_types_used().Get(SourceFile::SOURCE_M)) {
255 const CTool* tool =
256 source_set->toolchain()->GetToolAsC(CTool::kCToolObjC);
257 if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
258 GetPCHOutputFiles(source_set, CTool::kCToolObjC, &tool_outputs);
259 obj_files->Append(tool_outputs.begin(), tool_outputs.end());
260 }
261 }
262 if (source_set->source_types_used().Get(SourceFile::SOURCE_MM)) {
263 const CTool* tool =
264 source_set->toolchain()->GetToolAsC(CTool::kCToolObjCxx);
265 if (tool && tool->precompiled_header_type() == CTool::PCH_MSVC) {
266 GetPCHOutputFiles(source_set, CTool::kCToolObjCxx, &tool_outputs);
267 obj_files->Append(tool_outputs.begin(), tool_outputs.end());
268 }
269 }
270 }
271 }
272
WriteCompilerBuildLine(const std::vector<SourceFile> & sources,const std::vector<OutputFile> & extra_deps,const std::vector<OutputFile> & order_only_deps,const char * tool_name,const std::vector<OutputFile> & outputs,bool can_write_source_info)273 void NinjaBinaryTargetWriter::WriteCompilerBuildLine(
274 const std::vector<SourceFile>& sources,
275 const std::vector<OutputFile>& extra_deps,
276 const std::vector<OutputFile>& order_only_deps,
277 const char* tool_name,
278 const std::vector<OutputFile>& outputs,
279 bool can_write_source_info) {
280 out_ << "build";
281 path_output_.WriteFiles(out_, outputs);
282
283 out_ << ": " << rule_prefix_ << tool_name;
284 path_output_.WriteFiles(out_, sources);
285
286 if (!extra_deps.empty()) {
287 out_ << " |";
288 path_output_.WriteFiles(out_, extra_deps);
289 }
290
291 if (!order_only_deps.empty()) {
292 out_ << " ||";
293 path_output_.WriteFiles(out_, order_only_deps);
294 }
295 out_ << std::endl;
296
297 if (!sources.empty() && can_write_source_info) {
298 out_ << " "
299 << "source_file_part = " << sources[0].GetName();
300 out_ << std::endl;
301 out_ << " "
302 << "source_name_part = "
303 << FindFilenameNoExtension(&sources[0].value());
304 out_ << std::endl;
305 }
306 }
307
WriteCustomLinkerFlags(std::ostream & out,const Tool * tool)308 void NinjaBinaryTargetWriter::WriteCustomLinkerFlags(
309 std::ostream& out,
310 const Tool* tool) {
311
312 if (tool->AsC() || (tool->AsRust() && tool->AsRust()->MayLink())) {
313 // First the ldflags from the target and its config.
314 RecursiveTargetConfigStringsToStream(kRecursiveWriterKeepDuplicates,
315 target_, &ConfigValues::ldflags,
316 GetFlagOptions(), out);
317 }
318 }
319
WriteLibrarySearchPath(std::ostream & out,const Tool * tool)320 void NinjaBinaryTargetWriter::WriteLibrarySearchPath(
321 std::ostream& out,
322 const Tool* tool) {
323 // Write library search paths that have been recursively pushed
324 // through the dependency tree.
325 const UniqueVector<SourceDir>& all_lib_dirs = target_->all_lib_dirs();
326 if (!all_lib_dirs.empty()) {
327 // Since we're passing these on the command line to the linker and not
328 // to Ninja, we need to do shell escaping.
329 PathOutput lib_path_output(path_output_.current_dir(),
330 settings_->build_settings()->root_path_utf8(),
331 ESCAPE_NINJA_COMMAND);
332 for (size_t i = 0; i < all_lib_dirs.size(); i++) {
333 out << " " << tool->lib_dir_switch();
334 lib_path_output.WriteDir(out, all_lib_dirs[i],
335 PathOutput::DIR_NO_LAST_SLASH);
336 }
337 }
338
339 const auto& all_framework_dirs = target_->all_framework_dirs();
340 if (!all_framework_dirs.empty()) {
341 // Since we're passing these on the command line to the linker and not
342 // to Ninja, we need to do shell escaping.
343 PathOutput framework_path_output(
344 path_output_.current_dir(),
345 settings_->build_settings()->root_path_utf8(), ESCAPE_NINJA_COMMAND);
346 for (size_t i = 0; i < all_framework_dirs.size(); i++) {
347 out << " " << tool->framework_dir_switch();
348 framework_path_output.WriteDir(out, all_framework_dirs[i],
349 PathOutput::DIR_NO_LAST_SLASH);
350 }
351 }
352 }
353
WriteLinkerFlags(std::ostream & out,const Tool * tool,const SourceFile * optional_def_file)354 void NinjaBinaryTargetWriter::WriteLinkerFlags(
355 std::ostream& out,
356 const Tool* tool,
357 const SourceFile* optional_def_file) {
358 // First any ldflags
359 WriteCustomLinkerFlags(out, tool);
360 // Then the library search path
361 WriteLibrarySearchPath(out, tool);
362
363 if (optional_def_file) {
364 out_ << " /DEF:";
365 path_output_.WriteFile(out, *optional_def_file);
366 }
367 }
368
WriteLibs(std::ostream & out,const Tool * tool)369 void NinjaBinaryTargetWriter::WriteLibs(std::ostream& out, const Tool* tool) {
370 // Libraries that have been recursively pushed through the dependency tree.
371 // Since we're passing these on the command line to the linker and not
372 // to Ninja, we need to do shell escaping.
373 PathOutput lib_path_output(
374 path_output_.current_dir(), settings_->build_settings()->root_path_utf8(),
375 ESCAPE_NINJA_COMMAND);
376 EscapeOptions lib_escape_opts;
377 lib_escape_opts.mode = ESCAPE_NINJA_COMMAND;
378 const UniqueVector<LibFile>& all_libs = target_->all_libs();
379 for (size_t i = 0; i < all_libs.size(); i++) {
380 const LibFile& lib_file = all_libs[i];
381 const std::string& lib_value = lib_file.value();
382 if (lib_file.is_source_file()) {
383 out << " " << tool->linker_arg();
384 lib_path_output.WriteFile(out, lib_file.source_file());
385 } else {
386 out << " " << tool->lib_switch();
387 EscapeStringToStream(out, lib_value, lib_escape_opts);
388 }
389 }
390 }
391
WriteFrameworks(std::ostream & out,const Tool * tool)392 void NinjaBinaryTargetWriter::WriteFrameworks(std::ostream& out,
393 const Tool* tool) {
394 // Frameworks that have been recursively pushed through the dependency tree.
395 FrameworksWriter writer(tool->framework_switch());
396 const auto& all_frameworks = target_->all_frameworks();
397 for (size_t i = 0; i < all_frameworks.size(); i++) {
398 writer(all_frameworks[i], out);
399 }
400
401 FrameworksWriter weak_writer(tool->weak_framework_switch());
402 const auto& all_weak_frameworks = target_->all_weak_frameworks();
403 for (size_t i = 0; i < all_weak_frameworks.size(); i++) {
404 weak_writer(all_weak_frameworks[i], out);
405 }
406 }
407
WriteSwiftModules(std::ostream & out,const Tool * tool,const std::vector<OutputFile> & swiftmodules)408 void NinjaBinaryTargetWriter::WriteSwiftModules(
409 std::ostream& out,
410 const Tool* tool,
411 const std::vector<OutputFile>& swiftmodules) {
412 // Since we're passing these on the command line to the linker and not
413 // to Ninja, we need to do shell escaping.
414 PathOutput swiftmodule_path_output(
415 path_output_.current_dir(), settings_->build_settings()->root_path_utf8(),
416 ESCAPE_NINJA_COMMAND);
417
418 for (const OutputFile& swiftmodule : swiftmodules) {
419 out << " " << tool->swiftmodule_switch();
420 swiftmodule_path_output.WriteFile(out, swiftmodule);
421 }
422 }
423