Merge from Chromium at DEPS revision 262940

This commit was generated by merge_to_master.py.

Change-Id: I746d5be76019b85e4be03e63a7511c9b432cb3b8
diff --git a/pylib/gyp/generator/android.py b/pylib/gyp/generator/android.py
index 131a265..0149c27 100644
--- a/pylib/gyp/generator/android.py
+++ b/pylib/gyp/generator/android.py
@@ -55,7 +55,7 @@
 generator_extra_sources_for_rules = []
 
 
-SHARED_FOOTER = """\
+ALL_MODULES_FOOTER = """\
 # "gyp_all_modules" is a concatenation of the "gyp_all_modules" targets from
 # all the included sub-makefiles. This is just here to clarify.
 gyp_all_modules:
@@ -133,7 +133,7 @@
     self.android_top_dir = android_top_dir
 
   def Write(self, qualified_target, relative_target, base_path, output_filename,
-            spec, configs, part_of_all):
+            spec, configs, part_of_all, write_alias_target):
     """The main entry point: writes a .mk file for a single target.
 
     Arguments:
@@ -144,6 +144,8 @@
       output_filename: output .mk file name to write
       spec, configs: gyp info
       part_of_all: flag indicating this target is part of 'all'
+      write_alias_target: flag indicating whether to create short aliases for
+                          this target
     """
     gyp.common.EnsureDirExists(output_filename)
 
@@ -186,11 +188,13 @@
     self.WriteLn('LOCAL_MODULE_TAGS := optional')
     if self.toolset == 'host':
       self.WriteLn('LOCAL_IS_HOST_MODULE := true')
+    self.WriteLn('LOCAL_MODULE_TARGET_ARCH := $(TARGET_$(GYP_VAR_PREFIX)ARCH)')
 
     # Grab output directories; needed for Actions and Rules.
-    self.WriteLn('gyp_intermediate_dir := $(call local-intermediates-dir)')
+    self.WriteLn('gyp_intermediate_dir := '
+                 '$(call local-intermediates-dir,,$(GYP_VAR_PREFIX))')
     self.WriteLn('gyp_shared_intermediate_dir := '
-                 '$(call intermediates-dir-for,GYP,shared)')
+                 '$(call intermediates-dir-for,GYP,shared,,,$(GYP_VAR_PREFIX))')
     self.WriteLn()
 
     # List files this target depends on so that actions/rules/copies/sources
@@ -226,7 +230,8 @@
     if spec.get('sources', []) or extra_sources:
       self.WriteSources(spec, configs, extra_sources)
 
-    self.WriteTarget(spec, configs, deps, link_deps, part_of_all)
+    self.WriteTarget(spec, configs, deps, link_deps, part_of_all,
+                     write_alias_target)
 
     # Update global list of target outputs, used in dependency tracking.
     target_outputs[qualified_target] = ('path', self.output_binary)
@@ -337,13 +342,10 @@
     """
     if len(rules) == 0:
       return
-    rule_trigger = '%s_rule_trigger' % self.android_module
 
-    did_write_rule = False
     for rule in rules:
       if len(rule.get('rule_sources', [])) == 0:
         continue
-      did_write_rule = True
       name = make.StringToMakefileVariable('%s_%s' % (self.relative_target,
                                                       rule['rule_name']))
       self.WriteLn('\n### Generated for rule "%s":' % name)
@@ -412,13 +414,9 @@
           # Make each output depend on the main output, with an empty command
           # to force make to notice that the mtime has changed.
           self.WriteLn('%s: %s ;' % (output, main_output))
-        self.WriteLn('.PHONY: %s' % (rule_trigger))
-        self.WriteLn('%s: %s' % (rule_trigger, main_output))
-        self.WriteLn('')
-    if did_write_rule:
-      extra_sources.append(rule_trigger)  # Force all rules to run.
-      self.WriteLn('### Finished generating for all rules')
-      self.WriteLn('')
+        self.WriteLn()
+
+    self.WriteLn()
 
 
   def WriteCopies(self, copies, extra_outputs):
@@ -612,16 +610,16 @@
       prefix = ''
 
     if spec['toolset'] == 'host':
-      suffix = '_host_gyp'
+      suffix = '_$(TARGET_$(GYP_VAR_PREFIX)ARCH)_host_gyp'
     else:
       suffix = '_gyp'
 
     if self.path:
-      name = '%s%s_%s%s' % (prefix, self.path, self.target, suffix)
+      middle = make.StringToMakefileVariable('%s_%s' % (self.path, self.target))
     else:
-      name = '%s%s%s' % (prefix, self.target, suffix)
+      middle = make.StringToMakefileVariable(self.target)
 
-    return make.StringToMakefileVariable(name)
+    return ''.join([prefix, middle, suffix])
 
 
   def ComputeOutputParts(self, spec):
@@ -683,15 +681,15 @@
       if self.toolset == 'host':
         path = '$(HOST_OUT_INTERMEDIATE_LIBRARIES)'
       else:
-        path = '$(TARGET_OUT_INTERMEDIATE_LIBRARIES)'
+        path = '$($(GYP_VAR_PREFIX)TARGET_OUT_INTERMEDIATE_LIBRARIES)'
     else:
       # Other targets just get built into their intermediate dir.
       if self.toolset == 'host':
         path = '$(call intermediates-dir-for,%s,%s,true)' % (self.android_class,
                                                             self.android_module)
       else:
-        path = '$(call intermediates-dir-for,%s,%s)' % (self.android_class,
-                                                        self.android_module)
+        path = ('$(call intermediates-dir-for,%s,%s,,,$(GYP_VAR_PREFIX))'
+                % (self.android_class, self.android_module))
 
     assert spec.get('product_dir') is None # TODO: not supported?
     return os.path.join(path, self.ComputeOutputBasename(spec))
@@ -819,12 +817,15 @@
                    'LOCAL_SHARED_LIBRARIES')
 
 
-  def WriteTarget(self, spec, configs, deps, link_deps, part_of_all):
+  def WriteTarget(self, spec, configs, deps, link_deps, part_of_all,
+                  write_alias_target):
     """Write Makefile code to produce the final target of the gyp spec.
 
     spec, configs: input from gyp.
     deps, link_deps: dependency lists; see ComputeDeps()
     part_of_all: flag indicating this target is part of 'all'
