MSVC: Rewrite manifest file handling with Makefile and Ninja

Add a helper class private to "cmcmd.cxx" to contain the implementation.
Update the link logic to use the intermediate files directory for each
target to hold manifest and resource files before embedding into the
binary.  Preserve the old behavior of placing the .manifest file next
to the binary when not linking incrementally even though it will be
embedded.
This commit is contained in:
Brad King 2015-09-15 15:51:19 -04:00
parent d488b5c976
commit da00be6359
3 changed files with 227 additions and 204 deletions

View File

@ -274,8 +274,8 @@ set (CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL_INIT ${CMAKE_EXE_LINKER_FLAGS_MINSIZER
macro(__windows_compiler_msvc lang) macro(__windows_compiler_msvc lang)
if(NOT MSVC_VERSION LESS 1400) if(NOT MSVC_VERSION LESS 1400)
# for 2005 make sure the manifest is put in the dll with mt # for 2005 make sure the manifest is put in the dll with mt
set(_CMAKE_VS_LINK_DLL "<CMAKE_COMMAND> -E vs_link_dll ") set(_CMAKE_VS_LINK_DLL "<CMAKE_COMMAND> -E vs_link_dll --intdir=<OBJECT_DIR> ")
set(_CMAKE_VS_LINK_EXE "<CMAKE_COMMAND> -E vs_link_exe ") set(_CMAKE_VS_LINK_EXE "<CMAKE_COMMAND> -E vs_link_exe --intdir=<OBJECT_DIR> ")
endif() endif()
set(CMAKE_${lang}_CREATE_SHARED_LIBRARY set(CMAKE_${lang}_CREATE_SHARED_LIBRARY
"${_CMAKE_VS_LINK_DLL}<CMAKE_LINKER> ${CMAKE_CL_NOLOGO} <OBJECTS> ${CMAKE_START_TEMP_FILE} /out:<TARGET> /implib:<TARGET_IMPLIB> /pdb:<TARGET_PDB> /dll /version:<TARGET_VERSION_MAJOR>.<TARGET_VERSION_MINOR>${_PLATFORM_LINK_FLAGS} <LINK_FLAGS> <LINK_LIBRARIES> ${CMAKE_END_TEMP_FILE}") "${_CMAKE_VS_LINK_DLL}<CMAKE_LINKER> ${CMAKE_CL_NOLOGO} <OBJECTS> ${CMAKE_START_TEMP_FILE} /out:<TARGET> /implib:<TARGET_IMPLIB> /pdb:<TARGET_PDB> /dll /version:<TARGET_VERSION_MAJOR>.<TARGET_VERSION_MINOR>${_PLATFORM_LINK_FLAGS} <LINK_FLAGS> <LINK_LIBRARIES> ${CMAKE_END_TEMP_FILE}")

View File

@ -1355,6 +1355,34 @@ int cmcmd::WindowsCEEnvironment(const char* version, const std::string& name)
return -1; return -1;
} }
class cmVSLink
{
int Type;
bool Verbose;
bool Incremental;
bool LinkGeneratesManifest;
std::vector<std::string> LinkCommand;
std::string LinkerManifestFile;
std::string ManifestFile;
std::string ManifestFileRC;
std::string ManifestFileRes;
std::string TargetFile;
public:
cmVSLink(int type, bool verbose)
: Type(type)
, Verbose(verbose)
, Incremental(false)
, LinkGeneratesManifest(true)
{}
bool Parse(std::vector<std::string>::const_iterator argBeg,
std::vector<std::string>::const_iterator argEnd);
int Link();
private:
int LinkIncremental();
int LinkNonIncremental();
int RunMT(std::string const& out, bool notify);
};
// For visual studio 2005 and newer manifest files need to be embedded into // For visual studio 2005 and newer manifest files need to be embedded into
// exe and dll's. This code does that in such a way that incremental linking // exe and dll's. This code does that in such a way that incremental linking
// still works. // still works.
@ -1364,11 +1392,7 @@ int cmcmd::VisualStudioLink(std::vector<std::string>& args, int type)
{ {
return -1; return -1;
} }
bool verbose = false; bool verbose = cmSystemTools::GetEnv("VERBOSE")? true:false;
if(cmSystemTools::GetEnv("VERBOSE"))
{
verbose = true;
}
std::vector<std::string> expandedArgs; std::vector<std::string> expandedArgs;
for(std::vector<std::string>::iterator i = args.begin(); for(std::vector<std::string>::iterator i = args.begin();
i != args.end(); ++i) i != args.end(); ++i)
@ -1389,79 +1413,19 @@ int cmcmd::VisualStudioLink(std::vector<std::string>& args, int type)
expandedArgs.push_back(*i); expandedArgs.push_back(*i);
} }
} }
bool hasIncremental = false;
bool hasManifest = true;
for(std::vector<std::string>::iterator i = expandedArgs.begin();
i != expandedArgs.end(); ++i)
{
if(cmSystemTools::Strucmp(i->c_str(), "/INCREMENTAL:YES") == 0)
{
hasIncremental = true;
}
if(cmSystemTools::Strucmp(i->c_str(), "/INCREMENTAL") == 0)
{
hasIncremental = true;
}
if(cmSystemTools::Strucmp(i->c_str(), "/MANIFEST:NO") == 0)
{
hasManifest = false;
}
}
if(hasIncremental && hasManifest)
{
if(verbose)
{
std::cout << "Visual Studio Incremental Link with embedded manifests\n";
}
return cmcmd::VisualStudioLinkIncremental(expandedArgs, type, verbose);
}
if(verbose)
{
if(!hasIncremental)
{
std::cout << "Visual Studio Non-Incremental Link\n";
}
else
{
std::cout << "Visual Studio Incremental Link without manifests\n";
}
}
return cmcmd::VisualStudioLinkNonIncremental(expandedArgs,
type, hasManifest, verbose);
}
int cmcmd::ParseVisualStudioLinkCommand(std::vector<std::string>& args, cmVSLink vsLink(type, verbose);
std::vector<std::string>& command, if (!vsLink.Parse(expandedArgs.begin()+2, expandedArgs.end()))
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.empty() || command.empty())
{ {
return -1; return -1;
} }
return 0; return vsLink.Link();
} }
bool cmcmd::RunCommand(const char* comment, static bool RunCommand(const char* comment,
std::vector<std::string>& command, std::vector<std::string>& command,
bool verbose, bool verbose,
int* retCodeOut) int* retCodeOut = 0)
{ {
if(verbose) if(verbose)
{ {
@ -1503,8 +1467,126 @@ bool cmcmd::RunCommand(const char* comment,
return retCode == 0; return retCode == 0;
} }
int cmcmd::VisualStudioLinkIncremental(std::vector<std::string>& args, bool cmVSLink::Parse(std::vector<std::string>::const_iterator argBeg,
int type, bool verbose) std::vector<std::string>::const_iterator argEnd)
{
// Parse our own arguments.
std::string intDir;
std::vector<std::string>::const_iterator arg = argBeg;
while (arg != argEnd && cmHasLiteralPrefix(*arg, "-"))
{
if (*arg == "--")
{
++arg;
break;
}
else if (cmHasLiteralPrefix(*arg, "--intdir="))
{
intDir = arg->substr(9);
++arg;
}
else
{
std::cerr << "unknown argument '" << *arg << "'\n";
return false;
}
}
if (intDir.empty())
{
return false;
}
// The rest of the arguments form the link command.
if (arg == argEnd)
{
return false;
}
this->LinkCommand.insert(this->LinkCommand.begin(), arg, argEnd);
// Parse the link command to extract information we need.
for (; arg != argEnd; ++arg)
{
if (cmSystemTools::Strucmp(arg->c_str(), "/INCREMENTAL:YES") == 0)
{
this->Incremental = true;
}
else if (cmSystemTools::Strucmp(arg->c_str(), "/INCREMENTAL") == 0)
{
this->Incremental = true;
}
else if (cmSystemTools::Strucmp(arg->c_str(), "/MANIFEST:NO") == 0)
{
this->LinkGeneratesManifest = false;
}
else if (cmHasLiteralPrefix(*arg, "/Fe"))
{
this->TargetFile = arg->substr(3);
}
else if (cmHasLiteralPrefix(*arg, "/out:"))
{
this->TargetFile = arg->substr(5);
}
}
if (this->TargetFile.empty())
{
return false;
}
this->ManifestFile = intDir + "/embed.manifest";
this->LinkerManifestFile = intDir + "/intermediate.manifest";
if (this->Incremental)
{
// We will compile a resource containing the manifest and
// pass it to the link command.
this->ManifestFileRC = intDir + "/manifest.rc";
this->ManifestFileRes = intDir + "/manifest.res";
this->LinkCommand.push_back(this->ManifestFileRes);
}
else
{
// CMake places the linker-generated manifest next to the binary (as if it
// were not to be embedded) when not linking incrementally.
this->ManifestFile = this->TargetFile + ".manifest";
this->LinkerManifestFile = this->ManifestFile;
}
if (this->LinkGeneratesManifest)
{
this->LinkCommand.push_back("/MANIFEST");
this->LinkCommand.push_back("/MANIFESTFILE:" + this->LinkerManifestFile);
}
return true;
}
int cmVSLink::Link()
{
if (this->Incremental &&
this->LinkGeneratesManifest)
{
if (this->Verbose)
{
std::cout << "Visual Studio Incremental Link with embedded manifests\n";
}
return LinkIncremental();
}
if (this->Verbose)
{
if (!this->Incremental)
{
std::cout << "Visual Studio Non-Incremental Link\n";
}
else
{
std::cout << "Visual Studio Incremental Link without manifests\n";
}
}
return LinkNonIncremental();
}
int cmVSLink::LinkIncremental()
{ {
// This follows the steps listed here: // This follows the steps listed here:
// http://blogs.msdn.com/zakramer/archive/2006/05/22/603558.aspx // http://blogs.msdn.com/zakramer/archive/2006/05/22/603558.aspx
@ -1528,161 +1610,116 @@ int cmcmd::VisualStudioLinkIncremental(std::vector<std::string>& args,
// 7. Finally, the Linker does another incremental link, but since the // 7. Finally, the Linker does another incremental link, but since the
// only thing that has changed is the *.res file that contains the // only thing that has changed is the *.res file that contains the
// manifest it is a short link. // manifest it is a short link.
std::vector<std::string> linkCommand;
std::string targetName; // Create a resource file referencing the manifest.
if(cmcmd::ParseVisualStudioLinkCommand(args, linkCommand, targetName) == -1) std::string absManifestFile =
cmSystemTools::CollapseFullPath(this->ManifestFile);
if (this->Verbose)
{ {
return -1; std::cout << "Create " << this->ManifestFileRC << "\n";
} }
std::string manifestArg = "/MANIFESTFILE:";
std::vector<std::string> rcCommand;
rcCommand.push_back(cmSystemTools::FindProgram("rc.exe"));
std::vector<std::string> 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 << "\n"; cmsys::ofstream fout(this->ManifestFileRC.c_str());
}
// Create input file for rc command
cmsys::ofstream fout(resourceInputFile.c_str());
if (!fout) if (!fout)
{ {
return -1; return -1;
} }
std::string manifestFile = targetName; fout << this->Type << " /* CREATEPROCESS_MANIFEST_RESOURCE_ID */ "
manifestFile += ".embed.manifest"; "24 /* RT_MANIFEST */ \"" << absManifestFile << "\"";
std::string fullPath= cmSystemTools::CollapseFullPath(manifestFile);
fout << type << " /* CREATEPROCESS_MANIFEST_RESOURCE_ID "
"*/ 24 /* RT_MANIFEST */ " << "\"" << fullPath << "\"";
fout.close();
manifestArg += tempManifest;
// add the manifest arg to the linkCommand
linkCommand.push_back("/MANIFEST");
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 << "\n";
} }
cmsys::ofstream foutTmp(manifestFile.c_str());
// If we have not previously generated a manifest file,
// generate an empty one so the resource compiler succeeds.
if (!cmSystemTools::FileExists(this->ManifestFile))
{
if (this->Verbose)
{
std::cout << "Create empty: " << this->ManifestFile << "\n";
} }
std::string resourceFile = manifestFile; cmsys::ofstream foutTmp(this->ManifestFile.c_str());
resourceFile += ".res"; }
// add the resource file to the end of the link command
linkCommand.push_back(resourceFile); // Compile the resource file.
std::string outputOpt = "/fo"; std::vector<std::string> rcCommand;
outputOpt += resourceFile; rcCommand.push_back(cmSystemTools::FindProgram("rc.exe"));
rcCommand.push_back(outputOpt); rcCommand.push_back("/fo" + this->ManifestFileRes);
rcCommand.push_back(resourceInputFile); rcCommand.push_back(this->ManifestFileRC);
// Run rc command to create resource if (!RunCommand("RC Pass 1", rcCommand, this->Verbose))
if(!cmcmd::RunCommand("RC Pass 1", rcCommand, verbose))
{ {
return -1; return -1;
} }
// Now run the link command to link and create manifest
if(!cmcmd::RunCommand("LINK Pass 1", linkCommand, verbose)) // Run the link command (possibly generates intermediate manifest).
if (!RunCommand("LINK Pass 1", this->LinkCommand, this->Verbose))
{ {
return -1; return -1;
} }
// create mt command
std::string outArg("/out:"); // Run the manifest tool to create the final manifest.
outArg+= manifestFile; int mtRet = this->RunMT("/out:" + this->ManifestFile, true);
mtCommand.push_back("/nologo");
mtCommand.push_back(outArg); // If mt returns 1090650113 (or 187 on a posix host) then it updated the
mtCommand.push_back("/notify_update"); // manifest file so we need to embed it again. Otherwise we are done.
mtCommand.push_back("/manifest");
mtCommand.push_back(tempManifest);
// now run mt.exe to create the final manifest file
int mtRet =0;
if(!cmcmd::RunCommand("MT", mtCommand, verbose, &mtRet))
{
return -1;
}
// 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.
// (when hosted on a posix system the value is 187)
if (mtRet != 1090650113 && mtRet != 187) if (mtRet != 1090650113 && mtRet != 187)
{ {
return mtRet; return mtRet;
} }
// update the resource file with the new manifest from the mt command.
if(!cmcmd::RunCommand("RC Pass 2", rcCommand, verbose)) // Compile the resource file again.
if (!RunCommand("RC Pass 2", rcCommand, this->Verbose))
{ {
return -1; return -1;
} }
// Run the final incremental link that will put the new manifest resource
// into the file incrementally. // Link incrementally again to use the updated resource.
if(!cmcmd::RunCommand("FINAL LINK", linkCommand, verbose)) if (!RunCommand("FINAL LINK", this->LinkCommand, this->Verbose))
{ {
return -1; return -1;
} }
return 0; return 0;
} }
int cmcmd::VisualStudioLinkNonIncremental(std::vector<std::string>& args, int cmVSLink::LinkNonIncremental()
int type,
bool hasManifest,
bool verbose)
{ {
std::vector<std::string> linkCommand; // Run the link command (possibly generates intermediate manifest).
std::string targetName; if (!RunCommand("LINK", this->LinkCommand, this->Verbose))
if(cmcmd::ParseVisualStudioLinkCommand(args, linkCommand, targetName) == -1)
{ {
return -1; return -1;
} }
// Run the link command as given
if (hasManifest) // If we have no manifest files we are done.
{ if (!this->LinkGeneratesManifest)
linkCommand.push_back("/MANIFEST");
}
if(!cmcmd::RunCommand("LINK", linkCommand, verbose))
{
return -1;
}
if(!hasManifest)
{ {
return 0; return 0;
} }
// Run the manifest tool to embed the final manifest in the binary.
std::string mtOut =
"/outputresource:" + this->TargetFile + (this->Type == 1? ";#1" : ";#2");
return this->RunMT(mtOut, false);
}
int cmVSLink::RunMT(std::string const& out, bool notify)
{
std::vector<std::string> mtCommand; std::vector<std::string> mtCommand;
mtCommand.push_back(cmSystemTools::FindProgram("mt.exe")); mtCommand.push_back(cmSystemTools::FindProgram("mt.exe"));
mtCommand.push_back("/nologo"); mtCommand.push_back("/nologo");
mtCommand.push_back("/manifest"); mtCommand.push_back("/manifest");
std::string manifestFile = targetName; if (this->LinkGeneratesManifest)
manifestFile += ".manifest";
mtCommand.push_back(manifestFile);
std::string outresource = "/outputresource:";
outresource += targetName;
outresource += ";#";
if(type == 1)
{ {
outresource += "1"; mtCommand.push_back(this->LinkerManifestFile);
} }
else if(type == 2) mtCommand.push_back(out);
if (notify)
{ {
outresource += "2"; // Add an undocumented option that enables a special return
// code to notify us when the manifest is modified.
mtCommand.push_back("/notify_update");
} }
mtCommand.push_back(outresource); int mtRet = 0;
// Now use the mt tool to embed the manifest into the exe or dll if (!RunCommand("MT", mtCommand, this->Verbose, &mtRet))
if(!cmcmd::RunCommand("MT", mtCommand, verbose))
{ {
return -1; return -1;
} }
return 0; return mtRet;
} }

View File

@ -35,20 +35,6 @@ protected:
static int WindowsCEEnvironment(const char* version, static int WindowsCEEnvironment(const char* version,
const std::string& name); const std::string& name);
static int VisualStudioLink(std::vector<std::string>& args, int type); 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 hasManifest,
bool verbose);
static int ParseVisualStudioLinkCommand(std::vector<std::string>& args,
std::vector<std::string>& command,
std::string& targetName);
static bool RunCommand(const char* comment,
std::vector<std::string>& command,
bool verbose,
int* retCodeOut = 0);
}; };
#endif #endif