|  | // 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* kXCTestFileSuffixes[] = { | 
|  | "egtest.m", | 
|  | "egtest.mm", | 
|  | "xctest.m", | 
|  | "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}, | 
|  | {"ICECC_VERSION", true}, | 
|  | {"ICECC_CLANG_REMOTE_CPP", true}}; | 
|  |  | 
|  | 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) { | 
|  | std::string file_name = file.GetName(); | 
|  | for (size_t i = 0; i < arraysize(kXCTestFileSuffixes); ++i) { | 
|  | if (base::EndsWith(file_name, kXCTestFileSuffixes[i], | 
|  | base::CompareCase::SENSITIVE)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | 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"; | 
|  | } |