+    write_alias_target: flag indicating whether to create short aliases for this
+                        target
     """
     self.WriteLn('### Rules for final target.')
 
@@ -835,7 +836,7 @@
     # name 'gyp_all_modules' as the Android build system doesn't allow the use
     # of the Make target 'all' and because 'all_modules' is the equivalent of
     # the Make target 'all' on Android.
-    if part_of_all:
+    if part_of_all and write_alias_target:
       self.WriteLn('# Add target alias to "gyp_all_modules" target.')
       self.WriteLn('.PHONY: gyp_all_modules')
       self.WriteLn('gyp_all_modules: %s' % self.android_module)
@@ -844,7 +845,7 @@
     # Add an alias from the gyp target name to the Android module name. This
     # simplifies manual builds of the target, and is required by the test
     # framework.
-    if self.target != self.android_module:
+    if self.target != self.android_module and write_alias_target:
       self.WriteLn('# Alias gyp target name.')
       self.WriteLn('.PHONY: %s' % self.target)
       self.WriteLn('%s: %s' % (self.target, self.android_module))
@@ -873,6 +874,7 @@
     else:
       self.WriteLn('LOCAL_MODULE_PATH := $(PRODUCT_OUT)/gyp_stamp')
       self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true')
+      self.WriteLn('LOCAL_2ND_ARCH_VAR_PREFIX := $(GYP_VAR_PREFIX)')
       self.WriteLn()
       self.WriteLn('include $(BUILD_SYSTEM)/base_rules.mk')
       self.WriteLn()
@@ -880,6 +882,8 @@
       self.WriteLn('\t$(hide) echo "Gyp timestamp: $@"')
       self.WriteLn('\t$(hide) mkdir -p $(dir $@)')
       self.WriteLn('\t$(hide) touch $@')
+      self.WriteLn()
+      self.WriteLn('LOCAL_2ND_ARCH_VAR_PREFIX :=')
 
 
   def WriteList(self, value_list, variable=None, prefix='',
@@ -949,6 +953,7 @@
   generator_flags = params.get('generator_flags', {})
   builddir_name = generator_flags.get('output_dir', 'out')
   limit_to_target_all = generator_flags.get('limit_to_target_all', False)
+  write_alias_targets = generator_flags.get('write_alias_targets', True)
   android_top_dir = os.environ.get('ANDROID_BUILD_TOP')
   assert android_top_dir, '$ANDROID_BUILD_TOP not set; you need to run lunch.'
 
@@ -1044,7 +1049,8 @@
     writer = AndroidMkWriter(android_top_dir)
     android_module = writer.Write(qualified_target, relative_target, base_path,
                                   output_file, spec, configs,
-                                  part_of_all=part_of_all)
+                                  part_of_all=part_of_all,
+                                  write_alias_target=write_alias_targets)
     if android_module in android_modules:
       print ('ERROR: Android module names must be unique. The following '
              'targets both generate Android module name %s.\n  %s\n  %s' %
@@ -1060,6 +1066,7 @@
     include_list.add(mkfile_rel_path)
 
   root_makefile.write('GYP_CONFIGURATION ?= %s\n' % default_configuration)
+  root_makefile.write('GYP_VAR_PREFIX ?=\n')
 
   # Write out the sorted list of includes.
   root_makefile.write('\n')
@@ -1067,6 +1074,7 @@
     root_makefile.write('include $(LOCAL_PATH)/' + include_file + '\n')
   root_makefile.write('\n')
 
-  root_makefile.write(SHARED_FOOTER)
+  if write_alias_targets:
+    root_makefile.write(ALL_MODULES_FOOTER)
 
   root_makefile.close()
diff --git a/pylib/gyp/generator/xcode.py b/pylib/gyp/generator/xcode.py
index 8751810..7972459 100644
--- a/pylib/gyp/generator/xcode.py
+++ b/pylib/gyp/generator/xcode.py
@@ -5,6 +5,7 @@
 import filecmp
 import gyp.common
 import gyp.xcodeproj_file
+import gyp.xcode_ninja
 import errno
 import os
 import sys
@@ -575,6 +576,12 @@
 
 
 def GenerateOutput(target_list, target_dicts, data, params):
+  # Optionally configure each spec to use ninja as the external builder.
+  ninja_wrapper = params.get('flavor') == 'ninja'
+  if ninja_wrapper:
+    (target_list, target_dicts, data) = \
+        gyp.xcode_ninja.CreateWrapper(target_list, target_dicts, data, params)
+
   options = params['options']
   generator_flags = params.get('generator_flags', {})
   parallel_builds = generator_flags.get('xcode_parallel_builds', True)
@@ -703,7 +710,10 @@
     # and is made a dependency of this target.  This way the work is done
     # before the dependency checks for what should be recompiled.
     support_xct = None
-    if type != 'none' and (spec_actions or spec_rules):
+    # The Xcode "issues" don't affect xcode-ninja builds, since the dependency
+    # logic all happens in ninja.  Don't bother creating the extra targets in
+    # that case.
+    if type != 'none' and (spec_actions or spec_rules) and not ninja_wrapper:
       support_xccl = CreateXCConfigurationList(configuration_names);
       support_target_suffix = generator_flags.get(
           'support_target_suffix', ' Support')
diff --git a/pylib/gyp/input.py b/pylib/gyp/input.py
index f694e57..2b33dda 100644
--- a/pylib/gyp/input.py
+++ b/pylib/gyp/input.py
@@ -2172,6 +2172,7 @@
                 if not target_dict['configurations'][i].get('abstract')]
     target_dict['default_configuration'] = sorted(concrete)[0]
 
+  merged_configurations = {}
   for configuration in target_dict['configurations'].keys():
     old_configuration_dict = target_dict['configurations'][configuration]
     # Skip abstract configurations (saves work only).
@@ -2203,8 +2204,12 @@
     MergeConfigWithInheritance(new_configuration_dict, build_file,
                                target_dict, configuration, [])
 
-    # Put the new result back into the target dict as a configuration.
-    target_dict['configurations'][configuration] = new_configuration_dict
+    merged_configurations[configuration] = new_configuration_dict
+
+  # Put the new configurations back into the target dict as a configuration.
+  for configuration in merged_configurations.keys():
+    target_dict['configurations'][configuration] = (
+        merged_configurations[configuration])
 
   # Now drop all the abstract ones.
   for configuration in target_dict['configurations'].keys():
diff --git a/pylib/gyp/xcode_emulation.py b/pylib/gyp/xcode_emulation.py
index d86413a..d856e57 100644
--- a/pylib/gyp/xcode_emulation.py
+++ b/pylib/gyp/xcode_emulation.py
@@ -21,7 +21,125 @@
 # Populated lazily by XcodeVersion, for efficiency, and to fix an issue when
 # "xcodebuild" is called too quickly (it has been found to return incorrect
 # version number).
-XCODE_VERSION_CACHE = []
+XCODE_VERSION_CACHE = None
+
+# Populated lazily by GetXcodeArchsDefault, to an |XcodeArchsDefault| instance
+# corresponding to the installed version of Xcode.
+XCODE_ARCHS_DEFAULT_CACHE = None
+
+
+def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
+  """Constructs a dictionary with expansion for $(ARCHS_STANDARD) variable,
+  and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
+  mapping = {'$(ARCHS_STANDARD)': archs}
+  if archs_including_64_bit:
+    mapping['$(ARCHS_STANDARD_INCLUDING_64_BIT)'] = archs_including_64_bit
+  return mapping
+
+class XcodeArchsDefault(object):
+  """A class to resolve ARCHS variable from xcode_settings, resolving Xcode
+  macros and implementing filtering by VALID_ARCHS. The expansion of macros
+  depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
+  on the version of Xcode.
+  """
+
+  # Match variable like $(ARCHS_STANDARD).
+  variable_pattern = re.compile(r'\$\([a-zA-Z_][a-zA-Z0-9_]*\)$')
+
+  def __init__(self, default, mac, iphonesimulator, iphoneos):
+    self._default = (default,)
+    self._archs = {'mac': mac, 'ios': iphoneos, 'iossim': iphonesimulator}
+
+  def _VariableMapping(self, sdkroot):
+    """Returns the dictionary of variable mapping depending on the SDKROOT."""
+    sdkroot = sdkroot.lower()
+    if 'iphoneos' in sdkroot:
+      return self._archs['ios']
+    elif 'iphonesimulator' in sdkroot:
+      return self._archs['iossim']
+    else:
+      return self._archs['mac']
+
+  def _ExpandArchs(self, archs, sdkroot):
+    """Expands variables references in ARCHS, and remove duplicates."""
+    variable_mapping = self._VariableMapping(sdkroot)
+    expanded_archs = []
+    for arch in archs:
+      if self.variable_pattern.match(arch):
+        variable = arch
+        try:
+          variable_expansion = variable_mapping[variable]
+          for arch in variable_expansion:
+            if arch not in expanded_archs:
+              expanded_archs.append(arch)
+        except KeyError as e:
+          print 'Warning: Ignoring unsupported variable "%s".' % variable
+      elif arch not in expanded_archs:
+        expanded_archs.append(arch)
+    return expanded_archs
+
+  def ActiveArchs(self, archs, valid_archs, sdkroot):
+    """Expands variables references in ARCHS, and filter by VALID_ARCHS if it
+    is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
+    values present in VALID_ARCHS are kept)."""
+    expanded_archs = self._ExpandArchs(archs or self._default, sdkroot or '')
+    if valid_archs:
+      filtered_archs = []
+      for arch in expanded_archs:
+        if arch in valid_archs:
+          filtered_archs.append(arch)
+      expanded_archs = filtered_archs
+    return expanded_archs
+
+
+def GetXcodeArchsDefault():
+  """Returns the |XcodeArchsDefault| object to use to expand ARCHS for the
+  installed version of Xcode. The default values used by Xcode for ARCHS
+  and the expansion of the variables depends on the version of Xcode used.
+
+  For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
+  uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
+  $(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
+  and deprecated with Xcode 5.1.
+
+  For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
+  architecture as part of $(ARCHS_STANDARD) and default to only building it.
+
+  For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
+  of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
+  are also part of $(ARCHS_STANDARD).
+
+  All thoses rules are coded in the construction of the |XcodeArchsDefault|
+  object to use depending on the version of Xcode detected. The object is
+  for performance reason."""
+  global XCODE_ARCHS_DEFAULT_CACHE
+  if XCODE_ARCHS_DEFAULT_CACHE:
+    return XCODE_ARCHS_DEFAULT_CACHE
+  xcode_version, _ = XcodeVersion()
+  if xcode_version < '0500':
+    XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
+        '$(ARCHS_STANDARD)',
+        XcodeArchsVariableMapping(['i386']),
+        XcodeArchsVariableMapping(['i386']),
+        XcodeArchsVariableMapping(['armv7']))
+  elif xcode_version < '0510':
+    XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
+        '$(ARCHS_STANDARD_INCLUDING_64_BIT)',
+        XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
+        XcodeArchsVariableMapping(['i386'], ['i386', 'x86_64']),
+        XcodeArchsVariableMapping(
+            ['armv7', 'armv7s'],
+            ['armv7', 'armv7s', 'arm64']))
+  else:
+    XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
+        '$(ARCHS_STANDARD)',
+        XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
+        XcodeArchsVariableMapping(['i386', 'x86_64'], ['i386', 'x86_64']),
+        XcodeArchsVariableMapping(
+            ['armv7', 'armv7s', 'arm64'],
+            ['armv7', 'armv7s', 'arm64']))
+  return XCODE_ARCHS_DEFAULT_CACHE
+
 
 class XcodeSettings(object):
   """A class that understands the gyp 'xcode_settings' object."""
