• 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, CodeSigning 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 code_signing_rule_name = WriteCodeSigningRuleDefinition();
86 
87   std::vector<OutputFile> output_files;
88   WriteCopyBundleDataSteps(order_only_deps, &output_files);
89   WriteCompileAssetsCatalogStep(order_only_deps, &output_files);
90   WriteCodeSigningStep(code_signing_rule_name, order_only_deps, &output_files);
91 
92   for (const auto& pair : target_->data_deps())
93     order_only_deps.push_back(pair.ptr->dependency_output_file());
94   WriteStampForTarget(output_files, order_only_deps);
95 
96   // Write a phony target for the outer bundle directory. This allows other
97   // targets to treat the entire bundle as a single unit, even though it is
98   // a directory, so that it can be depended upon as a discrete build edge.
99   out_ << "build ";
100   path_output_.WriteFile(
101       out_,
102       OutputFile(settings_->build_settings(),
103                  target_->bundle_data().GetBundleRootDirOutput(settings_)));
104   out_ << ": phony " << target_->dependency_output_file().value();
105   out_ << std::endl;
106 }
107 
WriteCodeSigningRuleDefinition()108 std::string NinjaCreateBundleTargetWriter::WriteCodeSigningRuleDefinition() {
109   if (target_->bundle_data().code_signing_script().is_null())
110     return std::string();
111 
112   std::string target_label = target_->label().GetUserVisibleName(true);
113   std::string custom_rule_name(target_label);
114   base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
115   custom_rule_name.append("_code_signing_rule");
116 
117   out_ << "rule " << custom_rule_name << std::endl;
118   out_ << "  command = ";
119   path_output_.WriteFile(out_, settings_->build_settings()->python_path());
120   out_ << " ";
121   path_output_.WriteFile(out_, target_->bundle_data().code_signing_script());
122 
123   const SubstitutionList& args = target_->bundle_data().code_signing_args();
124   EscapeOptions args_escape_options;
125   args_escape_options.mode = ESCAPE_NINJA_COMMAND;
126 
127   for (const auto& arg : args.list()) {
128     out_ << " ";
129     SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_);
130   }
131   out_ << std::endl;
132   out_ << "  description = CODE SIGNING " << target_label << std::endl;
133   out_ << "  restat = 1" << std::endl;
134   out_ << std::endl;
135 
136   return custom_rule_name;
137 }
138 
WriteCopyBundleDataSteps(const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)139 void NinjaCreateBundleTargetWriter::WriteCopyBundleDataSteps(
140     const std::vector<OutputFile>& order_only_deps,
141     std::vector<OutputFile>* output_files) {
142   for (const BundleFileRule& file_rule : target_->bundle_data().file_rules())
143     WriteCopyBundleFileRuleSteps(file_rule, order_only_deps, output_files);
144 }
145 
WriteCopyBundleFileRuleSteps(const BundleFileRule & file_rule,const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)146 void NinjaCreateBundleTargetWriter::WriteCopyBundleFileRuleSteps(
147     const BundleFileRule& file_rule,
148     const std::vector<OutputFile>& order_only_deps,
149     std::vector<OutputFile>* output_files) {
150   // Note that we don't write implicit deps for copy steps. "copy_bundle_data"
151   // steps as this is most likely implemented using hardlink in the common case.
152   // See NinjaCopyTargetWriter::WriteCopyRules() for a detailed explanation.
153   for (const SourceFile& source_file : file_rule.sources()) {
154     // There is no need to check for errors here as the substitution will have
155     // been performed when computing the list of output of the target during
156     // the Target::OnResolved phase earlier.
157     OutputFile expanded_output_file;
158     file_rule.ApplyPatternToSourceAsOutputFile(
159         settings_, target_, target_->bundle_data(), source_file,
160         &expanded_output_file,
161         /*err=*/nullptr);
162     output_files->push_back(expanded_output_file);
163 
164     out_ << "build ";
165     path_output_.WriteFile(out_, expanded_output_file);
166     out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
167          << GeneralTool::kGeneralToolCopyBundleData << " ";
168     path_output_.WriteFile(out_, source_file);
169 
170     if (!order_only_deps.empty()) {
171       out_ << " ||";
172       path_output_.WriteFiles(out_, order_only_deps);
173     }
174 
175     out_ << std::endl;
176   }
177 }
178 
WriteCompileAssetsCatalogStep(const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)179 void NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogStep(
180     const std::vector<OutputFile>& order_only_deps,
181     std::vector<OutputFile>* output_files) {
182   if (!TargetRequireAssetCatalogCompilation(target_))
183     return;
184 
185   OutputFile compiled_catalog;
186   if (!target_->bundle_data().assets_catalog_sources().empty()) {
187     compiled_catalog =
188         OutputFile(settings_->build_settings(),
189                    target_->bundle_data().GetCompiledAssetCatalogPath());
190     output_files->push_back(compiled_catalog);
191   }
192 
193   OutputFile partial_info_plist;
194   if (!target_->bundle_data().partial_info_plist().is_null()) {
195     partial_info_plist =
196         OutputFile(settings_->build_settings(),
197                    target_->bundle_data().partial_info_plist());
198 
199     output_files->push_back(partial_info_plist);
200   }
201 
202   // If there are no asset catalog to compile but the "partial_info_plist" is
203   // non-empty, then add a target to generate an empty file (to avoid breaking
204   // code that depends on this file existence).
205   if (target_->bundle_data().assets_catalog_sources().empty()) {
206     DCHECK(!target_->bundle_data().partial_info_plist().is_null());
207 
208     out_ << "build ";
209     path_output_.WriteFile(out_, partial_info_plist);
210     out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
211          << GeneralTool::kGeneralToolStamp;
212     if (!order_only_deps.empty()) {
213       out_ << " ||";
214       path_output_.WriteFiles(out_, order_only_deps);
215     }
216     out_ << std::endl;
217     return;
218   }
219 
220   OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStamp(
221       target_->bundle_data().assets_catalog_deps());
222   DCHECK(!input_dep.value().empty());
223 
224   out_ << "build ";
225   path_output_.WriteFile(out_, compiled_catalog);
226   if (partial_info_plist != OutputFile()) {
227     // If "partial_info_plist" is non-empty, then add it to list of implicit
228     // outputs of the asset catalog compilation, so that target can use it
229     // without getting the ninja error "'foo', needed by 'bar', missing and
230     // no known rule to make it".
231     out_ << " | ";
232     path_output_.WriteFile(out_, partial_info_plist);
233   }
234 
235   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
236        << GeneralTool::kGeneralToolCompileXCAssets;
237 
238   SourceFileSet asset_catalog_bundles;
239   for (const auto& source : target_->bundle_data().assets_catalog_sources()) {
240     out_ << " ";
241     path_output_.WriteFile(out_, source);
242     asset_catalog_bundles.insert(source);
243   }
244 
245   out_ << " | ";
246   path_output_.WriteFile(out_, input_dep);
247 
248   if (!order_only_deps.empty()) {
249     out_ << " ||";
250     path_output_.WriteFiles(out_, order_only_deps);
251   }
252 
253   out_ << std::endl;
254 
255   out_ << "  product_type = " << target_->bundle_data().product_type()
256        << std::endl;
257 
258   if (partial_info_plist != OutputFile()) {
259     out_ << "  partial_info_plist = ";
260     path_output_.WriteFile(out_, partial_info_plist);
261     out_ << std::endl;
262   }
263 
264   const std::vector<SubstitutionPattern>& flags =
265       target_->bundle_data().xcasset_compiler_flags().list();
266   if (!flags.empty()) {
267     out_ << "  " << SubstitutionXcassetsCompilerFlags.ninja_name << " =";
268     EscapeOptions args_escape_options;
269     args_escape_options.mode = ESCAPE_NINJA_COMMAND;
270     for (const auto& flag : flags) {
271       out_ << " ";
272       SubstitutionWriter::WriteWithNinjaVariables(flag, args_escape_options,
273                                                   out_);
274     }
275     out_ << std::endl;
276   }
277 }
278 
279 OutputFile
WriteCompileAssetsCatalogInputDepsStamp(const std::vector<const Target * > & dependencies)280 NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStamp(
281     const std::vector<const Target*>& dependencies) {
282   DCHECK(!dependencies.empty());
283   if (dependencies.size() == 1)
284     return dependencies[0]->dependency_output_file();
285 
286   OutputFile xcassets_input_stamp_file =
287       GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
288   xcassets_input_stamp_file.value().append(target_->label().name());
289   xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp");
290 
291   out_ << "build ";
292   path_output_.WriteFile(out_, xcassets_input_stamp_file);
293   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
294        << GeneralTool::kGeneralToolStamp;
295 
296   for (const Target* target : dependencies) {
297     out_ << " ";
298     path_output_.WriteFile(out_, target->dependency_output_file());
299   }
300   out_ << std::endl;
301   return xcassets_input_stamp_file;
302 }
303 
WriteCodeSigningStep(const std::string & code_signing_rule_name,const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)304 void NinjaCreateBundleTargetWriter::WriteCodeSigningStep(
305     const std::string& code_signing_rule_name,
306     const std::vector<OutputFile>& order_only_deps,
307     std::vector<OutputFile>* output_files) {
308   if (code_signing_rule_name.empty())
309     return;
310 
311   OutputFile code_signing_input_stamp_file =
312       WriteCodeSigningInputDepsStamp(order_only_deps, output_files);
313   DCHECK(!code_signing_input_stamp_file.value().empty());
314 
315   out_ << "build";
316   std::vector<OutputFile> code_signing_output_files;
317   SubstitutionWriter::GetListAsOutputFiles(
318       settings_, target_->bundle_data().code_signing_outputs(),
319       &code_signing_output_files);
320   path_output_.WriteFiles(out_, code_signing_output_files);
321 
322   // Since the code signature step depends on all the files from the bundle,
323   // the create_bundle stamp can just depends on the output of the signature
324   // script (dependencies are transitive).
325   *output_files = std::move(code_signing_output_files);
326 
327   out_ << ": " << code_signing_rule_name;
328   out_ << " | ";
329   path_output_.WriteFile(out_, code_signing_input_stamp_file);
330   out_ << std::endl;
331 }
332 
WriteCodeSigningInputDepsStamp(const std::vector<OutputFile> & order_only_deps,std::vector<OutputFile> * output_files)333 OutputFile NinjaCreateBundleTargetWriter::WriteCodeSigningInputDepsStamp(
334     const std::vector<OutputFile>& order_only_deps,
335     std::vector<OutputFile>* output_files) {
336   std::vector<SourceFile> code_signing_input_files;
337   code_signing_input_files.push_back(
338       target_->bundle_data().code_signing_script());
339   code_signing_input_files.insert(
340       code_signing_input_files.end(),
341       target_->bundle_data().code_signing_sources().begin(),
342       target_->bundle_data().code_signing_sources().end());
343   for (const OutputFile& output_file : *output_files) {
344     code_signing_input_files.push_back(
345         output_file.AsSourceFile(settings_->build_settings()));
346   }
347 
348   DCHECK(!code_signing_input_files.empty());
349   if (code_signing_input_files.size() == 1 && order_only_deps.empty())
350     return OutputFile(settings_->build_settings(), code_signing_input_files[0]);
351 
352   OutputFile code_signing_input_stamp_file =
353       GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ);
354   code_signing_input_stamp_file.value().append(target_->label().name());
355   code_signing_input_stamp_file.value().append(".codesigning.inputdeps.stamp");
356 
357   out_ << "build ";
358   path_output_.WriteFile(out_, code_signing_input_stamp_file);
359   out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
360        << GeneralTool::kGeneralToolStamp;
361 
362   for (const SourceFile& source : code_signing_input_files) {
363     out_ << " ";
364     path_output_.WriteFile(out_, source);
365   }
366   if (!order_only_deps.empty()) {
367     out_ << " ||";
368     path_output_.WriteFiles(out_, order_only_deps);
369   }
370   out_ << std::endl;
371   return code_signing_input_stamp_file;
372 }
373