ninja/win: Support VCLinkerTool.GenerateManifest

Currently Ninja generator always expects VCLinkerTool.GenerateManifest is true. With this CL, it will be configurable like MSVS generator.

BUG=gyp:389
TEST=python gyptest.py test/win/gyptest-link-generate-manifest.py
R=scottmg@chromium.org

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

git-svn-id: http://gyp.googlecode.com/svn/trunk@1802 78cadc50-ecff-11dd-a971-7dbc132099af
diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py
index f799fdf..a40c7fe 100644
--- a/pylib/gyp/generator/ninja.py
+++ b/pylib/gyp/generator/ninja.py
@@ -1581,18 +1581,24 @@
 def _AddWinLinkRules(master_ninja, embed_manifest, link_incremental):
   """Adds link rules for Windows platform to |master_ninja|."""
   def FullLinkCommand(ldcmd, out, binary_type):
-    cmd = ('cmd /c %(ldcmd)s'
-           ' && %(python)s gyp-win-tool manifest-wrapper $arch'
-           ' cmd /c if exist %(out)s.manifest del %(out)s.manifest'
-           ' && %(python)s gyp-win-tool manifest-wrapper $arch'
-           ' $mt -nologo -manifest $manifests')
+    """Returns a one-liner written for cmd.exe to handle multiphase linker
+    operations including manifest file generation. The command will be
+    structured as follows:
+      cmd /c (linkcmd1 a b) && (linkcmd2 x y) && ... &&
+      if not "$manifests"=="" ((manifestcmd1 a b) && (manifestcmd2 x y) && ... )
+    Note that $manifests becomes empty when no manifest file is generated."""
+    link_commands = ['%(ldcmd)s',
+                     'if exist %(out)s.manifest del %(out)s.manifest']
+    mt_cmd = ('%(python)s gyp-win-tool manifest-wrapper'
+              ' $arch $mt -nologo -manifest $manifests')
     if embed_manifest and not link_incremental:
       # Embed manifest into a binary. If incremental linking is enabled,
       # embedding is postponed to the re-linking stage (see below).
-      cmd += ' -outputresource:%(out)s;%(resname)s'
+      mt_cmd += ' -outputresource:%(out)s;%(resname)s'
     else:
       # Save manifest as an external file.
-      cmd += ' -out:%(out)s.manifest'
+      mt_cmd += ' -out:%(out)s.manifest'
+    manifest_commands = [mt_cmd]
     if link_incremental:
       # There is no point in generating separate rule for the case when
       # incremental linking is enabled, but manifest embedding is disabled.
@@ -1600,11 +1606,14 @@
       # See also implementation of _GetWinLinkRuleNameSuffix().
       assert embed_manifest
       # Make .rc file out of manifest, compile it to .res file and re-link.
