Introduce '--no-duplicate-basename-check' option to disable the check of duplicate basenames

With this CL, the check of duplicate basenames in the same source list can be disabled with '--no-duplicate-basename-check' option.

Now GYP generators can handle duplicate basenames except for VCProj generator for Visual C++ 2008 and Makefile generator on Mac. Given that these two generators are no longer actively used, and supposed to be deprecated in future, providing an option to disable this validation should be helpful for some GYP users.

Note that these two generators continue to treat duplicate basenames continue as an error regardless of '--no-duplicate-basename-check'.

BUG=gyp:264, gyp:384
R=scottmg@chromium.org

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

git-svn-id: http://gyp.googlecode.com/svn/trunk@1947 78cadc50-ecff-11dd-a971-7dbc132099af
diff --git a/pylib/gyp/__init__.py b/pylib/gyp/__init__.py
index 30edea5..1cd57b0 100755
--- a/pylib/gyp/__init__.py
+++ b/pylib/gyp/__init__.py
@@ -49,7 +49,7 @@
 
 def Load(build_files, format, default_variables={},
          includes=[], depth='.', params=None, check=False,
-         circular_check=True):
+         circular_check=True, duplicate_basename_check=True):
   """
   Loads one or more specified build files.
   default_variables and includes will be copied before use.
@@ -126,6 +126,7 @@
   # Process the input specific to this generator.
   result = gyp.input.Load(build_files, default_variables, includes[:],
                           depth, generator_input_info, check, circular_check,
+                          duplicate_basename_check,
                           params['parallel'], params['root_targets'])
   return [generator] + result
 
@@ -324,6 +325,16 @@
   parser.add_option('--no-circular-check', dest='circular_check',
                     action='store_false', default=True, regenerate=False,
                     help="don't check for circular relationships between files")
+  # --no-duplicate-basename-check disables the check for duplicate basenames
+  # in a static_library/shared_library project. Visual C++ 2008 generator
+  # doesn't support this configuration. Libtool on Mac also generates warnings
+  # when duplicate basenames are passed into Make generator on Mac.
+  # TODO(yukawa): Remove this option when these legacy generators are
+  # deprecated.
+  parser.add_option('--no-duplicate-basename-check',
+                    dest='duplicate_basename_check', action='store_false',
+                    default=True, regenerate=False,
+                    help="don't check for duplicate basenames")
   parser.add_option('--no-parallel', action='store_true', default=False,
                     help='Disable multiprocessing')
   parser.add_option('-S', '--suffix', dest='suffix', default='',
@@ -496,11 +507,10 @@
               'root_targets': options.root_targets}
 
     # Start with the default variables from the command line.
-    [generator, flat_list, targets, data] = Load(build_files, format,
-                                                 cmdline_default_variables,
-                                                 includes, options.depth,
-                                                 params, options.check,
-                                                 options.circular_check)
+    [generator, flat_list, targets, data] = Load(
+        build_files, format, cmdline_default_variables, includes, options.depth,
+        params, options.check, options.circular_check,
+        options.duplicate_basename_check)
 
     # TODO(mark): Pass |data| for now because the generator needs a list of
     # build files that came in.  In the future, maybe it should just accept
diff --git a/pylib/gyp/generator/make.py b/pylib/gyp/generator/make.py
index b88a433..8c31d10 100644
--- a/pylib/gyp/generator/make.py
+++ b/pylib/gyp/generator/make.py
@@ -29,6 +29,7 @@
 import gyp.common
 import gyp.xcode_emulation
 from gyp.common import GetEnvironFallback
+from gyp.common import GypError
 
 generator_default_variables = {
   'EXECUTABLE_PREFIX': '',
@@ -631,6 +632,38 @@
   return s.replace(' ', quote)
 
 
+# TODO: Avoid code duplication with _ValidateSourcesForMSVSProject in msvs.py.
+def _ValidateSourcesForOSX(spec, all_sources):
+  """Makes sure if duplicate basenames are not specified in the source list.
+
+  Arguments:
+    spec: The target dictionary containing the properties of the target.
+  """
+  if spec.get('type', None) != 'static_library':
+    return
+
+  basenames = {}
+  for source in all_sources:
+    name, ext = os.path.splitext(source)
+    is_compiled_file = ext in [
+        '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S']
+    if not is_compiled_file:
+      continue
+    basename = os.path.basename(name)  # Don't include extension.
+    basenames.setdefault(basename, []).append(source)
+
+  error = ''
+  for basename, files in basenames.iteritems():
+    if len(files) > 1:
+      error += '  %s: %s\n' % (basename, ' '.join(files))
+
+  if error:
+    print('static library %s has several files with the same basename:\n' %
+          spec['target_name'] + error + 'libtool on OS X will generate' +
+          ' warnings for them.')
+    raise GypError('Duplicate basenames in sources section, see list above')
+
+
 # Map from qualified target to path to output.
 target_outputs = {}
 # Map from qualified target to any linkable output.  A subset
@@ -758,6 +791,10 @@
     # Sources.
     all_sources = spec.get('sources', []) + extra_sources
     if all_sources:
+      if self.flavor == 'mac':
+        # libtool on OS X generates warnings for duplicate basenames in the same
+        # target.
+        _ValidateSourcesForOSX(spec, all_sources)
       self.WriteSources(
           configs, deps, all_sources, extra_outputs,
           extra_link_deps, part_of_all,
diff --git a/pylib/gyp/generator/msvs.py b/pylib/gyp/generator/msvs.py
index f548bb0..80e3104 100644
--- a/pylib/gyp/generator/msvs.py
+++ b/pylib/gyp/generator/msvs.py
@@ -921,6 +921,42 @@
     return _GenerateMSVSProject(project, options, version, generator_flags)
 
 
+# TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py.
+def _ValidateSourcesForMSVSProject(spec, version):
+  """Makes sure if duplicate basenames are not specified in the source list.
+
+  Arguments:
+    spec: The target dictionary containing the properties of the target.
+    version: The VisualStudioVersion object.
+  """
+  # This validation should not be applied to MSVC2010 and later.
+  assert not version.UsesVcxproj()
+
+  # TODO: Check if MSVC allows this for loadable_module targets.
+  if spec.get('type', None) not in ('static_library', 'shared_library'):
+    return
+  sources = spec.get('sources', [])
+  basenames = {}
+  for source in sources:
+    name, ext = os.path.splitext(source)
+    is_compiled_file = ext in [
+        '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S']
+    if not is_compiled_file:
+      continue
+    basename = os.path.basename(name)  # Don't include extension.
+    basenames.setdefault(basename, []).append(source)
+
+  error = ''
+  for basename, files in basenames.iteritems():
+    if len(files) > 1:
+      error += '  %s: %s\n' % (basename, ' '.join(files))
+
+  if error:
+    print('static library %s has several files with the same basename:\n' %
+          spec['target_name'] + error + 'MSVC08 cannot handle that.')
+    raise GypError('Duplicate basenames in sources section, see list above')
+
+
 def _GenerateMSVSProject(project, options, version, generator_flags):
   """Generates a .vcproj file.  It may create .rules and .user files too.
 
