Browse Source

ENH: add ability to have manifest files and incremental linking with make and nmake

Bill Hoffman 18 years ago
parent
commit
b479c6a8a9

+ 0 - 30
Modules/CMakeVCManifest.cmake

@@ -1,30 +0,0 @@
-
-# Leave the first line of this file empty so this module will not be
-# included in the documentation.
-
-# This script is invoked from Windows-cl.cmake and passed the TARGET
-# variable on the command line.
-
-# Conditionally embed the manifest in the executable if it exists.
-IF(EXISTS "${TARGET}.manifest")
-  # Construct the manifest embedding command.
-  SET(CMD
-    mt ${CMAKE_CL_NOLOGO} /manifest ${TARGET}.manifest
-    /outputresource:${TARGET}
-    )
-
-  # Run the embedding command.
-  EXECUTE_PROCESS(COMMAND ${CMD}\;\#2 RESULT_VARIABLE RESULT)
-
-  # Check whether the command failed.
-  IF(NOT "${RESULT}" MATCHES "^0$")
-    # The embedding failed remove the target and the manifest.
-    FILE(REMOVE ${TARGET} ${TARGET}.manifest)
-
-    # Describe the failure in a message.
-    STRING(REGEX REPLACE ";" " " CMD "${CMD}")
-    MESSAGE(FATAL_ERROR
-      "Failed to embed manifest in ${TARGET} using command \"${CMD};#2\""
-      )
-  ENDIF(NOT "${RESULT}" MATCHES "^0$")
-ENDIF(EXISTS "${TARGET}.manifest")

+ 0 - 30
Modules/CMakeVCManifestExe.cmake

@@ -1,30 +0,0 @@
-
-# Leave the first line of this file empty so this module will not be
-# included in the documentation.
-
-# This script is invoked from Windows-cl.cmake and passed the TARGET
-# variable on the command line.
-
-# Conditionally embed the manifest in the executable if it exists.
-IF(EXISTS "${TARGET}.manifest")
-  # Construct the manifest embedding command.
-  SET(CMD
-    mt ${CMAKE_CL_NOLOGO} /manifest ${TARGET}.manifest
-    /outputresource:${TARGET}
-    )
-
-  # Run the embedding command.
-  EXECUTE_PROCESS(COMMAND ${CMD}\;\#1 RESULT_VARIABLE RESULT)
-
-  # Check whether the command failed.
-  IF(NOT "${RESULT}" MATCHES "^0$")
-    # The embedding failed remove the target and the manifest.
-    FILE(REMOVE ${TARGET} ${TARGET}.manifest)
-
-    # Describe the failure in a message.
-    STRING(REGEX REPLACE ";" " " CMD "${CMD}")
-    MESSAGE(FATAL_ERROR
-      "Failed to embed manifest in ${TARGET} using command \"${CMD};#1\""
-      )
-  ENDIF(NOT "${RESULT}" MATCHES "^0$")
-ENDIF(EXISTS "${TARGET}.manifest")

+ 4 - 14
Modules/Platform/Windows-cl.cmake

@@ -167,25 +167,15 @@ ENDIF(CMAKE_FORCE_WIN64)
 
 # default to Debug builds
 IF(MSVC_VERSION GREATER 1310)
-  # Not used by generator directly but referenced below.
-  SET(CMAKE_CREATE_LIB_MANIFEST
-    "$(CMAKE_COMMAND) -DTARGET=<TARGET> -DCMAKE_CL_NOLOGO=${CMAKE_CL_NOLOGO} -P \"${CMAKE_ROOT}/Modules/CMakeVCManifest.cmake\"")
-  SET(CMAKE_CREATE_EXE_MANIFEST
-    "$(CMAKE_COMMAND) -DTARGET=<TARGET> -DCMAKE_CL_NOLOGO=${CMAKE_CL_NOLOGO} -P \"${CMAKE_ROOT}/Modules/CMakeVCManifestExe.cmake\"")
-
   # for 2005 make sure the manifest is put in the dll with mt
-  SET(CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY}"
-    ${CMAKE_CREATE_LIB_MANIFEST})
-  SET(CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE}"
-    ${CMAKE_CREATE_LIB_MANIFEST})
+  SET(CMAKE_CXX_CREATE_SHARED_LIBRARY "$(CMAKE_COMMAND) -E vs_link_dll ${CMAKE_CXX_CREATE_SHARED_LIBRARY}")
+  SET(CMAKE_CXX_CREATE_SHARED_MODULE "$(CMAKE_COMMAND) -E vs_link_dll ${CMAKE_CXX_CREATE_SHARED_MODULE}")
   # create a C shared library
   SET(CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY}")
   # create a C shared module just copy the shared library rule
   SET(CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE}")
