Merge from Chromium at DEPS revision 258528

This commit was generated by merge_to_master.py.

Change-Id: I7981e2b0356dfc87f80e08615dfb9144a31ff55a
diff --git a/pylib/gyp/generator/msvs.py b/pylib/gyp/generator/msvs.py
index e20cee1..d1e91c1 100644
--- a/pylib/gyp/generator/msvs.py
+++ b/pylib/gyp/generator/msvs.py
@@ -81,6 +81,7 @@
     'msvs_external_builder_out_dir',
     'msvs_external_builder_build_cmd',
     'msvs_external_builder_clean_cmd',
+    'msvs_external_builder_clcompile_cmd',
 ]
 
 
@@ -1862,6 +1863,14 @@
         'clean',
         '$(ProjectName)',
       ]
+    if not spec.get('msvs_external_builder_clcompile_cmd'):
+      spec['msvs_external_builder_clcompile_cmd'] = [
+        sys.executable,
+        '$(OutDir)/gyp-win-tool',
+        'cl-compile',
+        '$(ProjectDir)',
+        '$(SelectedFiles)',
+      ]
 
 
 def CalculateVariables(default_variables, params):
@@ -3233,7 +3242,9 @@
 def _GetMSBuildExternalBuilderTargets(spec):
   """Return a list of MSBuild targets for external builders.
 
-  Right now, only "Build" and "Clean" targets are generated.
+  The "Build" and "Clean" targets are always generated.  If the spec contains
+  'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also
+  be generated, to support building selected C/C++ files.
 
   Arguments:
     spec: The gyp target spec.
@@ -3252,7 +3263,17 @@
   clean_target = ['Target', {'Name': 'Clean'}]
   clean_target.append(['Exec', {'Command': clean_cmd}])
 
-  return [build_target, clean_target]
+  targets = [build_target, clean_target]
+
+  if spec.get('msvs_external_builder_clcompile_cmd'):
+    clcompile_cmd = _BuildCommandLineForRuleRaw(
+        spec, spec['msvs_external_builder_clcompile_cmd'],
+        False, False, False, False)
+    clcompile_target = ['Target', {'Name': 'ClCompile'}]
+    clcompile_target.append(['Exec', {'Command': clcompile_cmd}])
+    targets.append(clcompile_target)
+
+  return targets
 
 
 def _GetMSBuildExtensions(props_files_of_rules):
diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py
index 053e5e9..efbe854 100644
--- a/pylib/gyp/generator/ninja.py
+++ b/pylib/gyp/generator/ninja.py
@@ -344,7 +344,7 @@
     return os.path.normpath(os.path.join(obj, self.base_dir, path_dir,
                                          path_basename))
 
-  def WriteCollapsedDependencies(self, name, targets):
+  def WriteCollapsedDependencies(self, name, targets, order_only=None):
     """Given a list of targets, return a path for a single file
     representing the result of building all the targets or None.
 
@@ -352,10 +352,11 @@
 
     assert targets == filter(None, targets), targets
     if len(targets) == 0:
+      assert not order_only
       return None
-    if len(targets) > 1:
+    if len(targets) > 1 or order_only:
       stamp = self.GypPathToUniqueOutput(name + '.stamp')
-      targets = self.ninja.build(stamp, 'stamp', targets)
+      targets = self.ninja.build(stamp, 'stamp', targets, order_only=order_only)
       self.ninja.newline()
     return targets[0]
 
@@ -619,12 +620,14 @@
     env = self.GetToolchainEnv()
     all_outputs = []
     for rule in rules:
-      # First write out a rule for the rule action.
-      name = '%s_%s' % (rule['rule_name'],
-                        hashlib.md5(self.qualified_target).hexdigest())
       # Skip a rule with no action and no inputs.
       if 'action' not in rule and not rule.get('rule_sources', []):
         continue
