• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_target_writer.h"
6 
7 #include <sstream>
8 
9 #include "base/files/file_util.h"
10 #include "base/strings/string_util.h"
11 #include "gn/config_values_extractors.h"
12 #include "gn/err.h"
13 #include "gn/escape.h"
14 #include "gn/filesystem_utils.h"
15 #include "gn/general_tool.h"
16 #include "gn/ninja_action_target_writer.h"
17 #include "gn/ninja_binary_target_writer.h"
18 #include "gn/ninja_bundle_data_target_writer.h"
19 #include "gn/ninja_copy_target_writer.h"
20 #include "gn/ninja_create_bundle_target_writer.h"
21 #include "gn/ninja_generated_file_target_writer.h"
22 #include "gn/ninja_group_target_writer.h"
23 #include "gn/ninja_utils.h"
24 #include "gn/output_file.h"
25 #include "gn/scheduler.h"
26 #include "gn/string_utils.h"
27 #include "gn/substitution_writer.h"
28 #include "gn/target.h"
29 #include "gn/trace.h"
30 
NinjaTargetWriter(const Target * target,std::ostream & out)31 NinjaTargetWriter::NinjaTargetWriter(const Target* target, std::ostream& out)
32     : settings_(target->settings()),
33       target_(target),
34       out_(out),
35       path_output_(settings_->build_settings()->build_dir(),
36                    settings_->build_settings()->root_path_utf8(),
37                    ESCAPE_NINJA) {}
38 
39 NinjaTargetWriter::~NinjaTargetWriter() = default;
40 
41 // static
RunAndWriteFile(const Target * target)42 std::string NinjaTargetWriter::RunAndWriteFile(const Target* target) {
43   const Settings* settings = target->settings();
44 
45   ScopedTrace trace(TraceItem::TRACE_FILE_WRITE,
46                     target->label().GetUserVisibleName(false));
47   trace.SetToolchain(settings->toolchain_label());
48 
49   if (g_scheduler->verbose_logging())
50     g_scheduler->Log("Computing", target->label().GetUserVisibleName(true));
51 
52   // It's ridiculously faster to write to a string and then write that to
53   // disk in one operation than to use an fstream here.
54   std::stringstream rules;
55 
56   // Call out to the correct sub-type of writer. Binary targets need to be
57   // written to separate files for compiler flag scoping, but other target
58   // types can have their rules coalesced.
59   //
60   // In ninja, if a rule uses a variable (like $include_dirs) it will use
61   // the value set by indenting it under the build line or it takes the value
62   // from the end of the invoking scope (otherwise the current file). It does
63   // not copy the value from what it was when the build line was encountered.
64   // To avoid writing lots of duplicate rules for defines and cflags, etc. on
65   // each source file build line, we use separate .ninja files with the shared
66   // variables set at the top.
67   //
68   // Groups and actions don't use this type of flag, they make unique rules
69   // or write variables scoped under each build line. As a result, they don't
70   // need the separate files.
71   bool needs_file_write = false;
72   if (target->output_type() == Target::BUNDLE_DATA) {
73     NinjaBundleDataTargetWriter writer(target, rules);
74     writer.Run();
75   } else if (target->output_type() == Target::CREATE_BUNDLE) {
76     NinjaCreateBundleTargetWriter writer(target, rules);
77     writer.Run();
78   } else if (target->output_type() == Target::COPY_FILES) {
79     NinjaCopyTargetWriter writer(target, rules);
80     writer.Run();
81   } else if (target->output_type() == Target::ACTION ||
82              target->output_type() == Target::ACTION_FOREACH) {
83     NinjaActionTargetWriter writer(target, rules);
84     writer.Run();
85   } else if (target->output_type() == Target::GROUP) {
86     NinjaGroupTargetWriter writer(target, rules);
87     writer.Run();
88   } else if (target->output_type() == Target::GENERATED_FILE) {
89     NinjaGeneratedFileTargetWriter writer(target, rules);
90     writer.Run();
91   } else if (target->IsBinary()) {
92     needs_file_write = true;
93     NinjaBinaryTargetWriter writer(target, rules);
94     writer.Run();
95   } else {
96     CHECK(0) << "Output type of target not handled.";
97   }
98 
99   if (needs_file_write) {
100     // Write the ninja file.
101     SourceFile ninja_file = GetNinjaFileForTarget(target);
102     base::FilePath full_ninja_file =
103         settings->build_settings()->GetFullPath(ninja_file);
104     base::CreateDirectory(full_ninja_file.DirName());
105     WriteFileIfChanged(full_ninja_file, rules.str(), nullptr);
106 
107     EscapeOptions options;
108     options.mode = ESCAPE_NINJA;
109 
110     // Return the subninja command to load the rules file.
111     std::string result = "subninja ";
112     result.append(EscapeString(
113         OutputFile(target->settings()->build_settings(), ninja_file).value(),
114         options, nullptr));
115     result.push_back('\n');
116     return result;
117   }
118 
119   // No separate file required, just return the rules.
120   return rules.str();
121 }
122 
WriteEscapedSubstitution(const Substitution * type)123 void NinjaTargetWriter::WriteEscapedSubstitution(const Substitution* type) {
124   EscapeOptions opts;
125   opts.mode = ESCAPE_NINJA;
126 
127   out_ << type->ninja_name << " = ";
128   EscapeStringToStream(
129       out_, SubstitutionWriter::GetTargetSubstitution(target_, type), opts);
130   out_ << std::endl;
131 }
132 
WriteSharedVars(const SubstitutionBits & bits)133 void NinjaTargetWriter::WriteSharedVars(const SubstitutionBits& bits) {
134   bool written_anything = false;
135 
136   // Target label.
137   if (bits.used.count(&SubstitutionLabel)) {
138     WriteEscapedSubstitution(&SubstitutionLabel);
139     written_anything = true;
140   }
141 
142   // Target label name
143   if (bits.used.count(&SubstitutionLabelName)) {
144     WriteEscapedSubstitution(&SubstitutionLabelName);
145     written_anything = true;
146   }
147 
148   // Root gen dir.
149   if (bits.used.count(&SubstitutionRootGenDir)) {
150     WriteEscapedSubstitution(&SubstitutionRootGenDir);
151     written_anything = true;
152   }
153 
154   // Root out dir.
155   if (bits.used.count(&SubstitutionRootOutDir)) {
156     WriteEscapedSubstitution(&SubstitutionRootOutDir);
157     written_anything = true;
158   }
159 
160   // Target gen dir.
161   if (bits.used.count(&SubstitutionTargetGenDir)) {
162     WriteEscapedSubstitution(&SubstitutionTargetGenDir);
163     written_anything = true;
164   }
165 
166   // Target out dir.
167   if (bits.used.count(&SubstitutionTargetOutDir)) {
168     WriteEscapedSubstitution(&SubstitutionTargetOutDir);
169     written_anything = true;
170   }
171 
172   // Target output name.
173   if (bits.used.count(&SubstitutionTargetOutputName)) {
174     WriteEscapedSubstitution(&SubstitutionTargetOutputName);
175     written_anything = true;
176   }
177 
178   // If we wrote any vars, separate them from the rest of the file that follows
179   // with a blank line.
180   if (written_anything)
181     out_ << std::endl;
182 }
183 
WriteInputDepsStampAndGetDep(const std::vector<const Target * > & extra_hard_deps,size_t num_stamp_uses) const184 std::vector<OutputFile> NinjaTargetWriter::WriteInputDepsStampAndGetDep(
185     const std::vector<const Target*>& extra_hard_deps,
186     size_t num_stamp_uses) const {
187   CHECK(target_->toolchain()) << "Toolchain not set on target "
188                               << target_->label().GetUserVisibleName(true);
189 
190   // ----------
191   // Collect all input files that are input deps of this target. Knowing the
192   // number before writing allows us to either skip writing the input deps
193   // stamp or optimize it. Use pointers to avoid copies here.
194   std::vector<const SourceFile*> input_deps_sources;
195   input_deps_sources.reserve(32);
196 
197   // Actions get implicit dependencies on the script itself.
198   if (target_->output_type() == Target::ACTION ||
199       target_->output_type() == Target::ACTION_FOREACH)
200     input_deps_sources.push_back(&target_->action_values().script());
201 
202   // Input files are only considered for non-binary targets which use an
203   // implicit dependency instead. The implicit depedency in this case is
204   // handled separately by the binary target writer.
205   if (!target_->IsBinary()) {
206     for (ConfigValuesIterator iter(target_); !iter.done(); iter.Next()) {
207       for (const auto& input : iter.cur().inputs())
208         input_deps_sources.push_back(&input);
209     }
210   }
211 
212   // For an action (where we run a script only once) the sources are the same
213   // as the inputs. For action_foreach, the sources will be operated on
214   // separately so don't handle them here.
215   if (target_->output_type() == Target::ACTION) {
216     for (const auto& source : target_->sources())
217       input_deps_sources.push_back(&source);
218   }
219 
220   // ----------
221   // Collect all target input dependencies of this target as was done for the
222   // files above.
223   std::vector<const Target*> input_deps_targets;
224   input_deps_targets.reserve(32);
225 
226   // Hard dependencies that are direct or indirect dependencies.
227   // These are large (up to 100s), hence why we check other
228   const std::set<const Target*>& hard_deps(target_->recursive_hard_deps());
229   for (const Target* target : hard_deps)
230     input_deps_targets.push_back(target);
231 
232   // Extra hard dependencies passed in. These are usually empty or small, and
233   // we don't want to duplicate the explicit hard deps of the target.
234   for (const Target* target : extra_hard_deps) {
235     if (!hard_deps.count(target))
236       input_deps_targets.push_back(target);
237   }
238 
239   // Toolchain dependencies. These must be resolved before doing anything.
240   // This just writes all toolchain deps for simplicity. If we find that
241   // toolchains often have more than one dependency, we could consider writing
242   // a toolchain-specific stamp file and only include the stamp here.
243   // Note that these are usually empty/small.
244   const LabelTargetVector& toolchain_deps = target_->toolchain()->deps();
245   for (const auto& toolchain_dep : toolchain_deps) {
246     // This could theoretically duplicate dependencies already in the list,
247     // but it shouldn't happen in practice, is inconvenient to check for,
248     // and only results in harmless redundant dependencies listed.
249     input_deps_targets.push_back(toolchain_dep.ptr);
250   }
251 
252   // ---------
253   // Write the outputs.
254 
255   if (input_deps_sources.size() + input_deps_targets.size() == 0)
256     return std::vector<OutputFile>();  // No input dependencies.
257 
258   // If we're only generating one input dependency, return it directly instead
259   // of writing a stamp file for it.
260   if (input_deps_sources.size() == 1 && input_deps_targets.size() == 0)
261     return std::vector<OutputFile>{
262         OutputFile(settings_->build_settings(), *input_deps_sources[0])};
263   if (input_deps_sources.size() == 0 && input_deps_targets.size() == 1) {
264     const OutputFile& dep = input_deps_targets[0]->dependency_output_file();
265     DCHECK(!dep.value().empty());
266     return std::vector<OutputFile>{dep};
267   }
268 
269   std::vector<OutputFile> outs;
270   // File input deps.
271   for (const SourceFile* source : input_deps_sources)
272     outs.push_back(OutputFile(settings_->build_settings(), *source));
273   // Target input deps. Sort by label so the output is deterministic (otherwise
274   // some of the targets will have gone through std::sets which will have
275   // sorted them by pointer).
276   std::sort(
277       input_deps_targets.begin(), input_deps_targets.end(),
278       [](const Target* a, const Target* b) { return a->label() < b->label(); });
279   for (auto* dep : input_deps_targets) {
280     DCHECK(!dep->dependency_output_file().value().empty());
281     outs.push_back(dep->dependency_output_file());
282   }
283 
284   // If there are multiple inputs, but the stamp file would be referenced only
285   // once, don't write it but depend on the inputs directly.
286   if (num_stamp_uses == 1u)
287     return outs;
288 
289   // Make a stamp file.
290   OutputFile input_stamp_file =
291       GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
292   input_stamp_file.value().append(target_->label().name());
293   input_stamp_file.value().append(".inputdeps.stamp");
294 
295   out_ << "build ";
296   path_output_.WriteFile(out_, input_stamp_file);
297   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
298        << GeneralTool::kGeneralToolStamp;
299   path_output_.WriteFiles(out_, outs);
300 
301   out_ << "\n";
302   return std::vector<OutputFile>{input_stamp_file};
303 }
304 
WriteStampForTarget(const std::vector<OutputFile> & files,const std::vector<OutputFile> & order_only_deps)305 void NinjaTargetWriter::WriteStampForTarget(
306     const std::vector<OutputFile>& files,
307     const std::vector<OutputFile>& order_only_deps) {
308   const OutputFile& stamp_file = target_->dependency_output_file();
309 
310   // First validate that the target's dependency is a stamp file. Otherwise,
311   // we shouldn't have gotten here!
312   CHECK(base::EndsWith(stamp_file.value(), ".stamp",
313                        base::CompareCase::INSENSITIVE_ASCII))
314       << "Output should end in \".stamp\" for stamp file output. Instead got: "
315       << "\"" << stamp_file.value() << "\"";
316 
317   out_ << "build ";
318   path_output_.WriteFile(out_, stamp_file);
319 
320   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
321        << GeneralTool::kGeneralToolStamp;
322   path_output_.WriteFiles(out_, files);
323 
324   if (!order_only_deps.empty()) {
325     out_ << " ||";
326     path_output_.WriteFiles(out_, order_only_deps);
327   }
328   out_ << std::endl;
329 }
330