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