Chromium Code Reviews| 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..d09957ff0e370545b4bbd1a4a6866f0c11529abe |
| --- /dev/null |
| +++ b/tools/gn/xcode_writer.cc |
| @@ -0,0 +1,425 @@ |
| +// 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 "third_party/re2/src/re2/re2.h" |
| +#include "tools/gn/args.h" |
| +#include "tools/gn/build_settings.h" |
| +#include "tools/gn/builder.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 { |
|
brettw
2016/04/28 21:16:17
Blank line after here.
sdefresne
2016/04/30 11:25:03
Done.
|
| +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++) { |
|
brettw
2016/04/28 21:16:17
No {}
sdefresne
2016/04/30 11:25:03
Done.
|
| + 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 |
|
brettw
2016/04/28 21:16:17
Blank line above here.
sdefresne
2016/04/30 11:25:03
Done.
|
| + |
| +// static |
| +bool XcodeWriter::RunAndWriteFiles(const std::string& workspace_name, |
| + const std::string& root_target_name, |
| + const std::string& ninja_extra_args, |
| + const std::string& target_filter_pattern, |
| + 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 = builder->GetAllResolvedTargets(); |
| + |
| + XcodeWriter workspace(workspace_name); |
| + workspace.CreateMainProject(targets, attributes, workspace.name_, source_path, |
| + config_name, root_target_name, ninja_extra_args, |
| + target_filter_pattern, target_os); |
| + |
| + workspace.CreateSourceForIndexingProject(targets, build_settings->build_dir(), |
| + attributes, "sources_for_indexing", |
| + 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 |
| +std::vector<const Target*> XcodeWriter::FilterTargets( |
| + const std::vector<const Target*>& all_targets, |
| + const std::string& target_filter_pattern) { |
| + std::vector<const Target*> targets; |
| + targets.reserve(all_targets.size()); |
| + |
| + std::unique_ptr<RE2> pattern; |
|
brettw
2016/04/28 21:16:17
Can you use GN "label_pattern"s here instead? This
sdefresne
2016/04/30 11:25:03
Done.
|
| + if (!target_filter_pattern.empty()) |
| + pattern.reset(new RE2(target_filter_pattern)); |
| + |
| + for (const Target* target : all_targets) { |
| + if (!target->settings()->is_default()) |
| + continue; |
| + |
| + if (target->output_type() != Target::CREATE_BUNDLE && |
| + target->output_type() != Target::EXECUTABLE) { |
| + continue; |
| + } |
| + |
| + if (pattern && !RE2::PartialMatch(target->label().name(), *pattern.get())) |
| + continue; |
| + |
| + targets.push_back(target); |
| + } |
| + |
| + std::sort(targets.begin(), targets.end()); |
|
brettw
2016/04/28 21:16:17
Can you add a comment here about what you're doing
sdefresne
2016/04/30 11:25:03
Done.
|
| + 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; |
| + |
| + const auto& iter = |
| + std::lower_bound(targets.begin(), targets.end(), pair.ptr); |
| + |
| + if (iter != targets.end() && *iter == pair.ptr) |
| + targets.erase(iter); |
| + } |
| + } |
| + |
| + std::sort(targets.begin(), targets.end(), |
| + [](const Target* a, const Target* b) { |
| + return a->label().name() < b->label().name(); |
| + }); |
| + |
| + return targets; |
| +} |
| + |
| +void XcodeWriter::CreateMainProject(const std::vector<const Target*>& targets, |
| + const PBXAttributes& attributes, |
| + const std::string& project_name, |
| + const std::string& source_path, |
| + const std::string& config_name, |
| + const std::string& root_target, |
| + const std::string& ninja_extra_args, |
| + const std::string& target_filter_pattern, |
| + TargetOsType target_os) { |
| + std::unique_ptr<PBXProject> main_project( |
| + new PBXProject(project_name, 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)); |
| + |
| + const std::vector<const Target*> filtered_targets = |
| + XcodeWriter::FilterTargets(targets, target_filter_pattern); |
| + for (const Target* target : filtered_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::CreateSourceForIndexingProject( |
| + const std::vector<const Target*>& targets, |
| + const SourceDir& root_build_dir, |
| + const PBXAttributes& attributes, |
| + const std::string& project_name, |
| + 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(project_name, config_name, source_path, attributes)); |
| + |
| + std::sort(sources.begin(), 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"; |
| +} |