+
+      # First write out a rule for the rule action.
+      name = '%s_%s' % (rule['rule_name'],
+                        hashlib.md5(self.qualified_target).hexdigest())
+
       args = rule['action']
       description = self.GenerateDescription(
           'RULE',
@@ -653,8 +656,22 @@
           return path.replace('\\', '/')
         return path
 
+      inputs = [self.GypPathToNinja(i, env) for i in rule.get('inputs', [])]
+
+      # If there are n source files matching the rule, and m additional rule
+      # inputs, then adding 'inputs' to each build edge written below will
+      # write m * n inputs. Collapsing reduces this to m + n.
+      sources = rule.get('rule_sources', [])
+      num_inputs = len(inputs)
+      if prebuild:
+        num_inputs += 1
+      if num_inputs > 2 and len(sources) > 2:
+        inputs = [
+            self.WriteCollapsedDependencies(name, inputs, order_only=prebuild)]
+        prebuild = []
+
       # For each source file, write an edge that generates all the outputs.
-      for source in rule.get('rule_sources', []):
+      for source in sources:
         source = os.path.normpath(source)
         dirname, basename = os.path.split(source)
         root, ext = os.path.splitext(basename)
@@ -663,9 +680,6 @@
         outputs = [self.ExpandRuleVariables(o, root, dirname,
                                             source, ext, basename)
                    for o in rule['outputs']]
-        inputs = [self.ExpandRuleVariables(i, root, dirname,
-                                           source, ext, basename)
-                  for i in rule.get('inputs', [])]
 
         if int(rule.get('process_outputs_as_sources', False)):
           extra_sources += outputs
@@ -703,7 +717,6 @@
           else:
             assert var == None, repr(var)
 
-        inputs = [self.GypPathToNinja(i, env) for i in inputs]
         outputs = [self.GypPathToNinja(o, env) for o in outputs]
         extra_bindings.append(('unique_name',
             hashlib.md5(outputs[0]).hexdigest()))
diff --git a/pylib/gyp/msvs_emulation.py b/pylib/gyp/msvs_emulation.py
index 6de77d7..6b5dfc2 100644
--- a/pylib/gyp/msvs_emulation.py
+++ b/pylib/gyp/msvs_emulation.py
@@ -390,12 +390,6 @@
     cflags = filter(lambda x: not x.startswith('/MP'), cflags)
     return cflags
 
-  def GetPrecompiledHeader(self, config, gyp_to_build_path):
-    """Returns an object that handles the generation of precompiled header
-    build steps."""
-    config = self._TargetConfig(config)
-    return _PchHelper(self, config, gyp_to_build_path)
-
   def _GetPchFlags(self, config, extension):
     """Get the flags to be added to the cflags for precompiled header support.
     """
@@ -789,7 +783,7 @@
   def GetObjDependencies(self, sources, objs, arch):
     """Given a list of sources files and the corresponding object files,
     returns a list of the pch files that should be depended upon. The
-    additional wrapping in the return value is for interface compatability
+    additional wrapping in the return value is for interface compatibility
     with make.py on Mac, and xcode_emulation.py."""
     assert arch is None
     if not self._PchHeader():
diff --git a/pylib/gyp/win_tool.py b/pylib/gyp/win_tool.py
index 5872f07..7e2d968 100755
--- a/pylib/gyp/win_tool.py
+++ b/pylib/gyp/win_tool.py
@@ -13,6 +13,7 @@
 import re
 import shutil
 import subprocess
+import stat
 import string
 import sys
 
@@ -89,9 +90,19 @@
     """Emulation of rm -rf out && cp -af in out."""
     if os.path.exists(dest):
       if os.path.isdir(dest):
-        shutil.rmtree(dest)
+        def _on_error(fn, path, excinfo):
+          # The operation failed, possibly because the file is set to
+          # read-only. If that's why, make it writable and try the op again.
+          if not os.access(path, os.W_OK):
+            os.chmod(path, stat.S_IWRITE)
+          fn(path)
+        shutil.rmtree(dest, onerror=_on_error)
       else:
+        if not os.access(dest, os.W_OK):
+          # Attempt to make the file writable before deleting it.
+          os.chmod(dest, stat.S_IWRITE)
         os.unlink(dest)
+
     if os.path.isdir(source):
       shutil.copytree(source, dest)
     else:
@@ -288,5 +299,16 @@
     dir = dir[0] if dir else None
     return subprocess.call(args, shell=True, env=env, cwd=dir)
 
+  def ExecClCompile(self, project_dir, selected_files):
+    """Executed by msvs-ninja projects when the 'ClCompile' target is used to
+    build selected C/C++ files."""
+    project_dir = os.path.relpath(project_dir, BASE_DIR)
+    selected_files = selected_files.split(';')
+    ninja_targets = [os.path.join(project_dir, filename) + '^^'
+        for filename in selected_files]
+    cmd = ['ninja.exe']
+    cmd.extend(ninja_targets)
+    return subprocess.call(cmd, shell=True, cwd=BASE_DIR)
+
 if __name__ == '__main__':
   sys.exit(main(sys.argv[1:]))
diff --git a/test/rules/src/special-variables.gyp b/test/rules/src/special-variables.gyp
index fc55665..d1443af 100644
--- a/test/rules/src/special-variables.gyp
+++ b/test/rules/src/special-variables.gyp
@@ -13,7 +13,6 @@
           'extension': 'S',
           'inputs': [
             'as.bat',
-            '$(InputPath)'
           ],
           'outputs': [
             '$(IntDir)/$(InputName).obj',
diff --git a/test/win/command-quote/command-quote.gyp b/test/win/command-quote/command-quote.gyp
index 8489c50..faf7246 100644
--- a/test/win/command-quote/command-quote.gyp
+++ b/test/win/command-quote/command-quote.gyp
@@ -15,7 +15,6 @@
         'rule_name': 'build_with_batch',
         'msvs_cygwin_shell': 0,
         'extension': 'S',
-        'inputs': ['<(RULE_INPUT_PATH)'],
         'outputs': ['output.obj'],
         'action': ['call go.bat', '<(RULE_INPUT_PATH)', 'output.obj'],
       },],