-      cmd += (' && %(python)s gyp-win-tool manifest-to-rc $arch'
-              ' %(out)s.manifest %(out)s.manifest.rc %(resname)s'
-              ' && %(python)s gyp-win-tool rc-wrapper $arch $rc'
-              ' %(out)s.manifest.rc'
-              ' && %(ldcmd)s %(out)s.manifest.res')
+      manifest_commands += [
+        ('%(python)s gyp-win-tool manifest-to-rc $arch %(out)s.manifest'
+         ' %(out)s.manifest.rc %(resname)s'),
+        '%(python)s gyp-win-tool rc-wrapper $arch $rc %(out)s.manifest.rc',
+        '%(ldcmd)s %(out)s.manifest.res']
+    cmd = 'cmd /c %s && if not "$manifests"=="" (%s)' % (
+      ' && '.join(['(%s)' % c for c in link_commands]),
+      ' && '.join(['(%s)' % c for c in manifest_commands]))
     resource_name = {
       'exe': '1',
       'dll': '2',
diff --git a/pylib/gyp/msvs_emulation.py b/pylib/gyp/msvs_emulation.py
index 9ea86db..9c575bd 100644
--- a/pylib/gyp/msvs_emulation.py
+++ b/pylib/gyp/msvs_emulation.py
@@ -512,18 +512,26 @@
       ldflags.append('/NXCOMPAT')
 
     have_def_file = filter(lambda x: x.startswith('/DEF:'), ldflags)
-    manifest_flags, intermediate_manifest_file = self._GetLdManifestFlags(
-        config, manifest_base_name, is_executable and not have_def_file)
+    manifest_flags, manifest_files = self._GetLdManifestFlags(
+        config, manifest_base_name, gyp_to_build_path,
+        is_executable and not have_def_file)
     ldflags.extend(manifest_flags)
-    manifest_files = self._GetAdditionalManifestFiles(config, gyp_to_build_path)
-    manifest_files.append(intermediate_manifest_file)
-
     return ldflags, manifest_files
 
-  def _GetLdManifestFlags(self, config, name, allow_isolation):
+  def _GetLdManifestFlags(self, config, name, gyp_to_build_path,
+                          allow_isolation):
     """Returns the set of flags that need to be added to the link to generate
-    a default manifest, as well as the name of the generated file."""
-    # The manifest is generated by default.
+    a default manifest, as well as the list of all the manifest files to be
+    merged by the manifest tool."""
+    generate_manifest = self._Setting(('VCLinkerTool', 'GenerateManifest'),
+                                      config,
+                                      default='true')
+    if generate_manifest != 'true':
+      # This means not only that the linker should not generate the intermediate
+      # manifest but also that the manifest tool should do nothing even when
+      # additional manifests are specified.
+      return ['/MANIFEST:NO'], []
+
     output_name = name + '.intermediate.manifest'
     flags = [
       '/MANIFEST',
@@ -551,7 +559,11 @@
 
     if allow_isolation:
       flags.append('/ALLOWISOLATION')
-    return flags, output_name
+
+    manifest_files = [output_name]
+    manifest_files += self._GetAdditionalManifestFiles(config,
+                                                       gyp_to_build_path)
+    return flags, manifest_files
 
   def _GetAdditionalManifestFiles(self, config, gyp_to_build_path):
     """Gets additional manifest files that are added to the default one
diff --git a/test/win/gyptest-link-generate-manifest.py b/test/win/gyptest-link-generate-manifest.py
index e7d9bc7..ff03afd 100644
--- a/test/win/gyptest-link-generate-manifest.py
+++ b/test/win/gyptest-link-generate-manifest.py
@@ -14,31 +14,106 @@
 import sys
 
 if sys.platform == 'win32':
+  import pywintypes
+  import win32api
+  import winerror
+
+  RT_MANIFEST = 24
+
+  class LoadLibrary(object):
+    """Context manager for loading and releasing binaries in Windows.
+    Yields the handle of the binary loaded."""
+    def __init__(self, path):
+      self._path = path
+      self._handle = None
+
+    def __enter__(self):
+      self._handle = win32api.LoadLibrary(self._path)
+      return self._handle
+
+    def __exit__(self, type, value, traceback):
+      win32api.FreeLibrary(self._handle)
+
+  def extract_manifest(path, resource_name):
+    """Reads manifest from |path| and returns it as a string.
+    Returns None is there is no such manifest."""
+    with LoadLibrary(path) as handle:
+      try:
+        return win32api.LoadResource(handle, RT_MANIFEST, resource_name)
+      except pywintypes.error as error:
+        if error.args[0] == winerror.ERROR_RESOURCE_DATA_NOT_FOUND:
+          return None
+        else:
+          raise
+
   test = TestGyp.TestGyp(formats=['msvs', 'ninja'])
 
   CHDIR = 'linker-flags'
   test.run_gyp('generate-manifest.gyp', chdir=CHDIR)
   test.build('generate-manifest.gyp', test.ALL, chdir=CHDIR)
-  test.built_file_must_exist('test_manifest_exe.exe.manifest', chdir=CHDIR)
-  test.built_file_must_exist('test_manifest_dll.dll.manifest', chdir=CHDIR)
 
-  # Must contain the Win7 support GUID, but not the Vista one (from
-  # extra2.manifest).
-  extra1_manifest = test.built_file_path(
-      'test_manifest_extra1.exe.manifest', chdir=CHDIR)
-  test.must_contain(extra1_manifest, '35138b9a-5d96-4fbd-8e2d-a2440225f93a')
-  test.must_not_contain(extra1_manifest, 'e2011457-1546-43c5-a5fe-008deee3d3f0')
+  def test_manifest(filename, generate_manifest, embedded_manifest,
+                    extra_manifest):
+    exe_file = test.built_file_path(filename, chdir=CHDIR)
+    if not generate_manifest:
+      test.must_not_exist(exe_file + '.manifest')
+      manifest = extract_manifest(exe_file, 1)
+      test.fail_test(manifest)
+      return
+    if embedded_manifest:
+      manifest = extract_manifest(exe_file, 1)
+      test.fail_test(not manifest)
+    else:
+      test.must_exist(exe_file + '.manifest')
+      manifest = test.read(exe_file + '.manifest')
+      test.fail_test(not manifest)
+      test.fail_test(extract_manifest(exe_file, 1))
+    if generate_manifest:
+      test.must_contain_any_line(manifest, 'requestedExecutionLevel')
+    if extra_manifest:
+      test.must_contain_any_line(manifest,
+                                 '35138b9a-5d96-4fbd-8e2d-a2440225f93a')
+      test.must_contain_any_line(manifest,
+                                 'e2011457-1546-43c5-a5fe-008deee3d3f0')
 
-  # Must contain both.
-  extra2_manifest = test.built_file_path(
-      'test_manifest_extra2.exe.manifest', chdir=CHDIR)
-  test.must_contain(extra2_manifest, '35138b9a-5d96-4fbd-8e2d-a2440225f93a')
-  test.must_contain(extra2_manifest, 'e2011457-1546-43c5-a5fe-008deee3d3f0')
-
-  # Same as extra2, but using list syntax instead.
-  extra_list_manifest = test.built_file_path(
-      'test_manifest_extra_list.exe.manifest', chdir=CHDIR)
-  test.must_contain(extra_list_manifest, '35138b9a-5d96-4fbd-8e2d-a2440225f93a')
-  test.must_contain(extra_list_manifest, 'e2011457-1546-43c5-a5fe-008deee3d3f0')
-
+  test_manifest('test_generate_manifest_true.exe',
+                generate_manifest=True,
+                embedded_manifest=False,
+                extra_manifest=False)
+  test_manifest('test_generate_manifest_false.exe',
+                generate_manifest=False,
+                embedded_manifest=False,
+                extra_manifest=False)
+  test_manifest('test_generate_manifest_default.exe',
+                generate_manifest=True,
+                embedded_manifest=False,
+                extra_manifest=False)
+  test_manifest('test_generate_manifest_true_as_embedded.exe',
+                generate_manifest=True,
+                embedded_manifest=True,
+                extra_manifest=False)
+  test_manifest('test_generate_manifest_false_as_embedded.exe',
+                generate_manifest=False,
+                embedded_manifest=True,
+                extra_manifest=False)
+  test_manifest('test_generate_manifest_default_as_embedded.exe',
+                generate_manifest=True,
+                embedded_manifest=True,
+                extra_manifest=False)
+  test_manifest('test_generate_manifest_true_with_extra_manifest.exe',
+                generate_manifest=True,
+                embedded_manifest=False,
+                extra_manifest=True)
+  test_manifest('test_generate_manifest_false_with_extra_manifest.exe',
+                generate_manifest=False,
+                embedded_manifest=False,
+                extra_manifest=True)
+  test_manifest('test_generate_manifest_true_with_extra_manifest_list.exe',
+                generate_manifest=True,
+                embedded_manifest=False,
+                extra_manifest=True)
+  test_manifest('test_generate_manifest_false_with_extra_manifest_list.exe',
+                generate_manifest=False,
+                embedded_manifest=False,
+                extra_manifest=True)
   test.pass_test()
diff --git a/test/win/linker-flags/generate-manifest.gyp b/test/win/linker-flags/generate-manifest.gyp
index fe5ee74..41f888f 100644
--- a/test/win/linker-flags/generate-manifest.gyp
+++ b/test/win/linker-flags/generate-manifest.gyp
@@ -1,64 +1,156 @@
-# 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.

-

-{

- 'targets': [

-    {

-      'target_name': 'test_manifest_exe',

-      'type': 'executable',

-      'sources': ['hello.cc'],

-      'msvs_settings': {

-        'VCManifestTool': {

-          'EmbedManifest': 'false',

-        }

-      },

-    },

-    {

-      'target_name': 'test_manifest_dll',

-      'type': 'shared_library',

-      'sources': ['hello.cc'],

-      'msvs_settings': {

-        'VCManifestTool': {

-          'EmbedManifest': 'false',

-        }

-      },

-    },

-    {

-      'target_name': 'test_manifest_extra1',

-      'type': 'executable',

-      'sources': ['hello.cc'],

-      'msvs_settings': {

-        'VCManifestTool': {

-          'EmbedManifest': 'false',

-          'AdditionalManifestFiles': 'extra.manifest',

-        }

-      },

-    },

-    {

-      'target_name': 'test_manifest_extra2',

-      'type': 'executable',

-      'sources': ['hello.cc'],

-      'msvs_settings': {

-        'VCManifestTool': {

-          'EmbedManifest': 'false',

-          'AdditionalManifestFiles': 'extra.manifest;extra2.manifest',

-        }

-      },

-    },

-    {

-      'target_name': 'test_manifest_extra_list',

-      'type': 'executable',

-      'sources': ['hello.cc'],

-      'msvs_settings': {

-        'VCManifestTool': {

-          'EmbedManifest': 'false',

-          'AdditionalManifestFiles': [

-            'extra.manifest',

-            'extra2.manifest'

-          ],

-        }

-      },

-    },

-  ]

-}

+# 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.
+
+{
+ 'targets': [
+    {
+      'target_name': 'test_generate_manifest_true',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'true',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'false',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_false',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'false',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'false',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_default',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'false',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_true_as_embedded',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'true',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'true',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_false_as_embedded',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'false',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'true',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_default_as_embedded',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'true',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_true_with_extra_manifest',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'true',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'false',
+          'AdditionalManifestFiles': 'extra.manifest;extra2.manifest',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_false_with_extra_manifest',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'false',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'false',
+          'AdditionalManifestFiles': 'extra.manifest;extra2.manifest',
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_true_with_extra_manifest_list',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'true',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'false',
+          'AdditionalManifestFiles': [
+            'extra.manifest',
+            'extra2.manifest',
+          ],
+        },
+      },
+    },
+    {
+      'target_name': 'test_generate_manifest_false_with_extra_manifest_list',
+      'type': 'executable',
+      'sources': ['hello.cc'],
+      'msvs_settings': {
+        'VCLinkerTool': {
+          'EnableUAC': 'true',
+          'GenerateManifest': 'false',
+        },
+        'VCManifestTool': {
+          'EmbedManifest': 'false',
+          'AdditionalManifestFiles': [
+            'extra.manifest',
+            'extra2.manifest',
+          ],
+        },
+      },
+    },
+  ]
+}