Index: tools/gn/xcode_writer.cc |
diff --git a/tools/gn/xcode_writer.cc b/tools/gn/xcode_writer.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..4168f7f068d5171746ecbe6b06d88a352f175868 |
--- /dev/null |
+++ b/tools/gn/xcode_writer.cc |
@@ -0,0 +1,432 @@ |
+// 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 { |
+ |
+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 GetArchs(const Args& args) { |
+ const Value* target_cpu_value = args.GetArgOverride(variables::kTargetCpu); |
+ if (target_cpu_value) { |
+ if (target_cpu_value->type() == Value::STRING) { |
+ if (target_cpu_value->string_value() == "x86") |
+ return "i386"; |
+ if (target_cpu_value->string_value() == "x64") |
+ return "x86_64"; |
+ if (target_cpu_value->string_value() == "arm") |
+ return "armv7"; |
+ if (target_cpu_value->string_value() == "armv7") |
+ return "armv7"; |
+ if (target_cpu_value->string_value() == "arm64") |
+ return "armv64"; |
+ } |
+ } |
+ return "x86_64"; |
+} |
+ |
+std::string GetBuildScript(const std::string& target_name, |
+ const std::string& build_path, |
+ const std::string& ninja_extra_args) { |
+ std::stringstream script; |
+ script << "echo note: \"Compile and copy " << target_name << " via ninja\"\n" |
+ << "exec "; |
+ if (!build_path.empty()) |
+ script << "env PATH=\"" << build_path << "\" "; |
+ 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(); |
+} |
+ |
+class CollectPBXObjectsPerClassHelper : public PBXObjectVisitor { |
+ public: |
+ CollectPBXObjectsPerClassHelper() {} |
+ |
+ 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, |
+ 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["ARCHS"] = GetArchs(build_settings->build_args()); |
+ attributes["SDKROOT"] = "macosx10.11"; |
+ 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, attributes, source_path, config_name, |
+ root_target_name, ninja_extra_args, |
+ target_os); |
+ |
+ workspace.CreateSourcesProject(all_targets, build_settings->build_dir(), |
+ attributes, source_path, config_name, |
+ target_os); |
+ |
+ return workspace.WriteFiles(build_settings, err); |
+} |
+ |
+XcodeWriter::XcodeWriter(const std::string& name) : name_(name) { |
+ if (name_.empty()) |
+ name_.assign("all"); |
+} |
+ |
+XcodeWriter::~XcodeWriter() {} |
+ |
+// 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 PBXAttributes& attributes, |
+ const std::string& source_path, |
+ const std::string& config_name, |
+ const std::string& root_target, |
+ const std::string& ninja_extra_args, |
+ TargetOsType target_os) { |
+ std::unique_ptr<PBXProject> main_project( |
+ new PBXProject("products", config_name, source_path, attributes)); |
+ |
+ std::string build_path; |
+ std::unique_ptr<base::Environment> env(base::Environment::Create()); |
+ env->GetVar("PATH", &build_path); |
+ |
+ main_project->AddAggregateTarget( |
+ "All", GetBuildScript(root_target, build_path, ninja_extra_args)); |
+ |
+ 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(), build_path, |
+ ninja_extra_args)); |
+ break; |
+ |
+ case Target::CREATE_BUNDLE: |
+ if (target->bundle_data().product_type().empty()) |
+ continue; |
+ |
+ main_project->AddNativeTarget( |
+ target->label().name(), std::string(), |
+ target->bundle_data() |
+ .GetBundleRootDirOutput(target->settings()) |
+ .Resolve(base::FilePath()) |
+ .AsUTF8Unsafe(), |
+ target->bundle_data().product_type(), |
+ GetBuildScript(target->label().name(), build_path, |
+ ninja_extra_args)); |
+ break; |
+ |
+ default: |
+ break; |
+ } |
+ } |
+ |
+ projects_.push_back(std::move(main_project)); |
+} |
+ |
+void XcodeWriter::CreateSourcesProject( |
+ const std::vector<const Target*>& targets, |
+ const SourceDir& root_build_dir, |
+ const PBXAttributes& attributes, |
+ const std::string& source_path, |
+ const std::string& config_name, |
+ TargetOsType target_os) { |
+ std::vector<SourceFile> sources; |
+ for (const Target* target : targets) { |
+ if (!target->settings()->is_default()) |
+ continue; |
+ |
+ for (const SourceFile& source : target->sources()) { |
+ if (source.is_system_absolute()) |
+ continue; |
+ |
+ if (IsStringInOutputDir(root_build_dir, source.value())) |
+ continue; |
+ |
+ sources.push_back(source); |
+ } |
+ } |
+ |
+ std::unique_ptr<PBXProject> sources_for_indexing( |
+ new PBXProject("sources", config_name, source_path, attributes)); |
+ |
+ // Sort sources to ensure determinisn 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) { |
+ base::FilePath source_path = source.Resolve(base::FilePath()); |
+ sources_for_indexing->AddSourceFile(source_path.AsUTF8Unsafe()); |
+ } |
+ |
+ projects_.push_back(std::move(sources_for_indexing)); |
+} |
+ |
+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 (const auto& object : pair.second) { |
+ object->Print(out, 2); |
+ } |
+ out << "/* End " << ToString(pair.first) << " section */\n"; |
+ } |
+ |
+ out << "\t};\n" |
+ << "\trootObject = " << project->Reference() << ";\n" |
+ << "}\n"; |
+} |