@@ -268,9 +386,12 @@
 
   def GetActiveArchs(self, configname):
     """Returns the architectures this target should be built for."""
-    # TODO: Look at VALID_ARCHS, ONLY_ACTIVE_ARCH; possibly set
-    # CURRENT_ARCH / NATIVE_ARCH env vars?
-    return self.xcode_settings[configname].get('ARCHS', [self._DefaultArch()])
+    config_settings = self.xcode_settings[configname]
+    xcode_archs_default = GetXcodeArchsDefault()
+    return xcode_archs_default.ActiveArchs(
+        config_settings.get('ARCHS'),
+        config_settings.get('VALID_ARCHS'),
+        config_settings.get('SDKROOT'))
 
   def _GetSdkVersionInfoItem(self, sdk, infoitem):
     # xcodebuild requires Xcode and can't run on Command Line Tools-only
@@ -389,7 +510,8 @@
     if arch is not None:
       archs = [arch]
     else:
-      archs = self._Settings().get('ARCHS', [self._DefaultArch()])
+      assert self.configname
+      archs = self.GetActiveArchs(self.configname)
     if len(archs) != 1:
       # TODO: Supporting fat binaries will be annoying.
       self._WarnUnimplemented('ARCHS')
@@ -646,7 +768,8 @@
     if arch is not None:
       archs = [arch]
     else:
