Source code for gdeps.cmake

#!python3

# Copyright 2007-2017 Gemr. All Rights Reserved.
# Licensed to MIT see LICENSE.txt

import os
import re
import warnings

import gdeps as GDeps

__author__ = 'Suryavarman (http://sourceforge.net/u/suryavarman/profile/)'


[docs]class CMakerapReport(GDeps.Reporting): def __init__(self): GDeps.Reporting.__init__(self) self.m_ErrorRegexp = r'' self.m_ErrorRegexpMultiline = r'(CMake error :.*\n.*\n.*error.*)|(.*CMake Error:.*\n.*\n.*error.*)|(.*CMake Error at.*\n.*\n.*)|(.*send_error.*)' self.m_WarningRegexp = r'(.*Could NOT find.*|.*Could not locate.*)'
[docs]class CMakeListener: """ Override this class to define some actions inside CMake process"""
[docs] def afterMake(self, inCmake, inIde, inCompiler, inTargetArgs): """ Or before ide build """ pass
[docs]class CMake(GDeps.Make): """ CMake command line implementation **See:** https://cmake.org/cmake/help/v3.4/manual/cmake.1.html """ def __init__(self, inConfigFile, inSectionName, inFolderDir, inSolutionName, inArgs, inTargetsArgs, inCompilers, inIdes, inExePath="", inListeners=[]): """ :param inExePath: by default cmake exetubale path is define in the config file """ GDeps.Make.__init__(self, inConfigFile, inSectionName, inFolderDir, inSolutionName, inArgs, inTargetsArgs, inCompilers, inIdes, inExePath, inTypeName=CMake.getTypeName()) self.m_CurrentPoints = "" self.m_Listeners = inListeners self.Load() @staticmethod
[docs] def getTypeName(): return "CMake"
[docs] def downFolder(self, inLocalName): if not os.path.isdir(inLocalName): os.makedirs(inLocalName) self.m_CurrentPoints += "." print(self.m_CurrentPoints + "BEGIN : " + inLocalName) os.chdir(inLocalName)
[docs] def upFolder(self): print(self.m_CurrentPoints + "END. ") if len(self.m_CurrentPoints): self.m_CurrentPoints = self.m_CurrentPoints[0: len(self.m_CurrentPoints) - 1] os.chdir(os.getcwd() + "/../")
[docs] def setupCompilerPATHS(self, inCompiler): """ **See:** https://cmake.org/Wiki/CMake_FAQ#Method_2:_use_cmake_-D """ self.m_CompilerPATHS = "" if inCompiler.m_TypeName == GDeps.Mingw_32.getTypeName() or inCompiler.m_TypeName == GDeps.Mingw_64.getTypeName(): self.m_CompilerPATHS = ('-DCMAKE_CXX_COMPILER:FILEPATH="' + inCompiler.getCxxDir() + '"') self.m_CompilerPATHS += (' -DCMAKE_C_COMPILER:FILEPATH="' + inCompiler.getCDir() + '"') self.m_CompilerPATHS += (' -DCMAKE_AR:FILEPATH="' + inCompiler.getArDir() + '"') self.m_CompilerPATHS += (' -DCMAKE_RC_COMPILER:FILEPATH="' + inCompiler.getRcDir() + '"') self.m_CompilerPATHS += (' -DCMAKE_MAKE_PROGRAM:FILEPATH="' + inCompiler.getMakeDir() + '"') self.m_CompilerPATHS += (' -DCMAKE_LINKER:FILEPATH="' + inCompiler.getLinkDir() + '"') self.m_CompilerPATHS += (' -DCMAKE_NM:FILEPATH="' + inCompiler.getNmDir() + '"') self.m_CompilerPATHS += (' -DCMAKE_OBJCOPY:FILEPATH="' + inCompiler.getObjDIr() + '"') self.m_CompilerPATHS += (' -DCMAKE_OBJDUMP:FILEPATH="' + inCompiler.getObjDumb() + '"') self.m_CompilerPATHS += (' -DCMAKE_RANLIB:FILEPATH="' + inCompiler.getRanLib() + '"') self.m_CompilerPATHS += (' -DCMAKE_STRIP:FILEPATH="' + inCompiler.getStrip() + '"')
[docs] def make(self): aMacroReporting = GDeps.Make.make(self) if not self.m_Ides: warnings.warn(self.m_TypeName + " There are no ide") for aIde in self.m_Ides: if os.path.isdir(self.m_FolderDir): aCurrentDir = os.getcwd() os.chdir(self.m_FolderDir) self.downFolder(aIde.m_TypeName) if not aIde.m_Compilers: warnings.warn(self.m_TypeName + " There are no compiler") for aCompiler in aIde.m_Compilers: self.downFolder(aCompiler.m_TypeName) # todo use m_SectionName and not m_TypeName, we can hade different configuration for the same type name aCompiler.setvars() self.setupCompilerPATHS(aCompiler) if not self.m_TargetsArgs: warnings.warn(self.m_TypeName + " There are no targets") for aTargetArgs in self.m_TargetsArgs: # Delete the old content because of Cmake have some problems to regenerate the config. if os.path.isdir(aTargetArgs.m_TargetType.name): log_rmdir_path = os.path.normpath(os.getcwd() + r'\\remove_dir_' + aTargetArgs.m_TargetType.name + '.log') with open(log_rmdir_path, 'w'): pass GDeps.rmdir(aTargetArgs.m_TargetType.name, inLogFilePath=log_rmdir_path, inWaitUnlock=False, inWaitTime=10) aMacroReporting.append(GDeps.rmdir_Report(), aTargetArgs.m_TargetType.name + '_remove_target_folder', log_rmdir_path, False) self.downFolder(aTargetArgs.m_TargetType.name) self.makeTarget(aIde, aCompiler, aTargetArgs, aMacroReporting) aLogFilePath = self.getLogFilePath(aTargetArgs.m_TargetType, aCompiler, aIde) for listener in self.m_Listeners: listener.afterMake(self, aIde, aCompiler, aTargetArgs) self.build(aIde, aCompiler, aTargetArgs, aLogFilePath) # Report aReportId = aIde.getBuildName(aCompiler, aTargetArgs.m_TargetType) + " " + self.m_SolutionName aMacroReporting.append(aCompiler, "Compiler_Report_" + aReportId, aLogFilePath, False) print(aLogFilePath) aMacroReporting.append(CMakerapReport(), "CMake_Compiler_Report_" + aReportId, aLogFilePath, False) aMacroReporting.append(aIde, "Ide_Report_" + aReportId, aLogFilePath, False) self.upFolder() self.upFolder() self.upFolder() os.chdir(aCurrentDir) else: warnings.warn(self.m_FolderDir + " not found or is not a directory.") return aMacroReporting
[docs] def makeTarget(self, inIde, inCompiler, inTargetArgs, inMacroReporting): aMakeId = self.getMakeID(inIde, inCompiler) aG = '-G' + '"' + aMakeId + '"' aBuildType = self.getBuildType(inTargetArgs.m_TargetType) aBuildTypeArgs = '-DCMAKE_BUILD_TYPE:STRING=' + aBuildType aBuildTypeArgs += ' -DBUILD_SHARED_LIBS:BOOL=' + ('OFF' if inTargetArgs.m_TargetType == GDeps.Target.release_static or inTargetArgs.m_TargetType == GDeps.Target.debug_static else 'ON') aCMakeArgs = GDeps.getAllParamsString(self.m_Args, inTargetArgs, inCompiler, ' ', '=', True) if aCMakeArgs and aCMakeArgs[0] == ' ': aCMakeArgs = aCMakeArgs[1:-1] if self.m_CompilerPATHS and aCMakeArgs and aCMakeArgs[-1] != ' ': aCMakeArgs += ' ' if self.m_CompilerPATHS and self.m_CompilerPATHS[ -1] == ' ': # todo why GDeps.getAllParamsString don't remove the last separator aCMakeArgs += self.m_CompilerPATHS[1:-1] else: aCMakeArgs += self.m_CompilerPATHS if aCMakeArgs and aCMakeArgs[-1] == ' ': aCMakeArgs = aCMakeArgs[0:-1] aCMakeArgs += ' -DCMAKE_SUPPRESS_DEVELOPER_WARNINGS:INTERNAL=FALSE' aCMakeArgs += ' -DCMAKE_BACKWARDS_COMPATIBILITY:STRING=2.4' # + ' -DSHELL= if aCMakeArgs and aCMakeArgs[0] != ' ': aCMakeArgs = ' ' + aCMakeArgs # aCmd = '"' + self.m_ExePath + '\\cmake.exe"' + ' ../../../ ' + aG + ' ' + aBuildTypeArgs + ' -Wno-dev ' + aCMakeArgs aCmd = '"' + os.path.normpath(self.m_ExePath + r'\\cmake.exe') + '"' + ' "' + os.path.normpath(self.m_FolderDir) + '" ' + aG + ' ' + aBuildTypeArgs + ' -Wno-dev' + aCMakeArgs aReportId = 'CMake_' + aMakeId + '_' + aBuildType aCMakeFilesPath = os.path.normpath(os.getcwd() + r"\\CMakeFiles") aLogFilePath = os.path.normpath(aCMakeFilesPath + r"\\CMakeOutput.log") aLogFilePath2 = os.path.normpath(aCMakeFilesPath + r"\\CMakeOutput2.log") # GDeps.call(aCmd) # GDeps.callWithLogFile(inCommand=aCmd, inLogFilePath=aLogFilePath2, inOverwriteLog=True, inEnv=os.environ.copy()) # for some reason we have to create batch file to unstuck CMake aBatchFile = os.path.normpath(os.getcwd() + r"\\CMake_CMD_LINE.bat") with open(aBatchFile, 'w') as aBat: aBat.write(aCmd) print(aBatchFile + ' has been created.') # to test if the batch has been call if os.path.exists(aLogFilePath2): os.remove(aLogFilePath2) # the CMakeFiles directory has to be create before to call CMake with the log file. if not os.path.exists(aCMakeFilesPath): os.makedirs(aCMakeFilesPath) GDeps.callWithLogFile(inCommand='CALL "' + aBatchFile + '"', inLogFilePath=aLogFilePath2, inOverwriteLog=True, inEnv=os.environ.copy()) if os.path.exists(aLogFilePath2): inMacroReporting.append(CMakerapReport(), aReportId, aLogFilePath2, False) else: warnings.warn(aLogFilePath2 + " doesn't exist.") if os.path.exists(aLogFilePath): inMacroReporting.append(CMakerapReport(), aReportId, aLogFilePath, False) else: warnings.warn(aLogFilePath + " doesn't exist.")
@staticmethod
[docs] def getMakeID(inIde, inCompiler): """ Return the CMake Generator ID **See:** https://cmake.org/cmake/help/v3.4/manual/cmake-generators.7.html#manual:cmake-generators%287%29 .. todo:: check the CMake version and add or not the suffix for the Visual Studio For compatibility with CMake versions prior to 3.0, one may specify this generator using the name “Visual Studio 12” without the year component. **Clang:** https://stackoverflow.com/questions/7031126/switching-between-gcc-and-clang-llvm-using-cmake **Architecture:** https://stackoverflow.com/questions/5334095/cmake-multiarchitecture-compilation """ if inIde.m_TypeName == GDeps.CodeBlocks.getTypeName(): return "CodeBlocks - MinGW Makefiles" if inIde.m_TypeName == GDeps.Visual2008.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x86: return "Visual Studio 9 2008" if inIde.m_TypeName == GDeps.Visual2010.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x86: return "Visual Studio 10" if inIde.m_TypeName == GDeps.Visual2010.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x64: return "Visual Studio 10 Win64" if inIde.m_TypeName == GDeps.Visual2012.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x86: return "Visual Studio 11" if inIde.m_TypeName == GDeps.Visual2012.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x64: return "Visual Studio 11 Win64" if inIde.m_TypeName == GDeps.Visual2013.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x86: return "Visual Studio 12" if inIde.m_TypeName == GDeps.Visual2013.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x64: return "Visual Studio 12 Win64" if inIde.m_TypeName == GDeps.Visual2015.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x86: return "Visual Studio 14 2015" if inIde.m_TypeName == GDeps.Visual2015.getTypeName() and inCompiler.getAdressModel() == GDeps.AdressModel.x64: return "Visual Studio 14 2015 Win64" warnings.warn("CMake : MakeID : NOT FOUND!") return ""
@staticmethod
[docs] def getBuildType(inTargetType): """ :return: the CMake associate target ID. Is not necessary the same IDE buildType. **See:** https://cmake.org/cmake/help/v3.4/variable/CMAKE_BUILD_TYPE.html """ aResult = "" if inTargetType == GDeps.Target.release_static: aResult = "Release" if inTargetType == GDeps.Target.release_dynamic: aResult = "Release" if inTargetType == GDeps.Target.debug_static: aResult = "Debug" if inTargetType == GDeps.Target.debug_dynamic: aResult = "Debug" assert aResult, "CMake : BuildType " + repr(inTargetType) + " : NOT FOUND" return aResult
@staticmethod
[docs] def getWildCard(inIde): """ :return: the ide project wildcard ex : worspace, sln… """ aResult = "" if inIde.m_TypeName == GDeps.CodeBlocks.getTypeName(): aResult = "cbp" if inIde.m_TypeName == GDeps.Visual2008.getTypeName() or \ inIde.m_TypeName == GDeps.Visual2010.getTypeName() or \ inIde.m_TypeName == GDeps.Visual2012.getTypeName() or \ inIde.m_TypeName == GDeps.Visual2013.getTypeName() or \ inIde.m_TypeName == GDeps.Visual2015.getTypeName(): return "sln" assert aResult, "CMake : Solution Wildcard : NOT FOUND" return aResult
[docs] def getLogFilePath(self, inTargetType, inCompiler, inIde): """ :return: the log file path function of the build type and the model address """ aBuildType = inIde.getBuildType(inTargetType) aCompilerType = inIde.getBuildName(inCompiler, inTargetType) aLogFilePath = os.path.normpath(self.m_FolderDir) + '/build_' + aBuildType + '_' + aCompilerType + '.log' aLogFilePath = os.path.normpath(aLogFilePath) return aLogFilePath
[docs] def build(self, inIde, inCompiler, inTargetArgs, inLogFilePath): aWildCard = self.getWildCard(inIde) # # try: aTargetType = inIde.getBuildType(inTargetArgs.m_TargetType) if inIde.m_TypeName == GDeps.CodeBlocks.getTypeName(): """ CMake generate an "all" target, doesn't matter the target type """ aTargetType = "all" inIde.build(os.getcwd(), self.m_SolutionName, inCompiler, aTargetType, inLogFilePath, inWildCard=aWildCard, inRebuildAll=True)
# except : # aError = sys.exc_info()[0] # print( "<p>GDeps.Ide.build Error: %s</p>" % aError ) # print( "CMake build failed : SolutionName = " + self.m_SolutionName + "." + aWildCard + " Build description : " + inIde.getBuildName( inCompiler, inTargetType ) + " CurrentDir " + os.getcwd() ) @staticmethod
[docs] def get_build_path(inFolderDir, inIde, inCompiler, inTarget): """ :return: The path generate by gdeps.cmake for this configuration. Example: 'C:\gdeps\Projects\ogredeps\ogredeps\CodeBlocks\Mingw_32\release_dynamic' :rtype: str """ return os.path.normpath( inFolderDir + r'\\' + inIde.getTypeName() + r'\\' + inCompiler.getTypeName() + r'\\' + inTarget.name)
[docs] class generate_cmake_param:
[docs] def write(self, inProjectName): return ''
[docs] class generate_cmake_dep(generate_cmake_param): def __init__(self, inName, inNeedPath=True): self.m_Name = inName self.m_NeedPath = inNeedPath
[docs] def write(self, inProjectName): text = '' if self.m_NeedPath: text = 'set(' + self.m_Name + ' CACHE STRING "")' + os.linesep text += 'target_link_libraries(' + inProjectName + ' ${' + self.m_Name + '})' + os.linesep else: text += 'target_link_libraries(' + inProjectName + ' ' + self.m_Name + ')' + os.linesep return text
[docs] class generate_cmake_include(generate_cmake_param): def __init__(self, inName): self.m_Name = inName
[docs] def write(self, inProjectName): text = 'set(' + self.m_Name + ' CACHE PATH "")' + os.linesep text += 'include_directories(${' + self.m_Name + '})' + os.linesep return text
@staticmethod
[docs] def generate_cmake_list(inFolderDir, inProjectName, inFilesList=[], inExcludeFiles=[], inFindFiles=True, inRecursive=True, inExcludeDirectories=[], inIncludeDirectoriesList=[], inExcludeIncludeDirectories=[], inAdditionalDirectories=[], inIsLib=True, inWildcards=['cpp', 'h', 'cxx', 'tpp', 'c', 'hxx', 'hpp'], inDefinitions=[], inParams=[]): """ Helper to generate a very simple CMakelist.txt. :param inFolderDir: The CmakeList will be create here and the sources and includes files will be search inside this folder. :type inFolderDir: str :param inProjectName: The name of the project without space separators :type inProjectName: str :param inFilesList: You can specify a list of files. (The path is relative to the folder dir, The file name with the extension) :type inFilesList: array of (str,str) :param inExcludeFiles: You can specify a list of ignored files. (The path is relative to the folder dir, The file name with the extension) :type inExcludeFiles: array of (str,str) :param inFindFiles: If true gdeps will search every files matching to the extension. (inWildcards) :type inFindFiles: bool :param inRecursive: If True gdeps will search inside to the sub folders :type inRecursive: bool :param inExcludeDirectories: List of directories will be exclude from the files searching and from the include directories description. :type inExcludeDirectories: array of str :param inIncludeDirectoriesList: If it's not empty the include directories description will be define by this array of directories. :type inIncludeDirectoriesList: array of str :param inExcludeIncludeDirectories: inExcludeIncludeDirectories will be use to exclude some directories from include directories description. :type inExcludeIncludeDirectories: array of str :param inAdditionalDirectories: Specify the additional directories here. >>> ['./../'] :type inAdditionalDirectories: array of str :param inIsLib: If True cmake will create a project to generate a lib if not an executable. :type inIsLib: bool :param inWildcards: Specify the extension files like cxx, hpp, htt etc. :type inWildcards: array of str :param inDefinitions: Define the compilation definitions. Example: >>> ['DOUBLE_PRECISION', 'BUILD_DLL', 'USE_OPTION1'] :type inDefinitions: array of str :param inParams: Additional dependencies to build the project :type inParams: array of generate_cmake_param .. todo:: target_link_libraries .. note:: to use the CMakelists.txt you have GDeps_DEF_32 and GDeps_DEF_64 to define your definitions. >>> release = gdeps.TargetArgs(gdeps.Target.release_static, inArgs={}, inx86Args={'-DGDeps_DEF_32': '-D_M_IX86'}, inx64Args={'-DGDeps_DEF_64': '-D_M_X64'}, inCompilersArgs={}, inIdeArgs={}) By default the type is STATIC. The type can be STATIC or SHARED based on whether the current value of the variable BUILD_SHARED_LIBS is true. See https://cmake.org/cmake/help/cmake2.6docs.html#command:add_library """ text = 'cmake_minimum_required (VERSION 2.6)' + os.linesep text += 'project(' + inProjectName + ')' + os.linesep cmake_var_sources = inProjectName + '_SOURCES' text += 'set(' + cmake_var_sources + os.linesep file_dirs = [] """ Tuple (path, file name)""" regex = r'(' wildcards_size = len(inWildcards) for i in range(wildcards_size): if i == wildcards_size - 1: regex += r'\.' + inWildcards[i] else: regex += r'\.' + inWildcards[i] + '|' regex += r')+$' #Exclude cmake generated folders exclude_dirs = ['Visual2008', 'Visual2010', 'Visual2012', 'Visual2013', 'Visual2015', 'CodeBlocks'] exclude_dirs += inExcludeDirectories exclude_dirs = [os.path.normpath(directory) for directory in exclude_dirs] exclude_files_dir = [] exclude_files_names = [] for exclude_file_def in inExcludeFiles: exclude_files_dir.append(os.path.normpath(exclude_file_def[0]).replace('\\', '/')) exclude_files_names.append(exclude_file_def[1]) def find_files(inDir): if os.path.normpath(inDir) not in exclude_dirs: paths = os.listdir(inDir) for path in paths: full_path = os.path.normpath(inDir + r'/' + path) if os.path.exists(full_path): if os.path.isdir(full_path) and inRecursive: find_files(full_path) elif os.path.isfile(full_path) and re.search(regex, path) is not None and inDir not in exclude_files_dir and path not in exclude_files_names: file_dirs.append((inDir, path)) if inFindFiles: current_dir = os.getcwd() os.chdir(inFolderDir) find_files('.') os.chdir(current_dir) else: file_dirs = inFilesList for file_dir in file_dirs: text += ' ' + os.path.normpath(file_dir[0] + r'/' + file_dir[1]).replace('\\', '/') + os.linesep text += ')' + os.linesep text += os.linesep text += '# need include directories for ' + inProjectName + ' and all the sub libs' + os.linesep exclude_dirs = [] for directory in inExcludeIncludeDirectories: exclude_dirs.append(os.path.normpath(directory)) include_directories = [] if inIncludeDirectoriesList: excludeIncludeDirectories = [] for directory in inExcludeIncludeDirectories: excludeIncludeDirectories.append(os.path.normpath(directory)) for include_directory in excludeIncludeDirectories: if include_directory not in exclude_dirs: include_directories.append(include_directory) else: for file_dir in file_dirs: include_directory = os.path.normpath(file_dir[0]) if include_directory not in exclude_dirs: include_directories.append(include_directory) include_directories_cleanup = include_directories.copy() include_directories.clear() for file_dir in include_directories_cleanup: include_directories.append(file_dir.replace('\\', '/')) deph_files_max = 0 for include_dir in include_directories: depth = include_dir.count('/') if depth > deph_files_max: deph_files_max = depth # include_directories.add('.') # relative_directory = './../' # include_directories_copy = include_directories.copy() # for i in range(deph_files_max): # include_directories.add(relative_directory) # # for include_diectory in include_directories_copy: # directory = '' # for folder_name in reversed(include_diectory.split('/')): # include_directories.add(relative_directory + folder_name) # directory = folder_name + '/' + directory # include_directories.add(relative_directory + directory) # relative_directory += '../' for file_dir in inAdditionalDirectories: include_directories.append(file_dir.replace('\\', '/')) for file_dir in include_directories: text += 'include_directories(' + file_dir + ')' + os.linesep if inDefinitions: text += 'add_definitions(' for definition in inDefinitions: text += ' -D' + definition text += ')' + os.linesep text += 'set(GDeps_DEF_32 "" CACHE STRING "Definitions for 32 bits version ex: -DX86")' + os.linesep text += 'set(GDeps_DEF_64 "" CACHE STRING "Definitions for 64 bits version ex: -DX64")' + os.linesep text += 'add_definitions(${GDeps_DEF_32})' + os.linesep # if it's x64 has to be empty text += 'add_definitions(${GDeps_DEF_64})' + os.linesep # if it's x86 has to be empty if inIsLib: text += 'if(BUILD_SHARED_LIBS)' + os.linesep text += ' add_library(' + inProjectName + ' SHARED ${' + cmake_var_sources + '})' + os.linesep text += 'else()' + os.linesep text += ' add_library(' + inProjectName + ' STATIC ${' + cmake_var_sources + '})' + os.linesep text += 'endif()' + os.linesep else: text += 'add_executable(' + inProjectName + ' ${' + cmake_var_sources + '})' + os.linesep for inParam in inParams: text += inParam.write(inProjectName=inProjectName) with open(os.path.normpath(inFolderDir + r'/' + 'CMakeLists.txt'), 'w') as file: file.write(text)