summaryrefslogtreecommitdiff
path: root/sw/ideScripts/updateMakefile.py
diff options
context:
space:
mode:
Diffstat (limited to 'sw/ideScripts/updateMakefile.py')
-rwxr-xr-xsw/ideScripts/updateMakefile.py522
1 files changed, 522 insertions, 0 deletions
diff --git a/sw/ideScripts/updateMakefile.py b/sw/ideScripts/updateMakefile.py
new file mode 100755
index 0000000..86fbed1
--- /dev/null
+++ b/sw/ideScripts/updateMakefile.py
@@ -0,0 +1,522 @@
+'''
+Generate (replace existing) Makefile file in workspace folder wtih data from
+original Makefile and 'c_cpp_properties.json'.
+'''
+
+import os
+import datetime
+from subprocess import Popen, PIPE
+
+import utilities as utils
+import templateStrings as tmpStr
+
+import updatePaths as pth
+import updateWorkspaceSources as wks
+import updateBuildData as build
+
+__version__ = utils.__version__
+
+
+class MakefileStrings():
+ projectName = 'TARGET'
+ buildDir = 'BUILD_DIR'
+
+ cSources = 'C_SOURCES'
+ asmSources = 'ASM_SOURCES'
+ ldSources = 'LIBS'
+ cDefines = 'C_DEFS'
+ asmDefines = 'AS_DEFS'
+ cIncludes = 'C_INCLUDES'
+ asmIncludes = 'AS_INCLUDES'
+ ldIncludes = 'LIBDIR'
+ cFlags = 'CFLAGS'
+ asmFlags = 'ASFLAGS'
+ ldFlags = 'LDFLAGS'
+
+
+class Makefile():
+ def __init__(self):
+ self.mkfStr = MakefileStrings()
+ self.cPStr = wks.CPropertiesStrings()
+
+ def checkMakefileFile(self):
+ '''
+ Check if 'Makefile' file exists. If it doesn't, report as error.
+ '''
+ if not utils.pathExists(utils.makefilePath):
+ errorMsg = "Makefile does not exist! Did CubeMX generated Makefile?\n"
+ errorMsg += "File name must be 'Makefile'."
+ utils.printAndQuit(errorMsg)
+
+ def restoreOriginalMakefile(self):
+ '''
+ Check wether current 'Makefile' has print capabilities. If it has, this means it was already altered by this script.
+ If it was, replace it with backup copy: 'Makefile.backup'.
+ If it does not have print capabilities, it is assumed 'Makefile' was regenerated with CubeMX
+ tool - print function is added and backup file is overwritten with this new 'Makefile'.
+
+ At the end, fresh 'Makefile' with print function should be available.
+ '''
+ if utils.pathExists(utils.makefilePath):
+ # Makefile exists, check if it is original (no print capabilities)
+ if self.hasPrintCapabilities(utils.makefilePath):
+ # Makefile exists, already modified
+ if utils.pathExists(utils.makefileBackupPath):
+ # can original file be restored from backup file?
+ if self.hasPrintCapabilities(utils.makefileBackupPath):
+ errorMsg = "Both, 'Makefile' and 'Makefile.backup' exists, but they are both modified!\n"
+ errorMsg += "Did you manually delete, replace or modify any of Makefiles?\n"
+ errorMsg += "-> Delete all Makefiles and regenerate with CubeMX."
+ utils.printAndQuit(errorMsg)
+ else:
+ # original will be restored from backup file
+ print("Existing 'Makefile' file will be restored from 'Makefile.backup'.")
+ utils.copyAndRename(utils.makefileBackupPath, utils.makefilePath)
+ else:
+ errorMsg = "'Makefile.backup' does not exist, while 'Makefile' was already modified!\n"
+ errorMsg += "Did you manually delete, replace or modify any of Makefiles?\n"
+ errorMsg += "-> Delete all Makefiles and regenerate with CubeMX."
+ utils.printAndQuit(errorMsg)
+ else:
+ print("Existing 'Makefile' file found (original).")
+ utils.copyAndRename(utils.makefilePath, utils.makefileBackupPath)
+ elif utils.pathExists(utils.makefileBackupPath):
+ # Makefile does not exist, but Makefile.backup does
+ if self.hasPrintCapabilities(utils.makefileBackupPath):
+ errorMsg = "'Makefile.backup' exists, but is already modified!\n"
+ errorMsg += "Did you manually delete, replace or modify any of Makefiles?\n"
+ errorMsg += "-> Delete all Makefiles and regenerate with CubeMX."
+ utils.printAndQuit(errorMsg)
+ else:
+ # original will be restored from backup file
+ print("'Makefile' file will be restored from 'Makefile.backup'.")
+ utils.copyAndRename(utils.makefileBackupPath, utils.makefilePath)
+ else:
+ errorMsg = "No Makefiles available, unable to proceed!\n"
+ errorMsg += "-> Regenerate with CubeMX."
+ utils.printAndQuit(errorMsg)
+
+ self.addMakefileCustomFunctions(pathToMakefile=utils.makefilePath)
+
+ def getMakefileData(self, makeExePath, gccExePath):
+ '''
+ Get Makefile data.
+ Returns data in dictionary.
+ '''
+ dataDictionaryList = {}
+
+ # project name
+ projectName = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.projectName)[0]
+ dataDictionaryList[self.mkfStr.projectName] = projectName
+
+ # dir name
+ buildDirName = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.buildDir)[0]
+ dataDictionaryList[self.mkfStr.buildDir] = buildDirName
+
+ # source files
+ cSourcesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.cSources)
+ dataDictionaryList[self.mkfStr.cSources] = cSourcesList
+
+ asmSourcesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.asmSources)
+ dataDictionaryList[self.mkfStr.asmSources] = asmSourcesList
+
+ ldSourcesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.ldSources)
+ # ldSourcesList = utils.stripStartOfString(ldSourcesList, '-l') # more readable without stripping
+ dataDictionaryList[self.mkfStr.ldSources] = ldSourcesList
+
+ # defines
+ asmDefinesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.asmDefines)
+ asmDefinesList = utils.stripStartOfString(asmDefinesList, '-D')
+ dataDictionaryList[self.mkfStr.asmDefines] = asmDefinesList
+
+ cDefinesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.cDefines)
+ cDefinesList = utils.stripStartOfString(cDefinesList, '-D')
+ dataDictionaryList[self.mkfStr.cDefines] = cDefinesList
+
+ # source & include directories
+ asmIncludesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.asmIncludes)
+ asmIncludesList = utils.stripStartOfString(asmIncludesList, '-I')
+ dataDictionaryList[self.mkfStr.asmIncludes] = asmIncludesList
+
+ cIncludesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.cIncludes)
+ cIncludesList = utils.stripStartOfString(cIncludesList, '-I')
+ dataDictionaryList[self.mkfStr.cIncludes] = cIncludesList
+
+ ldIncludesList = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.ldIncludes)
+ ldIncludesList = utils.stripStartOfString(ldIncludesList, '-L')
+ dataDictionaryList[self.mkfStr.ldIncludes] = ldIncludesList
+
+ # flags
+ cFlags = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.cFlags)
+ dataDictionaryList[self.mkfStr.cFlags] = cFlags
+
+ asmFlags = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.asmFlags)
+ dataDictionaryList[self.mkfStr.asmFlags] = asmFlags
+
+ ldFlags = self.getMakefileVariable(makeExePath, gccExePath, self.mkfStr.ldFlags)
+ dataDictionaryList[self.mkfStr.ldFlags] = ldFlags
+
+ return dataDictionaryList
+
+ def parseMakefileData(self, data, startString):
+ '''
+ Fetch and unparse data from existing Makefile (generated by CubeMX) starting with 'startString'.
+ '''
+ endOfLineChars = "\\"
+ startString = startString + ' = '
+ NOT_FOUND = -1
+
+ items = []
+ # find start and end of defines and
+ for lineIndex, line in enumerate(data):
+ line = line.rstrip('\n') # strip string of '\n'
+
+ startCharacter = line.find(startString)
+ if startCharacter != NOT_FOUND: # search for start string
+
+ # check if one-liner
+ if line.find(endOfLineChars) == NOT_FOUND:
+ line = line[len(startString):]
+ if len(line) != 0: # check for 'SOMETHING = ' (empty line after '=')
+ # not an empty line after '='
+ items.append(line) # strip string of start and and characters
+ return items
+
+ else: # multiline item in Makefile
+ for line2 in data[lineIndex+1:]:
+ line2 = line2.rstrip('\n')
+ if line2.find(endOfLineChars) != NOT_FOUND:
+ line2 = line2.rstrip('\\') # strip of '\'
+ line2 = line2.rstrip(' ') # strip of ' '
+ items.append(line2)
+ else:
+ line2 = line2.rstrip('\\') # strip of '\'
+ line2 = line2.rstrip(' ') # strip of ' '
+ items.append(line2)
+ return items
+
+ errorMsg = "String item '" + str(startString) + "' not found!\n"
+ errorMsg += "Invalid/changed Makefile or this script is outdated (change in CubeMX Makefile syntax?)."
+ utils.printAndQuit(errorMsg)
+
+ def createNewMakefile(self):
+ '''
+ Merge existing Makefile data and user fields from existing 'c_cpp_properties.json.'
+ '''
+ cP = wks.CProperties()
+ cPropertiesData = cP.getCPropertiesData()
+
+ with open(utils.makefilePath, 'r') as makefile:
+ data = makefile.readlines()
+
+ # sources
+ cSources = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_cSources)
+ data = self.searchAndAppend(data, self.mkfStr.cSources, cSources)
+
+ asmSources = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_asmSources)
+ data = self.searchAndAppend(data, self.mkfStr.asmSources, asmSources)
+
+ ldSources = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_ldSources)
+ data = self.searchAndAppend(data, self.mkfStr.ldSources, ldSources, preappend='-l:')
+
+ # includes
+ cIncludes = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_cIncludes)
+ data = self.searchAndAppend(data, self.mkfStr.cIncludes, cIncludes, preappend='-I')
+
+ asmIncludes = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_asmIncludes)
+ data = self.searchAndAppend(data, self.mkfStr.asmIncludes, asmIncludes, preappend='-I')
+
+ ldIncludes = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_ldIncludes)
+ data = self.searchAndAppend(data, self.mkfStr.ldIncludes, ldIncludes, preappend='-L')
+
+ # defines
+ cDefines = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_cDefines)
+ data = self.searchAndAppend(data, self.mkfStr.cDefines, cDefines, preappend='-D')
+
+ asmDefines = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_asmDefines)
+ data = self.searchAndAppend(data, self.mkfStr.asmDefines, asmDefines, preappend='-D')
+
+ # compiler flags
+ cFlags = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_cFlags)
+ data = self.searchAndAppend(data, self.mkfStr.cFlags, cFlags)
+
+ asmFlags = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_asmFlags)
+ data = self.searchAndAppend(data, self.mkfStr.asmFlags, asmFlags)
+
+ ldFlags = cP.getCPropertiesKeyData(cPropertiesData, self.cPStr.user_ldFlags)
+ data = self.searchAndAppend(data, self.mkfStr.ldFlags, ldFlags)
+
+ data = self.replaceMakefileHeader(data)
+
+ try:
+ with open(utils.makefilePath, 'w') as makefile:
+ for line in data:
+ makefile.write(line)
+ print("New Makefile data succesfully written.")
+
+ except Exception as err:
+ errorMsg = "Exception error writing new data to Makefile:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def searchAndAppend(self, data, searchString, appendData, preappend=None):
+ '''
+ Search for string in 'data' list and append 'appendData' according to Makefile syntax.
+ if 'preappend' is defined, each item of 'appendData' is preappended with this string.
+ '''
+ NOT_FOUND = -1
+
+ if preappend is not None:
+ appendData = utils.preappendString(appendData, preappend)
+
+ for lineIndex, line in enumerate(data):
+ line = line.rstrip('\n') # strip string of '\n'
+
+ if line.find(searchString) != NOT_FOUND: # search for start string
+ if line[0] == '#': # this is a comment
+ continue
+
+ if line.find("\\") == NOT_FOUND:
+ # one-liner, no '\' sign at the end of the line
+ if isinstance(appendData, list): # if this is list
+ if appendData: # and it is not empty
+ if len(appendData) == 1: # this list has only one item, add it without '\'
+ if line[-1] != ' ': # avoid double spaces
+ line += " "
+ data[lineIndex] = line + appendData[0] + "\n"
+
+ else:
+ # this is list with multiple items, '\' will be needed
+ line += " \\\n"
+ data[lineIndex] = line
+
+ for itemIndex, item in enumerate(appendData):
+ stringToInsert = item
+ if item != appendData[-1]: # for last item do not append "\"
+ stringToInsert += "\\"
+ stringToInsert += "\n" # new line must always be added
+ data.insert(lineIndex + itemIndex + 1, stringToInsert)
+
+ return data
+
+ else: # appendData is string (not list)
+ if appendData != '':
+ if data[lineIndex][-1] != ' ': # avoid double spaces
+ data[lineIndex] += " "
+ data[lineIndex] += appendData + "\n"
+
+ return data
+ else: # already a multi-liner, append at the beginning, but in new line
+ if isinstance(appendData, list):
+ for itemIndex, item in enumerate(appendData):
+ stringToInsert = item + " \\\n"
+ data.insert(lineIndex + itemIndex + 1, stringToInsert)
+ else: # appendData is string (not list)
+ data[lineIndex] += item + " \\\n"
+
+ return data
+
+ errorMsg = "String item " + str(searchString) + " not found!"
+ utils.printAndQuit(errorMsg)
+
+ def searchAndCleanData(self, data, searchString):
+ '''
+ Search for string in 'data' list and clear all belonging data according to Makefile syntax.
+ '''
+ NOT_FOUND = -1
+
+ for lineIndex, line in enumerate(data):
+ line = line.rstrip('\n') # strip string of '\n'
+
+ if line.find(searchString) != NOT_FOUND: # search for start string
+ if line[0] == '#': # this is a comment
+ continue
+ if line.find("\\") == NOT_FOUND:
+ # keep searchString and equaliy sign, append '\n'
+ equalitySignCharIndex = line.find('=')
+ data[lineIndex] = data[lineIndex][: equalitySignCharIndex+1] + ' \n'
+ return data
+
+ else: # multi-liner, get last line index and delete this lines
+ lastLineIndex = lineIndex + 1
+ while data[lastLineIndex].rstrip('\n') != '':
+ lastLineIndex = lastLineIndex + 1
+ if lastLineIndex >= len(data):
+ errorMsg = "Unable to find end of multi-line Makefile item (" + searchString + "). "
+ errorMsg += "Was Makefile manually modified?"
+ utils.printAndQuit(errorMsg)
+ # delete this lines
+ delLineIndex = lineIndex + 1
+ constLineIndex = lineIndex + 1 # this line will be deleted until an empty line is present
+ while delLineIndex != lastLineIndex:
+ del data[constLineIndex]
+ delLineIndex = delLineIndex + 1
+ # keep searchString and equaliy sign, append '\n'
+ equalitySignCharIndex = line.find('=')
+ data[lineIndex] = line[: equalitySignCharIndex+1] + ' \n'
+ return data
+
+ errorMsg = "String item " + str(searchString) + " not found!"
+ utils.printAndQuit(errorMsg)
+
+ ########################################################################################################################
+
+ def getMakefileVariable(self, makeExePath, gccExePath, variableName):
+ '''
+ Open subproces, call make print-variableName and catch stout.
+ Syntax with absolute paths:
+ "path to make.exe with spaces" GCC_PATH="path to gccsomething.exe with spaces" print-VARIABLE
+
+ With
+ '''
+ # change directory to the same folder as Makefile
+ cwd = os.getcwd()
+ os.chdir(utils.workspacePath)
+
+ printStatement = "print-" + str(variableName)
+ gccExeFolderPath = os.path.dirname(gccExePath)
+ # gccPath = "\"\"GCC_PATH=" + gccExeFolderPath
+ gccPath = "GCC_PATH=\"" + gccExeFolderPath + "\""
+ arguments = [makeExePath, gccPath, printStatement]
+
+ proc = Popen(arguments, stdout=PIPE)
+ returnString = str((proc.communicate()[0]).decode('UTF-8'))
+ returnString = returnString.rstrip('\n')
+ returnString = returnString.rstrip('\r')
+
+ os.chdir(cwd) # change directory back to where it was
+
+ if returnString.find("make: *** No rule to make target") != -1:
+ errorMsg = "Can't retrieve " + variableName + " value from makefile."
+ utils.printAndQuit(errorMsg)
+
+ # remove "VARIABLE=" string start. This string must be present, or 'Echo is off.' is displayed for empy variables.
+ if returnString.find(tmpStr.printMakefileDefaultString) != -1:
+ returnString = returnString.replace(tmpStr.printMakefileDefaultString, '')
+
+ returnStringList = returnString.split(' ') # split string to list and remove empty items
+ returnStringListCopy = []
+ for itemIndex, item in enumerate(returnStringList):
+ # handle strings where print statement (print-variableName) is present, like '-MF"print-VARIABLE"'
+ quotedPrintStatement = "\"" + printStatement + "\""
+ if item.find(quotedPrintStatement) != -1:
+ item = item.replace(quotedPrintStatement, '')
+ elif item.find(printStatement) != -1:
+ item = item.replace(printStatement, '')
+
+ # handle empty items
+ if item not in ['', ' ']:
+ returnStringListCopy.append(item)
+
+ return returnStringListCopy
+
+ def replaceMakefileHeader(self, data):
+ '''
+ Change header, to distinguish between original and new Makefile.
+ '''
+ # first find last line before '# target', that must not be changed
+ lastLine = None
+ for lineIndex, line in enumerate(data):
+ twoLinesAhead = data[lineIndex + 2] # first line is ######... and second should be '# target'
+ twoLinesAhead = twoLinesAhead.rstrip('\n') # strip string of '\n'
+ if twoLinesAhead.find("# target") != -1: # search for start string
+ lastLine = lineIndex
+ break
+ if lastLine is None:
+ print('') # previously there was no new line
+ errorMsg = "Makefile '# target' string missing.\n"
+ errorMsg += "Invalid/changed Makefile or this script is outdated (change in CubeMX Makefile syntax?)."
+ utils.printAndQuit(errorMsg)
+
+ else: # '# target' line found
+ # delete current header
+ lineIndex = 0
+ while lineIndex != lastLine:
+ lineIndex = lineIndex + 1
+ del data[0]
+
+ # add new header
+ for line in reversed(tmpStr.makefileHeader.splitlines()):
+ if line.find(tmpStr.versionString) != -1:
+ line = line.replace('***', __version__)
+ if line.find(tmpStr.lastRunString) != -1:
+ timestamp = datetime.datetime.now()
+ line = line.replace('***', str(timestamp))
+
+ line = line + "\n"
+ data.insert(0, line)
+
+ return data
+
+ def hasPrintCapabilities(self, pathToMakefile):
+ '''
+ Check wether current Makefile has 'print-variable' function.
+ Returns True or False.
+ '''
+ with open(pathToMakefile, 'r+') as makefile:
+ data = makefile.readlines()
+
+ # Try to find existing print function
+ for line in reversed(data):
+ line = line.rstrip('\n') # strip string of '\n'
+ if line.find(tmpStr.printMakefileVariableFunction) != -1:
+ # existing print function found!
+ return True
+
+ return False
+
+ def addMakefileCustomFunctions(self, pathToMakefile):
+ '''
+ Add all functions to makefile:
+ - print-variable
+ - clean-build-dir
+
+ This function is called only if current Makefile does not have 'print-variable' capabilities.
+ '''
+ with open(pathToMakefile, 'r+') as makefile:
+ makefileDataLines = makefile.readlines()
+
+ makefileDataLines = self.addPrintVariableFunction(makefileDataLines)
+
+ makefile.seek(0)
+ makefile.truncate()
+ for line in makefileDataLines:
+ makefile.write(line)
+
+ def addPrintVariableFunction(self, makefileDataLines):
+ '''
+ Add print Makefile variable capabilities to Makefile
+ '''
+ makefileDataLines.append("\n\n")
+ for line in tmpStr.printMakefileVariable.splitlines():
+ line = line + "\n"
+ makefileDataLines.append(line)
+
+ print("Makefile 'print-variable' function added.")
+ return makefileDataLines
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ utils.verifyFolderStructure()
+
+ paths = pth.UpdatePaths()
+ bData = build.BuildData()
+ cP = wks.CProperties()
+ makefile = Makefile()
+
+ # Makefile must exist
+ makefile.checkMakefileFile() # no point in continuing if Makefile does not exist
+
+ buildData = bData.prepareBuildData()
+
+ makefile.restoreOriginalMakefile()
+ makeExePath = buildData[bData.bStr.buildToolsPath]
+ gccExePath = buildData[bData.bStr.gccExePath]
+ makefileData = makefile.getMakefileData(makeExePath, gccExePath)
+
+ buildData = bData.addMakefileDataToBuildDataFile(buildData, makefileData)
+
+ # get data from 'c_cpp_properties.json' and create new Makefile
+ cP.checkCPropertiesFile()
+ makefile.createNewMakefile() # reads 'c_cpp_properties.json' internally