Associate xctest files with corresponding xctest targets.

Previously, our generated Xcode project has the issue that Xcode couldn't find
our xctests. And to fix it, one must find the list of xctest files under each of
the application targets and then associate them with the corresponding xctest
target.

This CL associates xctest files with xctest targets and because our compilation
is done via ninja, it also adds '-help' as per file compiler flags to each
xctest file reference to prevent Xcode from trying to compile the xctest files.

BUG=614818

Review-Url: https://codereview.chromium.org/2616773003
Cr-Original-Commit-Position: refs/heads/master@{#442120}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 4ce240b3ecb8ff7d7b902f2ccdf361411915610a
diff --git a/tools/gn/xcode_object.cc b/tools/gn/xcode_object.cc
index eeae7e4..9854095 100644
--- a/tools/gn/xcode_object.cc
+++ b/tools/gn/xcode_object.cc
@@ -487,6 +487,19 @@
   DCHECK(!source_path.empty());
   std::string::size_type sep = navigator_path.find("/");
   if (sep == std::string::npos) {
+    // Prevent same file reference being created and added multiple times.
+    for (const auto& child : children_) {
+      if (child->Class() != PBXFileReferenceClass)
+        continue;
+
+      PBXFileReference* child_as_file_reference =
+          static_cast<PBXFileReference*>(child.get());
+      if (child_as_file_reference->Name() == navigator_path &&
+          child_as_file_reference->path() == source_path) {
+        return child_as_file_reference;
+      }
+    }
+
     children_.push_back(base::MakeUnique<PBXFileReference>(
         navigator_path, source_path, std::string()));
     return static_cast<PBXFileReference*>(children_.back().get());
@@ -675,12 +688,13 @@
   target_for_indexing_ = static_cast<PBXNativeTarget*>(targets_.back().get());
 }
 
-void PBXProject::AddNativeTarget(const std::string& name,
-                                 const std::string& type,
-                                 const std::string& output_name,
-                                 const std::string& output_type,
-                                 const std::string& shell_script,
-                                 const PBXAttributes& extra_attributes) {
+PBXNativeTarget* PBXProject::AddNativeTarget(
+    const std::string& name,
+    const std::string& type,
+    const std::string& output_name,
+    const std::string& output_type,
+    const std::string& shell_script,
+    const PBXAttributes& extra_attributes) {
   base::StringPiece ext = FindExtension(&output_name);
   PBXFileReference* product = static_cast<PBXFileReference*>(
       products_->AddChild(base::MakeUnique<PBXFileReference>(
@@ -700,6 +714,7 @@
   targets_.push_back(base::MakeUnique<PBXNativeTarget>(
       name, shell_script, config_name_, attributes, output_type, product_name,
       product));
+  return static_cast<PBXNativeTarget*>(targets_.back().get());
 }
 
 void PBXProject::SetProjectDirPath(const std::string& project_dir_path) {
diff --git a/tools/gn/xcode_object.h b/tools/gn/xcode_object.h
index 3454e85..a6ab32a 100644
--- a/tools/gn/xcode_object.h
+++ b/tools/gn/xcode_object.h
@@ -190,6 +190,8 @@
   std::string Name() const override;
   void Print(std::ostream& out, unsigned indent) const override;
 
+  const std::string& path() const { return path_; }
+
  private:
   std::string name_;
   std::string path_;
@@ -293,12 +295,13 @@
   void AddAggregateTarget(const std::string& name,
                           const std::string& shell_script);
   void AddIndexingTarget();
-  void AddNativeTarget(const std::string& name,
-                       const std::string& type,
-                       const std::string& output_name,
-                       const std::string& output_type,
-                       const std::string& shell_script,
-                       const PBXAttributes& extra_attributes = PBXAttributes());
+  PBXNativeTarget* AddNativeTarget(
+      const std::string& name,
+      const std::string& type,
+      const std::string& output_name,
+      const std::string& output_type,
+      const std::string& shell_script,
+      const PBXAttributes& extra_attributes = PBXAttributes());
 
   void SetProjectDirPath(const std::string& project_dir_path);
   void SetProjectRoot(const std::string& project_root);
diff --git a/tools/gn/xcode_writer.cc b/tools/gn/xcode_writer.cc
index 5030b5a..242f0b0 100644
--- a/tools/gn/xcode_writer.cc
+++ b/tools/gn/xcode_writer.cc
@@ -32,10 +32,16 @@
 namespace {
 
 using TargetToFileList = std::unordered_map<const Target*, Target::FileList>;
+using TargetToNativeTarget =
+    std::unordered_map<const Target*, PBXNativeTarget*>;
+using FileToTargets = std::map<SourceFile,
+                               std::vector<const Target*>,
+                               bool (*)(const SourceFile&, const SourceFile&)>;
 
 const char kEarlGreyFileNameIdentifier[] = "egtest.mm";
 const char kXCTestFileNameIdentifier[] = "xctest.mm";
 const char kXCTestModuleTargetNamePostfix[] = "_module";
+const char kXCTestFileReferenceFolder[] = "xctests/";
 
 struct SafeEnvironmentVariableInfo {
   const char* name;
@@ -47,6 +53,15 @@
     {"USER", true}, {"TMPDIR", false},
 };
 
+bool CompareSourceFiles(const SourceFile& lhs, const SourceFile& rhs) {
+  if (lhs.GetName() < rhs.GetName())
+    return true;
+  else if (lhs.GetName() > rhs.GetName())
+    return false;
+  else
+    return lhs.value() < rhs.value();
+}
+
 XcodeWriter::TargetOsType GetTargetOs(const Args& args) {
   const Value* target_os_value = args.GetArgOverride(variables::kTargetOs);
   if (target_os_value) {
@@ -193,6 +208,31 @@
   }
 }
 
+// Maps each xctest file to a list of xctest application targets that contains
+// the file.
+void MapXCTestFileToApplicationTargets(
+    const std::vector<const Target*>& xctest_application_targets,
+    const std::vector<Target::FileList>& xctest_file_lists,
+    FileToTargets* xctest_file_to_application_targets) {
+  DCHECK_EQ(xctest_application_targets.size(), xctest_file_lists.size());
+
+  for (size_t i = 0; i < xctest_application_targets.size(); ++i) {
+    const Target* xctest_application_target = xctest_application_targets[i];
+    DCHECK(IsApplicationTarget(xctest_application_target));
+
+    for (const SourceFile& source : xctest_file_lists[i]) {
+      auto iter = xctest_file_to_application_targets->find(source);
+      if (iter == xctest_file_to_application_targets->end()) {
+        iter =
+            xctest_file_to_application_targets
+                ->insert(std::make_pair(source, std::vector<const Target*>()))
+                .first;
+      }
+      iter->second.push_back(xctest_application_target);
+    }
+  }
+}
+
 class CollectPBXObjectsPerClassHelper : public PBXObjectVisitor {
  public:
   CollectPBXObjectsPerClassHelper() {}
@@ -417,6 +457,8 @@
   main_project->AddAggregateTarget(
       "All", GetBuildScript(root_target, ninja_extra_args, env.get()));
 
+  TargetToNativeTarget xctest_application_to_module_native_target;
+
   for (const Target* target : targets) {
     switch (target->output_type()) {
       case Target::EXECUTABLE:
@@ -447,7 +489,7 @@
           extra_attributes["DEBUG_INFORMATION_FORMAT"] = "dwarf";
         }
 
-        main_project->AddNativeTarget(
+        PBXNativeTarget* native_target = main_project->AddNativeTarget(
             target->label().name(), std::string(),
             RebasePath(target->bundle_data()
                            .GetBundleRootDirOutput(target->settings())
@@ -456,6 +498,17 @@
             target->bundle_data().product_type(),
             GetBuildScript(target->label().name(), ninja_extra_args, env.get()),
             extra_attributes);
+
+        if (!IsXCTestModuleTarget(target))
+          continue;
+
+        // Populate |xctest_application_to_module_native_target| for XCTest
+        // module targets.
+        const Target* application_target =
+            FindXCTestApplicationTarget(target, xctest_application_targets);
+        xctest_application_to_module_native_target.insert(
+            std::make_pair(application_target, native_target));
+
         break;
       }
 
@@ -464,6 +517,34 @@
     }
   }
 
+  FileToTargets xctest_file_to_application_targets(CompareSourceFiles);
+  MapXCTestFileToApplicationTargets(xctest_application_targets,
+                                    xctest_file_lists,
+                                    &xctest_file_to_application_targets);
+
+  // Add xctest files to the "Compiler Sources" of corresponding xctest native
+  // targets.
+  SourceDir source_dir("//");
+  for (const auto& item : xctest_file_to_application_targets) {
+    const SourceFile& source = item.first;
+    for (const Target* xctest_application_target : item.second) {
+      std::string navigator_path =
+          kXCTestFileReferenceFolder + source.GetName();
+      std::string source_path = RebasePath(source.value(), source_dir,
+                                           build_settings->root_path_utf8());
+      PBXNativeTarget* xctest_module_native_target =
+          xctest_application_to_module_native_target[xctest_application_target];
+
+      // Test files need to be known to Xcode for proper indexing and for
+      // discovery of tests function for XCTest, but the compilation is done
+      // via ninja and thus must prevent Xcode from compiling the files by
+      // adding '-help' as per file compiler flag.
+      main_project->AddSourceFile(navigator_path, source_path,
+                                  CompilerFlags::HELP,
+                                  xctest_module_native_target);
+    }
+  }
+
   projects_.push_back(std::move(main_project));
 }