Generate a single project with --ide=xcode

Historically we generated multiple project and a workspace to group them
together, however the workspace is no longer required as there is just a
single project file generated.

Remove support for generating a workspace alongside the project, but
keep the embedded workspace used to configure the build system.

Bug: none
Change-Id: If82797f4357232d29318e7b23f12ccbd6cbcf4ef
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/9000
Reviewed-by: Brett Wilson <brettw@chromium.org>
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
diff --git a/docs/reference.md b/docs/reference.md
index 79b01a2..7896bec 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -764,8 +764,8 @@
 #### **Xcode Flags**
 
 ```
-  --workspace=<file_name>
-      Override defaut workspace file name ("all"). The workspace file is
+  --xcode-project=<file_name>
+      Override defaut Xcode project file name ("all"). The project file is
       written to the root build directory.
 
   --ninja-executable=<string>
diff --git a/src/gn/command_gen.cc b/src/gn/command_gen.cc
index e9c35bb..8424165 100644
--- a/src/gn/command_gen.cc
+++ b/src/gn/command_gen.cc
@@ -48,7 +48,7 @@
 const char kSwitchNoDeps[] = "no-deps";
 const char kSwitchRootTarget[] = "root-target";
 const char kSwitchSln[] = "sln";
-const char kSwitchWorkspace[] = "workspace";
+const char kSwitchXcodeProject[] = "xcode-project";
 const char kSwitchJsonFileName[] = "json-file-name";
 const char kSwitchJsonIdeScript[] = "json-ide-script";
 const char kSwitchJsonIdeScriptArgs[] = "json-ide-script-args";
@@ -58,8 +58,13 @@
 // Extracts extra parameters for XcodeWriter from command-line flags.
 XcodeWriter::Options XcodeWriterOptionsFromCommandLine(
     const base::CommandLine& command_line) {
+  std::string project_name =
+      command_line.GetSwitchValueASCII(kSwitchXcodeProject);
+  if (project_name.empty())
+    project_name = "all";
+
   return {
-      command_line.GetSwitchValueASCII(kSwitchWorkspace),
+      std::move(project_name),
       command_line.GetSwitchValueASCII(kSwitchRootTarget),
       command_line.GetSwitchValueASCII(kSwitchNinjaExecutable),
       command_line.GetSwitchValueASCII(kSwitchNinjaExtraArgs),
@@ -403,8 +408,8 @@
 
 Xcode Flags
 
-  --workspace=<file_name>
-      Override defaut workspace file name ("all"). The workspace file is
+  --xcode-project=<file_name>
+      Override defaut Xcode project file name ("all"). The project file is
       written to the root build directory.
 
   --ninja-executable=<string>
diff --git a/src/gn/xcode_writer.cc b/src/gn/xcode_writer.cc
index 206850f..c1a8538 100644
--- a/src/gn/xcode_writer.cc
+++ b/src/gn/xcode_writer.cc
@@ -406,105 +406,91 @@
 
 }  // namespace
 
-class XcodeProject;
-
-// Xcode uses workspace to:
-// - group multiple projects together
-// - configure settings shared by all targets (like build setting).
-//
-// The class XcodeBaseWorkspace is used to share code to generate the two
-// types of workspace. The only difference between them is where they are
-// located (either besides the .xcodeproj or inside them) and how the
-// projects are references (either by path or by referencing the containing
-// project).
-//
-// As the generator only creates a single project, the standalone workspace
-// is not technically required, but developers are used to opening it instead
-// opening the project directly.
-//
-// The embedded workspace is not needed if developer open the standalone
-// workspace (as the settings from the standalone workspace will be used
-// in that case). However, there have been multiple occurrences of developer
-// encountering tricky build failure because they opened the project directly.
-//
-// Having the two kind of workspace is a temporary solution to avoid breaking
-// workflow of developer used to opening the standalone workspace while still
-// ensuring that developers opening the project directly have an environment
-// that is working.
-class XcodeBaseWorkspace {
+// Class representing the workspace embedded in an xcodeproj file used to
+// configure the build settings shared by all targets in the project (used
+// to configure the build system to "Legacy build system").
+class XcodeWorkspace {
  public:
-  XcodeBaseWorkspace(const BuildSettings* settings);
-  virtual ~XcodeBaseWorkspace();
+  XcodeWorkspace(const BuildSettings* settings);
+  ~XcodeWorkspace();
 
-  XcodeBaseWorkspace(const XcodeBaseWorkspace&) = delete;
-  XcodeBaseWorkspace& operator=(const XcodeBaseWorkspace&) = delete;
+  XcodeWorkspace(const XcodeWorkspace&) = delete;
+  XcodeWorkspace& operator=(const XcodeWorkspace&) = delete;
 
   // Generates the .xcworkspace files to disk.
   bool WriteWorkspace(const std::string& name, Err* err) const;
 
- protected:
-  const BuildSettings* build_settings() const { return build_settings_; }
-
  private:
-  using FileContentGenerator =
-      void (XcodeBaseWorkspace::*)(std::ostream&) const;
+  // Writes the workspace data file.
+  bool WriteWorkspaceDataFile(const std::string& name, Err* err) const;
 
-  // Helper function to write workspace file.
-  bool WriteWorkspaceFile(const std::string& filename,
-                          FileContentGenerator file_content_generator,
-                          Err* err) const;
-
-  // Writes the $name.xcworkspace/contents.xcworkspacedata file to |out|.
-  virtual void WriteWorkspaceDataFileContent(std::ostream& out) const = 0;
-
-  // Writes the $name.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
-  // file to |out|.
-  void WriteSettingsFileContent(std::ostream& out) const;
+  // Writes the settings file.
+  bool WriteSettingsFile(const std::string& name, Err* err) const;
 
   const BuildSettings* build_settings_ = nullptr;
 };
 
-// Class corresponding to the generated workspace contained in a project.
-class XcodeInnerWorkspace : public XcodeBaseWorkspace {
- public:
-  XcodeInnerWorkspace(const BuildSettings* settings);
-  ~XcodeInnerWorkspace() override;
+XcodeWorkspace::XcodeWorkspace(const BuildSettings* build_settings)
+    : build_settings_(build_settings) {}
 
- private:
-  // XcodeBaseWorkspace implementation.
-  void WriteWorkspaceDataFileContent(std::ostream& out) const override;
-};
+XcodeWorkspace::~XcodeWorkspace() = default;
 
-// Sub-class XcodeOuterWorkspace corresponds to a standalone workspace that
-// points to .xcodeproj located next to the .xcworkspace. It can refer to
-// multiple project (in the generator it owns the projects).
-class XcodeOuterWorkspace : public XcodeBaseWorkspace {
- public:
-  XcodeOuterWorkspace(const BuildSettings* settings,
-                      XcodeWriter::Options options);
-  ~XcodeOuterWorkspace() override;
+bool XcodeWorkspace::WriteWorkspace(const std::string& name, Err* err) const {
+  return WriteWorkspaceDataFile(name, err) && WriteSettingsFile(name, err);
+}
 
-  // Adds a project to the workspace.
-  XcodeProject* CreateProject(const std::string& name);
+bool XcodeWorkspace::WriteWorkspaceDataFile(const std::string& name,
+                                            Err* err) const {
+  const SourceFile source_file =
+      build_settings_->build_dir().ResolveRelativeFile(
+          Value(nullptr, name + "/contents.xcworkspacedata"), err);
+  if (source_file.is_null())
+    return false;
 
-  // Returns the name of the workspace.
-  const std::string& Name() const;
+  std::stringstream out;
+  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      << "<Workspace\n"
+      << "   version = \"1.0\">\n"
+      << "   <FileRef\n"
+      << "      location = \"self:\">\n"
+      << "   </FileRef>\n"
+      << "</Workspace>\n";
 
- private:
-  // XcodeBaseWorkspace implementation
-  void WriteWorkspaceDataFileContent(std::ostream& out) const override;
+  return WriteFileIfChanged(build_settings_->GetFullPath(source_file),
+                            out.str(), err);
+}
 
-  XcodeWriter::Options options_;
-  std::map<std::string, std::unique_ptr<XcodeProject>> projects_;
-};
+bool XcodeWorkspace::WriteSettingsFile(const std::string& name,
+                                       Err* err) const {
+  const SourceFile source_file =
+      build_settings_->build_dir().ResolveRelativeFile(
+          Value(nullptr, name + "/xcshareddata/WorkspaceSettings.xcsettings"),
+          err);
+  if (source_file.is_null())
+    return false;
 
-// Sub-class XcodeInnerWorkspace corresponds to a workspace that is embedded
-// inside an .xcodeproj. It only refer to the surrounding project via :self.
+  std::stringstream out;
+  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+      << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
+      << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
+      << "<plist version=\"1.0\">\n"
+      << "<dict>\n"
+      << "\t<key>BuildSystemType</key>\n"
+      << "\t<string>Original</string>\n"
+      << "</dict>\n"
+      << "</plist>\n";
+
+  return WriteFileIfChanged(build_settings_->GetFullPath(source_file),
+                            out.str(), err);
+}
+
+// Class responsible for constructing and writing the .xcodeproj from the
+// targets known to gn. It currently requires using the "Legacy build system"
+// so it will embed an .xcworkspace file to force the setting.
 class XcodeProject {
  public:
   XcodeProject(const BuildSettings* build_settings,
-               XcodeWriter::Options options,
-               const std::string& name);
+               XcodeWriter::Options options);
   ~XcodeProject();
 
   // Recursively finds "source" files from |builder| and adds them to the
@@ -568,11 +554,10 @@
 };
 
 XcodeProject::XcodeProject(const BuildSettings* build_settings,
-                           XcodeWriter::Options options,
-                           const std::string& name)
+                           XcodeWriter::Options options)
     : build_settings_(build_settings),
       options_(options),
-      project_(name,
+      project_(options.project_name,
                ConfigNameFromBuildSettings(build_settings),
                SourcePathFromBuildSettings(build_settings),
                ProjectAttributesFromBuildSettings(build_settings)) {}
@@ -817,9 +802,9 @@
     return false;
   }
 
-  XcodeInnerWorkspace inner_workspace(build_settings_);
-  return inner_workspace.WriteWorkspace(project_.Name() + ".xcodeproj/project",
-                                        err);
+  XcodeWorkspace workspace(build_settings_);
+  return workspace.WriteWorkspace(
+      project_.Name() + ".xcodeproj/project.xcworkspace", err);
 }
 
 std::optional<std::vector<const Target*>> XcodeProject::GetTargetsFromBuilder(
@@ -950,126 +935,22 @@
       << "}\n";
 }
 
-XcodeBaseWorkspace::XcodeBaseWorkspace(const BuildSettings* build_settings)
-    : build_settings_(build_settings) {}
-
-XcodeBaseWorkspace::~XcodeBaseWorkspace() = default;
-
-bool XcodeBaseWorkspace::WriteWorkspace(const std::string& name,
-                                        Err* err) const {
-  if (!WriteWorkspaceFile(name + ".xcworkspace/contents.xcworkspacedata",
-                          &XcodeBaseWorkspace::WriteWorkspaceDataFileContent,
-                          err)) {
-    return false;
-  }
-
-  if (!WriteWorkspaceFile(
-          name + ".xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
-          &XcodeBaseWorkspace::WriteSettingsFileContent, err)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool XcodeBaseWorkspace::WriteWorkspaceFile(
-    const std::string& filename,
-    FileContentGenerator file_content_generator,
-    Err* err) const {
-  const SourceFile source_file =
-      build_settings_->build_dir().ResolveRelativeFile(Value(nullptr, filename),
-                                                       err);
-  if (source_file.is_null())
-    return false;
-
-  std::stringstream string_out;
-  (this->*file_content_generator)(string_out);
-
-  return WriteFileIfChanged(build_settings_->GetFullPath(source_file),
-                            string_out.str(), err);
-}
-
-void XcodeBaseWorkspace::WriteSettingsFileContent(std::ostream& out) const {
-  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-      << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" "
-      << "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
-      << "<plist version=\"1.0\">\n"
-      << "<dict>\n"
-      << "\t<key>BuildSystemType</key>\n"
-      << "\t<string>Original</string>\n"
-      << "</dict>\n"
-      << "</plist>\n";
-}
-
-XcodeInnerWorkspace::XcodeInnerWorkspace(const BuildSettings* build_settings)
-    : XcodeBaseWorkspace(build_settings) {}
-
-XcodeInnerWorkspace::~XcodeInnerWorkspace() = default;
-
-void XcodeInnerWorkspace::WriteWorkspaceDataFileContent(
-    std::ostream& out) const {
-  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-      << "<Workspace\n"
-      << "   version = \"1.0\">\n"
-      << "   <FileRef\n"
-      << "      location = \"self:\">\n"
-      << "   </FileRef>\n"
-      << "</Workspace>\n";
-}
-
-XcodeOuterWorkspace::XcodeOuterWorkspace(const BuildSettings* build_settings,
-                                         XcodeWriter::Options options)
-    : XcodeBaseWorkspace(build_settings), options_(options) {}
-
-XcodeOuterWorkspace::~XcodeOuterWorkspace() = default;
-
-XcodeProject* XcodeOuterWorkspace::CreateProject(const std::string& name) {
-  DCHECK(!base::ContainsKey(projects_, name));
-  projects_.insert(std::make_pair(
-      name, std::make_unique<XcodeProject>(build_settings(), options_, name)));
-  auto iter = projects_.find(name);
-  DCHECK(iter != projects_.end());
-  DCHECK(iter->second);
-  return iter->second.get();
-}
-
-const std::string& XcodeOuterWorkspace::Name() const {
-  static std::string all("all");
-  return !options_.workspace_name.empty() ? options_.workspace_name : all;
-}
-
-void XcodeOuterWorkspace::WriteWorkspaceDataFileContent(
-    std::ostream& out) const {
-  out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-      << "<Workspace version = \"1.0\">\n";
-  for (const auto& pair : projects_) {
-    out << "  <FileRef location = \"group:" << pair.first
-        << ".xcodeproj\"></FileRef>\n";
-  }
-  out << "</Workspace>\n";
-}
-
 // static
 bool XcodeWriter::RunAndWriteFiles(const BuildSettings* build_settings,
                                    const Builder& builder,
                                    Options options,
                                    Err* err) {
-  XcodeOuterWorkspace workspace(build_settings, options);
-
-  XcodeProject* products = workspace.CreateProject("products");
-  if (!products->AddSourcesFromBuilder(builder, err))
+  XcodeProject project(build_settings, options);
+  if (!project.AddSourcesFromBuilder(builder, err))
     return false;
 
-  if (!products->AddTargetsFromBuilder(builder, err))
+  if (!project.AddTargetsFromBuilder(builder, err))
     return false;
 
-  if (!products->AssignIds(err))
+  if (!project.AssignIds(err))
     return false;
 
-  if (!products->WriteFile(err))
-    return false;
-
-  if (!workspace.WriteWorkspace(workspace.Name(), err))
+  if (!project.WriteFile(err))
     return false;
 
   return true;
diff --git a/src/gn/xcode_writer.h b/src/gn/xcode_writer.h
index 69d4903..62f6ab5 100644
--- a/src/gn/xcode_writer.h
+++ b/src/gn/xcode_writer.h
@@ -22,8 +22,8 @@
  public:
   // Controls some parameters and behaviour of the RunAndWriteFiles().
   struct Options {
-    // Name of the generated workspace file. Defaults to "all" is empty.
-    std::string workspace_name;
+    // Name of the generated project file. Defaults to "all" is empty.
+    std::string project_name;
 
     // Name of the ninja target to use for the "All" target in the generated
     // project. If empty, no target will be passed to ninja which will thus