Improve generation of Xcode project

Use relative paths in PBXFileReference instead of full paths, which
reduces the size of the generated Xcode (from 19Mb to 16Mb for the
Chromium project).

Ensure that the "Products" group is the last item in the main group
and add "productRefGroup" property to the main project object. Both
together allow Xcode to hide it thus reducing visual clutter in the
application.

List the source file directly from the main group object, removing
the "Source" group, and one level of folder in the Xcode UI further
reducing visual clutter (this rely on Xcode hiding the "Products"
group).

Bug: chromium: 1331345
Change-Id: Ia6734af8451e82fe3d01ae0ac4ef0c7ff56dd0d2
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/14141
Reviewed-by: Brett Wilson <brettw@chromium.org>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/src/gn/xcode_object.cc b/src/gn/xcode_object.cc
index 4f2b66b..2bd65e3 100644
--- a/src/gn/xcode_object.cc
+++ b/src/gn/xcode_object.cc
@@ -138,8 +138,8 @@
     {"xcdatamodel", "wrapper.xcdatamodel"},
     {"xcdatamodeld", "wrapper.xcdatamodeld"},
     {"xctest", "wrapper.cfbundle"},
-    {"xpc", "wrapper.xpc-service"},
     {"xib", "file.xib"},
+    {"xpc", "wrapper.xpc-service"},
     {"y", "sourcecode.yacc"},
 };
 
@@ -254,19 +254,26 @@
 struct PBXGroupComparator {
   using PBXObjectPtr = std::unique_ptr<PBXObject>;
   bool operator()(const PBXObjectPtr& lhs, const PBXObjectPtr& rhs) {
+    if (lhs.get() == rhs.get())
+      return false;
+
+    // Ensure that PBXGroup that should sort last are sorted last.
+    const bool lhs_sort_last = SortLast(lhs);
+    const bool rhs_sort_last = SortLast(rhs);
+    if (lhs_sort_last != rhs_sort_last)
+      return rhs_sort_last;
+
     if (lhs->Class() != rhs->Class())
       return rhs->Class() < lhs->Class();
 
-    if (lhs->Class() == PBXGroupClass) {
-      PBXGroup* lhs_group = static_cast<PBXGroup*>(lhs.get());
-      PBXGroup* rhs_group = static_cast<PBXGroup*>(rhs.get());
-      return lhs_group->name() < rhs_group->name();
-    }
+    return lhs->Name() < rhs->Name();
+  }
 
-    DCHECK_EQ(lhs->Class(), PBXFileReferenceClass);
-    PBXFileReference* lhs_file = static_cast<PBXFileReference*>(lhs.get());
-    PBXFileReference* rhs_file = static_cast<PBXFileReference*>(rhs.get());
-    return lhs_file->Name() < rhs_file->Name();
+  bool SortLast(const PBXObjectPtr& ptr) {
+    if (ptr->Class() != PBXGroupClass)
+      return false;
+
+    return static_cast<PBXGroup*>(ptr.get())->SortLast();
   }
 };
 }  // namespace
@@ -535,10 +542,9 @@
     PrintProperty(out, rules, "includeInIndex", 0u);
   } else {
     std::string_view ext = FindExtension(&name_);
-    if (HasExplicitFileType(ext))
-      PrintProperty(out, rules, "explicitFileType", GetSourceType(ext));
-    else
-      PrintProperty(out, rules, "lastKnownFileType", GetSourceType(ext));
+    const char* prop_name =
+        HasExplicitFileType(ext) ? "explicitFileType" : "lastKnownFileType";
+    PrintProperty(out, rules, prop_name, GetSourceType(ext));
   }
 
   if (!name_.empty() && name_ != path_)
@@ -587,6 +593,7 @@
                                           const std::string& source_path) {
   DCHECK(!navigator_path.empty());
   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.
@@ -597,12 +604,12 @@
       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) {
+          child_as_file_reference->path() == navigator_path) {
         return child_as_file_reference;
       }
     }
 
-    return CreateChild<PBXFileReference>(navigator_path, source_path,
+    return CreateChild<PBXFileReference>(navigator_path, navigator_path,
                                          std::string());
   }
 
@@ -661,28 +668,47 @@
   out << indent_str << Reference() << " = {\n";
   PrintProperty(out, rules, "isa", ToString(Class()));
   PrintProperty(out, rules, "children", children_);
-  if (!name_.empty())
+  if (!name_.empty() && name_ != path_)
     PrintProperty(out, rules, "name", name_);
-  if (is_source_ && !path_.empty())
+  if (!path_.empty())
     PrintProperty(out, rules, "path", path_);
   PrintProperty(out, rules, "sourceTree", "<group>");
   out << indent_str << "};\n";
 }
 