@@ -29,7 +28,6 @@
         'rule_name': 'build_with_batch2',
         'msvs_cygwin_shell': 0,
         'extension': 'S',
-        'inputs': ['<(RULE_INPUT_PATH)'],
         'outputs': ['output2.obj'],
         'action': ['call', 'go.bat', '<(RULE_INPUT_PATH)', 'output2.obj'],
       },],
@@ -43,7 +41,6 @@
         'rule_name': 'build_with_batch3',
         'msvs_cygwin_shell': 0,
         'extension': 'S',
-        'inputs': ['<(RULE_INPUT_PATH)'],
         'outputs': ['output3.obj'],
         'action': ['bat with spaces.bat', '<(RULE_INPUT_PATH)', 'output3.obj'],
       },],
@@ -57,7 +54,6 @@
         'rule_name': 'build_with_batch3',
         'msvs_cygwin_shell': 1,
         'extension': 'S',
-        'inputs': ['<(RULE_INPUT_PATH)'],
         'outputs': ['output4.obj'],
         'arguments': ['-v'],
         'action': ['python', '-c', 'import shutil; '
@@ -73,7 +69,6 @@
         'rule_name': 'build_with_batch3',
         'msvs_cygwin_shell': 1,
         'extension': 'S',
-        'inputs': ['<(RULE_INPUT_PATH)'],
         'outputs': ['output5.obj'],
         'action': ['python', '-c', "import shutil; "
           "shutil.copy('<(RULE_INPUT_PATH)', 'output5.obj')"],
diff --git a/test/win/command-quote/subdir/and/another/in-subdir.gyp b/test/win/command-quote/subdir/and/another/in-subdir.gyp
index be363bb..3dff4c4 100644
--- a/test/win/command-quote/subdir/and/another/in-subdir.gyp
+++ b/test/win/command-quote/subdir/and/another/in-subdir.gyp
@@ -18,7 +18,6 @@
         'rule_name': 'build_with_batch4',
         'msvs_cygwin_shell': 0,
         'extension': 'S',
-        'inputs': ['<(RULE_INPUT_PATH)'],
         'outputs': ['output4.obj'],
         'action': ['<@(filepath)', '<(RULE_INPUT_PATH)', 'output4.obj'],
       },],
