Merge from Chromium at DEPS revision 275586

This commit was generated by merge_to_master.py.

Change-Id: I59c2593abd79123d99b1b4cb99aeebe8f2eac41b
diff --git a/buildbot/buildbot_run.py b/buildbot/buildbot_run.py
index 979073c..cc3a25a 100755
--- a/buildbot/buildbot_run.py
+++ b/buildbot/buildbot_run.py
@@ -107,7 +107,7 @@
       cwd=ANDROID_DIR)
 
 
-def GypTestFormat(title, format=None, msvs_version=None):
+def GypTestFormat(title, format=None, msvs_version=None, tests=[]):
   """Run the gyp tests for a given format, emitting annotator tags.
 
   See annotator docs at:
@@ -131,7 +131,7 @@
        '--passed',
        '--format', format,
        '--path', CMAKE_BIN_DIR,
-       '--chdir', 'trunk'])
+       '--chdir', 'trunk'] + tests)
   if format == 'android':
     # gyptest needs the environment setup from envsetup/lunch in order to build
     # using the 'android' backend, so this is done in a single shell.
@@ -173,6 +173,12 @@
   elif sys.platform == 'win32':
     retcode += GypTestFormat('ninja')
     if os.environ['BUILDBOT_BUILDERNAME'] == 'gyp-win64':
+      retcode += GypTestFormat('msvs-ninja-2012', format='msvs-ninja',
+                               msvs_version='2012',
+                               tests=[
+                                   'test\generator-output\gyptest-actions.py',
+                                   'test\generator-output\gyptest-relocate.py',
+                                   'test\generator-output\gyptest-rules.py'])
       retcode += GypTestFormat('msvs-2010', format='msvs', msvs_version='2010')
       retcode += GypTestFormat('msvs-2012', format='msvs', msvs_version='2012')
   else:
diff --git a/pylib/gyp/MSVSSettings.py b/pylib/gyp/MSVSSettings.py
index 205b3b5..b4e0a78 100644
--- a/pylib/gyp/MSVSSettings.py
+++ b/pylib/gyp/MSVSSettings.py
@@ -417,11 +417,11 @@
   if '$' in s:
     replace_map = {
         '$(ConfigurationName)': '$(Configuration)',
-        '$(InputDir)': '%(RootDir)%(Directory)',
+        '$(InputDir)': '%(RelativeDir)',
         '$(InputExt)': '%(Extension)',
         '$(InputFileName)': '%(Filename)%(Extension)',
         '$(InputName)': '%(Filename)',
-        '$(InputPath)': '%(FullPath)',
+        '$(InputPath)': '%(Identity)',
         '$(ParentName)': '$(ProjectFileName)',
         '$(PlatformName)': '$(Platform)',
         '$(SafeInputName)': '%(Filename)',
diff --git a/pylib/gyp/generator/android.py b/pylib/gyp/generator/android.py
index ebc8990..cb97669 100644
--- a/pylib/gyp/generator/android.py
+++ b/pylib/gyp/generator/android.py
@@ -188,6 +188,7 @@
     self.WriteLn('LOCAL_MODULE_TAGS := optional')
     if self.toolset == 'host':
       self.WriteLn('LOCAL_IS_HOST_MODULE := true')
+      self.WriteLn('LOCAL_MULTILIB := $(GYP_HOST_MULTILIB)')
     else:
       self.WriteLn('LOCAL_MODULE_TARGET_ARCH := '
                    '$(TARGET_$(GYP_VAR_PREFIX)ARCH)')
@@ -195,7 +196,7 @@
     # Grab output directories; needed for Actions and Rules.
     if self.toolset == 'host':
       self.WriteLn('gyp_intermediate_dir := '
-                   '$(call local-intermediates-dir)')
+                   '$(call local-intermediates-dir,,$(GYP_HOST_VAR_PREFIX))')
     else:
       self.WriteLn('gyp_intermediate_dir := '
                    '$(call local-intermediates-dir,,$(GYP_VAR_PREFIX))')
@@ -317,12 +318,19 @@
       self.WriteLn('%s: export PATH := $(subst $(ANDROID_BUILD_PATHS),,$(PATH))'
                    % main_output)
 
+      # Don't allow spaces in input/output filenames, but make an exception for
+      # filenames which start with '$(' since it's okay for there to be spaces
+      # inside of make function/macro invocations.
       for input in inputs:
-        assert ' ' not in input, (
-            "Spaces in action input filenames not supported (%s)"  % input)
+        if not input.startswith('$(') and ' ' in input:
+          raise gyp.common.GypError(
+              'Action input filename "%s" in target %s contains a space' %
+              (input, self.target))
       for output in outputs:
-        assert ' ' not in output, (
-            "Spaces in action output filenames not supported (%s)"  % output)
+        if not output.startswith('$(') and ' ' in output:
+          raise gyp.common.GypError(
+              'Action output filename "%s" in target %s contains a space' %
+              (output, self.target))
 
       self.WriteLn('%s: %s $(GYP_TARGET_DEPENDENCIES)' %
                    (main_output, ' '.join(map(self.LocalPathify, inputs))))
@@ -687,14 +695,15 @@
       path = '$(gyp_shared_intermediate_dir)'
     elif self.type == 'shared_library':
       if self.toolset == 'host':
-        path = '$(HOST_OUT_INTERMEDIATE_LIBRARIES)'
+        path = '$($(GYP_HOST_VAR_PREFIX)HOST_OUT_INTERMEDIATE_LIBRARIES)'
       else:
         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)
+        path = ('$(call intermediates-dir-for,%s,%s,true,,'
+                '$(GYP_HOST_VAR_PREFIX))' % (self.android_class,
+                                             self.android_module))
       else:
         path = ('$(call intermediates-dir-for,%s,%s,,,$(GYP_VAR_PREFIX))'
                 % (self.android_class, self.android_module))
@@ -884,6 +893,8 @@
       self.WriteLn('LOCAL_UNINSTALLABLE_MODULE := true')
       if self.toolset == 'target':
         self.WriteLn('LOCAL_2ND_ARCH_VAR_PREFIX := $(GYP_VAR_PREFIX)')
+      else:
+        self.WriteLn('LOCAL_2ND_ARCH_VAR_PREFIX := $(GYP_HOST_VAR_PREFIX)')
       self.WriteLn()
       self.WriteLn('include $(BUILD_SYSTEM)/base_rules.mk')
       self.WriteLn()
@@ -891,9 +902,8 @@
       self.WriteLn('\t$(hide) echo "Gyp timestamp: $@"')
       self.WriteLn('\t$(hide) mkdir -p $(dir $@)')
       self.WriteLn('\t$(hide) touch $@')
-      if self.toolset == 'target':
-        self.WriteLn()
-        self.WriteLn('LOCAL_2ND_ARCH_VAR_PREFIX :=')
+      self.WriteLn()
+      self.WriteLn('LOCAL_2ND_ARCH_VAR_PREFIX :=')
 
 
   def WriteList(self, value_list, variable=None, prefix='',
@@ -1077,6 +1087,8 @@
 
   root_makefile.write('GYP_CONFIGURATION ?= %s\n' % default_configuration)
   root_makefile.write('GYP_VAR_PREFIX ?=\n')
+  root_makefile.write('GYP_HOST_VAR_PREFIX ?=\n')
+  root_makefile.write('GYP_HOST_MULTILIB ?=\n')
 
   # Write out the sorted list of includes.
   root_makefile.write('\n')
diff --git a/pylib/gyp/generator/msvs.py b/pylib/gyp/generator/msvs.py
index f361571..a4ebd7c 100644
--- a/pylib/gyp/generator/msvs.py
+++ b/pylib/gyp/generator/msvs.py
@@ -12,6 +12,7 @@
 
 import gyp.common
 import gyp.easy_xml as easy_xml
+import gyp.generator.ninja as ninja_generator
 import gyp.MSVSNew as MSVSNew
 import gyp.MSVSProject as MSVSProject
 import gyp.MSVSSettings as MSVSSettings
@@ -284,7 +285,7 @@
   if [x for x in cmd if '$(InputDir)' in x]:
     input_dir_preamble = (
       'set INPUTDIR=$(InputDir)\n'
-      'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n'
+      'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n'
       'set INPUTDIR=%INPUTDIR:~0,-1%\n'
       )
   else:
@@ -819,17 +820,21 @@
 def _AdjustSourcesForRules(spec, rules, sources, excluded_sources):
   # Add outputs generated by each rule (if applicable).
   for rule in rules:
-    # Done if not processing outputs as sources.
-    if int(rule.get('process_outputs_as_sources', False)):
-      # Add in the outputs from this rule.
-      trigger_files = _FindRuleTriggerFiles(rule, sources)
-      for trigger_file in trigger_files:
+    # Add in the outputs from this rule.
+    trigger_files = _FindRuleTriggerFiles(rule, sources)
+    for trigger_file in trigger_files:
+      # Remove trigger_file from excluded_sources to let the rule be triggered
+      # (e.g. rule trigger ax_enums.idl is added to excluded_sources
+      # because it's also in an action's inputs in the same project)
+      excluded_sources.discard(_FixPath(trigger_file))
+      # Done if not processing outputs as sources.
+      if int(rule.get('process_outputs_as_sources', False)):
         inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
         inputs = OrderedSet(_FixPaths(inputs))
         outputs = OrderedSet(_FixPaths(outputs))
         inputs.remove(_FixPath(trigger_file))
         sources.update(inputs)
-        if spec['type'] != 'none' and not spec.get('msvs_external_builder'):
+        if not spec.get('msvs_external_builder'):
           excluded_sources.update(inputs)
         sources.update(outputs)
 
@@ -1388,7 +1393,7 @@
     # Add all inputs to sources and excluded sources.
     inputs = OrderedSet(inputs)
     sources.update(inputs)
-    if spec['type'] != 'none' and not spec.get('msvs_external_builder'):
+    if not spec.get('msvs_external_builder'):
       excluded_sources.update(inputs)
     if int(a.get('process_outputs_as_sources', False)):
       _AddNormalizedSources(sources, a.get('outputs', []))
@@ -1783,7 +1788,7 @@
   return projects
 
 
-def _InitNinjaFlavor(options, target_list, target_dicts):
+def _InitNinjaFlavor(params, target_list, target_dicts):
   """Initialize targets for the ninja flavor.
 
   This sets up the necessary variables in the targets to generate msvs projects
