Improve ninja's Xcode emulation

When cloning the target dictionaries for "iphoneos", correctly filter
the ARCHS value to only include value that apply to the SDK (iphoneos
or iphonesimulator).

Take into consideration the VALID_ARCHS filter if defined and support
the $(ARCHS_STANDARD) and $(ARCHS_STANDARD_INCLUDING_64_BIT) defaults
from Xcode 5.0.

If ARCHS is not set (which is the default for most of the projects)
then result will be identical, but it can now be set to the macro
$(ARCHS_STANDARD_INCLUDING_64_BIT) to build correctly for arm64 and
armv7 with both ninja and Xcode (including xcodebuild).

Update test/ios/gyptest-archs.py to test build for both simulator
and device, with different configuration (no ARCHS and filter that
only keep 32-bit platforms, explicit ARCHS with filters for 32-bit
or 64-bit platforms, and explicit ARCHS with no filter). This breaks
the test with the "make" generator as its support for Xcode emulation
is inexistant.

BUG=http://crbug.com/339477
R=justincohen@chromium.org, mark@chromium.org, thakist@chromium.org

Review URL: https://codereview.chromium.org/138533006

git-svn-id: http://gyp.googlecode.com/svn/trunk@1850 78cadc50-ecff-11dd-a971-7dbc132099af
diff --git a/pylib/gyp/xcode_emulation.py b/pylib/gyp/xcode_emulation.py
index 7eaa45f..87edc0a 100644
--- a/pylib/gyp/xcode_emulation.py
+++ b/pylib/gyp/xcode_emulation.py
@@ -1429,18 +1429,66 @@
   return False
 
 
