Avoid MSB8012 on Windows

If using product extension only in build files, MSVS will complain that the
target extension does not match the product extension, MSB8012. Add gyp
support for specifying target extension to fix this warning.

Used for e.g. PyAuto/OpAuto .pyd files.

Patch from sigbjorn@opera.com.

R=scottmg@chromium.org

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

git-svn-id: http://gyp.googlecode.com/svn/trunk@1749 78cadc50-ecff-11dd-a971-7dbc132099af
diff --git a/pylib/gyp/generator/msvs.py b/pylib/gyp/generator/msvs.py
index bc3b4c2..05783d6 100644
--- a/pylib/gyp/generator/msvs.py
+++ b/pylib/gyp/generator/msvs.py
@@ -1203,6 +1203,24 @@
   return out_file, vc_tool, msbuild_tool
 
 
+def _GetOutputTargetExt(spec):
+  """Returns the extension for this target, including the dot
+
+  If product_extension is specified, set target_extension to this to avoid
+  MSB8012, returns None otherwise. Ignores any target_extension settings in
+  the input files.
+
+  Arguments:
+    spec: The target dictionary containing the properties of the target.
+  Returns:
+    A string with the extension, or None
+  """
+  target_extension = spec.get('product_extension')
+  if target_extension:
+    return '.' + target_extension
+  return None
+
+
 def _GetDefines(config):
   """Returns the list of preprocessor definitions for this configuation.
 
@@ -2652,6 +2670,9 @@
     out_file = msbuild_settings[msbuild_tool].get('OutputFile')
     if out_file:
       msbuild_attributes['TargetPath'] = _FixPath(out_file)
+    target_ext = msbuild_settings[msbuild_tool].get('TargetExt')
+    if target_ext:
+      msbuild_attributes['TargetExt'] = target_ext
 
   return msbuild_attributes
 
@@ -2687,6 +2708,9 @@
     if attributes.get('TargetPath'):
       _AddConditionalProperty(properties, condition, 'TargetPath',
                               attributes['TargetPath'])
+    if attributes.get('TargetExt'):
+      _AddConditionalProperty(properties, condition, 'TargetExt',
+                              attributes['TargetExt'])
 
     if new_paths:
       _AddConditionalProperty(properties, condition, 'ExecutablePath',
@@ -2807,6 +2831,7 @@
   libraries = _GetLibraries(spec)
   library_dirs = _GetLibraryDirs(configuration)
   out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True)
+  target_ext = _GetOutputTargetExt(spec)
   defines = _GetDefines(configuration)
   if converted:
     # Visual Studio 2010 has TR1
@@ -2844,6 +2869,9 @@
   if out_file:
     _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file,
                 only_if_unset=True)
+  if target_ext:
+    _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext,
+                only_if_unset=True)
   # Add defines.
   _ToolAppend(msbuild_settings, 'ClCompile',
               'PreprocessorDefinitions', defines)
diff --git a/test/lib/TestGyp.py b/test/lib/TestGyp.py
index 930db75..bd9b9ea 100644
--- a/test/lib/TestGyp.py
+++ b/test/lib/TestGyp.py
@@ -155,6 +155,13 @@
     """
     return self.must_not_match(self.built_file_path(name, **kw), contents)
 
+  def built_file_must_not_contain(self, name, contents, **kw):
+    """
+    Fails the test if the specified built file name contains the specified
+    contents.
+    """
+    return self.must_not_contain(self.built_file_path(name, **kw), contents)
+
   def copy_test_configuration(self, source_dir, dest_dir):
     """
     Copies the test configuration from the specified source_dir
diff --git a/test/target/gyptest-target.py b/test/target/gyptest-target.py
new file mode 100644
index 0000000..4338db7
--- /dev/null
+++ b/test/target/gyptest-target.py
@@ -0,0 +1,37 @@
+#!/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.
+
+"""
+Verifies simplest-possible build of a "Hello, world!" program
+using non-default extension. In particular, verifies how
+target_extension is used to avoid MSB8012 for msvs.
+"""
+
+import sys
+import TestGyp
+
+if sys.platform in ('win32', 'cygwin'):
+  test = TestGyp.TestGyp()
+
+  test.run_gyp('target.gyp')
+  test.build('target.gyp')
+
+  # executables
+  test.built_file_must_exist('hello1.stuff', test.EXECUTABLE, bare=True)
+  test.built_file_must_exist('hello2.exe', test.EXECUTABLE, bare=True)
+  test.built_file_must_not_exist('hello2.stuff', test.EXECUTABLE, bare=True)
+
+  # check msvs log for errors
+  if test.format == "msvs":
+    log_file = "obj\\hello1\\hello1.log"
+    test.built_file_must_exist(log_file)
+    test.built_file_must_not_contain(log_file, "MSB8012")
+
+    log_file = "obj\\hello2\\hello2.log"
+    test.built_file_must_exist(log_file)
+    test.built_file_must_not_contain(log_file, "MSB8012")
+
+  test.pass_test()
diff --git a/test/target/hello.c b/test/target/hello.c
new file mode 100644
index 0000000..3d535d3
--- /dev/null
+++ b/test/target/hello.c
@@ -0,0 +1,7 @@
+/* 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. */
+
+void main(void) {
+  printf("Hello, world!\n");
+}
diff --git a/test/target/target.gyp b/test/target/target.gyp
new file mode 100644
index 0000000..c87e30f
--- /dev/null
+++ b/test/target/target.gyp
@@ -0,0 +1,24 @@
+# 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.
+
+{
+  'targets': [
+    {
+      'target_name': 'hello1',
+      'product_extension': 'stuff',
+      'type': 'executable',
+      'sources': [
+        'hello.c',
+      ],
+    },
+    {
+      'target_name': 'hello2',
+      'target_extension': 'stuff',
+      'type': 'executable',
+      'sources': [
+        'hello.c',
+      ],
+    }
+  ]
+}