@@ -946,6 +982,11 @@
   for config_name, config in spec['configurations'].iteritems():
     _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
 
+  # MSVC08 and prior version cannot handle duplicate basenames in the same
+  # target.
+  # TODO: Take excluded sources into consideration if possible.
+  _ValidateSourcesForMSVSProject(spec, version)
+
   # Prepare list of sources and excluded sources.
   gyp_file = os.path.split(project.build_file)[1]
   sources, excluded_sources = _PrepareListOfSources(spec, generator_flags,
diff --git a/pylib/gyp/input.py b/pylib/gyp/input.py
index b09a2c8..e0813f3 100644
--- a/pylib/gyp/input.py
+++ b/pylib/gyp/input.py
@@ -2463,7 +2463,10 @@
                                                              target_type))
 
 
-def ValidateSourcesInTarget(target, target_dict, build_file):
+def ValidateSourcesInTarget(target, target_dict, build_file,
+                            duplicate_basename_check):
+  if not duplicate_basename_check:
+    return
   # TODO: Check if MSVC allows this for loadable_module targets.
   if target_dict.get('type', None) not in ('static_library', 'shared_library'):
     return
@@ -2485,8 +2488,9 @@
 
   if error:
     print('static library %s has several files with the same basename:\n' %
-          target + error + 'Some build systems, e.g. MSVC08, '
-          'cannot handle that.')
+          target + error + 'Some build systems, e.g. MSVC08 and Make generator '
+          'for Mac, cannot handle that. Use --no-duplicate-basename-check to'
+          'disable this validation.')
     raise GypError('Duplicate basenames in sources section, see list above')
 
 