+def _IOSIsDeviceSDKROOT(sdkroot):
+  """Tests if |sdkroot| is a SDK for building for device."""
+  return 'iphoneos' in sdkroot.lower()
+
+
+def _IOSDefaultArchForSDKRoot(sdkroot):
+  """Returns the expansion of standard ARCHS macro depending on the version
+  of Xcode installed and configured, and which |sdkroot| to use (iphoneos or
+  simulator)."""
+  xcode_version, xcode_build = XcodeVersion()
+  if xcode_version < '0500':
+    if _IOSIsDeviceSDKROOT(sdkroot):
+      return {'$(ARCHS_STANDARD)': ['armv7']}
+    else:
+      return {'$(ARCHS_STANDARD)': ['i386']}
+  else:
+    if _IOSIsDeviceSDKROOT(sdkroot):
+      return {
+          '$(ARCHS_STANDARD)': ['armv7', 'armv7s'],
+          '$(ARCHS_STANDARD_INCLUDING_64_BIT)': ['armv7', 'armv7s', 'arm64'],
+      }
+    else:
+      return {
+          '$(ARCHS_STANDARD)': ['i386'],
+          '$(ARCHS_STANDARD_INCLUDING_64_BIT)': ['i386', 'x86_64'],
+      }
+
+
+def _FilterIOSArchitectureForSDKROOT(xcode_settings):
+  """Filter the ARCHS value from the |xcode_settings| dictionary to only
+  contains architectures valid for the sdk configured in SDKROOT value."""
+  defaults_archs = _IOSDefaultArchForSDKRoot(xcode_settings.get('SDKROOT', ''))
+  allowed_archs = set()
+  for archs in defaults_archs.itervalues():
+    allowed_archs.update(archs)
+  selected_archs = set()
+  for arch in (xcode_settings.get('ARCHS', []) or ['$(ARCHS_STANDARD)']):
+    if arch in defaults_archs:
+      selected_archs.update(defaults_archs[arch])
+    elif arch in allowed_archs:
+      selected_archs.add(arch)
+  valid_archs = set(xcode_settings.get('VALID_ARCHS', []))
+  if valid_archs:
+    selected_archs = selected_archs & valid_archs
+  xcode_settings['ARCHS'] = list(selected_archs)
+
+
 def _AddIOSDeviceConfigurations(targets):
   """Clone all targets and append -iphoneos to the name. Configure these targets
-  to build for iOS devices."""
-  for target_dict in targets.values():
-    for config_name in target_dict['configurations'].keys():
-      config = target_dict['configurations'][config_name]
-      new_config_name = config_name + '-iphoneos'
-      new_config_dict = copy.deepcopy(config)
-      if target_dict['toolset'] == 'target':
-        new_config_dict['xcode_settings']['ARCHS'] = ['armv7']
-        new_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
-      target_dict['configurations'][new_config_name] = new_config_dict
+  to build for iOS devices and use correct architectures for those builds."""
+  for target_dict in targets.itervalues():
+    toolset = target_dict['toolset']
+    configs = target_dict['configurations']
+    for config_name, config_dict in dict(configs).iteritems():
+      iphoneos_config_dict = copy.deepcopy(config_dict)
+      configs[config_name + '-iphoneos'] = iphoneos_config_dict
+      if toolset == 'target':
+        iphoneos_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
+      _FilterIOSArchitectureForSDKROOT(iphoneos_config_dict['xcode_settings'])
+      _FilterIOSArchitectureForSDKROOT(config_dict['xcode_settings'])
   return targets
 
 def CloneConfigurationForDeviceAndEmulator(target_dicts):
diff --git a/test/ios/app-bundle/test-archs.gyp b/test/ios/app-bundle/test-archs.gyp
index 761fd6b..b1558c9 100644
--- a/test/ios/app-bundle/test-archs.gyp
+++ b/test/ios/app-bundle/test-archs.gyp
@@ -5,101 +5,106 @@
   'make_global_settings': [
     ['CC', '/usr/bin/clang'],
   ],
+  'target_defaults': {
+    'product_extension': 'bundle',
+    'mac_bundle_resources': [
+      'TestApp/English.lproj/InfoPlist.strings',
+      'TestApp/English.lproj/MainMenu.xib',
+    ],
+    'link_settings': {
+      'libraries': [
+        '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
+        '$(SDKROOT)/System/Library/Frameworks/UIKit.framework',
+      ],
+    },
+    'xcode_settings': {
+      'OTHER_CFLAGS': [
+        '-fobjc-abi-version=2',
+      ],
+      'CODE_SIGNING_REQUIRED': 'NO',
+      'SDKROOT': 'iphonesimulator',  # -isysroot
+      'TARGETED_DEVICE_FAMILY': '1,2',
+      'INFOPLIST_FILE': 'TestApp/TestApp-Info.plist',
+      'IPHONEOS_DEPLOYMENT_TARGET': '7.0',
+      'CONFIGURATION_BUILD_DIR':'build/Default',
+    },
+  },
   'targets': [
     {
-      'target_name': 'test_no_archs',
-      'product_name': 'Test No Archs',
+      'target_name': 'TestNoArchs',
+      'product_name': 'TestNoArchs',
       'type': 'executable',
-      'product_extension': 'bundle',
       'mac_bundle': 1,
       'sources': [
         'TestApp/main.m',
         'TestApp/only-compile-in-32-bits.m',
       ],
-      'mac_bundle_resources': [
-        'TestApp/English.lproj/InfoPlist.strings',
-        'TestApp/English.lproj/MainMenu.xib',
-      ],
-      'link_settings': {
-        'libraries': [
-          '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
-          '$(SDKROOT)/System/Library/Frameworks/UIKit.framework',
-        ],
-      },
       'xcode_settings': {
-        'OTHER_CFLAGS': [
-          '-fobjc-abi-version=2',
+        'VALID_ARCHS': [
+          'i386',
+          'x86_64',
+          'arm64',
+          'armv7',
         ],
-        'SDKROOT': 'iphonesimulator',  # -isysroot
-        'TARGETED_DEVICE_FAMILY': '1,2',
-        'INFOPLIST_FILE': 'TestApp/TestApp-Info.plist',
-        'IPHONEOS_DEPLOYMENT_TARGET': '7.0',
-        'CONFIGURATION_BUILD_DIR':'build/Default',
-      },
+      }
     },
     {
-      'target_name': 'test_archs_i386',
-      'product_name': 'Test Archs i386',
+      'target_name': 'TestArch32Bits',
+      'product_name': 'TestArch32Bits',
       'type': 'executable',
-      'product_extension': 'bundle',
       'mac_bundle': 1,
       'sources': [
         'TestApp/main.m',
         'TestApp/only-compile-in-32-bits.m',
       ],
-      'mac_bundle_resources': [
-        'TestApp/English.lproj/InfoPlist.strings',
-        'TestApp/English.lproj/MainMenu.xib',
-      ],
-      'link_settings': {
-        'libraries': [
-          '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
-          '$(SDKROOT)/System/Library/Frameworks/UIKit.framework',
-        ],
-      },
       'xcode_settings': {
-        'OTHER_CFLAGS': [
-          '-fobjc-abi-version=2',
+        'ARCHS': [
+          '$(ARCHS_STANDARD)',
         ],
-        'SDKROOT': 'iphonesimulator',  # -isysroot
-        'TARGETED_DEVICE_FAMILY': '1,2',
-        'INFOPLIST_FILE': 'TestApp/TestApp-Info.plist',
-        'IPHONEOS_DEPLOYMENT_TARGET': '7.0',
-        'CONFIGURATION_BUILD_DIR':'build/Default',
-        'ARCHS': ['i386'],
+        'VALID_ARCHS': [
+          'i386',
+          'armv7',
+        ],
       },
     },
     {
-      'target_name': 'test_archs_x86_64',
-      'product_name': 'Test Archs x86_64',
+      'target_name': 'TestArch64Bits',
+      'product_name': 'TestArch64Bits',
       'type': 'executable',
-      'product_extension': 'bundle',
       'mac_bundle': 1,
       'sources': [
         'TestApp/main.m',
         'TestApp/only-compile-in-64-bits.m',
       ],
-      'mac_bundle_resources': [
-        'TestApp/English.lproj/InfoPlist.strings',
-        'TestApp/English.lproj/MainMenu.xib',
-      ],
-      'link_settings': {
-        'libraries': [
-          '$(SDKROOT)/System/Library/Frameworks/Foundation.framework',
-          '$(SDKROOT)/System/Library/Frameworks/UIKit.framework',
-        ],
-      },
       'xcode_settings': {
-        'OTHER_CFLAGS': [
-          '-fobjc-abi-version=2',
+        'ARCHS': [
+          '$(ARCHS_STANDARD_INCLUDING_64_BIT)',
         ],
-        'SDKROOT': 'iphonesimulator',  # -isysroot
-        'TARGETED_DEVICE_FAMILY': '1,2',
-        'INFOPLIST_FILE': 'TestApp/TestApp-Info.plist',
-        'IPHONEOS_DEPLOYMENT_TARGET': '7.0',
-        'CONFIGURATION_BUILD_DIR':'build/Default',
-        'ARCHS': ['x86_64'],
+        'VALID_ARCHS': [
+          'x86_64',
+          'arm64',
+        ],
       },
     },
+    {
+      'target_name': 'TestMultiArchs',
+      'product_name': 'TestMultiArchs',
+      'type': 'executable',
+      'mac_bundle': 1,
+      'sources': [
+        'TestApp/main.m',
+      ],
+      'xcode_settings': {
+        'ARCHS': [
+          '$(ARCHS_STANDARD_INCLUDING_64_BIT)',
+        ],
+        'VALID_ARCHS': [
+          'x86_64',
+          'i386',
+          'arm64',
+          'armv7',
+        ],
+      }
+    },
   ],
 }
diff --git a/test/ios/gyptest-archs.py b/test/ios/gyptest-archs.py
index 87d0657..8afda18 100644
--- a/test/ios/gyptest-archs.py
+++ b/test/ios/gyptest-archs.py
@@ -8,22 +8,41 @@
 Verifies that device and simulator bundles are built correctly.
 """
 