-  SET(CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE}"
-    ${CMAKE_CREATE_EXE_MANIFEST})
-  SET(CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE}"
-    ${CMAKE_CREATE_EXE_MANIFEST})
+  SET(CMAKE_CXX_LINK_EXECUTABLE "$(CMAKE_COMMAND) -E vs_link_exe ${CMAKE_CXX_LINK_EXECUTABLE}")
+  SET(CMAKE_C_LINK_EXECUTABLE "$(CMAKE_COMMAND) -E vs_link_exe ${CMAKE_C_LINK_EXECUTABLE}")
 
   SET(CMAKE_BUILD_TYPE_INIT Debug)
   SET (CMAKE_CXX_FLAGS_INIT "/DWIN32 /D_WINDOWS /W3 /Zm1000 /EHsc /GR")

+ 5 - 1
Source/cmMakefile.cxx

@@ -1020,8 +1020,12 @@ void cmMakefile::AddLinkDirectory(const char* dir)
   // much bigger than 20.  We cannot use a set because of order
   // dependency of the link search path.
 
+  if(!dir)
+    {
+    return;
+    }
   // remove trailing slashes
-  if(dir && dir[strlen(dir)-1] == '/')
+  if(dir[strlen(dir)-1] == '/')
     {
     std::string newdir = dir;
     newdir = newdir.substr(0, newdir.size()-1);

+ 31 - 21
Source/cmSystemTools.cxx

@@ -563,29 +563,16 @@ std::vector<cmStdString> cmSystemTools::ParseArguments(const char* command)
   return args;
 }
 
