mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-11-24 14:26:09 +03:00
654 lines
23 KiB
C++
654 lines
23 KiB
C++
|
// 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/xcode_writer.h"
|
||
|
|
||
|
#include <iomanip>
|
||
|
#include <map>
|
||
|
#include <memory>
|
||
|
#include <sstream>
|
||
|
#include <string>
|
||
|
#include <utility>
|
||
|
|
||
|
#include "base/environment.h"
|
||
|
#include "base/logging.h"
|
||
|
#include "base/sha1.h"
|
||
|
#include "base/strings/string_number_conversions.h"
|
||
|
#include "base/strings/string_util.h"
|
||
|
#include "tools/gn/args.h"
|
||
|
#include "tools/gn/build_settings.h"
|
||
|
#include "tools/gn/builder.h"
|
||
|
#include "tools/gn/commands.h"
|
||
|
#include "tools/gn/deps_iterator.h"
|
||
|
#include "tools/gn/filesystem_utils.h"
|
||
|
#include "tools/gn/settings.h"
|
||
|
#include "tools/gn/source_file.h"
|
||
|
#include "tools/gn/target.h"
|
||
|
#include "tools/gn/value.h"
|
||
|
#include "tools/gn/variables.h"
|
||
|
#include "tools/gn/xcode_object.h"
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
using TargetToFileList = std::unordered_map<const Target*, Target::FileList>;
|
||
|
using TargetToTarget = std::unordered_map<const Target*, const Target*>;
|
||
|
using TargetToPBXTarget = std::unordered_map<const Target*, PBXTarget*>;
|
||
|
|
||
|
const char kEarlGreyFileNameIdentifier[] = "egtest.mm";
|
||
|
const char kXCTestFileNameIdentifier[] = "xctest.mm";
|
||
|
const char kXCTestModuleTargetNamePostfix[] = "_module";
|
||
|
const char kXCUITestRunnerTargetNamePostfix[] = "_runner";
|
||
|
|
||
|
struct SafeEnvironmentVariableInfo {
|
||
|
const char* name;
|
||
|
bool capture_at_generation;
|
||
|
};
|
||
|
|
||
|
SafeEnvironmentVariableInfo kSafeEnvironmentVariables[] = {
|
||
|
{"HOME", true}, {"LANG", true}, {"PATH", true},
|
||
|
{"USER", true}, {"TMPDIR", false},
|
||
|
};
|
||
|
|
||
|
XcodeWriter::TargetOsType GetTargetOs(const Args& args) {
|
||
|
const Value* target_os_value = args.GetArgOverride(variables::kTargetOs);
|
||
|
if (target_os_value) {
|
||
|
if (target_os_value->type() == Value::STRING) {
|
||
|
if (target_os_value->string_value() == "ios")
|
||
|
return XcodeWriter::WRITER_TARGET_OS_IOS;
|
||
|
}
|
||
|
}
|
||
|
return XcodeWriter::WRITER_TARGET_OS_MACOS;
|
||
|
}
|
||
|
|
||
|
std::string GetBuildScript(const std::string& target_name,
|
||
|
const std::string& ninja_extra_args,
|
||
|
base::Environment* environment) {
|
||
|
std::stringstream script;
|
||
|
script << "echo note: \"Compile and copy " << target_name << " via ninja\"\n"
|
||
|
<< "exec ";
|
||
|
|
||
|
// Launch ninja with a sanitized environment (Xcode sets many environment
|
||
|
// variable overridding settings, including the SDK, thus breaking hermetic
|
||
|
// build).
|
||
|
script << "env -i ";
|
||
|
for (const auto& variable : kSafeEnvironmentVariables) {
|
||
|
script << variable.name << "=\"";
|
||
|
|
||
|
std::string value;
|
||
|
if (variable.capture_at_generation)
|
||
|
environment->GetVar(variable.name, &value);
|
||
|
|
||
|
if (!value.empty())
|
||
|
script << value;
|
||
|
else
|
||
|
script << "$" << variable.name;
|
||
|
script << "\" ";
|
||
|
}
|
||
|
|
||
|
script << "ninja -C .";
|
||
|
if (!ninja_extra_args.empty())
|
||
|
script << " " << ninja_extra_args;
|
||
|
if (!target_name.empty())
|
||
|
script << " " << target_name;
|
||
|
script << "\nexit 1\n";
|
||
|
return script.str();
|
||
|
}
|
||
|
|
||
|
bool IsApplicationTarget(const Target* target) {
|
||
|
return target->output_type() == Target::CREATE_BUNDLE &&
|
||
|
target->bundle_data().product_type() ==
|
||
|
"com.apple.product-type.application";
|
||
|
}
|
||
|
|
||
|
bool IsXCUITestRunnerTarget(const Target* target) {
|
||
|
return IsApplicationTarget(target) &&
|
||
|
base::EndsWith(target->label().name(),
|
||
|
kXCUITestRunnerTargetNamePostfix,
|
||
|
base::CompareCase::SENSITIVE);
|
||
|
}
|
||
|
|
||
|
bool IsXCTestModuleTarget(const Target* target) {
|
||
|
return target->output_type() == Target::CREATE_BUNDLE &&
|
||
|
target->bundle_data().product_type() ==
|
||
|
"com.apple.product-type.bundle.unit-test" &&
|
||
|
base::EndsWith(target->label().name(), kXCTestModuleTargetNamePostfix,
|
||
|
base::CompareCase::SENSITIVE);
|
||
|
}
|
||
|
|
||
|
bool IsXCUITestModuleTarget(const Target* target) {
|
||
|
return target->output_type() == Target::CREATE_BUNDLE &&
|
||
|
target->bundle_data().product_type() ==
|
||
|
"com.apple.product-type.bundle.ui-testing" &&
|
||
|
base::EndsWith(target->label().name(), kXCTestModuleTargetNamePostfix,
|
||
|
base::CompareCase::SENSITIVE);
|
||
|
}
|
||
|
|
||
|
bool IsXCTestFile(const SourceFile& file) {
|
||
|
return base::EndsWith(file.GetName(), kEarlGreyFileNameIdentifier,
|
||
|
base::CompareCase::SENSITIVE) ||
|
||
|
base::EndsWith(file.GetName(), kXCTestFileNameIdentifier,
|
||
|
base::CompareCase::SENSITIVE);
|
||
|
}
|
||
|
|
||
|
const Target* FindApplicationTargetByName(
|
||
|
const std::string& target_name,
|
||
|
const std::vector<const Target*>& targets) {
|
||
|
for (const Target* target : targets) {
|
||
|
if (target->label().name() == target_name) {
|
||
|
DCHECK(IsApplicationTarget(target));
|
||
|
return target;
|
||
|
}
|
||
|
}
|
||
|
NOTREACHED();
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
// Adds |base_pbxtarget| as a dependency of |dependent_pbxtarget| in the
|
||
|
// generated Xcode project.
|
||
|
void AddPBXTargetDependency(const PBXTarget* base_pbxtarget,
|
||
|
PBXTarget* dependent_pbxtarget,
|
||
|
const PBXProject* project) {
|
||
|
auto container_item_proxy =
|
||
|
std::make_unique<PBXContainerItemProxy>(project, base_pbxtarget);
|
||
|
auto dependency = std::make_unique<PBXTargetDependency>(
|
||
|
base_pbxtarget, std::move(container_item_proxy));
|
||
|
|
||
|
dependent_pbxtarget->AddDependency(std::move(dependency));
|
||
|
}
|
||
|
|
||
|
// Adds the corresponding test application target as dependency of xctest or
|
||
|
// xcuitest module target in the generated Xcode project.
|
||
|
void AddDependencyTargetForTestModuleTargets(
|
||
|
const std::vector<const Target*>& targets,
|
||
|
const TargetToPBXTarget& bundle_target_to_pbxtarget,
|
||
|
const PBXProject* project) {
|
||
|
for (const Target* target : targets) {
|
||
|
if (!IsXCTestModuleTarget(target) && !IsXCUITestModuleTarget(target))
|
||
|
continue;
|
||
|
|
||
|
const Target* test_application_target = FindApplicationTargetByName(
|
||
|
target->bundle_data().xcode_test_application_name(), targets);
|
||
|
const PBXTarget* test_application_pbxtarget =
|
||
|
bundle_target_to_pbxtarget.at(test_application_target);
|
||
|
PBXTarget* module_pbxtarget = bundle_target_to_pbxtarget.at(target);
|
||
|
DCHECK(test_application_pbxtarget);
|
||
|
DCHECK(module_pbxtarget);
|
||
|
|
||
|
AddPBXTargetDependency(test_application_pbxtarget, module_pbxtarget,
|
||
|
project);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Searches the list of xctest files recursively under |target|.
|
||
|
void SearchXCTestFilesForTarget(const Target* target,
|
||
|
TargetToFileList* xctest_files_per_target) {
|
||
|
// Early return if already visited and processed.
|
||
|
if (xctest_files_per_target->find(target) != xctest_files_per_target->end())
|
||
|
return;
|
||
|
|
||
|
Target::FileList xctest_files;
|
||
|
for (const SourceFile& file : target->sources()) {
|
||
|
if (IsXCTestFile(file)) {
|
||
|
xctest_files.push_back(file);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Call recursively on public and private deps.
|
||
|
for (const auto& t : target->public_deps()) {
|
||
|
SearchXCTestFilesForTarget(t.ptr, xctest_files_per_target);
|
||
|
const Target::FileList& deps_xctest_files =
|
||
|
(*xctest_files_per_target)[t.ptr];
|
||
|
xctest_files.insert(xctest_files.end(), deps_xctest_files.begin(),
|
||
|
deps_xctest_files.end());
|
||
|
}
|
||
|
|
||
|
for (const auto& t : target->private_deps()) {
|
||
|
SearchXCTestFilesForTarget(t.ptr, xctest_files_per_target);
|
||
|
const Target::FileList& deps_xctest_files =
|
||
|
(*xctest_files_per_target)[t.ptr];
|
||
|
xctest_files.insert(xctest_files.end(), deps_xctest_files.begin(),
|
||
|
deps_xctest_files.end());
|
||
|
}
|
||
|
|
||
|
// Sort xctest_files to remove duplicates.
|
||
|
std::sort(xctest_files.begin(), xctest_files.end());
|
||
|
xctest_files.erase(std::unique(xctest_files.begin(), xctest_files.end()),
|
||
|
xctest_files.end());
|
||
|
|
||
|
xctest_files_per_target->insert(std::make_pair(target, xctest_files));
|
||
|
}
|
||
|
|
||
|
// Add all source files for indexing, both private and public.
|
||
|
void AddSourceFilesToProjectForIndexing(
|
||
|
const std::vector<const Target*>& targets,
|
||
|
PBXProject* project,
|
||
|
SourceDir source_dir,
|
||
|
const BuildSettings* build_settings) {
|
||
|
std::vector<SourceFile> sources;
|
||
|
for (const Target* target : targets) {
|
||
|
for (const SourceFile& source : target->sources()) {
|
||
|
if (IsStringInOutputDir(build_settings->build_dir(), source.value()))
|
||
|
continue;
|
||
|
|
||
|
sources.push_back(source);
|
||
|
}
|
||
|
|
||
|
if (target->all_headers_public())
|
||
|
continue;
|
||
|
|
||
|
for (const SourceFile& source : target->public_headers()) {
|
||
|
if (IsStringInOutputDir(build_settings->build_dir(), source.value()))
|
||
|
continue;
|
||
|
|
||
|
sources.push_back(source);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sort sources to ensure determinism of the project file generation and
|
||
|
// remove duplicate reference to the source files (can happen due to the
|
||
|
// bundle_data targets).
|
||
|
std::sort(sources.begin(), sources.end());
|
||
|
sources.erase(std::unique(sources.begin(), sources.end()), sources.end());
|
||
|
|
||
|
for (const SourceFile& source : sources) {
|
||
|
std::string source_file = RebasePath(source.value(), source_dir,
|
||
|
build_settings->root_path_utf8());
|
||
|
project->AddSourceFileToIndexingTarget(source_file, source_file,
|
||
|
CompilerFlags::NONE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add xctest files to the "Compiler Sources" of corresponding test module
|
||
|
// native targets.
|
||
|
void AddXCTestFilesToTestModuleTarget(const Target::FileList& xctest_file_list,
|
||
|
PBXNativeTarget* native_target,
|
||
|
PBXProject* project,
|
||
|
SourceDir source_dir,
|
||
|
const BuildSettings* build_settings) {
|
||
|
for (const SourceFile& source : xctest_file_list) {
|
||
|
std::string source_path = RebasePath(source.value(), source_dir,
|
||
|
build_settings->root_path_utf8());
|
||
|
|
||
|
// Test files need to be known to Xcode for proper indexing and for
|
||
|
// discovery of tests function for XCTest and XCUITest, but the compilation
|
||
|
// is done via ninja and thus must prevent Xcode from compiling the files by
|
||
|
// adding '-help' as per file compiler flag.
|
||
|
project->AddSourceFile(source_path, source_path, CompilerFlags::HELP,
|
||
|
native_target);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CollectPBXObjectsPerClassHelper : public PBXObjectVisitor {
|
||
|
public:
|
||
|
CollectPBXObjectsPerClassHelper() = default;
|
||
|
|
||
|
void Visit(PBXObject* object) override {
|
||
|
DCHECK(object);
|
||
|
objects_per_class_[object->Class()].push_back(object);
|
||
|
}
|
||
|
|
||
|
const std::map<PBXObjectClass, std::vector<const PBXObject*>>&
|
||
|
objects_per_class() const {
|
||
|
return objects_per_class_;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
std::map<PBXObjectClass, std::vector<const PBXObject*>> objects_per_class_;
|
||
|
|
||
|
DISALLOW_COPY_AND_ASSIGN(CollectPBXObjectsPerClassHelper);
|
||
|
};
|
||
|
|
||
|
std::map<PBXObjectClass, std::vector<const PBXObject*>>
|
||
|
CollectPBXObjectsPerClass(PBXProject* project) {
|
||
|
CollectPBXObjectsPerClassHelper visitor;
|
||
|
project->Visit(visitor);
|
||
|
return visitor.objects_per_class();
|
||
|
}
|
||
|
|
||
|
class RecursivelyAssignIdsHelper : public PBXObjectVisitor {
|
||
|
public:
|
||
|
RecursivelyAssignIdsHelper(const std::string& seed)
|
||
|
: seed_(seed), counter_(0) {}
|
||
|
|
||
|
void Visit(PBXObject* object) override {
|
||
|
std::stringstream buffer;
|
||
|
buffer << seed_ << " " << object->Name() << " " << counter_;
|
||
|
std::string hash = base::SHA1HashString(buffer.str());
|
||
|
DCHECK_EQ(hash.size() % 4, 0u);
|
||
|
|
||
|
uint32_t id[3] = {0, 0, 0};
|
||
|
const uint32_t* ptr = reinterpret_cast<const uint32_t*>(hash.data());
|
||
|
for (size_t i = 0; i < hash.size() / 4; i++)
|
||
|
id[i % 3] ^= ptr[i];
|
||
|
|
||
|
object->SetId(base::HexEncode(id, sizeof(id)));
|
||
|
++counter_;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
std::string seed_;
|
||
|
int64_t counter_;
|
||
|
|
||
|
DISALLOW_COPY_AND_ASSIGN(RecursivelyAssignIdsHelper);
|
||
|
};
|
||
|
|
||
|
void RecursivelyAssignIds(PBXProject* project) {
|
||
|
RecursivelyAssignIdsHelper visitor(project->Name());
|
||
|
project->Visit(visitor);
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
// static
|
||
|
bool XcodeWriter::RunAndWriteFiles(const std::string& workspace_name,
|
||
|
const std::string& root_target_name,
|
||
|
const std::string& ninja_extra_args,
|
||
|
const std::string& dir_filters_string,
|
||
|
const BuildSettings* build_settings,
|
||
|
const Builder& builder,
|
||
|
Err* err) {
|
||
|
const XcodeWriter::TargetOsType target_os =
|
||
|
GetTargetOs(build_settings->build_args());
|
||
|
|
||
|
PBXAttributes attributes;
|
||
|
switch (target_os) {
|
||
|
case XcodeWriter::WRITER_TARGET_OS_IOS:
|
||
|
attributes["SDKROOT"] = "iphoneos";
|
||
|
attributes["TARGETED_DEVICE_FAMILY"] = "1,2";
|
||
|
break;
|
||
|
case XcodeWriter::WRITER_TARGET_OS_MACOS:
|
||
|
attributes["SDKROOT"] = "macosx";
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const std::string source_path =
|
||
|
base::FilePath::FromUTF8Unsafe(
|
||
|
RebasePath("//", build_settings->build_dir()))
|
||
|
.StripTrailingSeparators()
|
||
|
.AsUTF8Unsafe();
|
||
|
|
||
|
std::string config_name = build_settings->build_dir()
|
||
|
.Resolve(base::FilePath())
|
||
|
.StripTrailingSeparators()
|
||
|
.BaseName()
|
||
|
.AsUTF8Unsafe();
|
||
|
DCHECK(!config_name.empty());
|
||
|
|
||
|
std::string::size_type separator = config_name.find('-');
|
||
|
if (separator != std::string::npos)
|
||
|
config_name = config_name.substr(0, separator);
|
||
|
|
||
|
std::vector<const Target*> targets;
|
||
|
std::vector<const Target*> all_targets = builder.GetAllResolvedTargets();
|
||
|
if (!XcodeWriter::FilterTargets(build_settings, all_targets,
|
||
|
dir_filters_string, &targets, err)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
XcodeWriter workspace(workspace_name);
|
||
|
workspace.CreateProductsProject(targets, all_targets, attributes, source_path,
|
||
|
config_name, root_target_name,
|
||
|
ninja_extra_args, build_settings, target_os);
|
||
|
|
||
|
return workspace.WriteFiles(build_settings, err);
|
||
|
}
|
||
|
|
||
|
XcodeWriter::XcodeWriter(const std::string& name) : name_(name) {
|
||
|
if (name_.empty())
|
||
|
name_.assign("all");
|
||
|
}
|
||
|
|
||
|
XcodeWriter::~XcodeWriter() = default;
|
||
|
|
||
|
// static
|
||
|
bool XcodeWriter::FilterTargets(const BuildSettings* build_settings,
|
||
|
const std::vector<const Target*>& all_targets,
|
||
|
const std::string& dir_filters_string,
|
||
|
std::vector<const Target*>* targets,
|
||
|
Err* err) {
|
||
|
// Filter targets according to the semicolon-delimited list of label patterns,
|
||
|
// if defined, first.
|
||
|
targets->reserve(all_targets.size());
|
||
|
if (dir_filters_string.empty()) {
|
||
|
*targets = all_targets;
|
||
|
} else {
|
||
|
std::vector<LabelPattern> filters;
|
||
|
if (!commands::FilterPatternsFromString(build_settings, dir_filters_string,
|
||
|
&filters, err)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
commands::FilterTargetsByPatterns(all_targets, filters, targets);
|
||
|
}
|
||
|
|
||
|
// Filter out all target of type EXECUTABLE that are direct dependency of
|
||
|
// a BUNDLE_DATA target (under the assumption that they will be part of a
|
||
|
// CREATE_BUNDLE target generating an application bundle). Sort the list
|
||
|
// of targets per pointer to use binary search for the removal.
|
||
|
std::sort(targets->begin(), targets->end());
|
||
|
|
||
|
for (const Target* target : all_targets) {
|
||
|
if (!target->settings()->is_default())
|
||
|
continue;
|
||
|
|
||
|
if (target->output_type() != Target::BUNDLE_DATA)
|
||
|
continue;
|
||
|
|
||
|
for (const auto& pair : target->GetDeps(Target::DEPS_LINKED)) {
|
||
|
if (pair.ptr->output_type() != Target::EXECUTABLE)
|
||
|
continue;
|
||
|
|
||
|
auto iter = std::lower_bound(targets->begin(), targets->end(), pair.ptr);
|
||
|
if (iter != targets->end() && *iter == pair.ptr)
|
||
|
targets->erase(iter);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sort the list of targets per-label to get a consistent ordering of them
|
||
|
// in the generated Xcode project (and thus stability of the file generated).
|
||
|
std::sort(targets->begin(), targets->end(),
|
||
|
[](const Target* a, const Target* b) {
|
||
|
return a->label().name() < b->label().name();
|
||
|
});
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void XcodeWriter::CreateProductsProject(
|
||
|
const std::vector<const Target*>& targets,
|
||
|
const std::vector<const Target*>& all_targets,
|
||
|
const PBXAttributes& attributes,
|
||
|
const std::string& source_path,
|
||
|
const std::string& config_name,
|
||
|
const std::string& root_target,
|
||
|
const std::string& ninja_extra_args,
|
||
|
const BuildSettings* build_settings,
|
||
|
TargetOsType target_os) {
|
||
|
std::unique_ptr<PBXProject> main_project(
|
||
|
new PBXProject("products", config_name, source_path, attributes));
|
||
|
|
||
|
std::vector<const Target*> bundle_targets;
|
||
|
TargetToPBXTarget bundle_target_to_pbxtarget;
|
||
|
|
||
|
std::string build_path;
|
||
|
std::unique_ptr<base::Environment> env(base::Environment::Create());
|
||
|
SourceDir source_dir("//");
|
||
|
AddSourceFilesToProjectForIndexing(all_targets, main_project.get(),
|
||
|
source_dir, build_settings);
|
||
|
main_project->AddAggregateTarget(
|
||
|
"All", GetBuildScript(root_target, ninja_extra_args, env.get()));
|
||
|
|
||
|
// Needs to search for xctest files under the application targets, and this
|
||
|
// variable is used to store the results of visited targets, thus making the
|
||
|
// search more efficient.
|
||
|
TargetToFileList xctest_files_per_target;
|
||
|
|
||
|
for (const Target* target : targets) {
|
||
|
switch (target->output_type()) {
|
||
|
case Target::EXECUTABLE:
|
||
|
if (target_os == XcodeWriter::WRITER_TARGET_OS_IOS)
|
||
|
continue;
|
||
|
|
||
|
main_project->AddNativeTarget(
|
||
|
target->label().name(), "compiled.mach-o.executable",
|
||
|
target->output_name().empty() ? target->label().name()
|
||
|
: target->output_name(),
|
||
|
"com.apple.product-type.tool",
|
||
|
GetBuildScript(target->label().name(), ninja_extra_args,
|
||
|
env.get()));
|
||
|
break;
|
||
|
|
||
|
case Target::CREATE_BUNDLE: {
|
||
|
if (target->bundle_data().product_type().empty())
|
||
|
continue;
|
||
|
|
||
|
// For XCUITest, two CREATE_BUNDLE targets are generated:
|
||
|
// ${target_name}_runner and ${target_name}_module, however, Xcode
|
||
|
// requires only one target named ${target_name} to run tests.
|
||
|
if (IsXCUITestRunnerTarget(target))
|
||
|
continue;
|
||
|
std::string pbxtarget_name = target->label().name();
|
||
|
if (IsXCUITestModuleTarget(target)) {
|
||
|
std::string target_name = target->label().name();
|
||
|
pbxtarget_name = target_name.substr(
|
||
|
0, target_name.rfind(kXCTestModuleTargetNamePostfix));
|
||
|
}
|
||
|
|
||
|
PBXAttributes xcode_extra_attributes =
|
||
|
target->bundle_data().xcode_extra_attributes();
|
||
|
|
||
|
const std::string& target_output_name =
|
||
|
RebasePath(target->bundle_data()
|
||
|
.GetBundleRootDirOutput(target->settings())
|
||
|
.value(),
|
||
|
build_settings->build_dir());
|
||
|
PBXNativeTarget* native_target = main_project->AddNativeTarget(
|
||
|
pbxtarget_name, std::string(), target_output_name,
|
||
|
target->bundle_data().product_type(),
|
||
|
GetBuildScript(pbxtarget_name, ninja_extra_args, env.get()),
|
||
|
xcode_extra_attributes);
|
||
|
|
||
|
bundle_targets.push_back(target);
|
||
|
bundle_target_to_pbxtarget.insert(
|
||
|
std::make_pair(target, native_target));
|
||
|
|
||
|
if (!IsXCTestModuleTarget(target) && !IsXCUITestModuleTarget(target))
|
||
|
continue;
|
||
|
|
||
|
// For XCTest, test files are compiled into the application bundle.
|
||
|
// For XCUITest, test files are compiled into the test module bundle.
|
||
|
const Target* target_with_xctest_files = nullptr;
|
||
|
if (IsXCTestModuleTarget(target)) {
|
||
|
target_with_xctest_files = FindApplicationTargetByName(
|
||
|
target->bundle_data().xcode_test_application_name(), targets);
|
||
|
} else if (IsXCUITestModuleTarget(target)) {
|
||
|
target_with_xctest_files = target;
|
||
|
} else {
|
||
|
NOTREACHED();
|
||
|
}
|
||
|
|
||
|
SearchXCTestFilesForTarget(target_with_xctest_files,
|
||
|
&xctest_files_per_target);
|
||
|
const Target::FileList& xctest_file_list =
|
||
|
xctest_files_per_target[target_with_xctest_files];
|
||
|
|
||
|
// Add xctest files to the "Compiler Sources" of corresponding xctest
|
||
|
// and xcuitest native targets for proper indexing and for discovery of
|
||
|
// tests function.
|
||
|
AddXCTestFilesToTestModuleTarget(xctest_file_list, native_target,
|
||
|
main_project.get(), source_dir,
|
||
|
build_settings);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Adding the corresponding test application target as a dependency of xctest
|
||
|
// or xcuitest module target in the generated Xcode project so that the
|
||
|
// application target is re-compiled when compiling the test module target.
|
||
|
AddDependencyTargetForTestModuleTargets(
|
||
|
bundle_targets, bundle_target_to_pbxtarget, main_project.get());
|
||
|
|
||
|
projects_.push_back(std::move(main_project));
|
||
|
}
|
||
|
|
||
|
bool XcodeWriter::WriteFiles(const BuildSettings* build_settings, Err* err) {
|
||
|
for (const auto& project : projects_) {
|
||
|
if (!WriteProjectFile(build_settings, project.get(), err))
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
SourceFile xcworkspacedata_file =
|
||
|
build_settings->build_dir().ResolveRelativeFile(
|
||
|
Value(nullptr, name_ + ".xcworkspace/contents.xcworkspacedata"), err);
|
||
|
if (xcworkspacedata_file.is_null())
|
||
|
return false;
|
||
|
|
||
|
std::stringstream xcworkspacedata_string_out;
|
||
|
WriteWorkspaceContent(xcworkspacedata_string_out);
|
||
|
|
||
|
return WriteFileIfChanged(build_settings->GetFullPath(xcworkspacedata_file),
|
||
|
xcworkspacedata_string_out.str(), err);
|
||
|
}
|
||
|
|
||
|
bool XcodeWriter::WriteProjectFile(const BuildSettings* build_settings,
|
||
|
PBXProject* project,
|
||
|
Err* err) {
|
||
|
SourceFile pbxproj_file = build_settings->build_dir().ResolveRelativeFile(
|
||
|
Value(nullptr, project->Name() + ".xcodeproj/project.pbxproj"), err);
|
||
|
if (pbxproj_file.is_null())
|
||
|
return false;
|
||
|
|
||
|
std::stringstream pbxproj_string_out;
|
||
|
WriteProjectContent(pbxproj_string_out, project);
|
||
|
|
||
|
if (!WriteFileIfChanged(build_settings->GetFullPath(pbxproj_file),
|
||
|
pbxproj_string_out.str(), err))
|
||
|
return false;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void XcodeWriter::WriteWorkspaceContent(std::ostream& out) {
|
||
|
out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||
|
<< "<Workspace version = \"1.0\">\n";
|
||
|
for (const auto& project : projects_) {
|
||
|
out << " <FileRef location = \"group:" << project->Name()
|
||
|
<< ".xcodeproj\"></FileRef>\n";
|
||
|
}
|
||
|
out << "</Workspace>\n";
|
||
|
}
|
||
|
|
||
|
void XcodeWriter::WriteProjectContent(std::ostream& out, PBXProject* project) {
|
||
|
RecursivelyAssignIds(project);
|
||
|
|
||
|
out << "// !$*UTF8*$!\n"
|
||
|
<< "{\n"
|
||
|
<< "\tarchiveVersion = 1;\n"
|
||
|
<< "\tclasses = {\n"
|
||
|
<< "\t};\n"
|
||
|
<< "\tobjectVersion = 46;\n"
|
||
|
<< "\tobjects = {\n";
|
||
|
|
||
|
for (auto& pair : CollectPBXObjectsPerClass(project)) {
|
||
|
out << "\n"
|
||
|
<< "/* Begin " << ToString(pair.first) << " section */\n";
|
||
|
std::sort(pair.second.begin(), pair.second.end(),
|
||
|
[](const PBXObject* a, const PBXObject* b) {
|
||
|
return a->id() < b->id();
|
||
|
});
|
||
|
for (auto* object : pair.second) {
|
||
|
object->Print(out, 2);
|
||
|
}
|
||
|
out << "/* End " << ToString(pair.first) << " section */\n";
|
||
|
}
|
||
|
|
||
|
out << "\t};\n"
|
||
|
<< "\trootObject = " << project->Reference() << ";\n"
|
||
|
<< "}\n";
|
||
|
}
|