Gary Oberbrunner avatar Gary Oberbrunner committed 01b7a24

Support automatically embedding manifests in EXEs and DLLs on Windows.

Comments (0)

Files changed (5)

 
 RELEASE 2.1.0.alpha.yyyymmdd - NEW DATE WILL BE INSERTED HERE
 
+  From Gary Oberbrunner and Sohail Somani:
+    - new construction variable WINDOWS_EMBED_MANIFEST to automatically
+      embed manifests in Windows EXEs and DLLs.
+
   From Gary Oberbrunner:
     - Adding None to an Action no longer fails (just returns original action)
 
 
   NEW FUNCTIONALITY
 
+    - SCons can now automatically embed manifests in Windows executables
+      and DLLs, by setting WINDOWS_EMBED_MANIFEST in the environment.
+
     - SCons now searches for site_scons dirs in several system-wide
       and per-user locations, in addition to the SConstruct top dir.
       This should enable much easier use of third-party (non-core)
   Rob Managan,
   Gary Oberbrunner,
   Evgeny Podjachev,
+  Sohail Somani,
   Anatoly Techtonik,
   Allen Weeks,
   Russel Winder,

src/engine/SCons/Tool/mslink.py

                             "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"))
 
     version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
-    if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0):
-        # MSVC 8 automatically generates .manifest files that must be installed
+    if version_num >= 8.0 and \
+            (env.get('WINDOWS_INSERT_MANIFEST', 0) or env.get('WINDOWS_EMBED_MANIFEST', 0)):
+        # MSVC 8 and above automatically generate .manifest files that must be installed
         extratargets.append(
             env.ReplaceIxes(dll,
                             '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp,
         raise SCons.Errors.UserError("An executable should have exactly one target with the suffix: %s" % env.subst("$PROGSUFFIX"))
 
     version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
-    if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0):
-        # MSVC 8 automatically generates .manifest files that have to be installed
+    if version_num >= 8.0 and \
+            (env.get('WINDOWS_INSERT_MANIFEST', 0) or env.get('WINDOWS_EMBED_MANIFEST', 0)):
+        # MSVC 8 and above automatically generate .manifest files that have to be installed
         extratargets.append(
             env.ReplaceIxes(exe,
                             "PROGPREFIX", "PROGSUFFIX",
         return ret
     return 0
 
+# These are the actual actions run to embed the manifest.
+# They are only called from the Check versions below.
+embedManifestExeAction = SCons.Action.Action('$MTEXECOM')
+embedManifestDllAction = SCons.Action.Action('$MTSHLIBCOM')
+
+def embedManifestDllCheck(target, source, env):
+    """Function run by embedManifestDllCheckAction to check for existence of manifest
+    and other conditions, and embed the manifest by calling embedManifestDllAction if so."""
+    if env.get('WINDOWS_EMBED_MANIFEST', 0):
+        manifestSrc = target[0].abspath + '.manifest'
+        if os.path.exists(manifestSrc):
+            ret = (embedManifestDllAction) ([target[0]],None,env)        
+            if ret:
+                raise SCons.Errors.UserError, "Unable to embed manifest into %s" % (target[0])
+            return ret
+        else:
+            print '(embed: no %s.manifest found; not embedding.)'%str(target[0])
+    return 0
+
+def embedManifestExeCheck(target, source, env):
+    """Function run by embedManifestExeCheckAction to check for existence of manifest
+    and other conditions, and embed the manifest by calling embedManifestExeAction if so."""
+    if env.get('WINDOWS_EMBED_MANIFEST', 0):
+        manifestSrc = target[0].abspath + '.manifest'
+        if os.path.exists(manifestSrc):
+            ret = (embedManifestExeAction) ([target[0]],None,env)
+            if ret:
+                raise SCons.Errors.UserError, "Unable to embed manifest into %s" % (target[0])
+            return ret
+        else:
+            print '(embed: no %s.manifest found; not embedding.)'%str(target[0])
+    return 0
+
+embedManifestDllCheckAction = SCons.Action.Action(embedManifestDllCheck, None)
+embedManifestExeCheckAction = SCons.Action.Action(embedManifestExeCheck, None)
+
 regServerAction = SCons.Action.Action("$REGSVRCOM", "$REGSVRCOMSTR")
 regServerCheck = SCons.Action.Action(RegServerFunc, None)
 shlibLinkAction = SCons.Action.Action('${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $_LIBDIRFLAGS $_LIBFLAGS $_PDB $_SHLINK_SOURCES")}')
-compositeShLinkAction = shlibLinkAction + regServerCheck
+compositeShLinkAction = shlibLinkAction + regServerCheck + embedManifestDllCheckAction
 ldmodLinkAction = SCons.Action.Action('${TEMPFILE("$LDMODULE $LDMODULEFLAGS $_LDMODULE_TARGETS $_LIBDIRFLAGS $_LIBFLAGS $_PDB $_LDMODULE_SOURCES")}')
-compositeLdmodAction = ldmodLinkAction + regServerCheck
+compositeLdmodAction = ldmodLinkAction + regServerCheck + embedManifestDllCheckAction
+exeLinkAction = SCons.Action.Action('${TEMPFILE("$LINK $LINKFLAGS /OUT:$TARGET.windows $_LIBDIRFLAGS $_LIBFLAGS $_PDB $SOURCES.windows")}')
+compositeLinkAction = exeLinkAction + embedManifestExeCheckAction
 
 def generate(env):
     """Add Builders and construction variables for ar to an Environment."""
     env['LINK']        = 'link'
     env['LINKFLAGS']   = SCons.Util.CLVar('/nologo')
     env['_PDB'] = pdbGenerator
-    env['LINKCOM'] = '${TEMPFILE("$LINK $LINKFLAGS /OUT:$TARGET.windows $_LIBDIRFLAGS $_LIBFLAGS $_PDB $SOURCES.windows")}'
+    env['LINKCOM'] = compositeLinkAction
     env.Append(PROGEMITTER = [prog_emitter])
     env['LIBDIRPREFIX']='/LIBPATH:'
     env['LIBDIRSUFFIX']=''
     env['REGSVRFLAGS'] = '/s '
     env['REGSVRCOM'] = '$REGSVR $REGSVRFLAGS ${TARGET.windows}'
 
+    env['WINDOWS_EMBED_MANIFEST'] = 0
+    env['MT'] = 'mt'
+    #env['MTFLAGS'] = ['-hashupdate']
+    env['MTFLAGS'] = SCons.Util.CLVar('/nologo')
+    # Note: use - here to prevent build failure if no manifest produced.
+    # This seems much simpler than a fancy system using a function action to see
+    # if the manifest actually exists before trying to run mt with it.
+    env['MTEXECOM']   = '-$MT $MTFLAGS -manifest ${TARGET}.manifest $_MANIFEST_SOURCES -outputresource:$TARGET;1'
+    env['MTSHLIBCOM'] = '-$MT $MTFLAGS -manifest ${TARGET}.manifest $_MANIFEST_SOURCES -outputresource:$TARGET;2'
+    # Future work garyo 27-Feb-11
+    env['_MANIFEST_SOURCES'] = None # _windowsManifestSources
+
     # Set-up ms tools paths
     msvc_setup_env_once(env)
 

src/engine/SCons/Tool/mslink.xml

 </summary>
 </cvar>
 
+<cvar name="WINDOWS_EMBED_MANIFEST">
+<summary>
+Set this variable to True or 1 to embed the compiler-generated manifest
+(normally <literal>${TARGET}.manifest</literal>)
+into all Windows exes and DLLs built with this environment,
+as a resource during their link step.
+This is done using &cv-link-MT; and &cv-link-MTEXECOM; and &cv-link-MTSHLIBCOM;.
+</summary>
+</cvar>
+
+<cvar name="MT">
+<summary>
+The program used on Windows systems to embed manifests into DLLs and EXEs.
+See also &cv-link-WINDOWS_EMBED_MANIFEST;.
+</summary>
+</cvar>
+
+<cvar name="MTFLAGS">
+<summary>
+Flags passed to the &cv-link-MT; manifest embedding program (Windows only).
+</summary>
+</cvar>
+
+<cvar name="MTEXECOM">
+<summary>
+The Windows command line used to embed manifests into executables.
+See also &cv-link-MTSHLIBCOM;.
+</summary>
+</cvar>
+
+<cvar name="MTSHLIBCOM">
+<summary>
+The Windows command line used to embed manifests into shared libraries (DLLs).
+See also &cv-link-MTEXECOM;.
+</summary>
+</cvar>
+
 <cvar name="REGSVR">
 <summary>
 The program used on Windows systems

test/MSVC/embed-manifest.py

+#!/usr/bin/env python
+#
+# __COPYRIGHT__
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+
+__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
+
+"""
+Verify that manifest files get embedded correctly in EXEs and DLLs
+"""
+
+import sys
+
+import TestSCons
+
+_exe = TestSCons._exe
+_dll = TestSCons._dll
+_lib = TestSCons._lib
+
+test = TestSCons.TestSCons()
+
+if sys.platform != 'win32':
+    msg = "Skipping Visual C/C++ test on non-Windows platform '%s'\n" % sys.platform
+    test.skip_test(msg)
+
+test.write('SConstruct', """\
+env=Environment(WINDOWS_EMBED_MANIFEST=True)
+env.Append(CCFLAGS = '/MD')
+exe=env.Program('test.cpp')
+dll=env.SharedLibrary('testdll.cpp')
+env.Command('exe-extracted.manifest', exe,
+  '$MT /nologo -inputresource:${SOURCE};1 -out:${TARGET}')
+env.Command('dll-extracted.manifest', dll,
+  '$MT /nologo -inputresource:${SOURCE};2 -out:${TARGET}')
+env2=Environment(WINDOWS_EMBED_MANIFEST=True) # no /MD here
+env2.Program('test-nomanifest', env2.Object('test-nomanifest', 'test.cpp'))
+""")
+
+test.write('test.cpp', """\
+#include <stdio.h>
+#include <stdlib.h>
+int
+main(int argc, char *argv)
+{
+    printf("test.cpp\\n");
+    exit (0);
+}
+""")
+
+test.write('testdll.cpp', """\
+int i;
+""")
+
+test.run(arguments = '.')
+
+test.must_exist('test%s' % _exe)
+test.must_exist('test%s.manifest' % _exe)
+test.must_contain('exe-extracted.manifest', '</assembly>')
+test.must_exist('testdll%s' % _dll)
+test.must_exist('testdll%s.manifest' % _dll)
+test.must_contain('dll-extracted.manifest', '</assembly>')
+
+test.pass_test()
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4:
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.