Fix generation of target in Xcode project when using output_name.

Target generating an application bundle whose name is different from
the target name needs to set productName and PRODUCT_NAME to the base
name of the application bundle (without .app) and set the name to the
target name otherwise Xcode refuses to launch the application.

BUG=297668

Review-Url: https://codereview.chromium.org/2005483002
Cr-Original-Commit-Position: refs/heads/master@{#395143}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 4db57d47a591423e7ecaf1bc965d35f094250e35
diff --git a/tools/gn/xcode_object.cc b/tools/gn/xcode_object.cc
index bc89944..3e9dd2c 100644
--- a/tools/gn/xcode_object.cc
+++ b/tools/gn/xcode_object.cc
@@ -8,10 +8,11 @@
 #include <sstream>
 #include <utility>
 
-#include "base/files/file_path.h"
 #include "base/logging.h"
+#include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_util.h"
+#include "tools/gn/filesystem_utils.h"
 
 // Helper methods -------------------------------------------------------------
 
@@ -89,67 +90,71 @@
   return buffer.str();
 }
 
-const char* GetSourceType(const base::FilePath::StringType& ext) {
-  std::map<base::FilePath::StringType, const char*> extension_map = {
-      {FILE_PATH_LITERAL(".a"), "archive.ar"},
-      {FILE_PATH_LITERAL(".app"), "wrapper.application"},
-      {FILE_PATH_LITERAL(".bdic"), "file"},
-      {FILE_PATH_LITERAL(".bundle"), "wrapper.cfbundle"},
-      {FILE_PATH_LITERAL(".c"), "sourcecode.c.c"},
-      {FILE_PATH_LITERAL(".cc"), "sourcecode.cpp.cpp"},
-      {FILE_PATH_LITERAL(".cpp"), "sourcecode.cpp.cpp"},
-      {FILE_PATH_LITERAL(".css"), "text.css"},
-      {FILE_PATH_LITERAL(".cxx"), "sourcecode.cpp.cpp"},
-      {FILE_PATH_LITERAL(".dart"), "sourcecode"},
-      {FILE_PATH_LITERAL(".dylib"), "compiled.mach-o.dylib"},
-      {FILE_PATH_LITERAL(".framework"), "wrapper.framework"},
-      {FILE_PATH_LITERAL(".h"), "sourcecode.c.h"},
-      {FILE_PATH_LITERAL(".hxx"), "sourcecode.cpp.h"},
-      {FILE_PATH_LITERAL(".icns"), "image.icns"},
-      {FILE_PATH_LITERAL(".java"), "sourcecode.java"},
-      {FILE_PATH_LITERAL(".js"), "sourcecode.javascript"},
-      {FILE_PATH_LITERAL(".kext"), "wrapper.kext"},
-      {FILE_PATH_LITERAL(".m"), "sourcecode.c.objc"},
-      {FILE_PATH_LITERAL(".mm"), "sourcecode.cpp.objcpp"},
-      {FILE_PATH_LITERAL(".nib"), "wrapper.nib"},
-      {FILE_PATH_LITERAL(".o"), "compiled.mach-o.objfile"},
-      {FILE_PATH_LITERAL(".pdf"), "image.pdf"},
-      {FILE_PATH_LITERAL(".pl"), "text.script.perl"},
-      {FILE_PATH_LITERAL(".plist"), "text.plist.xml"},
-      {FILE_PATH_LITERAL(".pm"), "text.script.perl"},
-      {FILE_PATH_LITERAL(".png"), "image.png"},
-      {FILE_PATH_LITERAL(".py"), "text.script.python"},
-      {FILE_PATH_LITERAL(".r"), "sourcecode.rez"},
-      {FILE_PATH_LITERAL(".rez"), "sourcecode.rez"},
-      {FILE_PATH_LITERAL(".s"), "sourcecode.asm"},
-      {FILE_PATH_LITERAL(".storyboard"), "file.storyboard"},
-      {FILE_PATH_LITERAL(".strings"), "text.plist.strings"},
-      {FILE_PATH_LITERAL(".swift"), "sourcecode.swift"},
-      {FILE_PATH_LITERAL(".ttf"), "file"},
-      {FILE_PATH_LITERAL(".xcassets"), "folder.assetcatalog"},
-      {FILE_PATH_LITERAL(".xcconfig"), "text.xcconfig"},
-      {FILE_PATH_LITERAL(".xcdatamodel"), "wrapper.xcdatamodel"},
-      {FILE_PATH_LITERAL(".xcdatamodeld"), "wrapper.xcdatamodeld"},
-      {FILE_PATH_LITERAL(".xib"), "file.xib"},
-      {FILE_PATH_LITERAL(".y"), "sourcecode.yacc"},
-  };
+struct SourceTypeForExt {
+  const char* ext;
+  const char* source_type;
+};
 
-  const auto& iter = extension_map.find(ext);
-  if (iter != extension_map.end()) {
-    return iter->second;
+const SourceTypeForExt kSourceTypeForExt[] = {
+    {".a", "archive.ar"},
+    {".app", "wrapper.application"},
+    {".bdic", "file"},
+    {".bundle", "wrapper.cfbundle"},
+    {".c", "sourcecode.c.c"},
+    {".cc", "sourcecode.cpp.cpp"},
+    {".cpp", "sourcecode.cpp.cpp"},
+    {".css", "text.css"},
+    {".cxx", "sourcecode.cpp.cpp"},
+    {".dart", "sourcecode"},
+    {".dylib", "compiled.mach-o.dylib"},
+    {".framework", "wrapper.framework"},
+    {".h", "sourcecode.c.h"},
+    {".hxx", "sourcecode.cpp.h"},
+    {".icns", "image.icns"},
+    {".java", "sourcecode.java"},
+    {".js", "sourcecode.javascript"},
+    {".kext", "wrapper.kext"},
+    {".m", "sourcecode.c.objc"},
+    {".mm", "sourcecode.cpp.objcpp"},
+    {".nib", "wrapper.nib"},
+    {".o", "compiled.mach-o.objfile"},
+    {".pdf", "image.pdf"},
+    {".pl", "text.script.perl"},
+    {".plist", "text.plist.xml"},
+    {".pm", "text.script.perl"},
+    {".png", "image.png"},
+    {".py", "text.script.python"},
+    {".r", "sourcecode.rez"},
+    {".rez", "sourcecode.rez"},
+    {".s", "sourcecode.asm"},
+    {".storyboard", "file.storyboard"},
+    {".strings", "text.plist.strings"},
+    {".swift", "sourcecode.swift"},
+    {".ttf", "file"},
+    {".xcassets", "folder.assetcatalog"},
+    {".xcconfig", "text.xcconfig"},
+    {".xcdatamodel", "wrapper.xcdatamodel"},
+    {".xcdatamodeld", "wrapper.xcdatamodeld"},
+    {".xib", "file.xib"},
+    {".y", "sourcecode.yacc"},
+};
+
+const char* GetSourceType(const base::StringPiece& ext) {
+  for (size_t i = 0; i < arraysize(kSourceTypeForExt); ++i) {
+    if (kSourceTypeForExt[i].ext == ext)
+      return kSourceTypeForExt[i].source_type;
   }
 
   return "text";
 }
 
-bool HasExplicitFileType(const base::FilePath::StringType& ext) {
-  return ext == FILE_PATH_LITERAL(".dart");
+bool HasExplicitFileType(const base::StringPiece& ext) {
+  return ext == ".dart";
 }
 
-bool IsSourceFileForIndexing(const base::FilePath::StringType& ext) {
-  return ext == FILE_PATH_LITERAL(".c") || ext == FILE_PATH_LITERAL(".cc") ||
-         ext == FILE_PATH_LITERAL(".cpp") || ext == FILE_PATH_LITERAL(".cxx") ||
-         ext == FILE_PATH_LITERAL(".m") || ext == FILE_PATH_LITERAL(".mm");
+bool IsSourceFileForIndexing(const base::StringPiece& ext) {
+  return ext == ".c" || ext == ".cc" || ext == ".cpp" || ext == ".cxx" ||
+         ext == ".m" || ext == ".mm";
 }
 
 void PrintValue(std::ostream& out, IndentRules rules, unsigned value) {
@@ -411,9 +416,7 @@
     PrintProperty(out, rules, "explicitFileType", type_);
     PrintProperty(out, rules, "includeInIndex", 0u);
   } else {
-    const base::FilePath::StringType ext =
-        base::FilePath::FromUTF8Unsafe(path_).Extension();
-
+    base::StringPiece ext = FindExtension(&path_);
     if (HasExplicitFileType(ext))
       PrintProperty(out, rules, "explicitFileType", GetSourceType(ext));
     else
@@ -539,10 +542,12 @@
                                  const std::string& config_name,
                                  const PBXAttributes& attributes,
                                  const std::string& product_type,
+                                 const std::string& product_name,
                                  const PBXFileReference* product_reference)
     : PBXTarget(name, shell_script, config_name, attributes),
       product_reference_(product_reference),
-      product_type_(product_type) {
+      product_type_(product_type),
+      product_name_(product_name) {
   DCHECK(product_reference_);
   build_phases_.push_back(base::WrapUnique(new PBXSourcesBuildPhase));
   source_build_phase_ =
@@ -574,7 +579,7 @@
   PrintProperty(out, rules, "buildRules", EmptyPBXObjectVector());
   PrintProperty(out, rules, "dependencies", EmptyPBXObjectVector());
   PrintProperty(out, rules, "name", name_);
-  PrintProperty(out, rules, "productName", name_);
+  PrintProperty(out, rules, "productName", product_name_);
   PrintProperty(out, rules, "productReference", product_reference_);
   PrintProperty(out, rules, "productType", product_type_);
   out << indent_str << "};\n";
@@ -603,8 +608,7 @@
 
 void PBXProject::AddSourceFile(const std::string& source_path) {
   PBXFileReference* file_reference = sources_->AddSourceFile(source_path);
-  const base::FilePath::StringType ext =
-      base::FilePath::FromUTF8Unsafe(source_path).Extension();
+  base::StringPiece ext = FindExtension(&source_path);
   if (!IsSourceFileForIndexing(ext))
     return;
 
@@ -621,7 +625,7 @@
     const char product_type[] = "com.apple.product-type.tool";
     targets_.push_back(base::WrapUnique(
         new PBXNativeTarget(name_, std::string(), config_name_, attributes,
-                            product_type, product_reference)));
+                            product_type, name_, product_reference)));
     target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
   }
 
@@ -645,20 +649,25 @@
                                  const std::string& output_name,
                                  const std::string& output_type,
                                  const std::string& shell_script) {
-  const base::FilePath::StringType ext =
-      base::FilePath::FromUTF8Unsafe(output_name).Extension();
+  base::StringPiece ext = FindExtension(&output_name);
+  PBXFileReference* product =
+      static_cast<PBXFileReference*>(products_->AddChild(base::WrapUnique(
+          new PBXFileReference(std::string(), output_name,
+                               type.empty() ? GetSourceType(ext) : type))));
 
-  PBXFileReference* product = static_cast<PBXFileReference*>(
-      products_->AddChild(base::WrapUnique(new PBXFileReference(
-          name, output_name, type.empty() ? GetSourceType(ext) : type))));
+  size_t ext_offset = FindExtensionOffset(output_name);
+  std::string product_name = ext_offset != std::string::npos
+                                 ? output_name.substr(0, ext_offset - 1)
+                                 : output_name;
 
   PBXAttributes attributes;
   attributes["CODE_SIGNING_REQUIRED"] = "NO";
   attributes["CONFIGURATION_BUILD_DIR"] = ".";
-  attributes["PRODUCT_NAME"] = name;
+  attributes["PRODUCT_NAME"] = product_name;
 
-  targets_.push_back(base::WrapUnique(new PBXNativeTarget(
-      name, shell_script, config_name_, attributes, output_type, product)));
+  targets_.push_back(base::WrapUnique(
+      new PBXNativeTarget(name, shell_script, config_name_, attributes,
+                          output_type, product_name, product)));
 }
 
 void PBXProject::SetProjectDirPath(const std::string& project_dir_path) {
diff --git a/tools/gn/xcode_object.h b/tools/gn/xcode_object.h
index 25f2415..2ad43cc 100644
--- a/tools/gn/xcode_object.h
+++ b/tools/gn/xcode_object.h
@@ -243,6 +243,7 @@
                   const std::string& config_name,
                   const PBXAttributes& attributes,
                   const std::string& product_type,
+                  const std::string& product_name,
                   const PBXFileReference* product_reference);
   ~PBXNativeTarget() override;
 
@@ -255,6 +256,7 @@
  private:
   const PBXFileReference* product_reference_;
   std::string product_type_;
+  std::string product_name_;
 
   DISALLOW_COPY_AND_ASSIGN(PBXNativeTarget);
 };
diff --git a/tools/gn/xcode_writer.cc b/tools/gn/xcode_writer.cc
index 4168f7f..10624fa 100644
--- a/tools/gn/xcode_writer.cc
+++ b/tools/gn/xcode_writer.cc
@@ -189,7 +189,7 @@
   XcodeWriter workspace(workspace_name);
   workspace.CreateProductsProject(targets, attributes, source_path, config_name,
                                   root_target_name, ninja_extra_args,
-                                  target_os);
+                                  build_settings, target_os);
 
   workspace.CreateSourcesProject(all_targets, build_settings->build_dir(),
                                  attributes, source_path, config_name,
@@ -266,6 +266,7 @@
     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));