@@ -2710,7 +2714,7 @@
 
 
 def Load(build_files, variables, includes, depth, generator_input_info, check,
-         circular_check, parallel, root_targets):
+         circular_check, duplicate_basename_check, parallel, root_targets):
   SetGeneratorGlobals(generator_input_info)
   # A generator can have other lists (in addition to sources) be processed
   # for rules.
@@ -2835,6 +2839,11 @@
     ProcessVariablesAndConditionsInDict(
         target_dict, PHASE_LATELATE, variables, build_file)
 
+  # TODO(thakis): Get vpx_scale/arm/scalesystemdependent.c to be renamed to
+  #               scalesystemdependent_arm_additions.c or similar.
+  if 'arm' in variables.get('target_arch', ''):
+    duplicate_basename_check = False
+
   # Make sure that the rules make sense, and build up rule_sources lists as
   # needed.  Not all generators will need to use the rule_sources lists, but
   # some may, and it seems best to build the list in a common spot.
@@ -2843,10 +2852,8 @@
     target_dict = targets[target]
     build_file = gyp.common.BuildFile(target)
     ValidateTargetType(target, target_dict)
-    # TODO(thakis): Get vpx_scale/arm/scalesystemdependent.c to be renamed to
-    #               scalesystemdependent_arm_additions.c or similar.
-    if 'arm' not in variables.get('target_arch', ''):
-      ValidateSourcesInTarget(target, target_dict, build_file)
+    ValidateSourcesInTarget(target, target_dict, build_file,
+                            duplicate_basename_check)
     ValidateRulesInTarget(target, target_dict, extra_sources_for_rules)
     ValidateRunAsInTarget(target, target_dict, build_file)
     ValidateActionsInTarget(target, target_dict, build_file)