@@ -1791,7 +1796,7 @@
   if they have not been set. This allows individual specs to override the
   default values initialized here.
   Arguments:
-    options: Options provided to the generator.
+    params: Params provided to the generator.
     target_list: List of target pairs: 'base/base.gyp:base'.
     target_dicts: Dict of target properties keyed on target pair.
   """
@@ -1805,8 +1810,12 @@
 
     spec['msvs_external_builder'] = 'ninja'
     if not spec.get('msvs_external_builder_out_dir'):
-      spec['msvs_external_builder_out_dir'] = \
-        options.depth + '/out/$(Configuration)'
+      gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target)
+      gyp_dir = os.path.dirname(gyp_file)
+      spec['msvs_external_builder_out_dir'] = os.path.join(
+          gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir),
+          ninja_generator.ComputeOutputDir(params),
+          '$(Configuration)')
     if not spec.get('msvs_external_builder_build_cmd'):
       spec['msvs_external_builder_build_cmd'] = [
         path_to_ninja,
@@ -1901,7 +1910,7 @@
 
   # Optionally configure each spec to use ninja as the external builder.
   if params.get('flavor') == 'ninja':
-    _InitNinjaFlavor(options, target_list, target_dicts)
+    _InitNinjaFlavor(params, target_list, target_dicts)
 
   # Prepare the set of configurations.
   configs = set()
diff --git a/pylib/gyp/win_tool.py b/pylib/gyp/win_tool.py
index 7e2d968..44e1b07 100755
--- a/pylib/gyp/win_tool.py
+++ b/pylib/gyp/win_tool.py
@@ -248,10 +248,11 @@
     # Processing C:\Program Files (x86)\Microsoft SDKs\...\include\objidl.idl
     # objidl.idl
     lines = out.splitlines()
-    prefix = 'Processing '
-    processing = set(os.path.basename(x) for x in lines if x.startswith(prefix))
+    prefixes = ('Processing ', '64 bit Processing ')
+    processing = set(os.path.basename(x)
+                     for x in lines if x.startswith(prefixes))
     for line in lines:
-      if not line.startswith(prefix) and line not in processing:
+      if not line.startswith(prefixes) and line not in processing:
         print line
     return popen.returncode
 
diff --git a/test/android/file.in b/test/android/file.in
new file mode 100644
index 0000000..68016f0
--- /dev/null
+++ b/test/android/file.in
@@ -0,0 +1 @@
+A boring test file
diff --git a/test/android/gyptest-make-functions.py b/test/android/gyptest-make-functions.py
new file mode 100755
index 0000000..cdf0e0e
--- /dev/null
+++ b/test/android/gyptest-make-functions.py
@@ -0,0 +1,24 @@
+#!/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 it's possible for gyp actions to use the result of calling a make
+function with "$()".
+"""
+
+import TestGyp
+
+test = TestGyp.TestGyp(formats=['android'])
+
+test.run_gyp('make_functions.gyp')
+
+test.build('make_functions.gyp', test.ALL)
+
+file_content = 'A boring test file\n'
+test.built_file_must_match('file.in', file_content)
+test.built_file_must_match('file.out', file_content)
+
+test.pass_test()
diff --git a/test/android/gyptest-space-filenames.py b/test/android/gyptest-space-filenames.py
new file mode 100755
index 0000000..c6caf26
--- /dev/null
+++ b/test/android/gyptest-space-filenames.py
@@ -0,0 +1,19 @@
+#!/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 action input/output filenames with spaces are rejected.
+"""
+
+import TestGyp
+
+test = TestGyp.TestGyp(formats=['android'])
+
+stderr = ('gyp: Action input filename "name with spaces" in target do_actions '
+          'contains a space\n')
+test.run_gyp('space_filenames.gyp', status=1, stderr=stderr)
+
+test.pass_test()
diff --git a/test/android/make_functions.gyp b/test/android/make_functions.gyp
new file mode 100644
index 0000000..4b617cc
--- /dev/null
+++ b/test/android/make_functions.gyp
@@ -0,0 +1,31 @@
+# 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': 'file-in',
+      'type': 'none',
+      'copies': [
+        {
+          'destination': '<(PRODUCT_DIR)',
+          'files': [ 'file.in' ],
+        },
+      ],
+    },
+    {
+      'target_name': 'file-out',
+      'type': 'none',
+      'dependencies': [ 'file-in' ],
+      'actions': [
+        {
+          'action_name': 'copy-file',
+          'inputs': [ '$(strip <(PRODUCT_DIR)/file.in)' ],
+          'outputs': [ '<(PRODUCT_DIR)/file.out' ],
+          'action': [ 'cp', '$(strip <(PRODUCT_DIR)/file.in)', '<(PRODUCT_DIR)/file.out' ],
+        }
+      ],
+    },
+  ],
+}
diff --git a/test/android/space_filenames.gyp b/test/android/space_filenames.gyp
new file mode 100644
index 0000000..487ac55
--- /dev/null
+++ b/test/android/space_filenames.gyp
@@ -0,0 +1,18 @@
+# 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': 'do_actions',
+      'type': 'none',
+      'actions': [{
+        'action_name': 'should_be_forbidden',
+        'inputs': [ 'name with spaces' ],
+        'outputs': [ '<(SHARED_INTERMEDIATE_DIR)/name with spaces' ],
+        'action': [ 'true' ],
+      }],
+    },
+  ],
+}
diff --git a/test/ios/gyptest-xcode-ninja.py b/test/ios/gyptest-xcode-ninja.py
index d2ff333..609db8c 100644
--- a/test/ios/gyptest-xcode-ninja.py
+++ b/test/ios/gyptest-xcode-ninja.py
@@ -16,12 +16,8 @@
 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'
+  # Run ninja and xcode-ninja
+  test.formats = ['ninja', 'xcode-ninja']
   test.run_gyp('test.gyp', chdir='app-bundle')
 
   # If it builds the target, it works.
diff --git a/test/lib/TestGyp.py b/test/lib/TestGyp.py
index 36b6281..cde04ca 100644
--- a/test/lib/TestGyp.py
+++ b/test/lib/TestGyp.py
@@ -78,6 +78,7 @@
   configuration and to run executables generated by those builds.
   """
 
