Merge tools/gyp from https://chromium.googlesource.com/external/gyp.git at ac2684beed6e8ca04ac5f837c9db869eefec7bb5

This commit was generated by merge_from_chromium.py.

Change-Id: I36616486742ab4c073a2b5e3baf84a21d3150b97
diff --git a/pylib/gyp/generator/analyzer.py b/pylib/gyp/generator/analyzer.py
index 6f3b610..dc55da6 100644
--- a/pylib/gyp/generator/analyzer.py
+++ b/pylib/gyp/generator/analyzer.py
@@ -9,11 +9,15 @@
 files: list of paths (relative) of the files to search for.
 targets: list of targets to search for. The target names are unqualified.
 
-The following (as JSON) is output:
+The following is output:
 error: only supplied if there is an error.
+warning: only supplied if there is a warning.
 targets: the set of targets passed in via targets that either directly or
   indirectly depend upon the set of paths supplied in files.
 status: indicates if any of the supplied files matched at least one target.
+
+If the generator flag analyzer_output_path is specified, output is written
+there. Otherwise output is written to stdout.
 """
 
 import gyp.common
@@ -293,6 +297,20 @@
       found.append(gyp.common.ParseQualifiedTarget(target)[1])
   return found
 
+def _WriteOutput(params, **values):
+  """Writes the output, either to stdout or a file is specified."""
+  output_path = params.get('generator_flags', {}).get(
+      'analyzer_output_path', None)
+  if not output_path:
+    print json.dumps(values)
+    return
+  try:
+    f = open(output_path, 'w')
+    f.write(json.dumps(values) + '\n')
+    f.close()
+  except IOError as e:
+    print 'Error writing to output file', output_path, str(e)
+
 def CalculateVariables(default_variables, params):
   """Calculate additional variables for use in the build (called by gyp)."""
   flavor = gyp.common.GetFlavor(params)
@@ -342,6 +360,7 @@
       print found_dependency_string if matched else no_dependency_string
       return
 
+    warning = None
     if matched:
       unqualified_mapping = _GetUnqualifiedToQualifiedMapping(
           all_targets, config.targets)
@@ -350,15 +369,21 @@
         for target in config.targets:
           if not target in unqualified_mapping:
             not_found.append(target)
-        raise Exception('Unable to find all targets: ' + str(not_found))
-      qualified_targets = [unqualified_mapping[x] for x in config.targets]
+        warning = 'Unable to find all targets: ' + str(not_found)
+      qualified_targets = []
+      for target in config.targets:
+        if target in unqualified_mapping:
+          qualified_targets.append(unqualified_mapping[target])
       output_targets = _GetTargetsDependingOn(all_targets, qualified_targets)
     else:
       output_targets = []
 
-    print json.dumps(
-      {'targets': output_targets,
-       'status': found_dependency_string if matched else no_dependency_string })
+    result_dict = { 'targets': output_targets,
+                    'status': found_dependency_string if matched else
+                              no_dependency_string }
+    if warning:
+      result_dict['warning'] = warning
+    _WriteOutput(params, **result_dict)
 
   except Exception as e:
-    print json.dumps({'error': str(e)})
+    _WriteOutput(params, error=str(e))
diff --git a/pylib/gyp/input.py b/pylib/gyp/input.py
index e0813f3..bb853a5 100644
--- a/pylib/gyp/input.py
+++ b/pylib/gyp/input.py
@@ -994,23 +994,29 @@
     # Prepare for the next match iteration.
     input_str = output
 
-  # Look for more matches now that we've replaced some, to deal with
-  # expanding local variables (variables defined in the same
-  # variables block as this one).
-  gyp.DebugOutput(gyp.DEBUG_VARIABLES, "Found output %r, recursing.", output)
-  if type(output) is list:
-    if output and type(output[0]) is list:
-      # Leave output alone if it's a list of lists.
-      # We don't want such lists to be stringified.
-      pass
-    else:
-      new_output = []
-      for item in output:
-        new_output.append(
-            ExpandVariables(item, phase, variables, build_file))
-      output = new_output
+  if output == input:
+    gyp.DebugOutput(gyp.DEBUG_VARIABLES,
+                    "Found only identity matches on %r, avoiding infinite "
+                    "recursion.",
+                    output)
   else:
-    output = ExpandVariables(output, phase, variables, build_file)
+    # Look for more matches now that we've replaced some, to deal with
+    # expanding local variables (variables defined in the same
+    # variables block as this one).
+    gyp.DebugOutput(gyp.DEBUG_VARIABLES, "Found output %r, recursing.", output)
+    if type(output) is list:
+      if output and type(output[0]) is list:
+        # Leave output alone if it's a list of lists.
+        # We don't want such lists to be stringified.
+        pass
+      else:
+        new_output = []
+        for item in output:
+          new_output.append(
+              ExpandVariables(item, phase, variables, build_file))
+        output = new_output
+    else:
+      output = ExpandVariables(output, phase, variables, build_file)
 
   # Convert all strings that are canonically-represented integers into integers.
   if type(output) is list:
diff --git a/pylib/gyp/xcodeproj_file.py b/pylib/gyp/xcodeproj_file.py
index 2b91f5f..7c7f1fb 100644
--- a/pylib/gyp/xcodeproj_file.py
+++ b/pylib/gyp/xcodeproj_file.py
@@ -2316,11 +2316,11 @@
 
         if force_extension is not None:
           # If it's a wrapper (bundle), set WRAPPER_EXTENSION.
+          # Extension override.
+          suffix = '.' + force_extension
           if filetype.startswith('wrapper.'):
             self.SetBuildSetting('WRAPPER_EXTENSION', force_extension)
           else:
-            # Extension override.
-            suffix = '.' + force_extension
             self.SetBuildSetting('EXECUTABLE_EXTENSION', force_extension)
 
           if filetype.startswith('compiled.mach-o.executable'):
diff --git a/test/analyzer/gyptest-analyzer.new.py b/test/analyzer/gyptest-analyzer.new.py
new file mode 100644
index 0000000..db7e125
--- /dev/null
+++ b/test/analyzer/gyptest-analyzer.new.py
@@ -0,0 +1,199 @@
+#!/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.
+
+"""Tests for analyzer
+"""
+
+import json
+import TestGyp
+
+# TODO(sky): when done migrating recipes rename to gyptest-analyzer and nuke
+# existing gyptest-analyzer.
+
+found = 'Found dependency'
+not_found = 'No dependencies'
+
+def _CreateTestFile(files, targets):
+  f = open('test_file', 'w')
+  to_write = {'files': files, 'targets': targets }
+  json.dump(to_write, f)
+  f.close()
+
+def _CreateBogusTestFile():
+  f = open('test_file','w')
+  f.write('bogus')
+  f.close()
+
+def _ReadOutputFileContents():
+  f = open('analyzer_output', 'r')
+  result = json.load(f)
+  f.close()
+  return result
+
+# NOTE: this would be clearer if it subclassed TestGypCustom, but that trips
+# over a bug in pylint (E1002).
+test = TestGyp.TestGypCustom(format='analyzer')
+
+def run_analyzer(*args, **kw):
+  """Runs the test specifying a particular config and output path."""
+  args += ('-Gconfig_path=test_file',
+           '-Ganalyzer_output_path=analyzer_output')
+  test.run_gyp('test.gyp', *args, **kw)
+
+def EnsureContains(targets=set(), matched=False):
+  """Verifies output contains |targets| and |direct_targets|."""
+  result = _ReadOutputFileContents()
+  if result.get('error', None):
+    print 'unexpected error', result.get('error')
+    test.fail_test()
+
+  if result.get('warning', None):
+    print 'unexpected warning', result.get('warning')
+    test.fail_test()
+
+  actual_targets = set(result['targets'])
+  if actual_targets != targets:
+    print 'actual targets:', actual_targets, '\nexpected targets:', targets
+    test.fail_test()
+
+  if matched and result['status'] != found:
+    print 'expected', found, 'got', result['status']
+    test.fail_test()
+  elif not matched and result['status'] != not_found:
+    print 'expected', not_found, 'got', result['status']
+    test.fail_test()
+
+def EnsureError(expected_error_string):
+  """Verifies output contains the error string."""
+  result = _ReadOutputFileContents()
+  if result.get('error', '').find(expected_error_string) == -1:
+    print 'actual error:', result.get('error', ''), '\nexpected error:', \
+        expected_error_string
+    test.fail_test()
+
+def EnsureWarning(expected_warning_string):
+  """Verifies output contains the warning string."""
+  result = _ReadOutputFileContents()
+  if result.get('warning', '').find(expected_warning_string) == -1:
+    print 'actual warning:', result.get('warning', ''), \
+        '\nexpected warning:', expected_warning_string
+    test.fail_test()
+
+# Verifies file_path must be specified.
+test.run_gyp('test.gyp',
+             stdout='Must specify files to analyze via file_path generator '
+             'flag\n')
+
+# Verifies config_path must point to a valid file.
+test.run_gyp('test.gyp', '-Gconfig_path=bogus_file',
+             '-Ganalyzer_output_path=analyzer_output')
+EnsureError('Unable to open file bogus_file')
+
+# Verify get error when bad target is specified.
+_CreateTestFile(['exe2.c'], ['bad_target'])
+run_analyzer()
+EnsureWarning('Unable to find all targets')
+
+# Verifies config_path must point to a valid json file.
+_CreateBogusTestFile()
+run_analyzer()
+EnsureError('Unable to parse config file test_file')
+
+# Trivial test of a source.
+_CreateTestFile(['foo.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Conditional source that is excluded.
+_CreateTestFile(['conditional_source.c'], [])
+run_analyzer()
+EnsureContains(matched=False)
+
+# Conditional source that is included by way of argument.
+_CreateTestFile(['conditional_source.c'], [])
+run_analyzer('-Dtest_variable=1')
+EnsureContains(matched=True)
+
+# Two unknown files.
+_CreateTestFile(['unknown1.c', 'unoknow2.cc'], [])
+run_analyzer()
+EnsureContains()
+
+# Two unknown files.
+_CreateTestFile(['unknown1.c', 'subdir/subdir_sourcex.c'], [])
+run_analyzer()
+EnsureContains()
+
+# Included dependency
+_CreateTestFile(['unknown1.c', 'subdir/subdir_source.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Included inputs to actions.
+_CreateTestFile(['action_input.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Don't consider outputs.
+_CreateTestFile(['action_output.c'], [])
+run_analyzer()
+EnsureContains(matched=False)
+
+# Rule inputs.
+_CreateTestFile(['rule_input.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Ignore path specified with PRODUCT_DIR.
+_CreateTestFile(['product_dir_input.c'], [])
+run_analyzer()
+EnsureContains(matched=False)
+
+# Path specified via a variable.
+_CreateTestFile(['subdir/subdir_source2.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Verifies paths with // are fixed up correctly.
+_CreateTestFile(['parent_source.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Verifies relative paths are resolved correctly.
+_CreateTestFile(['subdir/subdir_source.h'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Various permutations when passing in targets.
+_CreateTestFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe', 'exe3'])
+run_analyzer()
+EnsureContains(matched=True, targets={'exe3'})
+
+_CreateTestFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe'])
+run_analyzer()
+EnsureContains(matched=True)
+
+# Verifies duplicates are ignored.
+_CreateTestFile(['exe2.c', 'subdir/subdir2b_source.c'], ['exe', 'exe'])
+run_analyzer()
+EnsureContains(matched=True)
+
+_CreateTestFile(['exe2.c'], ['exe'])
+run_analyzer()
+EnsureContains(matched=True)
+
+_CreateTestFile(['exe2.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+_CreateTestFile(['subdir/subdir2b_source.c', 'exe2.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+_CreateTestFile(['exe2.c'], [])
+run_analyzer()
+EnsureContains(matched=True)
+
+test.pass_test()
diff --git a/test/mac/gyptest-loadable-module-bundle-product-extension.py b/test/mac/gyptest-loadable-module-bundle-product-extension.py
new file mode 100644
index 0000000..90c2083
--- /dev/null
+++ b/test/mac/gyptest-loadable-module-bundle-product-extension.py
@@ -0,0 +1,28 @@
+#!/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.
+
+"""
+Tests that loadable_modules don't collide when using the same name with
+different file extensions.
+"""
+
+import TestGyp
+
+import os
+import struct
+import sys
+
+if sys.platform == 'darwin':
+  test = TestGyp.TestGyp(formats=['ninja', 'make', 'xcode'])
+
+  CHDIR = 'loadable-module-bundle-product-extension'
+  test.run_gyp('test.gyp', chdir=CHDIR)
+  test.build('test.gyp', test.ALL, chdir=CHDIR)
+
+  test.must_exist(test.built_file_path('Collide.foo', chdir=CHDIR))
+  test.must_exist(test.built_file_path('Collide.bar', chdir=CHDIR))
+
+  test.pass_test()
diff --git a/test/mac/loadable-module-bundle-product-extension/src.cc b/test/mac/loadable-module-bundle-product-extension/src.cc
new file mode 100644
index 0000000..3d878e9
--- /dev/null
+++ b/test/mac/loadable-module-bundle-product-extension/src.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 test() {
+  return 1337;
+}
diff --git a/test/mac/loadable-module-bundle-product-extension/test.gyp b/test/mac/loadable-module-bundle-product-extension/test.gyp
new file mode 100644
index 0000000..684a2c0
--- /dev/null
+++ b/test/mac/loadable-module-bundle-product-extension/test.gyp
@@ -0,0 +1,24 @@
+# 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': 'test',
+    'type': 'none',
+    'dependencies': ['child_one', 'child_two'],
+  }, {
+    'target_name': 'child_one',
+    'product_name': 'Collide',
+    'product_extension': 'bar',
+    'sources': ['src.cc'],
+    'type': 'loadable_module',
+    'mac_bundle': 1,
+  }, {
+    'target_name': 'child_two',
+    'product_name': 'Collide',
+    'product_extension': 'foo',
+    'sources': ['src.cc'],
+    'type': 'loadable_module',
+    'mac_bundle': 1,
+  }],
+}