From afb00fef193b1000aa76e3a523899712065d53d0 Mon Sep 17 00:00:00 2001 From: Brad King Date: Mon, 5 Dec 2011 16:39:07 -0500 Subject: [PATCH] Add CMAKE_GNUtoMS option to convert GNU .dll.a to MS .lib Teach the Windows-GNU.cmake platform file to look for Visual Studio tools matching the target ABI. Add an extra step to the link command for shared libraries and executables that export symbols and on which a new GNUtoMS property is set (initialized by the CMAKE_GNUtoMS option). Tell the GNU linker to output a module definition (.def) file listing exported symbols in addition to the GNU-format import library (.dll.a). Pass the .def file to the MS "lib" tool to construct a MS-format DLL import library (.lib). Teach the install(TARGETS) command to install the MS import library next to the GNU one. Teach the install(EXPORT) and export() command to set the IMPORTED_IMPLIB property pointing at the import library to use the import library matching the tools in the importing project. --- .gitattributes | 1 + Modules/Platform/GNUtoMS_lib.bat.in | 3 ++ Modules/Platform/GNUtoMS_lib.cmake | 10 ++++ Modules/Platform/Windows-GNU-C-ABI.cmake | 1 + Modules/Platform/Windows-GNU-CXX-ABI.cmake | 1 + .../Platform/Windows-GNU-Fortran-ABI.cmake | 1 + Modules/Platform/Windows-GNU.cmake | 54 +++++++++++++++++++ Source/cmDocumentVariables.cxx | 9 ++++ Source/cmExportBuildFileGenerator.cxx | 2 + Source/cmInstallTargetGenerator.cxx | 26 ++++++++- .../cmMakefileExecutableTargetGenerator.cxx | 7 +++ Source/cmMakefileLibraryTargetGenerator.cxx | 7 +++ Source/cmMakefileTargetGenerator.cxx | 10 ++++ Source/cmTarget.cxx | 38 +++++++++++++ Source/cmTarget.h | 8 +++ 15 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 Modules/Platform/GNUtoMS_lib.bat.in create mode 100644 Modules/Platform/GNUtoMS_lib.cmake create mode 100644 Modules/Platform/Windows-GNU-C-ABI.cmake create mode 100644 Modules/Platform/Windows-GNU-CXX-ABI.cmake create mode 100644 Modules/Platform/Windows-GNU-Fortran-ABI.cmake diff --git a/.gitattributes b/.gitattributes index d9d64d639d..d21f1dda0e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ configure crlf=input *.sh.in crlf=input *.bat -crlf +*.bat.in -crlf *.dsp -crlf *.dsptemplate -crlf *.dsw -crlf diff --git a/Modules/Platform/GNUtoMS_lib.bat.in b/Modules/Platform/GNUtoMS_lib.bat.in new file mode 100644 index 0000000000..2da920ab00 --- /dev/null +++ b/Modules/Platform/GNUtoMS_lib.bat.in @@ -0,0 +1,3 @@ +@echo off +call "@CMAKE_GNUtoMS_BAT@" +lib /machine:"@CMAKE_GNUtoMS_ARCH@" %* diff --git a/Modules/Platform/GNUtoMS_lib.cmake b/Modules/Platform/GNUtoMS_lib.cmake new file mode 100644 index 0000000000..ca9b0f8da9 --- /dev/null +++ b/Modules/Platform/GNUtoMS_lib.cmake @@ -0,0 +1,10 @@ +# Usage: cmake -Dlib=lib.bat -Ddef=out.def -Ddll=out.dll -Dimp=out.dll.a -P GNUtoMS_lib.cmake +get_filename_component(name ${dll} NAME) # .dll file name +string(REGEX REPLACE "\\.dll\\.a$" ".lib" out "${imp}") # .dll.a -> .lib +execute_process( + COMMAND ${lib} /def:${def} /name:${name} /out:${out} + RESULT_VARIABLE res + ) +if(res) + message(FATAL_ERROR "lib failed: ${res}") +endif() diff --git a/Modules/Platform/Windows-GNU-C-ABI.cmake b/Modules/Platform/Windows-GNU-C-ABI.cmake new file mode 100644 index 0000000000..1189263fad --- /dev/null +++ b/Modules/Platform/Windows-GNU-C-ABI.cmake @@ -0,0 +1 @@ +__windows_compiler_gnu_abi(C) diff --git a/Modules/Platform/Windows-GNU-CXX-ABI.cmake b/Modules/Platform/Windows-GNU-CXX-ABI.cmake new file mode 100644 index 0000000000..f3c701c3c0 --- /dev/null +++ b/Modules/Platform/Windows-GNU-CXX-ABI.cmake @@ -0,0 +1 @@ +__windows_compiler_gnu_abi(CXX) diff --git a/Modules/Platform/Windows-GNU-Fortran-ABI.cmake b/Modules/Platform/Windows-GNU-Fortran-ABI.cmake new file mode 100644 index 0000000000..179280b4d8 --- /dev/null +++ b/Modules/Platform/Windows-GNU-Fortran-ABI.cmake @@ -0,0 +1 @@ +__windows_compiler_gnu_abi(Fortran) diff --git a/Modules/Platform/Windows-GNU.cmake b/Modules/Platform/Windows-GNU.cmake index 1d3e4b5a92..c255d6bafd 100644 --- a/Modules/Platform/Windows-GNU.cmake +++ b/Modules/Platform/Windows-GNU.cmake @@ -108,6 +108,8 @@ macro(__windows_compiler_gnu lang) set(CMAKE_${lang}_LINK_EXECUTABLE " -o -Wl,--out-implib, ${CMAKE_GNULD_IMAGE_VERSION} ") + list(APPEND CMAKE_${lang}_ABI_FILES "Platform/Windows-GNU-${lang}-ABI") + # Support very long lists of object files. if("${CMAKE_${lang}_RESPONSE_FILE_LINK_FLAG}" STREQUAL "@") foreach(rule CREATE_SHARED_MODULE CREATE_SHARED_LIBRARY LINK_EXECUTABLE) @@ -125,3 +127,55 @@ macro(__windows_compiler_gnu lang) endforeach() endif() endmacro() + +macro(__windows_compiler_gnu_abi lang) + if(CMAKE_NO_GNUtoMS) + set(CMAKE_GNUtoMS 0) + else() + option(CMAKE_GNUtoMS "Convert GNU import libraries to MS format (requires Visual Studio)" OFF) + endif() + + if(CMAKE_GNUtoMS AND NOT CMAKE_GNUtoMS_LIB) + # Find MS development environment setup script for this architecture. + if("${CMAKE_SIZEOF_VOID_P}" EQUAL 4) + find_program(CMAKE_GNUtoMS_VCVARS NAMES vcvars32.bat + DOC "Visual Studio vcvars32.bat" + PATHS + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\11.0\\Setup\\VC;ProductDir]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0\\Setup\\VC;ProductDir]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\9.0\\Setup\\VC;ProductDir]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\8.0\\Setup\\VC;ProductDir]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\7.1\\Setup\\VC;ProductDir]/bin" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\6.0\\Setup\\Microsoft Visual C++;ProductDir]/bin" + ) + set(CMAKE_GNUtoMS_ARCH x86) + elseif("${CMAKE_SIZEOF_VOID_P}" EQUAL 8) + find_program(CMAKE_GNUtoMS_VCVARS NAMES vcvarsamd64.bat + DOC "Visual Studio vcvarsamd64.bat" + PATHS + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\11.0\\Setup\\VC;ProductDir]/bin/amd64" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\10.0\\Setup\\VC;ProductDir]/bin/amd64" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\9.0\\Setup\\VC;ProductDir]/bin/amd64" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\8.0\\Setup\\VC;ProductDir]/bin/amd64" + ) + set(CMAKE_GNUtoMS_ARCH amd64) + endif() + set_property(CACHE CMAKE_GNUtoMS_VCVARS PROPERTY ADVANCED 1) + if(CMAKE_GNUtoMS_VCVARS) + # Create helper script to run lib.exe from MS environment. + string(REPLACE "/" "\\" CMAKE_GNUtoMS_BAT "${CMAKE_GNUtoMS_VCVARS}") + set(CMAKE_GNUtoMS_LIB ${CMAKE_BINARY_DIR}/CMakeFiles/CMakeGNUtoMS_lib.bat) + configure_file(${CMAKE_ROOT}/Modules/Platform/GNUtoMS_lib.bat.in ${CMAKE_GNUtoMS_LIB}) + else() + message(WARNING "Disabling CMAKE_GNUtoMS option because CMAKE_GNUtoMS_VCVARS is not set.") + set(CMAKE_GNUtoMS 0) + endif() + endif() + + if(CMAKE_GNUtoMS) + # Teach CMake how to create a MS import library at link time. + set(CMAKE_${lang}_GNUtoMS_RULE " -Wl,--output-def,.def" + " -Dlib=\"${CMAKE_GNUtoMS_LIB}\" -Ddef=\".def\" -Ddll=\"\" -Dimp=\"\" -P \"${CMAKE_ROOT}/Modules/Platform/GNUtoMS_lib.cmake\"" + ) + endif() +endmacro() diff --git a/Source/cmDocumentVariables.cxx b/Source/cmDocumentVariables.cxx index 7da07f8464..c8c83b9c11 100644 --- a/Source/cmDocumentVariables.cxx +++ b/Source/cmDocumentVariables.cxx @@ -1111,6 +1111,15 @@ void cmDocumentVariables::DefineVariables(cmake* cm) false, "Variables that Control the Build"); + cm->DefineProperty + ("CMAKE_GNUtoMS", cmProperty::VARIABLE, + "Convert GNU import libraries (.dll.a) to MS format (.lib).", + "This variable is used to initialize the GNUtoMS property on targets " + "when they are created. " + "See that target property for additional information.", + false, + "Variables that Control the Build"); + cm->DefineProperty ("CMAKE_DEBUG_POSTFIX", cmProperty::VARIABLE, "See variable CMAKE__POSTFIX.", diff --git a/Source/cmExportBuildFileGenerator.cxx b/Source/cmExportBuildFileGenerator.cxx index 7e73e36599..32595ee4b2 100644 --- a/Source/cmExportBuildFileGenerator.cxx +++ b/Source/cmExportBuildFileGenerator.cxx @@ -125,6 +125,8 @@ cmExportBuildFileGenerator std::string prop = "IMPORTED_IMPLIB"; prop += suffix; std::string value = target->GetFullPath(config, true); + target->GetImplibGNUtoMS(value, value, + "${CMAKE_IMPORT_LIBRARY_SUFFIX}"); properties[prop] = value; } } diff --git a/Source/cmInstallTargetGenerator.cxx b/Source/cmInstallTargetGenerator.cxx index 33ffbfb3ce..ac1c949443 100644 --- a/Source/cmInstallTargetGenerator.cxx +++ b/Source/cmInstallTargetGenerator.cxx @@ -101,6 +101,13 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, std::string to1 = toDir + targetNameImport; filesFrom.push_back(from1); filesTo.push_back(to1); + std::string targetNameImportLib; + if(this->Target->GetImplibGNUtoMS(targetNameImport, + targetNameImportLib)) + { + filesFrom.push_back(fromDirConfig + targetNameImportLib); + filesTo.push_back(toDir + targetNameImportLib); + } // An import library looks like a static library. type = cmTarget::STATIC_LIBRARY; @@ -157,6 +164,13 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os, std::string to1 = toDir + targetNameImport; filesFrom.push_back(from1); filesTo.push_back(to1); + std::string targetNameImportLib; + if(this->Target->GetImplibGNUtoMS(targetNameImport, + targetNameImportLib)) + { + filesFrom.push_back(fromDirConfig + targetNameImportLib); + filesTo.push_back(toDir + targetNameImportLib); + } // An import library looks like a static library. type = cmTarget::STATIC_LIBRARY; @@ -314,7 +328,11 @@ std::string cmInstallTargetGenerator::GetInstallFilename(cmTarget* target, if(nameType == NameImplib) { // Use the import library name. - fname = targetNameImport; + if(!target->GetImplibGNUtoMS(targetNameImport, fname, + "${CMAKE_IMPORT_LIBRARY_SUFFIX}")) + { + fname = targetNameImport; + } } else if(nameType == NameReal) { @@ -339,7 +357,11 @@ std::string cmInstallTargetGenerator::GetInstallFilename(cmTarget* target, if(nameType == NameImplib) { // Use the import library name. - fname = targetNameImport; + if(!target->GetImplibGNUtoMS(targetNameImport, fname, + "${CMAKE_IMPORT_LIBRARY_SUFFIX}")) + { + fname = targetNameImport; + } } else if(nameType == NameSO) { diff --git a/Source/cmMakefileExecutableTargetGenerator.cxx b/Source/cmMakefileExecutableTargetGenerator.cxx index 0fcd269074..78278cbbfa 100644 --- a/Source/cmMakefileExecutableTargetGenerator.cxx +++ b/Source/cmMakefileExecutableTargetGenerator.cxx @@ -241,6 +241,13 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink) exeCleanFiles.push_back(this->Convert(targetFullPathImport.c_str(), cmLocalGenerator::START_OUTPUT, cmLocalGenerator::UNCHANGED)); + std::string implib; + if(this->Target->GetImplibGNUtoMS(targetFullPathImport, implib)) + { + exeCleanFiles.push_back(this->Convert(implib.c_str(), + cmLocalGenerator::START_OUTPUT, + cmLocalGenerator::UNCHANGED)); + } } // List the PDB for cleaning only when the whole target is diff --git a/Source/cmMakefileLibraryTargetGenerator.cxx b/Source/cmMakefileLibraryTargetGenerator.cxx index 337b28611c..b4174cc36d 100644 --- a/Source/cmMakefileLibraryTargetGenerator.cxx +++ b/Source/cmMakefileLibraryTargetGenerator.cxx @@ -512,6 +512,13 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules libCleanFiles.push_back(this->Convert(targetFullPathImport.c_str(), cmLocalGenerator::START_OUTPUT, cmLocalGenerator::UNCHANGED)); + std::string implib; + if(this->Target->GetImplibGNUtoMS(targetFullPathImport, implib)) + { + libCleanFiles.push_back(this->Convert(implib.c_str(), + cmLocalGenerator::START_OUTPUT, + cmLocalGenerator::UNCHANGED)); + } } // List the PDB for cleaning only when the whole target is diff --git a/Source/cmMakefileTargetGenerator.cxx b/Source/cmMakefileTargetGenerator.cxx index 004e780cdc..a3a832bc88 100644 --- a/Source/cmMakefileTargetGenerator.cxx +++ b/Source/cmMakefileTargetGenerator.cxx @@ -1637,6 +1637,16 @@ void cmMakefileTargetGenerator std::string cmMakefileTargetGenerator::GetLinkRule(const char* linkRuleVar) { std::string linkRule = this->Makefile->GetRequiredDefinition(linkRuleVar); + if(this->Target->HasImplibGNUtoMS()) + { + std::string ruleVar = "CMAKE_"; + ruleVar += this->Target->GetLinkerLanguage(this->ConfigName); + ruleVar += "_GNUtoMS_RULE"; + if(const char* rule = this->Makefile->GetDefinition(ruleVar.c_str())) + { + linkRule += rule; + } + } return linkRule; } diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index dad0353ecd..48368decb2 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -979,6 +979,23 @@ void cmTarget::DefineProperties(cmake *cm) "If the variable CMAKE_Fortran_MODULE_DIRECTORY is set when a target " "is created its value is used to initialize this property."); + cm->DefineProperty + ("GNUtoMS", cmProperty::TARGET, + "Convert GNU import library (.dll.a) to MS format (.lib).", + "When linking a shared library or executable that exports symbols " + "using GNU tools on Windows (MinGW/MSYS) with Visual Studio installed " + "convert the import library (.dll.a) from GNU to MS format (.lib). " + "Both import libraries will be installed by install(TARGETS) and " + "exported by install(EXPORT) and export() to be linked by applications " + "with either GNU- or MS-compatible tools." + "\n" + "If the variable CMAKE_GNUtoMS is set when a target " + "is created its value is used to initialize this property. " + "The variable must be set prior to the first command that enables " + "a language such as project() or enable_language(). " + "CMake provides the variable as an option to the user automatically " + "when configuring on Windows with GNU tools."); + cm->DefineProperty ("XCODE_ATTRIBUTE_", cmProperty::TARGET, "Set Xcode target attributes directly.", @@ -1195,6 +1212,7 @@ void cmTarget::SetMakefile(cmMakefile* mf) this->SetPropertyDefault("RUNTIME_OUTPUT_DIRECTORY", 0); this->SetPropertyDefault("Fortran_FORMAT", 0); this->SetPropertyDefault("Fortran_MODULE_DIRECTORY", 0); + this->SetPropertyDefault("GNUtoMS", 0); this->SetPropertyDefault("OSX_ARCHITECTURES", 0); this->SetPropertyDefault("AUTOMOC", 0); this->SetPropertyDefault("AUTOMOC_MOC_OPTIONS", 0); @@ -3456,6 +3474,26 @@ void cmTarget::GetExecutableNames(std::string& name, pdbName = prefix+base+".pdb"; } +//---------------------------------------------------------------------------- +bool cmTarget::HasImplibGNUtoMS() +{ + return this->HasImportLibrary() && this->GetPropertyAsBool("GNUtoMS"); +} + +//---------------------------------------------------------------------------- +bool cmTarget::GetImplibGNUtoMS(std::string const& gnuName, + std::string& out, const char* newExt) +{ + if(this->HasImplibGNUtoMS() && + gnuName.size() > 6 && gnuName.substr(gnuName.size()-6) == ".dll.a") + { + out = gnuName.substr(0, gnuName.size()-6); + out += newExt? newExt : ".lib"; + return true; + } + return false; +} + //---------------------------------------------------------------------------- void cmTarget::GenerateTargetManifest(const char* config) { diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 0abdddb0a8..09fee6c9b2 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -369,6 +369,14 @@ public: std::string& impName, std::string& pdbName, const char* config); + /** Does this target have a GNU implib to convert to MS format? */ + bool HasImplibGNUtoMS(); + + /** Convert the given GNU import library name (.dll.a) to a name with a new + extension (.lib or ${CMAKE_IMPORT_LIBRARY_SUFFIX}). */ + bool GetImplibGNUtoMS(std::string const& gnuName, std::string& out, + const char* newExt = 0); + /** Add the target output files to the global generator manifest. */ void GenerateTargetManifest(const char* config);