+bool PBXGroup::SortLast() const {
+  return false;
+}
+
 PBXObject* PBXGroup::AddChildImpl(std::unique_ptr<PBXObject> child) {
   DCHECK(child);
   DCHECK(child->Class() == PBXGroupClass ||
          child->Class() == PBXFileReferenceClass);
 
-  PBXObject* child_ptr = child.get();
-  if (autosorted()) {
-    auto iter = std::lower_bound(children_.begin(), children_.end(), child,
-                                 PBXGroupComparator());
-    children_.insert(iter, std::move(child));
-  } else {
-    children_.push_back(std::move(child));
-  }
-  return child_ptr;
+  auto iter = std::lower_bound(children_.begin(), children_.end(), child,
+                               PBXGroupComparator());
+  return children_.insert(iter, std::move(child))->get();
+}
+
+// PBXMainGroup ---------------------------------------------------------------
+
+PBXMainGroup::PBXMainGroup(const std::string& source_path)
+    : PBXGroup(source_path, std::string()) {}
+
+PBXMainGroup::~PBXMainGroup() = default;
+
+std::string PBXMainGroup::Name() const {
+  return std::string();
+}
+
+// PBXProductsGroup -----------------------------------------------------------
+
+PBXProductsGroup::PBXProductsGroup() : PBXGroup(std::string(), "Products") {}
+
+PBXProductsGroup::~PBXProductsGroup() = default;
+
+bool PBXProductsGroup::SortLast() const {
+  return true;
 }
 
 // PBXNativeTarget ------------------------------------------------------------
@@ -751,13 +777,8 @@
                        const std::string& source_path,
                        const PBXAttributes& attributes)
     : name_(name), config_name_(config_name), target_for_indexing_(nullptr) {
-  main_group_ = std::make_unique<PBXGroup>();
-  main_group_->set_autosorted(false);
-
-  sources_ = main_group_->CreateChild<PBXGroup>(source_path, "Source");
-  sources_->set_is_source(true);
-
-  products_ = main_group_->CreateChild<PBXGroup>(std::string(), "Products");
+  main_group_ = std::make_unique<PBXMainGroup>(source_path);
+  products_ = main_group_->CreateChild<PBXProductsGroup>();
 
   configurations_ =
       std::make_unique<XCConfigurationList>(config_name, attributes, this);
@@ -778,7 +799,7 @@
                                const std::string& source_path,
                                PBXNativeTarget* target) {
   PBXFileReference* file_reference =
-      sources_->AddSourceFile(navigator_path, source_path);
+      main_group_->AddSourceFile(navigator_path, source_path);
   std::string_view ext = FindExtension(&source_path);
   if (!IsSourceFileForIndexing(ext))
     return;
@@ -805,7 +826,7 @@
   attributes["CLANG_ENABLE_OBJC_WEAK"] = "YES";
   attributes["CODE_SIGNING_REQUIRED"] = "NO";
   attributes["EXECUTABLE_PREFIX"] = "";
-  attributes["HEADER_SEARCH_PATHS"] = sources_->path();
+  attributes["HEADER_SEARCH_PATHS"] = main_group_->path();
   attributes["PRODUCT_NAME"] = "sources";
 
   PBXFileReference* product_reference =
@@ -914,6 +935,7 @@
   PrintProperty(out, rules, "knownRegions",
                 std::vector<std::string>({"en", "Base"}));
   PrintProperty(out, rules, "mainGroup", main_group_);
+  PrintProperty(out, rules, "productRefGroup", products_);
   PrintProperty(out, rules, "projectDirPath", project_dir_path_);
   PrintProperty(out, rules, "projectRoot", project_root_);
   PrintProperty(out, rules, "targets", targets_);
diff --git a/src/gn/xcode_object.h b/src/gn/xcode_object.h
index e29eb3d..544c17c 100644
--- a/src/gn/xcode_object.h
+++ b/src/gn/xcode_object.h
@@ -284,12 +284,6 @@
   PBXFileReference* AddSourceFile(const std::string& navigator_path,
                                   const std::string& source_path);
 
-  bool is_source() const { return is_source_; }
-  void set_is_source(bool is_source) { is_source_ = is_source; }
-
-  bool autosorted() const { return autosorted_; }
-  void set_autosorted(bool autosorted) { autosorted_ = autosorted; }
-
   template <typename T, typename... Args>
   T* CreateChild(Args&&... args) {
     return static_cast<T*>(
@@ -303,19 +297,43 @@
   void Visit(PBXObjectVisitorConst& visitor) const override;
   void Print(std::ostream& out, unsigned indent) const override;
 
+  // Returns whether the current PBXGroup should sort last when sorting
+  // children of a PBXGroup. This should only be used for the "Products"
+  // group which is hidden in Xcode UI when it is the last children of
+  // the main PBXProject group.
+  virtual bool SortLast() const;
+
  private:
   PBXObject* AddChildImpl(std::unique_ptr<PBXObject> child);
 
   std::vector<std::unique_ptr<PBXObject>> children_;
   std::string name_;
   std::string path_;
-  bool is_source_ = false;
-  bool autosorted_ = true;
 
   PBXGroup(const PBXGroup&) = delete;
   PBXGroup& operator=(const PBXGroup&) = delete;
 };
 
+// PBXMainGroup ---------------------------------------------------------------
+
+class PBXMainGroup : public PBXGroup {
+ public:
+  explicit PBXMainGroup(const std::string& source_path);
+  ~PBXMainGroup() override;
+
+  std::string Name() const override;
+};
+
+// PBXProductsGroup -----------------------------------------------------------
+
+class PBXProductsGroup : public PBXGroup {
+ public:
+  explicit PBXProductsGroup();
+  ~PBXProductsGroup() override;
+
+  bool SortLast() const override;
+};
+
 // PBXNativeTarget ------------------------------------------------------------
 
 class PBXNativeTarget : public PBXTarget {
@@ -395,7 +413,6 @@
   std::string name_;
   std::string config_name_;
 
-  PBXGroup* sources_ = nullptr;
   PBXGroup* products_ = nullptr;
   PBXNativeTarget* target_for_indexing_ = nullptr;