blob: 002c042b709d16622a26f4445a14c4bd87102641 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gn/ninja_create_bundle_target_writer.h"
#include <iterator>
#include "base/macros.h"
#include "base/strings/string_util.h"
#include "gn/filesystem_utils.h"
#include "gn/general_tool.h"
#include "gn/ninja_utils.h"
#include "gn/output_file.h"
#include "gn/scheduler.h"
#include "gn/substitution_writer.h"
#include "gn/target.h"
#include "gn/toolchain.h"
namespace {
bool TargetRequireAssetCatalogCompilation(const Target* target) {
return !target->bundle_data().assets_catalog_sources().empty() ||
!target->bundle_data().partial_info_plist().is_null();
}
void FailWithMissingToolError(const char* tool_name, const Target* target) {
g_scheduler->FailWithError(
Err(nullptr, std::string(tool_name) + " tool not defined",
"The toolchain " +
target->toolchain()->label().GetUserVisibleName(false) +
"\n"
"used by target " +
target->label().GetUserVisibleName(false) +
"\n"
"doesn't define a \"" +
tool_name + "\" tool."));
}
bool EnsureAllToolsAvailable(const Target* target) {
const char* kRequiredTools[] = {
GeneralTool::kGeneralToolCopyBundleData,
GeneralTool::kGeneralToolStamp,
};
for (size_t i = 0; i < std::size(kRequiredTools); ++i) {
if (!target->toolchain()->GetTool(kRequiredTools[i])) {
FailWithMissingToolError(kRequiredTools[i], target);
return false;
}
}
// The compile_xcassets tool is only required if the target has asset
// catalog resources to compile.
if (TargetRequireAssetCatalogCompilation(target)) {
if (!target->toolchain()->GetTool(
GeneralTool::kGeneralToolCompileXCAssets)) {
FailWithMissingToolError(GeneralTool::kGeneralToolCompileXCAssets,
target);
return false;
}
}
return true;
}
} // namespace
NinjaCreateBundleTargetWriter::NinjaCreateBundleTargetWriter(
const Target* target,
std::ostream& out)
: NinjaTargetWriter(target, out) {}
NinjaCreateBundleTargetWriter::~NinjaCreateBundleTargetWriter() = default;
void NinjaCreateBundleTargetWriter::Run() {
if (!EnsureAllToolsAvailable(target_))
return;
// Output users are CopyBundleData, CompileAssetsCatalog, CodeSigning and
// PhonyForTarget.
size_t num_output_uses = 4;
std::vector<OutputFile> order_only_deps = WriteInputDepsPhonyAndGetDep(
std::vector<const Target*>(), num_output_uses);
std::string code_signing_rule_name = WriteCodeSigningRuleDefinition();
std::vector<OutputFile> output_files;
WriteCopyBundleDataSteps(order_only_deps, &output_files);
WriteCompileAssetsCatalogStep(order_only_deps, &output_files);
WriteCodeSigningStep(code_signing_rule_name, order_only_deps, &output_files);
for (const auto& pair : target_->data_deps()) {
if (pair.ptr->dependency_output_file_or_phony())
order_only_deps.push_back(*pair.ptr->dependency_output_file_or_phony());
}
// If the target does not have a phony target to write, then we have nothing
// left to do.
if (!target_->dependency_output_file_or_phony())
return;
WritePhonyForTarget(output_files, order_only_deps);
// Write a phony target for the outer bundle directory. This allows other
// targets to treat the entire bundle as a single unit, even though it is
// a directory, so that it can be depended upon as a discrete build edge.
out_ << "build ";
path_output_.WriteFile(
out_,
OutputFile(settings_->build_settings(),
target_->bundle_data().GetBundleRootDirOutput(settings_)));
out_ << ": " << BuiltinTool::kBuiltinToolPhony << " ";
out_ << target_->dependency_output_file_or_phony()->value();
out_ << std::endl;
}
std::string NinjaCreateBundleTargetWriter::WriteCodeSigningRuleDefinition() {
if (target_->bundle_data().code_signing_script().is_null())
return std::string();
std::string target_label = target_->label().GetUserVisibleName(true);
std::string custom_rule_name(target_label);
base::ReplaceChars(custom_rule_name, ":/()", "_", &custom_rule_name);
custom_rule_name.append("_code_signing_rule");
out_ << "rule " << custom_rule_name << std::endl;
out_ << " command = ";
path_output_.WriteFile(out_, settings_->build_settings()->python_path());
out_ << " ";
path_output_.WriteFile(out_, target_->bundle_data().code_signing_script());
const SubstitutionList& args = target_->bundle_data().code_signing_args();
EscapeOptions args_escape_options;
args_escape_options.mode = ESCAPE_NINJA_COMMAND;
for (const auto& arg : args.list()) {
out_ << " ";
SubstitutionWriter::WriteWithNinjaVariables(arg, args_escape_options, out_);
}
out_ << std::endl;
out_ << " description = CODE SIGNING " << target_label << std::endl;
out_ << " restat = 1" << std::endl;
out_ << std::endl;
return custom_rule_name;
}
void NinjaCreateBundleTargetWriter::WriteCopyBundleDataSteps(
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* output_files) {
for (const BundleFileRule& file_rule : target_->bundle_data().file_rules())
WriteCopyBundleFileRuleSteps(file_rule, order_only_deps, output_files);
}
void NinjaCreateBundleTargetWriter::WriteCopyBundleFileRuleSteps(
const BundleFileRule& file_rule,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* output_files) {
// Note that we don't write implicit deps for copy steps. "copy_bundle_data"
// steps as this is most likely implemented using hardlink in the common case.
// See NinjaCopyTargetWriter::WriteCopyRules() for a detailed explanation.
for (const SourceFile& source_file : file_rule.sources()) {
// There is no need to check for errors here as the substitution will have
// been performed when computing the list of output of the target during
// the Target::OnResolved phase earlier.
OutputFile expanded_output_file;
file_rule.ApplyPatternToSourceAsOutputFile(
settings_, target_, target_->bundle_data(), source_file,
&expanded_output_file,
/*err=*/nullptr);
output_files->push_back(expanded_output_file);
out_ << "build ";
path_output_.WriteFile(out_, expanded_output_file);
out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
<< GeneralTool::kGeneralToolCopyBundleData << " ";
path_output_.WriteFile(out_, source_file);
if (!order_only_deps.empty()) {
out_ << " ||";
path_output_.WriteFiles(out_, order_only_deps);
}
out_ << std::endl;
}
}
void NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogStep(
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* output_files) {
if (!TargetRequireAssetCatalogCompilation(target_))
return;
OutputFile compiled_catalog;
if (!target_->bundle_data().assets_catalog_sources().empty()) {
compiled_catalog =
OutputFile(settings_->build_settings(),
target_->bundle_data().GetCompiledAssetCatalogPath());
output_files->push_back(compiled_catalog);
}
OutputFile partial_info_plist;
if (!target_->bundle_data().partial_info_plist().is_null()) {
partial_info_plist =
OutputFile(settings_->build_settings(),
target_->bundle_data().partial_info_plist());
output_files->push_back(partial_info_plist);
}
// If there are no asset catalog to compile but the "partial_info_plist" is
// non-empty, then add a target to generate an empty file (to avoid breaking
// code that depends on this file existence).
if (target_->bundle_data().assets_catalog_sources().empty()) {
DCHECK(!target_->bundle_data().partial_info_plist().is_null());
out_ << "build ";
path_output_.WriteFile(out_, partial_info_plist);
out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
<< GeneralTool::kGeneralToolStamp;
if (!order_only_deps.empty()) {
out_ << " ||";
path_output_.WriteFiles(out_, order_only_deps);
}
out_ << std::endl;
return;
}
OutputFile input_dep = WriteCompileAssetsCatalogInputDepsPhony(
target_->bundle_data().assets_catalog_deps());
DCHECK(!input_dep.value().empty());
out_ << "build ";
path_output_.WriteFile(out_, compiled_catalog);
if (partial_info_plist != OutputFile()) {
// If "partial_info_plist" is non-empty, then add it to list of implicit
// outputs of the asset catalog compilation, so that target can use it
// without getting the ninja error "'foo', needed by 'bar', missing and
// no known rule to make it".
out_ << " | ";
path_output_.WriteFile(out_, partial_info_plist);
}
out_ << ": " << GetNinjaRulePrefixForToolchain(settings_)
<< GeneralTool::kGeneralToolCompileXCAssets;
SourceFileSet asset_catalog_bundles;
for (const auto& source : target_->bundle_data().assets_catalog_sources()) {
out_ << " ";
path_output_.WriteFile(out_, source);
asset_catalog_bundles.insert(source);
}
out_ << " | ";
path_output_.WriteFile(out_, input_dep);
if (!order_only_deps.empty()) {
out_ << " ||";
path_output_.WriteFiles(out_, order_only_deps);
}
out_ << std::endl;
out_ << " product_type = " << target_->bundle_data().product_type()
<< std::endl;
if (partial_info_plist != OutputFile()) {
out_ << " partial_info_plist = ";
path_output_.WriteFile(out_, partial_info_plist);
out_ << std::endl;
}
const std::vector<SubstitutionPattern>& flags =
target_->bundle_data().xcasset_compiler_flags().list();
if (!flags.empty()) {
out_ << " " << SubstitutionXcassetsCompilerFlags.ninja_name << " =";
EscapeOptions args_escape_options;
args_escape_options.mode = ESCAPE_NINJA_COMMAND;
for (const auto& flag : flags) {
out_ << " ";
SubstitutionWriter::WriteWithNinjaVariables(
flag, args_escape_options, out_);
}
out_ << std::endl;
}
}
OutputFile
NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsPhony(
const std::vector<const Target*>& dependencies) {
DCHECK(!dependencies.empty());
if (dependencies.size() == 1) {
return dependencies[0]->dependency_output_file_or_phony()
? *dependencies[0]->dependency_output_file_or_phony()
: OutputFile{};
}
OutputFile xcassets_input_phony =
GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
xcassets_input_phony.value().append(target_->label().name());
xcassets_input_phony.value().append(".xcassets.inputdeps");
out_ << "build ";
path_output_.WriteFile(out_, xcassets_input_phony);
out_ << ": " << BuiltinTool::kBuiltinToolPhony;
for (const Target* target : dependencies) {
if (target->dependency_output_file_or_phony()) {
out_ << " ";
path_output_.WriteFile(out_, *target->dependency_output_file_or_phony());
}
}
out_ << std::endl;
return xcassets_input_phony;
}
void NinjaCreateBundleTargetWriter::WriteCodeSigningStep(
const std::string& code_signing_rule_name,
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* output_files) {
if (code_signing_rule_name.empty())
return;
OutputFile code_signing_input_phony =
WriteCodeSigningInputDepsPhony(order_only_deps, output_files);
DCHECK(!code_signing_input_phony.value().empty());
out_ << "build";
std::vector<OutputFile> code_signing_output_files;
SubstitutionWriter::GetListAsOutputFiles(
settings_, target_->bundle_data().code_signing_outputs(),
&code_signing_output_files);
path_output_.WriteFiles(out_, code_signing_output_files);
// Since the code signature step depends on all the files from the bundle,
// the create_bundle phony can just depends on the output of the signature
// script (dependencies are transitive).
*output_files = std::move(code_signing_output_files);
out_ << ": " << code_signing_rule_name;
out_ << " | ";
path_output_.WriteFile(out_, code_signing_input_phony);
out_ << std::endl;
}
OutputFile NinjaCreateBundleTargetWriter::WriteCodeSigningInputDepsPhony(
const std::vector<OutputFile>& order_only_deps,
std::vector<OutputFile>* output_files) {
std::vector<SourceFile> code_signing_input_files;
code_signing_input_files.push_back(
target_->bundle_data().code_signing_script());
code_signing_input_files.insert(
code_signing_input_files.end(),
target_->bundle_data().code_signing_sources().begin(),
target_->bundle_data().code_signing_sources().end());
for (const OutputFile& output_file : *output_files) {
code_signing_input_files.push_back(
output_file.AsSourceFile(settings_->build_settings()));
}
DCHECK(!code_signing_input_files.empty());
if (code_signing_input_files.size() == 1 && order_only_deps.empty())
return OutputFile(settings_->build_settings(), code_signing_input_files[0]);
OutputFile code_signing_input_phony =
GetBuildDirForTargetAsOutputFile(target_, BuildDirType::PHONY);
code_signing_input_phony.value().append(target_->label().name());
code_signing_input_phony.value().append(".codesigning.inputdeps");
out_ << "build ";
path_output_.WriteFile(out_, code_signing_input_phony);
out_ << ": " << BuiltinTool::kBuiltinToolPhony;
for (const SourceFile& source : code_signing_input_files) {
out_ << " ";
path_output_.WriteFile(out_, source);
}
if (!order_only_deps.empty()) {
out_ << " ||";
path_output_.WriteFiles(out_, order_only_deps);
}
out_ << std::endl;
return code_signing_input_phony;
}