diff --git a/test/errors/gyptest-errors.py b/test/errors/gyptest-errors.py
index 446607f..5f66bac 100755
--- a/test/errors/gyptest-errors.py
+++ b/test/errors/gyptest-errors.py
@@ -8,6 +8,9 @@
 Test that two targets with the same name generates an error.
 """
 
+import os
+import sys
+
 import TestGyp
 import TestCmd
 
@@ -39,6 +42,16 @@
 stderr = 'gyp: Duplicate basenames in sources section, see list above\n'
 test.run_gyp('duplicate_basenames.gyp', status=1, stderr=stderr)
 
+# Check if '--no-duplicate-basename-check' works.
+if ((test.format == 'make' and sys.platform == 'darwin') or
+    (test.format == 'msvs' and
+        int(os.environ.get('GYP_MSVS_VERSION', 2010)) < 2010)):
+  stderr = 'gyp: Duplicate basenames in sources section, see list above\n'
+  test.run_gyp('duplicate_basenames.gyp', '--no-duplicate-basename-check',
+               status=1, stderr=stderr)
+else:
+  test.run_gyp('duplicate_basenames.gyp', '--no-duplicate-basename-check')
+
 stderr = ("gyp: Dependency '.*missing_dep.gyp:missing.gyp#target' not found "
           "while trying to load target .*missing_dep.gyp:foo#target\n")
 test.run_gyp('missing_dep.gyp', status=1, stderr=stderr,
diff --git a/test/same-source-file-name/gyptest-fail-shared.py b/test/same-source-file-name/gyptest-fail-shared.py
deleted file mode 100755
index 5b57826..0000000
--- a/test/same-source-file-name/gyptest-fail-shared.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/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.
-
-"""
-Checks that gyp fails on shared_library targets which have several files with
-the same basename.
-"""
-
-import TestGyp
-
-test = TestGyp.TestGyp()
-
-test.run_gyp('double-shared.gyp', chdir='src', status=1, stderr=None)
-
-test.pass_test()
diff --git a/test/same-source-file-name/gyptest-fail-static.py b/test/same-source-file-name/gyptest-fail-static.py
deleted file mode 100755
index e84d37c..0000000
--- a/test/same-source-file-name/gyptest-fail-static.py
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/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.
-
-"""
-Checks that gyp fails on static_library targets which have several files with
-the same basename.
-"""
-
-import TestGyp
-
-test = TestGyp.TestGyp()
-
-test.run_gyp('double-static.gyp', chdir='src', status=1, stderr=None)
-
-test.pass_test()
diff --git a/test/same-source-file-name/gyptest-shared.py b/test/same-source-file-name/gyptest-shared.py
new file mode 100755
index 0000000..a57eb61
--- /dev/null
+++ b/test/same-source-file-name/gyptest-shared.py
@@ -0,0 +1,31 @@
+#!/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.
+
+"""
+Checks that gyp fails on shared_library targets which have several files with
+the same basename.
+"""
+
+import os
+
+import TestGyp
+
+test = TestGyp.TestGyp()
+
+# Fails by default for the compatibility with Visual C++ 2008 generator.
+# TODO: Update expected behavior when these legacy generators are deprecated.
+test.run_gyp('double-shared.gyp', chdir='src', status=1, stderr=None)
+
+if ((test.format == 'msvs') and
+       (int(os.environ.get('GYP_MSVS_VERSION', 2010)) < 2010)):
+  test.run_gyp('double-shared.gyp', '--no-duplicate-basename-check',
+               chdir='src', status=0, stderr=None)
+else:
+  test.run_gyp('double-shared.gyp', '--no-duplicate-basename-check',
+               chdir='src')
+  test.build('double-shared.gyp', test.ALL, chdir='src')
+
+test.pass_test()
diff --git a/test/same-source-file-name/gyptest-static.py b/test/same-source-file-name/gyptest-static.py
new file mode 100755
index 0000000..7fa2772
--- /dev/null
+++ b/test/same-source-file-name/gyptest-static.py
@@ -0,0 +1,34 @@
+#!/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.
+
+"""
+Checks that gyp fails on static_library targets which have several files with
+the same basename.
+"""
+
+import os
+import sys
+
+import TestGyp
+
+test = TestGyp.TestGyp()
+
+# Fails by default for the compatibility with legacy generators such as
+# VCProj generator for Visual C++ 2008 and Makefile generator on Mac.
+# TODO: Update expected behavior when these legacy generators are deprecated.
+test.run_gyp('double-static.gyp', chdir='src', status=1, stderr=None)
+
+if ((test.format == 'make' and sys.platform == 'darwin') or
+    (test.format == 'msvs' and
+        int(os.environ.get('GYP_MSVS_VERSION', 2010)) < 2010)):
+  test.run_gyp('double-static.gyp', '--no-duplicate-basename-check',
+               chdir='src', status=1, stderr=None)
+else:
+  test.run_gyp('double-static.gyp', '--no-duplicate-basename-check',
+               chdir='src')
+  test.build('double-static.gyp', test.ALL, chdir='src')
+
+test.pass_test()
diff --git a/test/same-source-file-name/src/double-shared.gyp b/test/same-source-file-name/src/double-shared.gyp
index 40d995f..438b50f 100644
--- a/test/same-source-file-name/src/double-shared.gyp
+++ b/test/same-source-file-name/src/double-shared.gyp
@@ -6,6 +6,7 @@
   'targets': [
     {
       'target_name': 'lib',
+      'product_name': 'test_shared_lib',
       'type': 'shared_library',
       'sources': [
         'prog2.c',
@@ -16,6 +17,11 @@
       'defines': [
         'PROG="prog2"',
       ],
+      'conditions': [
+        ['OS=="linux"', {
+          'cflags': ['-fPIC'],
+        }],
+      ],
     },
   ],
 }
diff --git a/test/same-source-file-name/src/double-static.gyp b/test/same-source-file-name/src/double-static.gyp
index 3da1242..e49c0e1 100644
--- a/test/same-source-file-name/src/double-static.gyp
+++ b/test/same-source-file-name/src/double-static.gyp
@@ -6,6 +6,7 @@
   'targets': [
     {
       'target_name': 'lib',
+      'product_name': 'test_static_lib',
       'type': 'static_library',
       'sources': [
         'prog1.c',