-      archs = self._Settings().get('ARCHS', [self._DefaultArch()])
+      assert self.configname
+      archs = self.GetActiveArchs(self.configname)
     if len(archs) != 1:
       # TODO: Supporting fat binaries will be annoying.
       self._WarnUnimplemented('ARCHS')
@@ -938,28 +1061,6 @@
           return sdk_root
     return ''
 
-  def _DefaultArch(self):
-    # For Mac projects, Xcode changed the default value used when ARCHS is not
-    # set from "i386" to "x86_64".
-    #
-    # For iOS projects, if ARCHS is unset, it defaults to "armv7 armv7s" when
-    # building for a device, and the simulator binaries are always build for
-    # "i386".
-    #
-    # For new projects, ARCHS is set to $(ARCHS_STANDARD_INCLUDING_64_BIT),
-    # which correspond to "armv7 armv7s arm64", and when building the simulator
-    # the architecture is either "i386" or "x86_64" depending on the simulated
-    # device (respectively 32-bit or 64-bit device).
-    #
-    # Since the value returned by this function is only used when ARCHS is not
-    # set, then on iOS we return "i386", as the default xcode project generator
-    # does not set ARCHS if it is not set in the .gyp file.
-    if self.isIOS:
-      return 'i386'
-    version, build = XcodeVersion()
-    if version >= '0500':
-      return 'x86_64'
-    return 'i386'
 
 class MacPrefixHeader(object):
   """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
@@ -1077,9 +1178,9 @@
   #    Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0
   #    BuildVersion: 10M2518
   # Convert that to '0463', '4H1503'.
+  global XCODE_VERSION_CACHE
   if XCODE_VERSION_CACHE:
-    assert len(XCODE_VERSION_CACHE) >= 2
-    return tuple(XCODE_VERSION_CACHE[:2])
+    return XCODE_VERSION_CACHE
   try:
     version_list = GetStdout(['xcodebuild', '-version']).splitlines()
     # In some circumstances xcodebuild exits 0 but doesn't return
@@ -1104,8 +1205,8 @@
   version = (version + '0' * (3 - len(version))).zfill(4)
   if build:
     build = build.split()[-1]
-  XCODE_VERSION_CACHE.extend((version, build))
-  return version, build
+  XCODE_VERSION_CACHE = (version, build)
+  return XCODE_VERSION_CACHE
 
 
 # This function ported from the logic in Homebrew's CLT version check
@@ -1434,53 +1535,6 @@
   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 and use correct architectures for those builds."""
@@ -1490,10 +1544,9 @@
     for config_name, config_dict in dict(configs).iteritems():
       iphoneos_config_dict = copy.deepcopy(config_dict)
       configs[config_name + '-iphoneos'] = iphoneos_config_dict