+import collections
 import plistlib
 import TestGyp
 import os
+import re
 import struct
 import subprocess
 import sys
 import tempfile
 
 
-def CheckFileType(file, expected):
+def BuildExpected(file, archs):
+  if len(archs) == 1:
+    return 'Non-fat file: %s is architecture: %s' % (file, archs[0])
+  return 'Architectures in the fat file: %s are: %s' % (file, ' '.join(archs))
+
+
+def CheckFileType(test, file, archs):
   proc = subprocess.Popen(['lipo', '-info', file], stdout=subprocess.PIPE)
   o = proc.communicate()[0].strip()
   assert not proc.returncode
-  if not expected in o:
-    print 'File: Expected %s, got %s' % (expected, o)
+  if len(archs) == 1:
+    pattern = re.compile('^Non-fat file: (.*) is architecture: (.*)$')
+  else:
+    pattern = re.compile('^Architectures in the fat file: (.*) are: (.*)$')
+  match = pattern.match(o)
+  if match is None:
+    print 'Ouput does not match expected pattern: %s' % (pattern.pattern)
     test.fail_test()
+  else:
+    found_file, found_archs = match.groups()
+    if found_file != file or set(found_archs.split()) != set(archs):
+      print 'Expected file %s with arch %s, got %s with arch %s' % (
+          file, ' '.join(archs), found_file, ' '.join(found_archs))
+      test.fail_test()
 
 
 def XcodeVersion():