diff --git a/test/win/vs-macros/containing-gyp.gyp b/test/win/vs-macros/containing-gyp.gyp
index fa799a4..c07b639 100644
--- a/test/win/vs-macros/containing-gyp.gyp
+++ b/test/win/vs-macros/containing-gyp.gyp
@@ -16,7 +16,6 @@
           'extension': 'S',
           'inputs': [
             'as.py',
-            '$(InputPath)'
           ],
           'outputs': [
             '$(IntDir)/$(InputName).obj',
diff --git a/test/win/vs-macros/input-output-macros.gyp b/test/win/vs-macros/input-output-macros.gyp
index b7a3c1e..b4520f8 100644
--- a/test/win/vs-macros/input-output-macros.gyp
+++ b/test/win/vs-macros/input-output-macros.gyp
@@ -13,7 +13,6 @@
           'rule_name': 'generate_file',
           'extension': 'blah',
           'inputs': [
-            '<(RULE_INPUT_PATH)',
             'do_stuff.py',
           ],
           'outputs': [
diff --git a/test/win/win-tool/copies_readonly_files.gyp b/test/win/win-tool/copies_readonly_files.gyp
new file mode 100644
index 0000000..3cd7e69
--- /dev/null
+++ b/test/win/win-tool/copies_readonly_files.gyp
@@ -0,0 +1,29 @@
+{
+  'targets': [
+    {
+      'target_name': 'foo',
+      'type': 'none',
+      'copies': [
+        {
+          'destination': '<(PRODUCT_DIR)/dest',
+          'files': [
+            'read-only-file',
+          ],
+        },
+      ],
+    },  # target: foo
+
+    {
+      'target_name': 'bar',
+      'type': 'none',
+      'copies': [
+        {
+          'destination': '<(PRODUCT_DIR)/dest',
+          'files': [
+            'subdir/',
+          ],
+        },
+      ],
+    },  # target: bar
+  ],
+}
diff --git a/test/win/win-tool/gyptest-win-tool-handles-readonly-files.py b/test/win/win-tool/gyptest-win-tool-handles-readonly-files.py
new file mode 100644
index 0000000..951b952
--- /dev/null
+++ b/test/win/win-tool/gyptest-win-tool-handles-readonly-files.py
@@ -0,0 +1,55 @@
+#!/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.
+
+"""
+Make sure overwriting read-only files works as expected (via win-tool).
+"""
+
+import TestGyp
+
+import filecmp
+import os
+import stat
+import sys
+
+if sys.platform == 'win32':
+  test = TestGyp.TestGyp(formats=['ninja'])
+
+  # First, create the source files.
+  os.makedirs('subdir')
+  read_only_files = ['read-only-file', 'subdir/A', 'subdir/B', 'subdir/C']
+  for f in read_only_files:
+    test.write(f, 'source_contents')
+    test.chmod(f, stat.S_IREAD)
+    if os.access(f, os.W_OK):
+      test.fail_test()
+
+  # Second, create the read-only destination files. Note that we are creating
+  # them where the ninja and win-tool will try to copy them to, in order to test
+  # that copies overwrite the files.
+  os.makedirs(test.built_file_path('dest/subdir'))
+  for f in read_only_files:
+    f = os.path.join('dest', f)
+    test.write(test.built_file_path(f), 'SHOULD BE OVERWRITTEN')
+    test.chmod(test.built_file_path(f), stat.S_IREAD)
+    # Ensure not writable.
+    if os.access(test.built_file_path(f), os.W_OK):
+      test.fail_test()
+
+  test.run_gyp('copies_readonly_files.gyp')
+  test.build('copies_readonly_files.gyp')
+
+  # Check the destination files were overwritten by ninja.
+  for f in read_only_files:
+    f = os.path.join('dest', f)
+    test.must_contain(test.built_file_path(f), 'source_contents')
+
+  # This will fail if the files are not the same mode or contents.
+  for f in read_only_files:
+    if not filecmp.cmp(f, test.built_file_path(os.path.join('dest', f))):
+      test.fail_test()
+
+  test.pass_test()