// 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 "tools/gn/ninja_create_bundle_target_writer.h" #include "base/macros.h" #include "base/strings/string_util.h" #include "tools/gn/filesystem_utils.h" #include "tools/gn/ninja_utils.h" #include "tools/gn/output_file.h" #include "tools/gn/scheduler.h" #include "tools/gn/substitution_writer.h" #include "tools/gn/target.h" #include "tools/gn/toolchain.h" namespace { void FailWithMissingToolError(Toolchain::ToolType tool, const Target* target) { const std::string& tool_name = Toolchain::ToolTypeToName(tool); g_scheduler->FailWithError( Err(nullptr, 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 Toolchain::ToolType kRequiredTools[] = { Toolchain::TYPE_COPY_BUNDLE_DATA, Toolchain::TYPE_COMPILE_XCASSETS, Toolchain::TYPE_STAMP, }; for (size_t i = 0; i < arraysize(kRequiredTools); ++i) { if (!target->toolchain()->GetTool(kRequiredTools[i])) { FailWithMissingToolError(kRequiredTools[i], 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; // Stamp users are CopyBundleData, CompileAssetsCatalog, CodeSigning and // StampForTarget. size_t num_stamp_uses = 4; std::vector order_only_deps = WriteInputDepsStampAndGetDep( std::vector(), num_stamp_uses); std::string code_signing_rule_name = WriteCodeSigningRuleDefinition(); std::vector 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()) order_only_deps.push_back(pair.ptr->dependency_output_file()); WriteStampForTarget(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_ << ": phony " << target_->dependency_output_file().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& order_only_deps, std::vector* 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& order_only_deps, std::vector* 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()) { OutputFile output_file = file_rule.ApplyPatternToSourceAsOutputFile( settings_, target_->bundle_data(), source_file); output_files->push_back(output_file); out_ << "build "; path_output_.WriteFile(out_, output_file); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << Toolchain::ToolTypeToName(Toolchain::TYPE_COPY_BUNDLE_DATA) << " "; 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& order_only_deps, std::vector* output_files) { if (target_->bundle_data().assets_catalog_sources().empty() && target_->bundle_data().partial_info_plist().is_null()) 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_) << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP); if (!order_only_deps.empty()) { out_ << " ||"; path_output_.WriteFiles(out_, order_only_deps); } out_ << std::endl; return; } OutputFile input_dep = WriteCompileAssetsCatalogInputDepsStamp( 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_) << Toolchain::ToolTypeToName(Toolchain::TYPE_COMPILE_XCASSETS); std::set 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; } } OutputFile NinjaCreateBundleTargetWriter::WriteCompileAssetsCatalogInputDepsStamp( const std::vector& dependencies) { DCHECK(!dependencies.empty()); if (dependencies.size() == 1) return dependencies[0]->dependency_output_file(); OutputFile xcassets_input_stamp_file = GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); xcassets_input_stamp_file.value().append(target_->label().name()); xcassets_input_stamp_file.value().append(".xcassets.inputdeps.stamp"); out_ << "build "; path_output_.WriteFile(out_, xcassets_input_stamp_file); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP); for (const Target* target : dependencies) { out_ << " "; path_output_.WriteFile(out_, target->dependency_output_file()); } out_ << std::endl; return xcassets_input_stamp_file; } void NinjaCreateBundleTargetWriter::WriteCodeSigningStep( const std::string& code_signing_rule_name, const std::vector& order_only_deps, std::vector* output_files) { if (code_signing_rule_name.empty()) return; OutputFile code_signing_input_stamp_file = WriteCodeSigningInputDepsStamp(order_only_deps, output_files); DCHECK(!code_signing_input_stamp_file.value().empty()); out_ << "build"; std::vector 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 stamp can just depends on the output of the signature // script (dependencies are transitive). output_files->swap(code_signing_output_files); out_ << ": " << code_signing_rule_name; out_ << " | "; path_output_.WriteFile(out_, code_signing_input_stamp_file); out_ << std::endl; } OutputFile NinjaCreateBundleTargetWriter::WriteCodeSigningInputDepsStamp( const std::vector& order_only_deps, std::vector* output_files) { std::vector 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_stamp_file = GetBuildDirForTargetAsOutputFile(target_, BuildDirType::OBJ); code_signing_input_stamp_file.value().append(target_->label().name()); code_signing_input_stamp_file.value().append(".codesigning.inputdeps.stamp"); out_ << "build "; path_output_.WriteFile(out_, code_signing_input_stamp_file); out_ << ": " << GetNinjaRulePrefixForToolchain(settings_) << Toolchain::ToolTypeToName(Toolchain::TYPE_STAMP); 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_stamp_file; }