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