+  formats = []
   build_tool = None
   build_tool_list = []
 
@@ -113,6 +114,8 @@
     self.gyp = os.path.abspath(gyp)
     self.no_parallel = False
 
+    self.formats = [self.format]
+
     self.initialize_build_tool()
 
     kw.setdefault('match', TestCommon.match_exact)
@@ -130,10 +133,11 @@
 
     super(TestGypBase, self).__init__(*args, **kw)
 
+    real_format = self.format.split('-')[-1]
     excluded_formats = set([f for f in formats if f[0] == '!'])
     included_formats = set(formats) - excluded_formats
-    if ('!'+self.format in excluded_formats or
-        included_formats and self.format not in included_formats):
+    if ('!'+real_format in excluded_formats or
+        included_formats and real_format not in included_formats):
       msg = 'Invalid test for %r format; skipping test.\n'
       self.skip_test(msg % self.format)
 
@@ -272,9 +276,13 @@
 
     # TODO:  --depth=. works around Chromium-specific tree climbing.
     depth = kw.pop('depth', '.')
-    run_args = ['--depth='+depth, '--format='+self.format, gyp_file]
+    run_args = ['--depth='+depth]
+    run_args.extend(['--format='+f for f in self.formats]);
+    run_args.append(gyp_file)
     if self.no_parallel:
       run_args += ['--no-parallel']
