From b479c6a8a98664c19eeaef40b71850c1f5315601 Mon Sep 17 00:00:00 2001 From: Bill Hoffman Date: Tue, 1 Jan 2008 15:13:41 -0500 Subject: [PATCH] ENH: add ability to have manifest files and incremental linking with make and nmake --- Modules/CMakeVCManifest.cmake | 30 ---- Modules/CMakeVCManifestExe.cmake | 30 ---- Modules/Platform/Windows-cl.cmake | 18 +- Source/cmMakefile.cxx | 6 +- Source/cmSystemTools.cxx | 52 +++--- Source/cmSystemTools.h | 10 ++ Source/cmake.cxx | 273 +++++++++++++++++++++++++++++- Source/cmake.h | 15 +- 8 files changed, 336 insertions(+), 98 deletions(-) delete mode 100644 Modules/CMakeVCManifest.cmake delete mode 100644 Modules/CMakeVCManifestExe.cmake diff --git a/Modules/CMakeVCManifest.cmake b/Modules/CMakeVCManifest.cmake deleted file mode 100644 index 6675f99d7..000000000 --- a/Modules/CMakeVCManifest.cmake +++ /dev/null @@ -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") diff --git a/Modules/CMakeVCManifestExe.cmake b/Modules/CMakeVCManifestExe.cmake deleted file mode 100644 index 5066bc869..000000000 --- a/Modules/CMakeVCManifestExe.cmake +++ /dev/null @@ -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") diff --git a/Modules/Platform/Windows-cl.cmake b/Modules/Platform/Windows-cl.cmake index c9b2822ca..74ce3863a 100644 --- a/Modules/Platform/Windows-cl.cmake +++ b/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= -DCMAKE_CL_NOLOGO=${CMAKE_CL_NOLOGO} -P \"${CMAKE_ROOT}/Modules/CMakeVCManifest.cmake\"") - SET(CMAKE_CREATE_EXE_MANIFEST - "$(CMAKE_COMMAND) -DTARGET= -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") diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx index c5df4fb2d..306c00bef 100644 --- a/Source/cmMakefile.cxx +++ b/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); diff --git a/Source/cmSystemTools.cxx b/Source/cmSystemTools.cxx index 6829bdc90..b489c48f9 100644 --- a/Source/cmSystemTools.cxx +++ b/Source/cmSystemTools.cxx @@ -563,29 +563,16 @@ std::vector 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) + +bool cmSystemTools::RunSingleCommand(std::vectorconst& command, + std::string* output , + int* retVal , const char* dir , + bool verbose , + double timeout ) { - if(s_DisableRunCommandOutput) - { - verbose = false; - } - - std::vector args = cmSystemTools::ParseArguments(command); - - if(args.size() < 1) - { - return false; - } - std::vector argv; - for(std::vector::const_iterator a = args.begin(); - a != args.end(); ++a) + for(std::vector::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 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, diff --git a/Source/cmSystemTools.h b/Source/cmSystemTools.h index 44fcb3d0b..fcfbafdb6 100644 --- a/Source/cmSystemTools.h +++ b/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 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 diff --git a/Source/cmake.cxx b/Source/cmake.cxx index 45f013fcd..1317cc40b 100644 --- a/Source/cmake.cxx +++ b/Source/cmake.cxx @@ -1451,7 +1451,14 @@ int cmake::ExecuteCMakeCommand(std::vector& 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& 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::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& args, + std::vector& command, + std::string& targetName) +{ + std::vector::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& command, + bool verbose, + int* retCodeOut) +{ + if(verbose) + { + std::cout << comment << ":\n"; + for(std::vector::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& 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 linkCommand; + std::string targetName; + if(cmake::ParseVisualStudioLinkCommand(args, linkCommand, targetName) == -1) + { + return -1; + } + std::string manifestArg = "/MANIFESTFILE:"; + std::vector rcCommand; + rcCommand.push_back(cmSystemTools::FindProgram("rc.exe")); + std::vector 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& args, + int type, + bool verbose) +{ + std::vector 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 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; +} diff --git a/Source/cmake.h b/Source/cmake.h index 265fc0a83..b43e77018 100644 --- a/Source/cmake.h +++ b/Source/cmake.h @@ -387,7 +387,20 @@ protected: static int ExecuteEchoColor(std::vector& args); static int ExecuteLinkScript(std::vector& args); - + static int VisualStudioLink(std::vector& args, int type); + static int VisualStudioLinkIncremental(std::vector& args, + int type, + bool verbose); + static int VisualStudioLinkNonIncremental(std::vector& args, + int type, + bool verbose); + static int ParseVisualStudioLinkCommand(std::vector& args, + std::vector& command, + std::string& targetName); + static bool RunCommand(const char* comment, + std::vector& command, + bool verbose, + int* retCodeOut = 0); cmVariableWatch* VariableWatch; ///! Find the full path to one of the cmake programs like ctest, cpack, etc.