blob: b12819e7f665d62721534ad02b0baf5b7109e86c [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/create_bundle_target_generator.h"
#include <map>
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "gn/filesystem_utils.h"
#include "gn/label_pattern.h"
#include "gn/parse_tree.h"
#include "gn/scope.h"
#include "gn/substitution_type.h"
#include "gn/target.h"
#include "gn/value.h"
#include "gn/value_extractors.h"
#include "gn/variables.h"
namespace {
// Retrieves value from `scope` named `name` or `old_name`. If the value comes
// from the `old_name` a warning is emitted to inform the name is obsolete.
const Value* GetValueFromScope(Scope* scope,
std::string_view name,
std::string_view old_name) {
const Value* value = scope->GetValue(name, true);
if (value)
return value;
value = scope->GetValue(old_name, true);
if (value) {
// If there is a value found with the old name, print a warning to the
// console and use that value. This is to avoid breaking the existing
// build rules in the wild.
Err err(*value, "Deprecated variable name",
base::StringPrintf(
"The name \"%s\" is deprecated, use \"%s\" instead.",
std::string(old_name).c_str(), std::string(name).c_str()));
err.PrintNonfatalToStdout();
}
return value;
}
} // namespace
CreateBundleTargetGenerator::CreateBundleTargetGenerator(
Target* target,
Scope* scope,
const FunctionCallNode* function_call,
Err* err)
: TargetGenerator(target, scope, function_call, err) {}
CreateBundleTargetGenerator::~CreateBundleTargetGenerator() = default;
void CreateBundleTargetGenerator::DoRun() {
target_->set_output_type(Target::CREATE_BUNDLE);
BundleData& bundle_data = target_->bundle_data();
if (!FillBundleDir(SourceDir(), variables::kBundleRootDir,
&bundle_data.root_dir()))
return;
if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleContentsDir,
&bundle_data.contents_dir()))
return;
if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleResourcesDir,
&bundle_data.resources_dir()))
return;
if (!FillBundleDir(bundle_data.root_dir(), variables::kBundleExecutableDir,
&bundle_data.executable_dir()))
return;
if (!FillXcodeExtraAttributes())
return;
if (!FillProductType())
return;
if (!FillPartialInfoPlist())
return;
if (!FillXcodeTestApplicationName())
return;
if (!FillPostProcessingScript())
return;
if (!FillPostProcessingSources())
return;
if (!FillPostProcessingOutputs())
return;
if (!FillPostProcessingArgs())
return;
if (!FillBundleDepsFilter())
return;
if (!FillXcassetCompilerFlags())
return;
if (!FillTransparent())
return;
}
bool CreateBundleTargetGenerator::FillBundleDir(
const SourceDir& bundle_root_dir,
std::string_view name,
SourceDir* bundle_dir) {
// All bundle_foo_dir properties are optional. They are only required if they
// are used in an expansion. The check is performed there.
const Value* value = scope_->GetValue(name, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::STRING, err_))
return false;
std::string str = value->string_value();
if (!str.empty() && str[str.size() - 1] != '/')
str.push_back('/');
if (!EnsureStringIsInOutputDir(GetBuildSettings()->build_dir(), str,
value->origin(), err_))
return false;
if (str != bundle_root_dir.value() &&
!IsStringInOutputDir(bundle_root_dir, str)) {
*err_ =
Err(value->origin(), "Path is not in bundle root dir.",
base::StringPrintf("The given file should be in the bundle root "
"directory or below.Normally you would do "
"\"$bundle_root_dir/foo\". I interpreted this "
"as \"%s\".",
str.c_str()));
return false;
}
*bundle_dir = SourceDir(std::move(str));
return true;
}
bool CreateBundleTargetGenerator::FillXcodeExtraAttributes() {
// Need to get a mutable value to mark all values in the scope as used. This
// cannot be done on a const Scope.
Value* value = scope_->GetMutableValue(variables::kXcodeExtraAttributes,
Scope::SEARCH_CURRENT, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::SCOPE, err_))
return false;
Scope* scope_value = value->scope_value();
Scope::KeyValueMap value_map;
scope_value->GetCurrentScopeValues(&value_map);
scope_value->MarkAllUsed();
std::map<std::string, std::string> xcode_extra_attributes;
for (const auto& iter : value_map) {
if (!iter.second.VerifyTypeIs(Value::STRING, err_))
return false;
xcode_extra_attributes.insert(
std::make_pair(std::string(iter.first), iter.second.string_value()));
}
target_->bundle_data().xcode_extra_attributes() =
std::move(xcode_extra_attributes);
return true;
}
bool CreateBundleTargetGenerator::FillProductType() {
const Value* value = scope_->GetValue(variables::kProductType, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::STRING, err_))
return false;
target_->bundle_data().product_type().assign(value->string_value());
return true;
}
bool CreateBundleTargetGenerator::FillPartialInfoPlist() {
const Value* value = scope_->GetValue(variables::kPartialInfoPlist, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::STRING, err_))
return false;
const BuildSettings* build_settings = scope_->settings()->build_settings();
SourceFile path = scope_->GetSourceDir().ResolveRelativeFile(
*value, err_, build_settings->root_path_utf8());
if (err_->has_error())
return false;
if (!EnsureStringIsInOutputDir(build_settings->build_dir(), path.value(),
value->origin(), err_))
return false;
target_->bundle_data().set_partial_info_plist(path);
return true;
}
bool CreateBundleTargetGenerator::FillXcodeTestApplicationName() {
const Value* value =
scope_->GetValue(variables::kXcodeTestApplicationName, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::STRING, err_))
return false;
target_->bundle_data().xcode_test_application_name().assign(
value->string_value());
return true;
}
bool CreateBundleTargetGenerator::FillPostProcessingScript() {
const Value* value = GetValueFromScope(
scope_, variables::kPostProcessingScript, variables::kCodeSigningScript);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::STRING, err_))
return false;
SourceFile script_file = scope_->GetSourceDir().ResolveRelativeFile(
*value, err_, scope_->settings()->build_settings()->root_path_utf8());
if (err_->has_error())
return false;
target_->bundle_data().set_post_processing_script(script_file);
return true;
}
bool CreateBundleTargetGenerator::FillPostProcessingSources() {
const Value* value =
GetValueFromScope(scope_, variables::kPostProcessingSources,
variables::kCodeSigningSources);
if (!value)
return true;
if (target_->bundle_data().post_processing_script().is_null()) {
*err_ = Err(function_call_, "No post-processing script.",
"You must define post_processing_script if you use "
"post_processing_sources.");
return false;
}
Target::FileList script_sources;
if (!ExtractListOfRelativeFiles(scope_->settings()->build_settings(), *value,
scope_->GetSourceDir(), &script_sources,
err_))
return false;
target_->bundle_data().post_processing_sources() = std::move(script_sources);
return true;
}
bool CreateBundleTargetGenerator::FillPostProcessingOutputs() {
const Value* value =
GetValueFromScope(scope_, variables::kPostProcessingOutputs,
variables::kCodeSigningOutputs);
if (!value)
return true;
if (target_->bundle_data().post_processing_script().is_null()) {
*err_ = Err(function_call_, "No post-processing script.",
"You must define post_processing_script if you use "
"post_processing_outputs.");
return false;
}
if (!value->VerifyTypeIs(Value::LIST, err_))
return false;
SubstitutionList& outputs = target_->bundle_data().post_processing_outputs();
if (!outputs.Parse(*value, err_))
return false;
if (outputs.list().empty()) {
*err_ = Err(function_call_, "Post-processing script has no output.",
"If you have no outputs, the build system can not tell when "
"your post-processing script needs to be run.");
return false;
}
// Validate that outputs are in the output dir.
CHECK_EQ(value->list_value().size(), outputs.list().size());
for (size_t i = 0; i < value->list_value().size(); ++i) {
if (!EnsureSubstitutionIsInOutputDir(outputs.list()[i],
value->list_value()[i]))
return false;
}
return true;
}
bool CreateBundleTargetGenerator::FillPostProcessingArgs() {
const Value* value = GetValueFromScope(scope_, variables::kPostProcessingArgs,
variables::kCodeSigningArgs);
if (!value)
return true;
if (target_->bundle_data().post_processing_script().is_null()) {
*err_ = Err(function_call_, "No post-processing script.",
"You must define post_processing_script if you use "
"post_processing_args.");
return false;
}
if (!value->VerifyTypeIs(Value::LIST, err_))
return false;
return target_->bundle_data().post_processing_args().Parse(*value, err_);
}
bool CreateBundleTargetGenerator::FillBundleDepsFilter() {
const Value* value = scope_->GetValue(variables::kBundleDepsFilter, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::LIST, err_))
return false;
const SourceDir& current_dir = scope_->GetSourceDir();
std::vector<LabelPattern>& bundle_deps_filter =
target_->bundle_data().bundle_deps_filter();
for (const auto& item : value->list_value()) {
bundle_deps_filter.push_back(LabelPattern::GetPattern(
current_dir, scope_->settings()->build_settings()->root_path_utf8(),
item, err_));
if (err_->has_error())
return false;
}
return true;
}
bool CreateBundleTargetGenerator::FillXcassetCompilerFlags() {
const Value* value = scope_->GetValue(variables::kXcassetCompilerFlags, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::LIST, err_))
return false;
return target_->bundle_data().xcasset_compiler_flags().Parse(*value, err_);
}
bool CreateBundleTargetGenerator::FillTransparent() {
const Value* value = scope_->GetValue(variables::kTransparent, true);
if (!value)
return true;
if (!value->VerifyTypeIs(Value::BOOLEAN, err_))
return false;
target_->bundle_data().set_transparent(value->boolean_value());
return true;
}