@@ -42,26 +61,46 @@
 
 if sys.platform == 'darwin':
   test = TestGyp.TestGyp()
+  if test.format == 'ninja' or test.format == 'xcode':
+    test_cases = [
+      ('Default', 'TestNoArchs', ['i386']),
+      ('Default', 'TestArch32Bits', ['i386']),
+      ('Default', 'TestArch64Bits', ['x86_64']),
+      ('Default', 'TestMultiArchs', ['i386', 'x86_64']),
+      ('Default-iphoneos', 'TestNoArchs', ['armv7']),
+      ('Default-iphoneos', 'TestArch32Bits', ['armv7']),
+      ('Default-iphoneos', 'TestArch64Bits', ['arm64']),
+      ('Default-iphoneos', 'TestMultiArchs', ['armv7', 'arm64']),
+    ]
 
-  test.run_gyp('test-archs.gyp', chdir='app-bundle')
-  test.set_configuration('Default')
+    xcode_version = XcodeVersion()
+    test.run_gyp('test-archs.gyp', chdir='app-bundle')
+    for configuration, target, archs in test_cases:
+      is_64_bit_build = ('arm64' in archs or 'x86_64' in archs)
+      is_device_build = configuration.endswith('-iphoneos')
 
-  # TODO(sdefresne): add 'Test Archs x86_64' once bots have been updated to
-  # a SDK version that supports "x86_64" architecture.
-  filenames = ['Test No Archs', 'Test Archs i386']
-  if XcodeVersion() >= '0500':
-    filenames.append('Test Archs x86_64')
+      kwds = collections.defaultdict(list)
+      if test.format == 'xcode' and is_device_build:
+        configuration, sdk = configuration.split('-')
+        kwds['arguments'].extend(['-sdk', sdk])
 
-  for filename in filenames:
-    target = filename.replace(' ', '_').lower()
-    test.build('test-archs.gyp', target, chdir='app-bundle')
-    result_file = test.built_file_path(
-        '%s.bundle/%s' % (filename, filename), chdir='app-bundle')
-    test.must_exist(result_file)
+      # TODO(sdefresne): remove those special-cases once the bots have been
+      # updated to use a more recent version of Xcode.
+      if xcode_version < '0500':
+        if is_64_bit_build:
+          continue
+        if test.format == 'xcode':
+          arch = 'i386'
+          if is_device_build:
+            arch = 'armv7'
+          kwds['arguments'].extend(['-arch', arch])
 
-    expected = 'i386'
-    if 'x86_64' in filename:
-      expected = 'x86_64'
-      CheckFileType(result_file, expected)
+      test.set_configuration(configuration)
+      filename = '%s.bundle/%s' % (target, target)
+      test.build('test-archs.gyp', target, chdir='app-bundle', **kwds)
+      result_file = test.built_file_path(filename, chdir='app-bundle')
 
-  test.pass_test()
+      test.must_exist(result_file)
+      CheckFileType(test, result_file, archs)
+
+    test.pass_test()