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