+    # TODO: if extra_args contains a '--build' flag
+    # we really want that to only apply to the last format (self.format).
     run_args.extend(self.extra_args)
     run_args.extend(args)
     return self.run(program=self.gyp, arguments=run_args, **kw)
@@ -747,6 +755,53 @@
   return path
 
 
+def FindMSBuildInstallation(msvs_version = 'auto'):
+  """Returns path to MSBuild for msvs_version or latest available.
+
+  Looks in the registry to find install location of MSBuild.
+  MSBuild before v4.0 will not build c++ projects, so only use newer versions.
+  """
+  import TestWin
+  registry = TestWin.Registry()
+
+  msvs_to_msbuild = {
+      '2013': r'12.0',
+      '2012': r'4.0',  # Really v4.0.30319 which comes with .NET 4.5.
+      '2010': r'4.0'}
+
+  msbuild_basekey = r'HKLM\SOFTWARE\Microsoft\MSBuild\ToolsVersions'
+  if not registry.KeyExists(msbuild_basekey):
+    print 'Error: could not find MSBuild base registry entry'
+    return None
+
+  msbuild_version = None
+  if msvs_version in msvs_to_msbuild:
+    msbuild_test_version = msvs_to_msbuild[msvs_version]
+    if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
+      msbuild_version = msbuild_test_version
+    else:
+      print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
+             'but corresponding MSBuild "%s" was not found.' %
+             (msvs_version, msbuild_version))
+  if not msbuild_version:
+    for msvs_version in sorted(msvs_to_msbuild, reverse=True):
+      msbuild_test_version = msvs_to_msbuild[msvs_version]
+      if registry.KeyExists(msbuild_basekey + '\\' + msbuild_test_version):
+        msbuild_version = msbuild_test_version
+        break
+  if not msbuild_version:
+    print 'Error: could not find MSBuild registry entry'
+    return None
+
+  msbuild_path = registry.GetValue(msbuild_basekey + '\\' + msbuild_version,
+                                   'MSBuildToolsPath')
+  if not msbuild_path:
+    print 'Error: could not get MSBuild registry entry value'
+    return None
+
+  return os.path.join(msbuild_path, 'MSBuild.exe')
+
+
 def FindVisualStudioInstallation():
   """Returns appropriate values for .build_tool and .uses_msbuild fields
   of TestGypBase for Visual Studio.
@@ -772,39 +827,28 @@
     msvs_version = flag.split('=')[-1]
   msvs_version = os.environ.get('GYP_MSVS_VERSION', msvs_version)
 
-  build_tool = None
   if msvs_version in possible_paths:
     # Check that the path to the specified GYP_MSVS_VERSION exists.
     path = possible_paths[msvs_version]
     for r in possible_roots:
-      bt = os.path.join(r, path)
-      if os.path.exists(bt):
-        build_tool = bt
+      build_tool = os.path.join(r, path)
+      if os.path.exists(build_tool):
         uses_msbuild = msvs_version >= '2010'
-        return build_tool, uses_msbuild
+        msbuild_path = FindMSBuildInstallation(msvs_version)
+        return build_tool, uses_msbuild, msbuild_path
     else:
       print ('Warning: Environment variable GYP_MSVS_VERSION specifies "%s" '
               'but corresponding "%s" was not found.' % (msvs_version, path))
-  if build_tool:
-    # We found 'devenv' on the path, use that and try to guess the version.
-    for version, path in possible_paths.iteritems():
-      if build_tool.find(path) >= 0:
-        uses_msbuild = version >= '2010'
-        return build_tool, uses_msbuild
-    else:
-      # If not, assume not MSBuild.
-      uses_msbuild = False
-    return build_tool, uses_msbuild
   # Neither GYP_MSVS_VERSION nor the path help us out.  Iterate through
   # the choices looking for a match.
   for version in sorted(possible_paths, reverse=True):
     path = possible_paths[version]
     for r in possible_roots:
-      bt = os.path.join(r, path)
-      if os.path.exists(bt):
-        build_tool = bt
+      build_tool = os.path.join(r, path)
+      if os.path.exists(build_tool):
         uses_msbuild = msvs_version >= '2010'
-        return build_tool, uses_msbuild
+        msbuild_path = FindMSBuildInstallation(msvs_version)
+        return build_tool, uses_msbuild, msbuild_path
   print 'Error: could not find devenv'
   sys.exit(1)
 
@@ -822,7 +866,8 @@
   def initialize_build_tool(self):
     super(TestGypOnMSToolchain, self).initialize_build_tool()
     if sys.platform in ('win32', 'cygwin'):
-      self.devenv_path, self.uses_msbuild = FindVisualStudioInstallation()
+      build_tools = FindVisualStudioInstallation()
+      self.devenv_path, self.uses_msbuild, self.msbuild_path = build_tools
       self.vsvars_path = TestGypOnMSToolchain._ComputeVsvarsPath(
           self.devenv_path)
 
@@ -1002,6 +1047,56 @@
     return self.workpath(*result)
 
 
+class TestGypMSVSNinja(TestGypNinja):
+  """
+  Subclass for testing the GYP Visual Studio Ninja generator.
+  """
+  format = 'msvs-ninja'
+
+  def initialize_build_tool(self):
+    super(TestGypMSVSNinja, self).initialize_build_tool()
+    # When using '--build', make sure ninja is first in the format list.
+    self.formats.insert(0, 'ninja')
+
+  def build(self, gyp_file, target=None, rebuild=False, clean=False, **kw):
+    """
+    Runs a Visual Studio build using the configuration generated
+    from the specified gyp_file.
+    """
+    arguments = kw.get('arguments', [])[:]
+    if target in (None, self.ALL, self.DEFAULT):
+      # Note: the Visual Studio generator doesn't add an explicit 'all' target.
+      # This will build each project. This will work if projects are hermetic,
+      # but may fail if they are not (a project may run more than once).
+      # It would be nice to supply an all.metaproj for MSBuild.
+      arguments.extend([gyp_file.replace('.gyp', '.sln')])
+    else:
+      # MSBuild documentation claims that one can specify a sln but then build a
+      # project target like 'msbuild a.sln /t:proj:target' but this format only
+      # supports 'Clean', 'Rebuild', and 'Publish' (with none meaning Default).
+      # This limitation is due to the .sln -> .sln.metaproj conversion.
+      # The ':' is not special, 'proj:target' is a target in the metaproj.
+      arguments.extend([target+'.vcxproj'])
+
+    if clean:
+      build = 'Clean'
+    elif rebuild:
+      build = 'Rebuild'
+    else:
+      build = 'Build'
+    arguments.extend(['/target:'+build])
+    configuration = self.configuration_buildname()
+    config = configuration.split('|')
+    arguments.extend(['/property:Configuration='+config[0]])
+    if len(config) > 1:
+      arguments.extend(['/property:Platform='+config[1]])
+    arguments.extend(['/property:BuildInParallel=false'])
+    arguments.extend(['/verbosity:minimal'])
+
+    kw['arguments'] = arguments
+    return self.run(program=self.msbuild_path, **kw)
+
+
 class TestGypXcode(TestGypBase):
   """
   Subclass for testing the GYP Xcode generator.