-bool cmSystemTools::RunSingleCommand(
-  const char* command, 
-  std::string* output,
-  int *retVal, 
-  const char* dir,
-  bool verbose,
-  double timeout)
-{
-  if(s_DisableRunCommandOutput)
-    {
-    verbose = false;
-    }
-
-  std::vector<cmStdString> args = cmSystemTools::ParseArguments(command);
 
-  if(args.size() < 1)
-    {
-    return false;
-    }
-  
+bool cmSystemTools::RunSingleCommand(std::vector<cmStdString>const& command,
+                                     std::string* output ,
+                                     int* retVal , const char* dir , 
+                                     bool verbose ,
+                                     double timeout )
+{
   std::vector<const char*> argv;
-  for(std::vector<cmStdString>::const_iterator a = args.begin();
-      a != args.end(); ++a)
+  for(std::vector<cmStdString>::const_iterator a = command.begin();
+      a != command.end(); ++a)
     {
     argv.push_back(a->c_str());
     }
@@ -700,6 +687,29 @@ bool cmSystemTools::RunSingleCommand(
   cmsysProcess_Delete(cp);
   return result;
 }
+
+bool cmSystemTools::RunSingleCommand(
+  const char* command, 
+  std::string* output,
+  int *retVal, 
+  const char* dir,
+  bool verbose,
+  double timeout)
+{
+  if(s_DisableRunCommandOutput)
+    {
+    verbose = false;
+    }
+
+  std::vector<cmStdString> args = cmSystemTools::ParseArguments(command);
+
+  if(args.size() < 1)
+    {
+    return false;
+    }
+  return cmSystemTools::RunSingleCommand(args, output,retVal, 
+                                         dir, verbose, timeout);
+}
 bool cmSystemTools::RunCommand(const char* command, 
                                std::string& output,
                                const char* dir,

+ 10 - 0
Source/cmSystemTools.h

@@ -209,6 +209,16 @@ public:
                                int* retVal = 0, const char* dir = 0, 
                                bool verbose = true,
                                double timeout = 0.0);
+  /** 
+   * In this version of RunSingleCommand, command[0] should be
+   * the command to run, and each argument to the command should
+   * be in comand[1]...command[command.size()]
+   */
+  static bool RunSingleCommand(std::vector<cmStdString> const& command,
+                               std::string* output = 0,
+                               int* retVal = 0, const char* dir = 0, 
+                               bool verbose = true,
+                               double timeout = 0.0);
 
   /**
    * Parse arguments out of a single string command

+ 272 - 1
Source/cmake.cxx

@@ -1451,7 +1451,14 @@ int cmake::ExecuteCMakeCommand(std::vector<std::string>& args)
       std::cerr << std::endl;
       return 1;
       }
-
+    else if (args[1] == "vs_link_exe")
+      {
+      return cmake::VisualStudioLink(args, 1);
+      }
+    else if (args[1] == "vs_link_dll")
+      {
+      return cmake::VisualStudioLink(args, 2);
+      }
 #ifdef CMAKE_BUILD_WITH_CMAKE
     // Internal CMake color makefile support.
     else if (args[1] == "cmake_echo_color")
@@ -3652,3 +3659,267 @@ static bool cmakeCheckStampFile(const char* stampName)
     return false;
     }
 }
+
+// For visual studio 2005 and newer manifest files need to be embeded into
+// exe and dll's.  This code does that in such a way that incremental linking
+// still works.
+int cmake::VisualStudioLink(std::vector<std::string>& args, int type)
+{
+  if(args.size() < 2)
+    {
+    return -1;
+    }
+  bool verbose = false;
+  if(cmSystemTools::GetEnv("VERBOSE"))
+    {
+    verbose = true;
+    }
+  // figure out if this is an incremental link or not and run the correct
+  // link function.
+  for(std::vector<std::string>::iterator i = args.begin();
+      i != args.end(); ++i)
+    {
+    if(cmSystemTools::Strucmp(i->c_str(), "/INCREMENTAL:YES") == 0)
+      {
+      return cmake::VisualStudioLinkIncremental(args, type, verbose);
+      }
+    }
+  return cmake::VisualStudioLinkNonIncremental(args, type, verbose);
+}
+
+int cmake::ParseVisualStudioLinkCommand(std::vector<std::string>& args, 
+                                        std::vector<cmStdString>& command,
+                                        std::string& targetName)
+{
+  std::vector<std::string>::iterator i = args.begin();
+  i++; // skip -E
+  i++; // skip vs_link_dll or vs_link_exe
+  command.push_back(*i);
+  i++; // move past link command
+  for(; i != args.end(); ++i)
+    {
+    command.push_back(*i);
+    if(i->find("/Fe") == 0)
+      {
+      targetName = i->substr(3);
+      }
+    if(i->find("/out:") == 0)
+      {
+      targetName = i->substr(5);
+      }
+    }
+  if(targetName.size() == 0 || command.size() == 0)
+    {
+    return -1;
+    }
+  return 0;
+}
+
+bool cmake::RunCommand(const char* comment,
+                       std::vector<cmStdString>& command,
+                       bool verbose,
+                       int* retCodeOut)
+{
+  if(verbose)
+    {
+    std::cout << comment << ":\n";
+    for(std::vector<cmStdString>::iterator i = command.begin();
+        i != command.end(); ++i)
+      {
+      std::cout << i->c_str() << " ";
+      }
+    std::cout << "\n";
+    }
+  std::string output;
+  int retCode =0;
+  // use rc command to create .res file
+  cmSystemTools::RunSingleCommand(command,
+                                  &output,
+                                  &retCode);
+  if(verbose)
+    {
+    std::cout << output << "\n";
+    }
+  // if retCodeOut is requested then always return true
+  // and set the retCodeOut to retCode
+  if(retCodeOut)
+    {
+    *retCodeOut = retCode;
+    return true;
+    }
+  if(retCode != 0)
+    {
+    std::cout << comment << " failed. with " << retCode << "\n";
+    }
+  return retCode == 0;
+}
+
+int cmake::VisualStudioLinkIncremental(std::vector<std::string>& args, 
+                                       int type, bool verbose)
+{
+  // This follows the steps listed here:
+  // http://blogs.msdn.com/zakramer/archive/2006/05/22/603558.aspx
+  
+  //    1.  Compiler compiles the application and generates the *.obj files.
+  //    2.  An empty manifest file is generated if this is a clean build and if
+  //    not the previous one is reused.
+  //    3.  The resource compiler (rc.exe) compiles the *.manifest file to a
+  //    *.res file.
+  //    4.  Linker generates the binary (EXE or DLL) with the /incremental
+  //    switch and embeds the dummy manifest file. The linker also generates
+  //    the real manifest file based on the binaries that your binary depends
+  //    on.
+  //    5.  The manifest tool (mt.exe) is then used to generate the final
+  //    manifest.
+  
+  // If the final manifest is changed, then 6 and 7 are run, if not
+  // they are skipped, and it is done.
+  
+  //    6.  The resource compiler is invoked one more time.
+  //    7.  Finally, the Linker does another incremental link, but since the
+  //    only thing that has changed is the *.res file that contains the
+  //    manifest it is a short link.
+  std::vector<cmStdString> linkCommand;
+  std::string targetName;
+  if(cmake::ParseVisualStudioLinkCommand(args, linkCommand, targetName) == -1)
+    {
+    return -1;
+    }
+  std::string manifestArg = "/MANIFESTFILE:";
+  std::vector<cmStdString> rcCommand;
+  rcCommand.push_back(cmSystemTools::FindProgram("rc.exe"));
+  std::vector<cmStdString> mtCommand;
+  mtCommand.push_back(cmSystemTools::FindProgram("mt.exe"));
+  std::string tempManifest;
+  tempManifest = targetName;
+  tempManifest += ".intermediate.manifest";
+  std::string resourceInputFile = targetName;
+  resourceInputFile += ".resource.txt";
+  if(verbose)
+    {
+    std::cout << "Create " << resourceInputFile.c_str() << "\n";
+    }
+  // Create input file for rc command
+  std::ofstream fout(resourceInputFile.c_str());
+  if(!fout)
+    {
+    return -1;
+    }
+  std::string manifestFile = targetName;
+  manifestFile += ".embed.manifest";
+  std::string fullPath=manifestFile;
+  fout << type << " /* CREATEPROCESS_MANIFEST_RESOURCE_ID "
+    "*/ 24 /* RT_MANIFEST */ " << "\"" << fullPath.c_str() << "\"";
+  fout.close();
+  manifestArg += tempManifest;
+  // add the manifest arg to the linkCommand
+  linkCommand.push_back(manifestArg);
+  // if manifestFile is not yet created, create an
+  // empty one
+  if(!cmSystemTools::FileExists(manifestFile.c_str()))
+    {
+    if(verbose)
+      {
+      std::cout << "Create empty: " << manifestFile.c_str() << "\n";
+      }
+    std::ofstream fout(manifestFile.c_str());
+    }
+  std::string resourceFile = manifestFile;
+  resourceFile += ".res";
+  // add the resource file to the end of the link command
+  linkCommand.push_back(resourceFile);
+  std::string outputOpt = "/fo";
+  outputOpt += resourceFile;
+  rcCommand.push_back(outputOpt);
+  rcCommand.push_back(resourceInputFile);
+  // Run rc command to create resource 
+  if(!cmake::RunCommand("RC Pass 1", rcCommand, verbose))
+    {
+    return -1;
+    }
+  // Now run the link command to link and create manifest
+  if(!cmake::RunCommand("LINK Pass 1", linkCommand, verbose))
+    {
+    return -1;
+    }
+  // create mt command 
+  std::string outArg("/out:");
+  outArg+= manifestFile;
+  mtCommand.push_back("/nologo");
+  mtCommand.push_back(outArg);
+  mtCommand.push_back("/notify_update");
+  mtCommand.push_back("/manifest");
+  mtCommand.push_back(tempManifest);
+  //  now run mt.exe to create the final manifest file
+  int mtRet =0;
+  cmake::RunCommand("MT", mtCommand, verbose, &mtRet);
+  // if mt returns 0, then the manifest was not changed and
+  // we do not need to do another link step
+  if(mtRet == 0)
+    {
+    return 0;
+    }
+  // check for magic mt return value if mt returns the magic number
+  // 1090650113 then it means that it updated the manifest file and we need
+  // to do the final link.  If mt has any value other than 0 or 1090650113
+  // then there was some problem with the command itself and there was an
+  // error so return the error code back out of cmake so make can report it.
+  if(mtRet != 1090650113)
+    {
+    return mtRet;
+    }
+  // update the resource file with the new manifest from the mt command.
+  if(!cmake::RunCommand("RC Pass 2", rcCommand, verbose))
+    {
+    return -1;
+    }
+  // Run the final incremental link that will put the new manifest resource
+  // into the file incrementally.
+  if(!cmake::RunCommand("FINAL LINK", linkCommand, verbose))
+    {
+    return -1;
+    }
+  return 0;
+}
+
+int cmake::VisualStudioLinkNonIncremental(std::vector<std::string>& args,
+                                          int type,
+                                          bool verbose)
+{
+  std::vector<cmStdString> linkCommand;
+  std::string targetName;
+  if(cmake::ParseVisualStudioLinkCommand(args, linkCommand, targetName) == -1)
+    {
+    return -1;
+    }
+  // Run the link command as given 
+  if(!cmake::RunCommand("LINK", linkCommand, verbose))
+    {
+    return -1;
+    }
+  std::vector<cmStdString> mtCommand;
+  mtCommand.push_back(cmSystemTools::FindProgram("mt.exe"));
+  mtCommand.push_back("/nologo");
+  mtCommand.push_back("/manifest");
+  std::string manifestFile = targetName;
+  manifestFile += ".manifest";
+  mtCommand.push_back(manifestFile);
+  std::string outresource = "/outputresource:";
+  outresource += targetName;
+  outresource += ";#";
+  if(type == 1)
+    {
+    outresource += "1";
+    }
+  else if(type == 2)
+    {
+    outresource += "2";
+    }
+  mtCommand.push_back(outresource);
+  // Now use the mt tool to embed the manifest into the exe or dll
+  if(!cmake::RunCommand("MT", mtCommand, verbose))
+    {
+    return -1;
+    }
+  return 0;
+}

+ 14 - 1
Source/cmake.h

@@ -387,7 +387,20 @@ protected:
 
   static int ExecuteEchoColor(std::vector<std::string>& args);
   static int ExecuteLinkScript(std::vector<std::string>& args);
-  
+  static int VisualStudioLink(std::vector<std::string>& args, int type);
+  static int VisualStudioLinkIncremental(std::vector<std::string>& args,
+                                         int type, 
+                                         bool verbose);
+  static int VisualStudioLinkNonIncremental(std::vector<std::string>& args,
+                                            int type,
+                                            bool verbose);
+  static int ParseVisualStudioLinkCommand(std::vector<std::string>& args, 
+                                          std::vector<cmStdString>& command, 
+                                          std::string& targetName);
+  static bool RunCommand(const char* comment,
+                         std::vector<cmStdString>& command,
+                         bool verbose,
+                         int* retCodeOut = 0);
   cmVariableWatch* VariableWatch;
   
   ///! Find the full path to one of the cmake programs like ctest, cpack, etc.