• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2016 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_create_bundle_target_writer.h"
6 
7 #include <iterator>
8 
9 #include "base/strings/string_util.h"
10 #include "gn/filesystem_utils.h"
11 #include "gn/general_tool.h"
12 #include "gn/ninja_utils.h"
13 #include "gn/output_file.h"
14 #include "gn/scheduler.h"
15 #include "gn/substitution_writer.h"
16 #include "gn/target.h"
17 #include "gn/toolchain.h"
18 
19 namespace {
20 
TargetRequireAssetCatalogCompilation(const Target * target)21 bool TargetRequireAssetCatalogCompilation(const Target* target) {
22   return !target->bundle_data().assets_catalog_sources().empty() ||
23          !target->bundle_data().partial_info_plist().is_null();
24 }
25 
FailWithMissingToolError(const char * tool_name,const Target * target)26 void FailWithMissingToolError(const char* tool_name, const Target* target) {
27   g_scheduler->FailWithError(
28       Err(nullptr, std::string(tool_name) + " tool not defined",
29           "The toolchain " +
30               target->toolchain()->label().GetUserVisibleName(false) +
31               "\n"
32               "used by target " +
33               target->label().GetUserVisibleName(false) +
34               "\n"
35               "doesn't define a \"" +
36               tool_name + "\" tool."));
37 }
38 
EnsureAllToolsAvailable(const Target * target)39 bool EnsureAllToolsAvailable(const Target* target) {
40   const char* kRequiredTools[] = {
41       GeneralTool::kGeneralToolCopyBundleData,
42       GeneralTool::kGeneralToolStamp,
43   };
44 
45   for (size_t i = 0; i < std::size(kRequiredTools); ++i) {
46     if (!target->toolchain()->GetTool(kRequiredTools[i])) {
47       FailWithMissingToolError(kRequiredTools[i], target);
48       return false;
49     }
50   }
51 
52   // The compile_xcassets tool is only required if the target has asset
53   // catalog resources to compile.
54   if (TargetRequireAssetCatalogCompilation(target)) {
55     if (!target->toolchain()->GetTool(
56             GeneralTool::kGeneralToolCompileXCAssets)) {
57       FailWithMissingToolError(GeneralTool::kGeneralToolCompileXCAssets,
58                                target);
59       return false;
60     }
61   }
62 
63   return true;
64 }
65 
66 }  // namespace
67 
NinjaCreateBundleTargetWriter(const Target * target,std::ostream & out)68 NinjaCreateBundleTargetWriter::NinjaCreateBundleTargetWriter(
69     const Target* target,
70     std::ostream& out)
71     : NinjaTargetWriter(target, out) {}
72 
73 NinjaCreateBundleTargetWriter::~NinjaCreateBundleTargetWriter() = default;
74 
Run()75 void NinjaCreateBundleTargetWriter::Run() {
76   if (!EnsureAllToolsAvailable(target_))
77     return;
78 
79   // Stamp users are CopyBundleData, CompileAssetsCatalog, PostProcessing and
80   // StampForTarget.
81   size_t num_stamp_uses = 4;
82   std::vector<OutputFile> order_only_deps = WriteInputDepsStampAndGetDep(
83       std::vector<const Target*>(), num_stamp_uses);
84 
85   std::string post_processing_rule_name = WritePostProcessingRuleDefinition();
86 
87   std::vector<OutputFile> output_files;
88   WriteCopyBundleDataSteps(order_only_deps, &output_files);
89   WriteCompileAssetsCatalogStep(order_only_deps, &output_files);
90   WritePostProcessingStep(post_processing_rule_name, order_only_deps,
91                           &output_files);
92 
93   for (const Target* data_dep : resolved().GetDataDeps(target_))
94     order_only_deps.push_back(data_dep->dependency_output_file());
95   WriteStampForTarget(output_files, order_only_deps);
96 
97   // Write a phony target for the outer bundle directory. This allows other
98   // targets to treat the entire bundle as a single unit, even though it is
99   // a directory, so that it can be depended upon as a discrete build edge.
100   out_ << "build ";
101 
102   WriteOutput(
103       OutputFile(settings_->build_settings(),
104                  target_->bundle_data().GetBundleRootDirOutput(settings_)));
105 
106   out_ << ": phony " << target_->dependency_output_file().value();
107   out_ << std::endl;
108 }
109 
WritePostProcessingRuleDefinition()110 std::string NinjaCreateBundleTargetWriter::WritePostProcessingRuleDefinition() {
111   if (target_->bundle_data().post_processing_script().is_null())
112     return std::string();
113 
114   std::string target_label = target_->label().GetUserVisibleName(true);
115   std::string custom_rule_name(target_label);
116   base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
117   custom_rule_name.append("_post_processing_rule");
118 
119   out_ << "rule " << custom_rule_name << std::endl;
120   out_ << "  command = ";
121   path_output_.WriteFile(out_, settings_->build_settings()->python_path());
122   out_ << " ";
123   path_output_.WriteFile(out_, target_->bundle_data().post_processing_script());
124 
125   const SubstitutionList& args = target_->bundle_data().post_processing_args();
126   EscapeOptions args_escape_options;
127   args_escape_options.mode = ESCAPE_NINJA_COMMAND;
128 
129   for (const auto& arg : args.list()) {
130     out_ << " ";
131     SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_);
132   }
133   out_ << std::endl;
134   out_ << "  description = POST PROCESSING " << target_label << std::endl;
135   out_ << "  restat = 1" << std::endl;
136   out_ << std::endl;
137 
138   return custom_rule_name;
139 }
140 
WriteCopyBundleDataSteps(const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)141 void NinjaCreateBundleTargetWriter::WriteCopyBundleDataSteps(
142     const std::vector<OutputFile>& order_only_deps,
143     std::vector<OutputFile>* output_files) {
144   for (const BundleFileRule& file_rule : target_->bundle_data().file_rules())
145     WriteCopyBundleFileRuleSteps(file_rule, order_only_deps, output_files);
146 }
147 
WriteCopyBundleFileRuleSteps(const BundleFileRule & file_rule,const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)148 void NinjaCreateBundleTargetWriter::WriteCopyBundleFileRuleSteps(
149     const BundleFileRule& file_rule,
150     const std::vector<OutputFile>& order_only_deps,
151     std::vector<OutputFile>* output_files) {
152   // Note that we don't write implicit deps for copy steps. "copy_bundle_data"
153   // steps as this is most likely implemented using hardlink in the common case.
154   // See NinjaCopyTargetWriter::WriteCopyRules() for a detailed explanation.
155   for (const SourceFile& source_file : file_rule.sources()) {
156     // There is no need to check for errors here as the substitution will have
157     // been performed when computing the list of output of the target during
158     // the Target::OnResolved phase earlier.
159     OutputFile expanded_output_file;
160     file_rule.ApplyPatternToSourceAsOutputFile(
161         settings_, target_, target_->bundle_data(), source_file,
162         &expanded_output_file,
163         /*err=*/nullptr);
164     output_files->push_back(expanded_output_file);
165 
166     out_ << "build ";
167     WriteOutput(std::move(expanded_output_file));
168     out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
169          << GeneralTool::kGeneralToolCopyBundleData << " ";
170     path_output_.WriteFile(out_, source_file);
171 
172     if (!order_only_deps.empty()) {
173       out_ << " ||";
174       path_output_.WriteFiles(out_, order_only_deps);
175     }
176 
177     out_ << std::endl;
178   }
179 }
180 
WriteCompileAssetsCatalogStep(const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)181 void NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogStep(
182     const std::vector<OutputFile>& order_only_deps,
183     std::vector<OutputFile>* output_files) {
184   if (!TargetRequireAssetCatalogCompilation(target_))
185     return;
186 
187   OutputFile compiled_catalog;
188   if (!target_->bundle_data().assets_catalog_sources().empty()) {
189     compiled_catalog =
190         OutputFile(settings_->build_settings(),
191                    target_->bundle_data().GetCompiledAssetCatalogPath());
192     output_files->push_back(compiled_catalog);
193   }
194 
195   OutputFile partial_info_plist;
196   if (!target_->bundle_data().partial_info_plist().is_null()) {
197     partial_info_plist =
198         OutputFile(settings_->build_settings(),
199                    target_->bundle_data().partial_info_plist());
200 
201     output_files->push_back(partial_info_plist);
202   }
203 
204   // If there are no asset catalog to compile but the "partial_info_plist" is
205   // non-empty, then add a target to generate an empty file (to avoid breaking
206   // code that depends on this file existence).
207   if (target_->bundle_data().assets_catalog_sources().empty()) {
208     DCHECK(!target_->bundle_data().partial_info_plist().is_null());
209 
210     out_ << "build ";
211     WriteOutput(partial_info_plist);
212     out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
213          << GeneralTool::kGeneralToolStamp;
214     if (!order_only_deps.empty()) {
215       out_ << " ||";
216       path_output_.WriteFiles(out_, order_only_deps);
217     }
218     out_ << std::endl;
219     return;
220   }
221 
222   OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStamp(
223       target_->bundle_data().assets_catalog_deps());
224   DCHECK(!input_dep.value().empty());
225 
226   out_ << "build ";
227   WriteOutput(std::move(compiled_catalog));
228   if (partial_info_plist != OutputFile()) {
229     // If "partial_info_plist" is non-empty, then add it to list of implicit
230     // outputs of the asset catalog compilation, so that target can use it
231     // without getting the ninja error "'foo', needed by 'bar', missing and
232     // no known rule to make it".
233     out_ << " | ";
234     WriteOutput(partial_info_plist);
235   }
236 
237   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
238        << GeneralTool::kGeneralToolCompileXCAssets;
239 
240   SourceFileSet asset_catalog_bundles;
241   for (const auto& source : target_->bundle_data().assets_catalog_sources()) {
242     out_ << " ";
243     path_output_.WriteFile(out_, source);
244     asset_catalog_bundles.insert(source);
245   }
246 
247   out_ << " | ";
248   path_output_.WriteFile(out_, input_dep);
249 
250   if (!order_only_deps.empty()) {
251     out_ << " ||";
252     path_output_.WriteFiles(out_, order_only_deps);
253   }
254 
255   out_ << std::endl;
256 
257   out_ << "  product_type = " << target_->bundle_data().product_type()
258        << std::endl;
259 
260   if (partial_info_plist != OutputFile()) {
261     out_ << "  partial_info_plist = ";
262     path_output_.WriteFile(out_, partial_info_plist);
263     out_ << std::endl;
264   }
265 
266   const std::vector<SubstitutionPattern>& flags =
267       target_->bundle_data().xcasset_compiler_flags().list();
268   if (!flags.empty()) {
269     out_ << "  " << SubstitutionXcassetsCompilerFlags.ninja_name << " =";
270     EscapeOptions args_escape_options;
271     args_escape_options.mode = ESCAPE_NINJA_COMMAND;
272     for (const auto& flag : flags) {
273       out_ << " ";
274       SubstitutionWriter::WriteWithNinjaVariables(flag, args_escape_options,
275                                                   out_);
276     }
277     out_ << std::endl;
278   }
279 }
280 
281 OutputFile
WriteCompileAssetsCatalogInputDepsStamp(const std::vector<const Target * > & dependencies)282 NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStamp(
283     const std::vector<const Target*>& dependencies) {
284   DCHECK(!dependencies.empty());
285   if (dependencies.size() == 1)
286     return dependencies[0]->dependency_output_file();
287 
288   OutputFile xcassets_input_stamp_file =
289       GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
290   xcassets_input_stamp_file.value().append(target_->label().name());
291   xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp");
292 
293   out_ << "build ";
294   WriteOutput(xcassets_input_stamp_file);
295   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
296        << GeneralTool::kGeneralToolStamp;
297 
298   for (const Target* target : dependencies) {
299     out_ << " ";
300     path_output_.WriteFile(out_, target->dependency_output_file());
301   }
302   out_ << std::endl;
303   return xcassets_input_stamp_file;
304 }
305 
WritePostProcessingStep(const std::string & post_processing_rule_name,const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)306 void NinjaCreateBundleTargetWriter::WritePostProcessingStep(
307     const std::string& post_processing_rule_name,
308     const std::vector<OutputFile>& order_only_deps,
309     std::vector<OutputFile>* output_files) {
310   if (post_processing_rule_name.empty())
311     return;
312 
313   OutputFile post_processing_input_stamp_file =
314       WritePostProcessingInputDepsStamp(order_only_deps, output_files);
315   DCHECK(!post_processing_input_stamp_file.value().empty());
316 
317   out_ << "build";
318   std::vector<OutputFile> post_processing_output_files;
319   SubstitutionWriter::GetListAsOutputFiles(
320       settings_, target_->bundle_data().post_processing_outputs(),
321       &post_processing_output_files);
322   WriteOutputs(post_processing_output_files);
323 
324   // Since the post-processing step depends on all the files from the bundle,
325   // the create_bundle stamp can just depends on the output of the signature
326   // script (dependencies are transitive).
327   *output_files = std::move(post_processing_output_files);
328 
329   out_ << ": " << post_processing_rule_name;
330   out_ << " | ";
331   path_output_.WriteFile(out_, post_processing_input_stamp_file);
332   out_ << std::endl;
333 }
334 
WritePostProcessingInputDepsStamp(const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)335 OutputFile NinjaCreateBundleTargetWriter::WritePostProcessingInputDepsStamp(
336     const std::vector<OutputFile>& order_only_deps,
337     std::vector<OutputFile>* output_files) {
338   std::vector<SourceFile> post_processing_input_files;
339   post_processing_input_files.push_back(
340       target_->bundle_data().post_processing_script());
341   post_processing_input_files.insert(
342       post_processing_input_files.end(),
343       target_->bundle_data().post_processing_sources().begin(),
344       target_->bundle_data().post_processing_sources().end());
345   for (const OutputFile& output_file : *output_files) {
346     post_processing_input_files.push_back(
347         output_file.AsSourceFile(settings_->build_settings()));
348   }
349 
350   DCHECK(!post_processing_input_files.empty());
351   if (post_processing_input_files.size() == 1 && order_only_deps.empty())
352     return OutputFile(settings_->build_settings(),
353                       post_processing_input_files[0]);
354 
355   OutputFile post_processing_input_stamp_file =
356       GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
357   post_processing_input_stamp_file.value().append(target_->label().name());
358   post_processing_input_stamp_file.value().append(
359       ".postprocessing.inputdeps.stamp");
360 
361   out_ << "build ";
362   WriteOutput(post_processing_input_stamp_file);
363   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
364        << GeneralTool::kGeneralToolStamp;
365 
366   for (const SourceFile& source : post_processing_input_files) {
367     out_ << " ";
368     path_output_.WriteFile(out_, source);
369   }
370   if (!order_only_deps.empty()) {
371     out_ << " ||";
372     path_output_.WriteFiles(out_, order_only_deps);
373   }
374   out_ << std::endl;
375   return post_processing_input_stamp_file;
376 }
377