@@ -1118,6 +1213,7 @@
   TestGypCMake,
   TestGypMake,
   TestGypMSVS,
+  TestGypMSVSNinja,
   TestGypNinja,
   TestGypXcode,
 ]
diff --git a/test/lib/TestWin.py b/test/lib/TestWin.py
new file mode 100644
index 0000000..1571c85
--- /dev/null
+++ b/test/lib/TestWin.py
@@ -0,0 +1,101 @@
+# 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.
+
+"""
+TestWin.py:  a collection of helpers for testing on Windows.
+"""
+
+import errno
+import os
+import re
+import sys
+import subprocess
+
+class Registry(object):
+  def _QueryBase(self, sysdir, key, value):
+    """Use reg.exe to read a particular key.
+
+    While ideally we might use the win32 module, we would like gyp to be
+    python neutral, so for instance cygwin python lacks this module.
+
+    Arguments:
+      sysdir: The system subdirectory to attempt to launch reg.exe from.
+      key: The registry key to read from.
+      value: The particular value to read.
+    Return:
+      stdout from reg.exe, or None for failure.
+    """
+    # Skip if not on Windows or Python Win32 setup issue
+    if sys.platform not in ('win32', 'cygwin'):
+      return None
+    # Setup params to pass to and attempt to launch reg.exe
+    cmd = [os.path.join(os.environ.get('WINDIR', ''), sysdir, 'reg.exe'),
+           'query', key]
+    if value:
+      cmd.extend(['/v', value])
+    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    # Get the stdout from reg.exe, reading to the end so p.returncode is valid
+    # Note that the error text may be in [1] in some cases
+    text = p.communicate()[0]
+    # Check return code from reg.exe; officially 0==success and 1==error
+    if p.returncode:
+      return None
+    return text
+
+  def Query(self, key, value=None):
+    """Use reg.exe to read a particular key through _QueryBase.
+
+    First tries to launch from %WinDir%\Sysnative to avoid WoW64 redirection. If
+    that fails, it falls back to System32.  Sysnative is available on Vista and
+    up and available on Windows Server 2003 and XP through KB patch 942589. Note
+    that Sysnative will always fail if using 64-bit python due to it being a
+    virtual directory and System32 will work correctly in the first place.
+
+    KB 942589 - http://support.microsoft.com/kb/942589/en-us.
+
+    Arguments:
+      key: The registry key.
+      value: The particular registry value to read (optional).
+    Return:
+      stdout from reg.exe, or None for failure.
+    """
+    text = None
+    try:
+      text = self._QueryBase('Sysnative', key, value)
+    except OSError, e:
+      if e.errno == errno.ENOENT:
+        text = self._QueryBase('System32', key, value)
+      else:
+        raise
+    return text
+
+  def GetValue(self, key, value):
+    """Use reg.exe to obtain the value of a registry key.
+
+    Args:
+      key: The registry key.
+      value: The particular registry value to read.
+    Return:
+      contents of the registry key's value, or None on failure.
+    """
+    text = self.Query(key, value)
+    if not text:
+      return None
+    # Extract value.
+    match = re.search(r'REG_\w+\s+([^\r]+)\r\n', text)
+    if not match:
+      return None
+    return match.group(1)
+
+  def KeyExists(self, key):
+    """Use reg.exe to see if a key exists.
+
+    Args:
+      key: The registry key to check.
+    Return:
+      True if the key exists
+    """
+    if not self.Query(key):
+      return False
+    return True
diff --git a/test/rules-dirname/gyptest-dirname.py b/test/rules-dirname/gyptest-dirname.py
index 46f3a1b..420b80f 100755
--- a/test/rules-dirname/gyptest-dirname.py
+++ b/test/rules-dirname/gyptest-dirname.py
@@ -10,8 +10,9 @@
 
 import TestGyp
 import os
