[tvos] Add target_xcode_platform to set Xcode project

This adds target_xcode_platform that indicates the kind of iOS or
iOS-based platform so that Xcode project is configured with tvOS when
target_xcode_platform == "tvos".

Bug: chromium:391914246
Change-Id: Ie18c370e1f0de78573e50a716b7da550dfb51ffe
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/18840
Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: David Turner <digit@google.com>
diff --git a/docs/reference.md b/docs/reference.md
index 91521b7..93bc0af 100644
--- a/docs/reference.md
+++ b/docs/reference.md
@@ -163,6 +163,7 @@
     *   [script: [file name] Script file for actions.](#var_script)
     *   [sources: [file list] Source files for a target.](#var_sources)
     *   [swiftflags: [string list] Flags passed to the swift compiler.](#var_swiftflags)
+    *   [target_xcode_platform: [string] The desired platform for the build.](#var_target_xcode_platform)
     *   [testonly: [boolean] Declares a target must only be used for testing.](#var_testonly)
     *   [transparent: [bool] True if the bundle is transparent.](#var_transparent)
     *   [visibility: [label list] A list of labels that can depend on a target.](#var_visibility)
@@ -6904,6 +6905,28 @@
      "deps" list. If a dependency is public, they will be applied
      recursively.
 ```
+### <a name="var_target_xcode_platform"></a>**target_xcode_platform**: The desired platform for the build.&nbsp;[Back to Top](#gn-reference)
+
+```
+  This value should be used to indicate the kind of iOS or iOS-based platform
+  that is being the desired platform for the primary object(s) of the build.
+
+  This should be set to the most specific value possible. So, "iphoneos" or
+  "tvos" should be used instead of "ios" where applicable, even though
+  iPhoneOS and tvOS are both iOS variants.
+
+  GN defaults this value to "iphoneos" and the configuration files should set
+  it to an appropriate value if it is not set via the command line or in the
+  args.gn file.
+
+  This value configures the base SDK and the targeted device families of the
+  generated Xcode project. only meaningful when generating with --ide=xcode.
+
+  Possible values
+
+  - "iphoneos"
+  - "tvos"
+```
 ### <a name="var_testonly"></a>**testonly**: Declares a target must only be used for testing.&nbsp;[Back to Top](#gn-reference)
 
 ```
diff --git a/src/gn/variables.cc b/src/gn/variables.cc
index d99fccf..561c2bc 100644
--- a/src/gn/variables.cc
+++ b/src/gn/variables.cc
@@ -2210,6 +2210,32 @@
   }
 )";
 
+const char kTargetXcodePlatform[] = "target_xcode_platform";
+const char kTargetXcodePlatform_HelpShort[] =
+    "target_xcode_platform: [string] The desired platform for the build.";
+const char kTargetXcodePlatform_Help[] =
+    R"(target_xcode_platform: The desired platform for the build.
+
+  This value should be used to indicate the kind of iOS or iOS-based platform
+  that is being the desired platform for the primary object(s) of the build.
+
+  This should be set to the most specific value possible. So, "iphoneos" or
+  "tvos" should be used instead of "ios" where applicable, even though
+  iPhoneOS and tvOS are both iOS variants.
+
+  GN defaults this value to "iphoneos" and the configuration files should set
+  it to an appropriate value if it is not set via the command line or in the
+  args.gn file.
+
+  This value configures the base SDK and the targeted device families of the
+  generated Xcode project. only meaningful when generating with --ide=xcode.
+
+  Possible values
+
+  - "iphoneos"
+  - "tvos"
+)";
+
 const char kTestonly[] = "testonly";
 const char kTestonly_HelpShort[] =
     "testonly: [boolean] Declares a target must only be used for testing.";
@@ -2489,6 +2515,7 @@
     INSERT_VARIABLE(Sources)
     INSERT_VARIABLE(Swiftflags)
     INSERT_VARIABLE(XcodeTestApplicationName)
+    INSERT_VARIABLE(TargetXcodePlatform)
     INSERT_VARIABLE(Testonly)
     INSERT_VARIABLE(Visibility)
     INSERT_VARIABLE(WalkKeys)
diff --git a/src/gn/variables.h b/src/gn/variables.h
index b8a0d54..036b972 100644
--- a/src/gn/variables.h
+++ b/src/gn/variables.h
@@ -374,6 +374,10 @@
 extern const char kXcodeExtraAttributes_HelpShort[];
 extern const char kXcodeExtraAttributes_Help[];
 
+extern const char kTargetXcodePlatform[];
+extern const char kTargetXcodePlatform_HelpShort[];
+extern const char kTargetXcodePlatform_Help[];
+
 extern const char kGenDeps[];
 extern const char kGenDeps_HelpShort[];
 extern const char kGenDeps_Help[];
diff --git a/src/gn/xcode_writer.cc b/src/gn/xcode_writer.cc
index ea84a3d..62945ad 100644
--- a/src/gn/xcode_writer.cc
+++ b/src/gn/xcode_writer.cc
@@ -48,6 +48,11 @@
   WRITER_TARGET_OS_MACOS,
 };
 
+enum TargetPlatformType {
+  WRITER_TARGET_PLATFORM_IPHONEOS,
+  WRITER_TARGET_PLATFORM_TVOS,
+};
+
 const char* kXCTestFileSuffixes[] = {
     "egtest.m",     "egtest.mm", "egtest.swift", "xctest.m",      "xctest.mm",
     "xctest.swift", "UITests.m", "UITests.mm",   "UITests.swift",
@@ -81,6 +86,25 @@
   return WRITER_TARGET_OS_MACOS;
 }
 
+std::optional<TargetPlatformType> GetTargetPlatform(const Args& args,
+                                                    const ParseNode* node,
+                                                    Err* err) {
+  const Value* target_platform_value =
+      args.GetArgOverride(variables::kTargetXcodePlatform);
+  if (target_platform_value) {
+    if (target_platform_value->type() == Value::STRING) {
+      if (target_platform_value->string_value() == "tvos")
+        return WRITER_TARGET_PLATFORM_TVOS;
+      if (target_platform_value->string_value() != "iphoneos") {
+        *err = Err(node, "Unknown target_platform value",
+                    target_platform_value->string_value());
+        return std::nullopt;
+      }
+    }
+  }
+  return WRITER_TARGET_PLATFORM_IPHONEOS;
+}
+
 std::string GetBuildScript(const std::string& target_name,
                            const std::string& ninja_executable,
                            const std::string& build_dir,
@@ -396,14 +420,26 @@
 
 // Returns the default attributes for the project from settings.
 PBXAttributes ProjectAttributesFromBuildSettings(
-    const BuildSettings* build_settings) {
+    const BuildSettings* build_settings,
+    const ParseNode* node,
+    Err* err) {
   const TargetOsType target_os = GetTargetOs(build_settings->build_args());
 
   PBXAttributes attributes;
   switch (target_os) {
-    case WRITER_TARGET_OS_IOS:
-      attributes["SDKROOT"] = "iphoneos";
-      attributes["TARGETED_DEVICE_FAMILY"] = "1,2";
+    case WRITER_TARGET_OS_IOS: {
+        const std::optional<TargetPlatformType> target_platform =
+          GetTargetPlatform(build_settings->build_args(), node, err);
+        if (!target_platform)
+          return {};
+        if (*target_platform == WRITER_TARGET_PLATFORM_TVOS) {
+          attributes["SDKROOT"] = "appletvos";
+          attributes["TARGETED_DEVICE_FAMILY"] = "3";
+        } else {
+          attributes["SDKROOT"] = "iphoneos";
+          attributes["TARGETED_DEVICE_FAMILY"] = "1,2";
+        }
+      }
       break;
     case WRITER_TARGET_OS_MACOS:
       attributes["SDKROOT"] = "macosx";
@@ -677,7 +713,9 @@
       project_(options.project_name,
                ConfigListFromOptions(options.configurations),
                SourcePathFromBuildSettings(build_settings),
-               ProjectAttributesFromBuildSettings(build_settings)) {}
+               ProjectAttributesFromBuildSettings(build_settings,
+                                                  /*node=*/nullptr,
+                                                  /*err=*/nullptr)) {}
 
 XcodeProject::~XcodeProject() = default;