+      configs[config_name + '-iphonesimulator'] = 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/pylib/gyp/xcode_ninja.py b/pylib/gyp/xcode_ninja.py
new file mode 100644
index 0000000..c886798
--- /dev/null
+++ b/pylib/gyp/xcode_ninja.py
@@ -0,0 +1,230 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Xcode-ninja wrapper project file generator.
+
+This updates the data structures passed to the Xcode gyp generator to build
+with ninja instead. The Xcode project itself is transformed into a list of
+executable targets, each with a build step to build with ninja, and a target
+with every source and resource file.  This appears to sidestep some of the
+major performance headaches experienced using complex projects and large number
+of targets within Xcode.
+"""
+
+import errno
+import gyp.generator.ninja
+import os
+import re
+import xml.sax.saxutils
+
+
+def _WriteWorkspace(main_gyp, sources_gyp):
+  """ Create a workspace to wrap main and sources gyp paths. """
+  (build_file_root, build_file_ext) = os.path.splitext(main_gyp)
+  workspace_path = build_file_root + '.xcworkspace'
+  try:
+    os.makedirs(workspace_path)
+  except OSError, e:
+    if e.errno != errno.EEXIST:
+      raise
+  output_string = '<?xml version="1.0" encoding="UTF-8"?>\n' + \
+                  '<Workspace version = "1.0">\n'
+  for gyp_name in [main_gyp, sources_gyp]:
+    name = os.path.splitext(os.path.basename(gyp_name))[0] + '.xcodeproj'
+    name = xml.sax.saxutils.quoteattr("group:" + name)
+    output_string += '  <FileRef location = %s></FileRef>\n' % name
+  output_string += '</Workspace>\n'
+
+  workspace_file = os.path.join(workspace_path, "contents.xcworkspacedata")
+
+  try:
+    with open(workspace_file, 'r') as input_file:
+      input_string = input_file.read()
+      if input_string == output_string:
+        return
+  except IOError:
+    # Ignore errors if the file doesn't exist.
+    pass
+
+  with open(workspace_file, 'w') as output_file:
+    output_file.write(output_string)
+
+def _TargetFromSpec(old_spec, params):
+  """ Create fake target for xcode-ninja wrapper. """
+  # Determine ninja top level build dir (e.g. /path/to/out).
+  ninja_toplevel = None
+  jobs = 0
+  if params:
+    options = params['options']
+    ninja_toplevel = \
+        os.path.join(options.toplevel_dir,
+                     gyp.generator.ninja.ComputeOutputDir(params))
+    jobs = params.get('generator_flags', {}).get('xcode_ninja_jobs', 0)
+
+  target_name = old_spec.get('target_name')
+  product_name = old_spec.get('product_name', target_name)
+
+  ninja_target = {}
+  ninja_target['target_name'] = target_name
+  ninja_target['product_name'] = product_name
+  ninja_target['toolset'] = old_spec.get('toolset')
+  ninja_target['default_configuration'] = old_spec.get('default_configuration')
+  ninja_target['configurations'] = {}
+
+  # Tell Xcode to look in |ninja_toplevel| for build products.
+  new_xcode_settings = {}
+  if ninja_toplevel:
+    new_xcode_settings['CONFIGURATION_BUILD_DIR'] = \
+        "%s/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)" % ninja_toplevel
+
+  if 'configurations' in old_spec:
+    for config in old_spec['configurations'].iterkeys():
+      old_xcode_settings = old_spec['configurations'][config]['xcode_settings']
+      if 'IPHONEOS_DEPLOYMENT_TARGET' in old_xcode_settings:
+        new_xcode_settings['CODE_SIGNING_REQUIRED'] = "NO"
+        new_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET'] = \
+            old_xcode_settings['IPHONEOS_DEPLOYMENT_TARGET']
+      ninja_target['configurations'][config] = {}
+      ninja_target['configurations'][config]['xcode_settings'] = \
+          new_xcode_settings
+
+  ninja_target['mac_bundle'] = old_spec.get('mac_bundle', 0)
+  ninja_target['type'] = old_spec['type']
+  if ninja_toplevel:
+    ninja_target['actions'] = [
+      {
+        'action_name': 'Compile and copy %s via ninja' % target_name,
+        'inputs': [],
+        'outputs': [],
+        'action': [
+          'env',
+          'PATH=%s' % os.environ['PATH'],
+          'ninja',
+          '-C',
+          new_xcode_settings['CONFIGURATION_BUILD_DIR'],
+          target_name,
+        ],
+        'message': 'Compile and copy %s via ninja' % target_name,
+      },
+    ]
+    if jobs > 0:
+      ninja_target['actions'][0]['action'].extend(('-j', jobs))
+  return ninja_target
+
+def CreateWrapper(target_list, target_dicts, data, params):
+  """Initialize targets for the ninja wrapper.
+
+  This sets up the necessary variables in the targets to generate Xcode projects
+  that use ninja as an external builder.
+  Arguments:
+    target_list: List of target pairs: 'base/base.gyp:base'.
+    target_dicts: Dict of target properties keyed on target pair.
+    data: Dict of flattened build files keyed on gyp path.
+    params: Dict of global options for gyp.
+  """
+  orig_gyp = params['build_files'][0]
+  for gyp_name, gyp_dict in data.iteritems():
+    if gyp_name == orig_gyp:
+      depth = gyp_dict['_DEPTH']
+
+  # Check for custom main gyp name, otherwise use the default CHROMIUM_GYP_FILE
+  # and prepend .ninja before the .gyp extension.
+  generator_flags = params.get('generator_flags', {})
+  main_gyp = generator_flags.get('xcode_ninja_main_gyp', None)
+  if main_gyp is None:
+    (build_file_root, build_file_ext) = os.path.splitext(orig_gyp)
+    main_gyp = build_file_root + ".ninja" + build_file_ext
+
+  # Create new |target_list|, |target_dicts| and |data| data structures.
+  new_target_list = []
+  new_target_dicts = {}
+  new_data = {}
+
+  # Set base keys needed for |data|.
+  new_data[main_gyp] = {}
+  new_data[main_gyp]['included_files'] = []
+  new_data[main_gyp]['targets'] = []
+  new_data[main_gyp]['xcode_settings'] = \
+      data[orig_gyp].get('xcode_settings', {})
+
+  for old_qualified_target in target_list:
+    spec = target_dicts[old_qualified_target]
+    if spec.get('type', '') == 'executable' and \
+        spec.get('product_extension', '') != 'bundle':
+
+      # Add to new_target_list.
+      target_name = spec.get('target_name')
+
+      # Filter target names if requested.
+      target_filter = generator_flags.get('xcode_ninja_target_filter', None)
+      if target_filter is not None:
+        if not re.search(target_filter, target_name):
+          continue;
+
+      new_target_name = '%s:%s#target' % (main_gyp, target_name)
+      new_target_list.append(new_target_name)
+
+      # Add to new_target_dicts.
+      new_target_dicts[new_target_name] = _TargetFromSpec(spec, params)
+
+      # Add to new_data.
+      for old_target in data[old_qualified_target.split(':')[0]]['targets']:
+        if old_target['target_name'] == target_name:
+          new_data_target = {}
+          new_data_target['target_name'] = old_target['target_name']
+          new_data_target['toolset'] = old_target['toolset']
+          new_data[main_gyp]['targets'].append(new_data_target)
+
+  # Create sources target.
+  sources_target_name = 'sources_for_indexing'
+  sources_target = _TargetFromSpec(
+    { 'target_name' : sources_target_name,
+      'toolset': 'target',
+      'default_configuration': 'Default',
+      'mac_bundle': '0',
+      'type': 'executable'
+    }, None)
+
+  # Tell Xcode to look everywhere for headers.
+  sources_target['configurations'] = {'Default': { 'include_dirs': [ depth ] } }
+
+  sources = []
+  for target, target_dict in target_dicts.iteritems():
+    base =  os.path.dirname(target)
+    files = target_dict.get('sources', []) + \
+            target_dict.get('mac_bundle_resources', [])
+    # Remove files starting with $. These are mostly intermediate files for the
+    # build system.
+    files = [ file for file in files if not file.startswith('$')]
+
+    # Make sources relative to root build file.
+    relative_path = os.path.dirname(main_gyp)
+    sources += [ os.path.relpath(os.path.join(base, file), relative_path)
+                    for file in files ]
+
+  sources_target['sources'] = sorted(set(sources))
+
+  # Put sources_to_index in it's own gyp.
+  sources_gyp = \
+      os.path.join(os.path.dirname(main_gyp), sources_target_name + ".gyp")
+  fully_qualified_target_name = \
+      '%s:%s#target' % (sources_gyp, sources_target_name)
+
+  # Add to new_target_list, new_target_dicts and new_data.
+  new_target_list.append(fully_qualified_target_name)
+  new_target_dicts[fully_qualified_target_name] = sources_target
+  new_data_target = {}
+  new_data_target['target_name'] = sources_target['target_name']
+  new_data_target['_DEPTH'] = depth
+  new_data_target['toolset'] = "target"
+  new_data[sources_gyp] = {}
+  new_data[sources_gyp]['targets'] = []
+  new_data[sources_gyp]['included_files'] = []
+  new_data[sources_gyp]['xcode_settings'] = \
+      data[orig_gyp].get('xcode_settings', {})
+  new_data[sources_gyp]['targets'].append(new_data_target)
+
+  # Write workspace to file.
+  _WriteWorkspace(main_gyp, sources_gyp)
+  return (new_target_list, new_target_dicts, new_data)
diff --git a/test/android/gyptest-noalias.py b/test/android/gyptest-noalias.py
new file mode 100755
index 0000000..4840c4c
--- /dev/null
+++ b/test/android/gyptest-noalias.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Verifies that disabling target aliases works.
+"""
+
+import TestGyp
+
+test = TestGyp.TestGyp(formats=['android'])
+
+test.run_gyp('hello.gyp', '-G', 'write_alias_targets=0')
+
+test.build('hello.gyp', 'hello', status=2, stderr=None)
+
+test.build('hello.gyp', 'gyp_all_modules', status=2, stderr=None)
+
+test.pass_test()
diff --git a/test/android/hello.c b/test/android/hello.c
new file mode 100644
index 0000000..e6bf622
--- /dev/null
+++ b/test/android/hello.c
@@ -0,0 +1,12 @@
+/* Copyright (c) 2014 Google Inc. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdio.h>
+
+int main()
+{
+  printf("Hello, world!\n");
+  return 0;
+}
diff --git a/test/android/hello.gyp b/test/android/hello.gyp
new file mode 100644
index 0000000..da58a2b
--- /dev/null
+++ b/test/android/hello.gyp
@@ -0,0 +1,15 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'targets': [
+    {
+      'target_name': 'hello',
+      'type': 'executable',
+      'sources': [
+        'hello.c',
+      ],
+    },
+  ],
+}
diff --git a/test/configurations/inheritance/duplicates.gyp b/test/configurations/inheritance/duplicates.gyp
new file mode 100644
index 0000000..6930ce3
--- /dev/null
+++ b/test/configurations/inheritance/duplicates.gyp
@@ -0,0 +1,27 @@
+# Copyright 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'target_defaults': {
+    'default_configuration': 'A',
+    'configurations': {
+      'A': {
+        'defines': ['SOMETHING'],
+      },
+      'B': {
+        'inherit_from': ['A'],
+      },
+    },
+    'cflags': ['-g'],
+  },
+  'targets': [
+    {
+      'target_name': 'configurations',
+      'type': 'executable',
+      'sources': [
+        'configurations.c',
+      ],
+    },
+  ],
+}
diff --git a/test/configurations/inheritance/duplicates.gypd.golden b/test/configurations/inheritance/duplicates.gypd.golden
new file mode 100644
index 0000000..719b708
--- /dev/null
+++ b/test/configurations/inheritance/duplicates.gypd.golden
@@ -0,0 +1,12 @@
+{'_DEPTH': '.',
+ 'included_files': ['duplicates.gyp'],
+ 'targets': [{'configurations': {'A': {'cflags': ['-g'],
+                                       'defines': ['SOMETHING']},
+                                 'B': {'cflags': ['-g'],
+                                       'defines': ['SOMETHING'],
+                                       'inherit_from': ['A']}},
+              'default_configuration': 'A',
+              'sources': ['configurations.c'],
+              'target_name': 'configurations',
+              'toolset': 'target',
+              'type': 'executable'}]}
diff --git a/test/configurations/inheritance/gyptest-duplicates.py b/test/configurations/inheritance/gyptest-duplicates.py
new file mode 100755
index 0000000..46687b4
--- /dev/null
+++ b/test/configurations/inheritance/gyptest-duplicates.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+# Copyright 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Verifies that configurations do not duplicate other settings.
+"""
+
+import TestGyp
+
+test = TestGyp.TestGyp(format='gypd')
+
+test.run_gyp('duplicates.gyp')
+
+# Verify the duplicates.gypd against the checked-in expected contents.
+#
+# Normally, we should canonicalize line endings in the expected
+# contents file setting the Subversion svn:eol-style to native,
+# but that would still fail if multiple systems are sharing a single
+# workspace on a network-mounted file system.  Consequently, we
+# massage the Windows line endings ('\r\n') in the output to the
+# checked-in UNIX endings ('\n').
+
+contents = test.read('duplicates.gypd').replace(
+    '\r', '').replace('\\\\', '/')
+expect = test.read('duplicates.gypd.golden').replace('\r', '')
+if not test.match(contents, expect):
+  print "Unexpected contents of `duplicates.gypd'"
+  test.diff(expect, contents, 'duplicates.gypd ')
+  test.fail_test()
+
+test.pass_test()
diff --git a/test/ios/app-bundle/test-crosscompile.gyp b/test/ios/app-bundle/test-crosscompile.gyp
new file mode 100644
index 0000000..d904958
--- /dev/null
+++ b/test/ios/app-bundle/test-crosscompile.gyp
@@ -0,0 +1,47 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+{
+  'make_global_settings': [
+    ['CC', '/usr/bin/clang'],
+  ],
+  'targets': [
+    # This target will not be built, but is here so that ninja Xcode emulation
+    # understand this is a multi-platform (ios + mac) build.
+    {
+      'target_name': 'TestDummy',
+      'product_name': 'TestDummy',
+      'toolsets': ['target'],
+      'type': 'executable',
+      'mac_bundle': 1,
+      'sources': [
+        'tool_main.cc',
+      ],
+      'xcode_settings': {
+        'SDKROOT': 'iphonesimulator',  # -isysroot
+        'TARGETED_DEVICE_FAMILY': '1,2',
+        'IPHONEOS_DEPLOYMENT_TARGET': '7.0',
+      },
+    },
+    {
+      'target_name': 'TestHost',
+      'product_name': 'TestHost',
+      'toolsets': ['host'],
+      'type': 'executable',
+      'mac_bundle': 0,
+      'sources': [
+        'tool_main.cc',
+      ],
+      'xcode_settings': {
+        'SDKROOT': 'macosx',
+        'ARCHS': [
+          '$(ARCHS_STANDARD)',
+          'x86_64',
+        ],
+        'VALID_ARCHS': [
+          'x86_64',
+        ],
+      }
+    }
+  ],
+}
diff --git a/test/ios/app-bundle/test.gyp b/test/ios/app-bundle/test.gyp
index 619976d..a8601e8 100644
--- a/test/ios/app-bundle/test.gyp
+++ b/test/ios/app-bundle/test.gyp
@@ -15,7 +15,6 @@
       'target_name': 'test_app',
       'product_name': 'Test App Gyp',
       'type': 'executable',
-      'product_extension': 'bundle',
       'mac_bundle': 1,
       'sources': [
         'TestApp/main.m',
diff --git a/test/ios/app-bundle/tool_main.cc b/test/ios/app-bundle/tool_main.cc
new file mode 100644
index 0000000..9dc3c94
--- /dev/null
+++ b/test/ios/app-bundle/tool_main.cc
@@ -0,0 +1,7 @@
+// Copyright (c) 2014 Google Inc. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+int main() {
+  return 0;
+}
diff --git a/test/ios/gyptest-app-ios.py b/test/ios/gyptest-app-ios.py
index 48da70d..37afbfe 100755
--- a/test/ios/gyptest-app-ios.py
+++ b/test/ios/gyptest-app-ios.py
@@ -20,27 +20,27 @@
   test.build('test.gyp', test.ALL, chdir='app-bundle')
 
   # Test that the extension is .bundle
-  test.built_file_must_exist('Test App Gyp.bundle/Test App Gyp',
+  test.built_file_must_exist('Test App Gyp.app/Test App Gyp',
                              chdir='app-bundle')
 
   # Info.plist
-  info_plist = test.built_file_path('Test App Gyp.bundle/Info.plist',
+  info_plist = test.built_file_path('Test App Gyp.app/Info.plist',
                                     chdir='app-bundle')
   # Resources
   test.built_file_must_exist(
-      'Test App Gyp.bundle/English.lproj/InfoPlist.strings',
+      'Test App Gyp.app/English.lproj/InfoPlist.strings',
       chdir='app-bundle')
   test.built_file_must_exist(
-      'Test App Gyp.bundle/English.lproj/MainMenu.nib',
+      'Test App Gyp.app/English.lproj/MainMenu.nib',
       chdir='app-bundle')
   test.built_file_must_exist(
-      'Test App Gyp.bundle/English.lproj/Main_iPhone.storyboardc',
+      'Test App Gyp.app/English.lproj/Main_iPhone.storyboardc',
       chdir='app-bundle')
 
   # Packaging
-  test.built_file_must_exist('Test App Gyp.bundle/PkgInfo',
+  test.built_file_must_exist('Test App Gyp.app/PkgInfo',
                              chdir='app-bundle')
-  test.built_file_must_match('Test App Gyp.bundle/PkgInfo', 'APPLause',
+  test.built_file_must_match('Test App Gyp.app/PkgInfo', 'APPLause',
                              chdir='app-bundle')
 
   test.pass_test()
diff --git a/test/ios/gyptest-archs.py b/test/ios/gyptest-archs.py
index 8870fec..38c5c61 100644
--- a/test/ios/gyptest-archs.py
+++ b/test/ios/gyptest-archs.py
@@ -12,52 +12,40 @@
 import TestMac
 
 import collections
-import plistlib
-import os
-import re
-import struct
-import subprocess
 import sys
-import tempfile
 
 
 if sys.platform == 'darwin':
   test = TestGyp.TestGyp(formats=['ninja', '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']),
   ]
 
+  if TestMac.Xcode.Version() < '0510':
+    test_cases.extend([
+        ('Default', 'TestNoArchs', ['i386']),
+        ('Default-iphoneos', 'TestNoArchs', ['armv7'])])
+
+  if TestMac.Xcode.Version() >= '0500':
+    test_cases.extend([
+        ('Default', 'TestArch64Bits', ['x86_64']),
+        ('Default', 'TestMultiArchs', ['i386', 'x86_64']),
+        ('Default-iphoneos', 'TestArch64Bits', ['arm64']),
+        ('Default-iphoneos', 'TestMultiArchs', ['armv7', 'arm64'])])
+
   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')
 
     kwds = collections.defaultdict(list)
-    if test.format == 'xcode' and is_device_build:
-      configuration, sdk = configuration.split('-')
-      kwds['arguments'].extend(['-sdk', sdk])
-
-    # TODO(sdefresne): remove those special-cases once the bots have been
-    # updated to use a more recent version of Xcode.
-    if TestMac.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])
-    elif TestMac.Xcode.Version() >= '0510':
-      if target == 'TestNoArchs':
-        continue
+    if test.format == 'xcode':
+      if is_device_build:
+        configuration, sdk = configuration.split('-')
+        kwds['arguments'].extend(['-sdk', sdk])
+      if TestMac.Xcode.Version() < '0500':
+        kwds['arguments'].extend(['-arch', archs[0]])
 
     test.set_configuration(configuration)
     filename = '%s.bundle/%s' % (target, target)
diff --git a/test/ios/gyptest-crosscompile.py b/test/ios/gyptest-crosscompile.py
new file mode 100644
index 0000000..a081683
--- /dev/null
+++ b/test/ios/gyptest-crosscompile.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Verifies that tools are built correctly.
+"""
+
+import TestGyp
+import TestMac
+
+import sys
+import os
+
+if sys.platform == 'darwin':
+  test = TestGyp.TestGyp(formats=['ninja', 'xcode'])
+
+  oldenv = os.environ.copy()
+  try:
+    os.environ['GYP_CROSSCOMPILE'] = '1'
+    test.run_gyp('test-crosscompile.gyp', chdir='app-bundle')
+  finally:
+    os.environ.clear()
+    os.environ.update(oldenv)
+
+  test.set_configuration('Default')
+  test.build('test-crosscompile.gyp', 'TestHost', chdir='app-bundle')
+  result_file = test.built_file_path('TestHost', chdir='app-bundle')
+  test.must_exist(result_file)
+  TestMac.CheckFileType(test, result_file, ['x86_64'])
+
+  test.pass_test()
diff --git a/test/ios/gyptest-xcode-ninja.py b/test/ios/gyptest-xcode-ninja.py
new file mode 100644
index 0000000..d2ff333
--- /dev/null
+++ b/test/ios/gyptest-xcode-ninja.py
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2012 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Verify that the xcode-ninja GYP_GENERATOR runs and builds correctly.
+"""
+
+import TestGyp
+
+import os
+import sys
+
+if sys.platform == 'darwin':
+  test = TestGyp.TestGyp(formats=['xcode'])
+
+  # Run ninja first
+  test.format = 'ninja'
+  test.run_gyp('test.gyp', chdir='app-bundle')
+
+  # Then run xcode-ninja
+  test.format = 'xcode-ninja'
+  test.run_gyp('test.gyp', chdir='app-bundle')
+
+  # If it builds the target, it works.
+  test.build('test.ninja.gyp', chdir='app-bundle')
+  test.pass_test()
diff --git a/test/lib/TestMac.py b/test/lib/TestMac.py
index 755d40e..68605d7 100644
--- a/test/lib/TestMac.py
+++ b/test/lib/TestMac.py
@@ -29,7 +29,7 @@
     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))
+          file, ' '.join(archs), found_file, found_archs)
       test.fail_test()
 
 
diff --git a/test/mac/archs/test-valid-archs.gyp b/test/mac/archs/test-valid-archs.gyp
new file mode 100644
index 0000000..c90ec1f
--- /dev/null
+++ b/test/mac/archs/test-valid-archs.gyp
@@ -0,0 +1,28 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'targets': [
+  {
+    'target_name': 'lib',
+    'product_name': 'Test',
+    'type': 'static_library',
+    'sources': [ 'my_file.cc' ],
+    'xcode_settings': {
+      'ARCHS': ['i386', 'x86_64', 'unknown-arch'],
+      'VALID_ARCHS': ['x86_64'],
+    },
+  },
+  {
+    'target_name': 'exe',
+    'product_name': 'Test',
+    'type': 'executable',
+    'dependencies': [ 'lib' ],
+    'sources': [ 'my_main_file.cc' ],
+    'xcode_settings': {
+      'ARCHS': ['i386', 'x86_64', 'unknown-arch'],
+      'VALID_ARCHS': ['x86_64'],
+    },
+  }]
+}
diff --git a/test/mac/gyptest-archs.py b/test/mac/gyptest-archs.py
index 8ec5b60..ff632d7 100644
--- a/test/mac/gyptest-archs.py
+++ b/test/mac/gyptest-archs.py
@@ -29,6 +29,12 @@
     expected_type = ['i386']
   TestMac.CheckFileType(test, result_file, expected_type)
 
+  test.run_gyp('test-valid-archs.gyp', chdir='archs')
+  test.build('test-valid-archs.gyp', test.ALL, chdir='archs')
+  result_file = test.built_file_path('Test', chdir='archs')
+  test.must_exist(result_file)
+  TestMac.CheckFileType(test, result_file, ['x86_64'])
+
   test.run_gyp('test-archs-x86_64.gyp', chdir='archs')
   test.build('test-archs-x86_64.gyp', test.ALL, chdir='archs')
   result_file = test.built_file_path('Test64', chdir='archs')