+import sys
 
-test = TestGyp.TestGyp(formats=['make', 'ninja', 'android', 'xcode'])
+test = TestGyp.TestGyp(formats=['make', 'ninja', 'android', 'xcode', 'msvs'])
 
 test.run_gyp('actions.gyp', chdir='src')
 
@@ -29,9 +30,21 @@
 else:
   chdir = 'relocate/src'
 test.run_built_executable('gencc_int_output', chdir=chdir, stdout=expect)
+if test.format == 'msvs':
+  test.run_built_executable('gencc_int_output_external', chdir=chdir,
+                            stdout=expect)
 
-test.must_match('relocate/src/subdir/foo/bar/baz.printed',
+test.must_match('relocate/src/subdir/foo/bar/baz.dirname',
                 os.path.join('foo', 'bar'))
-test.must_match('relocate/src/subdir/a/b/c.printed', os.path.join('a', 'b'))
+test.must_match('relocate/src/subdir/a/b/c.dirname',
+                os.path.join('a', 'b'))
+
+# FIXME the xcode and make generators incorrectly convert RULE_INPUT_PATH
+# to an absolute path, making the tests below fail!
+if test.format != 'xcode' and test.format != 'make':
+  test.must_match('relocate/src/subdir/foo/bar/baz.path',
+                  os.path.join('foo', 'bar', 'baz.printvars'))
+  test.must_match('relocate/src/subdir/a/b/c.path',
+                  os.path.join('a', 'b', 'c.printvars'))
 
 test.pass_test()
diff --git a/test/rules-dirname/src/subdir/input-rule-dirname.gyp b/test/rules-dirname/src/subdir/input-rule-dirname.gyp
index 36676f1..da749a2 100644
--- a/test/rules-dirname/src/subdir/input-rule-dirname.gyp
+++ b/test/rules-dirname/src/subdir/input-rule-dirname.gyp
@@ -5,6 +5,30 @@
 {
   'targets': [
     {
+      'target_name': 'print_rule_input_dirname',
+      'type': 'none',
+      'msvs_cygwin_shell': 0,
+      'sources': [
+        'foo/bar/baz.printvars',
+        'a/b/c.printvars',
+      ],
+      'rules': [
+        {
+          'rule_name': 'printvars',
+          'extension': 'printvars',
+          'inputs': [
+            'printvars.py',
+          ],
+          'outputs': [
+            '<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).dirname',
+          ],
+          'action': [
+            'python', '<@(_inputs)', '<(RULE_INPUT_DIRNAME)', '<@(_outputs)',
+          ],
+        },
+      ],
+    },
+    {
       'target_name': 'print_rule_input_path',
       'type': 'none',
       'msvs_cygwin_shell': 0,
@@ -20,10 +44,10 @@
             'printvars.py',
           ],
           'outputs': [
-            '<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).printed',
+            '<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).path',
           ],
           'action': [
-            'python', '<@(_inputs)', '<(RULE_INPUT_DIRNAME)', '<@(_outputs)',
+            'python', '<@(_inputs)', '<(RULE_INPUT_PATH)', '<@(_outputs)',
           ],
         },
       ],
@@ -32,25 +56,16 @@
       'target_name': 'gencc_int_output',
       'type': 'executable',
       'msvs_cygwin_shell': 0,
-      'msvs_cygwin_dirs': ['../../../../../../<(DEPTH)/third_party/cygwin'],
       'sources': [
         'nodir.gencc',
         'foo/bar/baz.gencc',
         'a/b/c.gencc',
-	'main.cc',
-      ],
-      'conditions': [
-        ['OS=="win"', {
-          'dependencies': [
-            'cygwin',
-          ],
-        }],
+        'main.cc',
       ],
       'rules': [
         {
           'rule_name': 'gencc',
           'extension': 'gencc',
-          'msvs_external_rule': 1,
           'inputs': [
             '<(DEPTH)/copy-file.py',
           ],
@@ -69,6 +84,38 @@
     ['OS=="win"', {
       'targets': [
         {
+          'target_name': 'gencc_int_output_external',
+          'type': 'executable',
+          'msvs_cygwin_shell': 0,
+          'msvs_cygwin_dirs': ['../../../../../../<(DEPTH)/third_party/cygwin'],
+          'sources': [
+            'nodir.gencc',
+            'foo/bar/baz.gencc',
+            'a/b/c.gencc',
+            'main.cc',
+          ],
+          'dependencies': [
+            'cygwin',
+          ],
+          'rules': [
+            {
+              'rule_name': 'gencc',
+              'extension': 'gencc',
+              'msvs_external_rule': 1,
+              'inputs': [
+                '<(DEPTH)/copy-file.py',
+              ],
+              'outputs': [
+                '<(INTERMEDIATE_DIR)/<(RULE_INPUT_DIRNAME)/<(RULE_INPUT_ROOT).cc',
+              ],
+              'action': [
+                'python', '<@(_inputs)', '<(RULE_INPUT_PATH)', '<@(_outputs)',
+              ],
+              'process_outputs_as_sources': 1,
+            },
+          ],
+        },
+        {
           'target_name': 'cygwin',
           'type': 'none',
           'actions': [
diff --git a/test/rules/gyptest-all.py b/test/rules/gyptest-all.py
index d869fd3..99b2109 100755
--- a/test/rules/gyptest-all.py
+++ b/test/rules/gyptest-all.py
@@ -50,6 +50,10 @@
 test.must_match('relocate/src/subdir2/file1.out2', 'Hello from file1.in\n')
 test.must_match('relocate/src/subdir2/file2.out2', 'Hello from file2.in\n')
 
+test.must_match('relocate/src/subdir2/file1.out4', 'Hello from file1.in\n')
+test.must_match('relocate/src/subdir2/file2.out4', 'Hello from file2.in\n')
+test.must_match('relocate/src/subdir2/file1.copy', 'Hello from file1.in\n')
+
 test.must_match('relocate/src/external/file1.external_rules.out',
                 'Hello from file1.in\n')
 test.must_match('relocate/src/external/file2.external_rules.out',
diff --git a/test/rules/gyptest-default.py b/test/rules/gyptest-default.py
index 117c53d..048c93c 100755
--- a/test/rules/gyptest-default.py
+++ b/test/rules/gyptest-default.py
@@ -47,6 +47,10 @@
 test.must_match('relocate/src/subdir2/file1.out2', 'Hello from file1.in\n')
 test.must_match('relocate/src/subdir2/file2.out2', 'Hello from file2.in\n')
 
+test.must_match('relocate/src/subdir2/file1.out4', 'Hello from file1.in\n')
+test.must_match('relocate/src/subdir2/file2.out4', 'Hello from file2.in\n')
+test.must_match('relocate/src/subdir2/file1.copy', 'Hello from file1.in\n')
+
 test.must_match('relocate/src/external/file1.external_rules.out',
                 'Hello from file1.in\n')
 test.must_match('relocate/src/external/file2.external_rules.out',
diff --git a/test/rules/src/actions.gyp b/test/rules/src/actions.gyp
index 5c0a40b..84376a7 100644
--- a/test/rules/src/actions.gyp
+++ b/test/rules/src/actions.gyp
@@ -9,6 +9,7 @@
       'type': 'none',
       'dependencies': [
         'subdir1/executable.gyp:*',
+        'subdir2/both_rule_and_action_input.gyp:*',
         'subdir2/never_used.gyp:*',
         'subdir2/no_inputs.gyp:*',
         'subdir2/no_action.gyp:*',
diff --git a/test/rules/src/subdir2/both_rule_and_action_input.gyp b/test/rules/src/subdir2/both_rule_and_action_input.gyp
new file mode 100644
index 0000000..e5e6f3e
--- /dev/null
+++ b/test/rules/src/subdir2/both_rule_and_action_input.gyp
@@ -0,0 +1,50 @@
+# 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.
+
+# Tests that if a rule input is also an action input, both the rule and action
+# are executed
+{
+  'targets': [
+    {
+      'target_name': 'files_both_rule_and_action_input',
+      'type': 'executable',
+      'msvs_cygwin_shell': 0,
+      'sources': [
+        'program.c',
+        'file1.in',
+        'file2.in',
+      ],
+      'rules': [
+        {
+          'rule_name': 'copy_file',
+          'extension': 'in',
+          'inputs': [
+            '../copy-file.py',
+          ],
+          'outputs': [
+            '<(RULE_INPUT_ROOT).out4',
+          ],
+          'action': [
+            'python', '<(_inputs)', '<(RULE_INPUT_PATH)', '<@(_outputs)',
+          ],
+        },
+      ],
+      'actions': [
+        {
+          'action_name': 'copy_file1_in',
+          'inputs': [
+            '../copy-file.py',
+            'file1.in',
+          ],
+          'outputs': [
+            'file1.copy',
+          ],
+          'action': [
+            'python', '<@(_inputs)', '<(_outputs)'
+          ],
+        },
+      ],
+    },
+  ],
+}
diff --git a/test/rules/src/subdir2/program.c b/test/rules/src/subdir2/program.c
new file mode 100644
index 0000000..c9ae7cd
--- /dev/null
+++ b/test/rules/src/subdir2/program.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(int argc, char *argv[])
+{
+  printf("Hello from program.c\n");
+  return 0;
+}
diff --git a/test/win/gyptest-midl-excluded.py b/test/win/gyptest-midl-excluded.py
new file mode 100644
index 0000000..70059ab
--- /dev/null
+++ b/test/win/gyptest-midl-excluded.py
@@ -0,0 +1,22 @@
+#!/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.
+
+"""
+Test that .idl files in actions and non-native rules are excluded.
+"""
+
+import TestGyp
+
+import sys
+
+if sys.platform == 'win32':
+  test = TestGyp.TestGyp(formats=['msvs', 'ninja'])
+
+  CHDIR = 'idl-excluded'
+  test.run_gyp('idl-excluded.gyp', chdir=CHDIR)
+  test.build('idl-excluded.gyp', test.ALL, chdir=CHDIR)
+
+  test.pass_test()
diff --git a/test/win/gyptest-midl-rules.py b/test/win/gyptest-midl-rules.py
index d615ba5..591a507 100644
--- a/test/win/gyptest-midl-rules.py
+++ b/test/win/gyptest-midl-rules.py
@@ -21,4 +21,8 @@
     test.set_configuration('Debug|%s' % platform)
     test.build('basic-idl.gyp', test.ALL, chdir=CHDIR)
 
+    # Make sure ninja win_tool.py filters out noisy lines.
+    if test.format == 'ninja' and 'Processing' in test.stdout():
+      test.fail_test()
+
     test.pass_test()
diff --git a/test/win/idl-excluded/bad.idl b/test/win/idl-excluded/bad.idl
new file mode 100644
index 0000000..38554e9
--- /dev/null
+++ b/test/win/idl-excluded/bad.idl
@@ -0,0 +1,6 @@
+// Copyright (c) 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+This is a dummy .idl file that will trigger an error if it is not excluded from
+the build.
diff --git a/test/win/idl-excluded/copy-file.py b/test/win/idl-excluded/copy-file.py
new file mode 100644
index 0000000..5a5feae
--- /dev/null
+++ b/test/win/idl-excluded/copy-file.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2009 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.
+import sys
+
+contents = open(sys.argv[1], 'r').read()
+open(sys.argv[2], 'wb').write(contents)
+
+sys.exit(0)
diff --git a/test/win/idl-excluded/idl-excluded.gyp b/test/win/idl-excluded/idl-excluded.gyp
new file mode 100644
index 0000000..972b7de
--- /dev/null
+++ b/test/win/idl-excluded/idl-excluded.gyp
@@ -0,0 +1,58 @@
+# Copyright (c) 2014 The Chromium Authors. 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': 'exclude_with_action',
+      'type': 'none',
+      'msvs_cygwin_shell': 0,
+      'actions': [{
+        'action_name': 'copy_action',
+        'inputs': [
+          'copy-file.py',
+          'bad.idl',
+        ],
+        'outputs': [
+          '<(INTERMEDIATE_DIR)/bad.idl',
+        ],
+        'action': [
+          'python', '<@(_inputs)', '<@(_outputs)',
+        ],
+      }],
+    },
+    {
+      'target_name': 'exclude_with_rule',
+      'type': 'none',
+      'msvs_cygwin_shell': 0,
+      'sources': [
+        'bad.idl',
+      ],
+      'rules': [{
+        'rule_name': 'copy_rule',
+        'extension': 'idl',
+        'inputs': [
+          'copy-file.py',
+        ],
+        'outputs': [
+          '<(INTERMEDIATE_DIR)/<(RULE_INPUT_ROOT).idl',
+        ],
+        'action': [
+          'python', '<@(_inputs)', '<(RULE_INPUT_PATH)', '<@(_outputs)',
+        ],
+      }],
+    },
+    {
+      'target_name': 'program',
+      'type': 'executable',
+      'sources': [
+        'program.cc',
+      ],
+      'dependencies': [
+        'exclude_with_action',
+        'exclude_with_rule',
+      ],
+    },
+  ],
+}
diff --git a/test/win/idl-excluded/program.cc b/test/win/idl-excluded/program.cc
new file mode 100644
index 0000000..9dc3c94
--- /dev/null
+++ b/test/win/idl-excluded/program.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;
+}