Fix Xcode 11.3.1 detection in an Xcode workspace

When using a Tulsi-generated project by itself in Xcode 11.3.1, Xcode
sets XCODE_VERSION_ACTUAL to 1131, which Tulsi parses correctly to get
the version 11.3.1.

But when used in an Xcode workspace, Xcode sets XCODE_VERSION_ACTUAL
to 1130, leading to Tulsi parsing 11.3 as the version, and later
attempts to use a non-existing version of Xcode to build, which results
in an error.

Fix by ignoring XCODE_VERSION_ACTUAL because it's unreliable; instead,
parse Xcode's version.plist to read the 'CFBundleShortVersionString'
entry, which seems to return correct results.

Caveat: this implementation uses plistlib, which has a slightly
different API in python2 and python3. This change uses the python2 API,
and will need to be updated to work with python3 in the future.
PiperOrigin-RevId: 315886285
diff --git a/src/TulsiGenerator/Scripts/bazel_build.py b/src/TulsiGenerator/Scripts/bazel_build.py
index f741b50..0723f7e 100755
--- a/src/TulsiGenerator/Scripts/bazel_build.py
+++ b/src/TulsiGenerator/Scripts/bazel_build.py
@@ -25,6 +25,7 @@
 import json
 import os
 import pipes
+import plistlib
 import re
 import shutil
 import signal
@@ -340,17 +341,39 @@
 
   @staticmethod
   def _GetXcodeVersionString():
-    """Returns Xcode version info from the environment as a string."""
-    reported_version = os.environ['XCODE_VERSION_ACTUAL']
-    match = re.match(r'(\d{2})(\d)(\d)$', reported_version)
-    if not match:
-      _PrintUnbuffered('Warning: Failed to extract Xcode version from %s' % (
-          reported_version))
+    """Returns Xcode version info from the Xcode's version.plist.
+
+    Just reading XCODE_VERSION_ACTUAL from the environment seems like
+    a more reasonable implementation, but has shown to be unreliable,
+    at least when using Xcode 11.3.1 and opening the project within an
+    Xcode workspace.
+    """
+    developer_dir = os.environ['DEVELOPER_DIR']
+    app_dir = developer_dir.split('.app')[0] + '.app'
+    version_plist_path = os.path.join(app_dir, 'Contents', 'version.plist')
+    try:
+      # python2 API to plistlib - needs updating if/when Tulsi bumps to python3
+      plist = plistlib.readPlist(version_plist_path)
+    except IOError:
+      _PrintXcodeWarning('Tulsi cannot determine Xcode version, error '
+                         'reading from {}'.format(version_plist_path))
       return None
-    major_version = int(match.group(1))
-    minor_version = int(match.group(2))
-    fix_version = int(match.group(3))
-    return '%d.%d.%d' % (major_version, minor_version, fix_version)
+    try:
+      # Example: "11.3.1", "11.3", "11.0"
+      key = 'CFBundleShortVersionString'
+      version_string = plist[key]
+    except KeyError:
+      _PrintXcodeWarning('Tulsi cannot determine Xcode version from {}, no '
+                         '"{}" key'.format(version_plist_path, key))
+      return None
+
+    # But we need to normalize to major.minor.patch, e.g. 11.3.0 or
+    # 11.0.0, so add one or two ".0" if needed (two just in case
+    # there is ever just a single version number like "12")
+    dots_count = version_string.count('.')
+    dot_zeroes_to_add = 2 - dots_count
+    version_string += '.0' * dot_zeroes_to_add
+    return version_string
 
   @staticmethod
   def _ComputeXcodeVersionFlag():