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