@@ -298,10 +299,10 @@
 
         main_project->AddNativeTarget(
             target->label().name(), std::string(),
-            target->bundle_data()
-                .GetBundleRootDirOutput(target->settings())
-                .Resolve(base::FilePath())
-                .AsUTF8Unsafe(),
+            RebasePath(target->bundle_data()
+                           .GetBundleRootDirOutput(target->settings())
+                           .value(),
+                       build_settings->build_dir()),
             target->bundle_data().product_type(),
             GetBuildScript(target->label().name(), build_path,
                            ninja_extra_args));
@@ -347,9 +348,10 @@
   std::sort(sources.begin(), sources.end());
   sources.erase(std::unique(sources.begin(), sources.end()), sources.end());
 
+  SourceDir source_dir("//");
   for (const SourceFile& source : sources) {
-    base::FilePath source_path = source.Resolve(base::FilePath());
-    sources_for_indexing->AddSourceFile(source_path.AsUTF8Unsafe());
+    std::string source_file = RebasePath(source.value(), source_dir);
+    sources_for_indexing->AddSourceFile(source_file);
   }
 
   projects_.push_back(std::move(sources_for_indexing));
diff --git a/tools/gn/xcode_writer.h b/tools/gn/xcode_writer.h
index dd162c6..8c4e304 100644
--- a/tools/gn/xcode_writer.h
+++ b/tools/gn/xcode_writer.h
@@ -22,6 +22,10 @@
 using PBXAttributes = std::map<std::string, std::string>;
 class PBXProject;
 
+namespace base {
+class FilePath;
+}
+
 class XcodeWriter {
  public:
   enum TargetOsType {
@@ -69,6 +73,7 @@
                              const std::string& config_name,
                              const std::string& root_target,
                              const std::string& ninja_extra_args,
+                             const BuildSettings* build_settings,
                              TargetOsType target_os);
 
   // Generates the "sources.xcodeproj" project that reference all source