summaryrefslogtreecommitdiff
path: root/sw/ideScripts
diff options
context:
space:
mode:
authorStijn Kuipers <stijnkuipers@gmail.com>2023-06-29 16:26:07 +0200
committerStijn Kuipers <stijnkuipers@gmail.com>2023-06-29 16:26:07 +0200
commitfb5a321dd7c2848128b04b306f3e1e59c87a3f70 (patch)
treea8ef6273f9f331ebb1971a9baf20a8c897955612 /sw/ideScripts
parentbae7568fd4dd0676b370be8548c7ec95d6521ba1 (diff)
downloadplinky-fb5a321dd7c2848128b04b306f3e1e59c87a3f70.tar.gz
Initial Filedump
Tadaaa!!
Diffstat (limited to 'sw/ideScripts')
-rwxr-xr-xsw/ideScripts/importKeilProject.py670
-rwxr-xr-xsw/ideScripts/templateStrings.py204
-rwxr-xr-xsw/ideScripts/update.py103
-rwxr-xr-xsw/ideScripts/updateBuildData.py371
-rwxr-xr-xsw/ideScripts/updateLaunchConfig.py198
-rwxr-xr-xsw/ideScripts/updateMakefile.py522
-rwxr-xr-xsw/ideScripts/updatePaths.py273
-rwxr-xr-xsw/ideScripts/updateTasks.py564
-rwxr-xr-xsw/ideScripts/updateWorkspaceFile.py105
-rwxr-xr-xsw/ideScripts/updateWorkspaceSources.py222
-rwxr-xr-xsw/ideScripts/utilities.py614
11 files changed, 3846 insertions, 0 deletions
diff --git a/sw/ideScripts/importKeilProject.py b/sw/ideScripts/importKeilProject.py
new file mode 100755
index 0000000..38dcdaa
--- /dev/null
+++ b/sw/ideScripts/importKeilProject.py
@@ -0,0 +1,670 @@
+'''
+This file gets data from Keil project and creates:
+ - base Makefile which can be used with VS Code STM32 IDE ideScripts
+ - VS Code workspace
+'''
+import copy
+import json
+import os
+import shutil
+import subprocess
+import sys
+from xml.dom import minidom
+
+import templateStrings as tmpStr
+import updateMakefile as mkf
+import utilities as utils
+from updateMakefile import MakefileStrings as mkfStr
+
+__version__ = '1.0'
+
+
+class Paths():
+ def __init__(self):
+ self.rootFolder = None # path where ideScripts folder is placed
+
+ self.cubeMxExe = None # path to STM32CubeMX executable
+ self.tmpCubeMxFolder = None # path to temporary folder, there CubeMx performs its magic
+ self.tmpCubeMxScript = None # path to temporary script file for CubeMx
+ self.tmpMakefile = None # tempory Makefile that is later modified and copied to ideScripts root folder
+ self.outputMakefile = None # final clean Makefile that is later used by ideScripts
+
+ self.keilProjectFolder = None # path to Keil project file directory
+ self.keilProject = None # path to Keil project file
+
+
+class KeilProjectData:
+ def __init__(self):
+ self.projName = None
+ self.cpuName = None
+ self.stmExactCpuName = None
+ self.svdFile = None
+
+ self.cDefines = []
+ self.asmDefines = []
+
+ self.cIncludes = [] # relative paths
+ self.asmIncludes = []
+
+ self.allSources = []
+ self.cSources = []
+ self.asmSources = []
+
+ self.cCompilerSettings = []
+ self.asmCompilerSettings = []
+ self.linkerSettings = []
+
+
+def getCubeMxExePath():
+ '''
+ Get absolute path to STM32CubeMX.exe either by windows default associated program or user input.
+ '''
+ cubeMxPath = utils.findExecutablePath('ioc', raiseException=False)
+ if cubeMxPath is not None:
+ if os.path.exists(cubeMxPath):
+ cubeMxPath = utils.pathWithForwardSlashes(cubeMxPath)
+ print("STM32CubeMX.exe path automatically updated.")
+ return cubeMxPath
+ else:
+ while cubeMxPath is None:
+ cubeMxPath = utils.getUserPath('STM32CubeMX.exe')
+ if os.path.exists(cubeMxPath):
+ cubeMxPath = utils.pathWithForwardSlashes(cubeMxPath)
+ return cubeMxPath
+ else:
+ cubeMxPath = None
+
+
+def getKeilProjectPath(paths: Paths):
+ '''
+ Try to find Keil *.uvprojx file. If found, this file is used as project file.
+ If not found, throw error.
+ If multiple files found, user is asked to enter specific file path.
+
+ Return files absolute paths: *.uvprojx
+ '''
+ KEIL_PROJECT_FILE_EXTENSION = '.uvprojx'
+
+ # Get the list of all files in directory tree at given path
+ allFiles = utils.getAllFilesInFolderTree(paths.rootFolder)
+ keilProjectFiles = []
+ for theFile in allFiles:
+ if theFile.find(KEIL_PROJECT_FILE_EXTENSION) != -1:
+ keilProjectFiles.append(theFile)
+
+ if len(keilProjectFiles) == 0:
+ errorMsg = "Unable to find any Keil project files ending with " + KEIL_PROJECT_FILE_EXTENSION + ". "
+ errorMsg += "Is folder structure correct?\n\t"
+ errorMsg += "Searched files in folder tree: " + paths.rootFolder
+ raise Exception(errorMsg)
+
+ elif len(keilProjectFiles) == 1:
+ # only one keil project file available, take this one
+ print("Keil project file found:", keilProjectFiles[0])
+ return keilProjectFiles[0]
+
+ else:
+ print("More than one Keil project files available. Select the right one.")
+ keilProjectPath = None
+ while keilProjectPath is None:
+ keilProjectPath = utils.getUserPath('Keil project (.uvprojx)')
+ if os.path.exists(keilProjectPath):
+ break
+ else:
+ keilProjectPath = None
+
+ print("Keil project path updated.")
+ return keilProjectPath
+
+
+def getKeilProjectData(paths: Paths) -> KeilProjectData:
+ '''
+ Read Keil project file and return filled KeilProjectData class.
+
+ Some blocks are placed in try...except statements - error is thrown if xml field does not contain any items.
+ '''
+ projData = KeilProjectData()
+
+ _, fileName = os.path.split(paths.keilProject)
+ projData.projName, _ = os.path.splitext(fileName)
+
+ projFileData = minidom.parse(paths.keilProject)
+ projData.cpuName = projFileData.getElementsByTagName('Device')[0].firstChild.data
+
+ svdFile = projFileData.getElementsByTagName('SFDFile')[0].firstChild.data
+ _, projData.svdFile = os.path.split(svdFile)
+
+ # c stuff
+ _cads = projFileData.getElementsByTagName('Cads')[0]
+ try: # c defines
+ cDefines = _cads.getElementsByTagName('Define')[0].firstChild.data
+ projData.cDefines = utils.stringToList(cDefines, ',')
+ except Exception as err:
+ print("WARNING: unable to get C Defines: error or no items")
+ try: # c include folders
+ cIncludes = _cads.getElementsByTagName('IncludePath')[0].firstChild.data
+ cIncludesList = utils.stringToList(cIncludes, ';')
+ projData.cIncludes = _fixRelativePaths(paths, cIncludesList)
+ except Exception as err:
+ print("WARNING: unable to get C Includes (folders): error or no items")
+ try: # c miscelaneous controls
+ cMiscControls = _cads.getElementsByTagName('MiscControls')[0].firstChild.data
+ projData.cCompilerSettings = utils.stringToList(cMiscControls, ',')
+ except Exception as err:
+ print("WARNING: unable to get C Miscelaneous settings: error or no items")
+
+ # asm stuff
+ _aads = projFileData.getElementsByTagName('Aads')[0]
+ try: # asm defines
+ asmDefines = _aads.getElementsByTagName('Define')[0].firstChild.data
+ projData.asmDefines = utils.stringToList(asmDefines, ',')
+ except Exception as err:
+ print("WARNING: unable to get Asm Defines: error or no items")
+ try: # asm include folders
+ asmIncludes = _aads.getElementsByTagName('IncludePath')[0].firstChild.data
+ asmIncludes = utils.stringToList(asmIncludes, ';')
+ projData.asmIncludes = _fixRelativePaths(paths, asmIncludes)
+ except Exception as err:
+ print("WARNING: unable to get Asm Includes (folders): error or no items")
+ try: # asm miscelaneous controls
+ asmMiscControls = _aads.getElementsByTagName('MiscControls')[0].firstChild.data
+ projData.asmCompilerSettings = utils.stringToList(asmMiscControls, ',')
+ except Exception as err:
+ print("WARNING: unable to get Asm Miscelaneous settings: error or no items")
+
+ # get linker misc controls
+ _lads = projFileData.getElementsByTagName('Cads')[0]
+ try: # asm miscelaneous controls
+ linkerMiscControls = _lads.getElementsByTagName('MiscControls')[0].firstChild.data
+ projData.linkerSettings = utils.stringToList(linkerMiscControls, ',')
+ except Exception as err:
+ print("WARNING: unable to get Linker Miscelaneous settings: error or no items")
+
+ # get all source files. Add only '.c' and '.s' files. Throw error on exception, this data is mandatory.
+ files = projFileData.getElementsByTagName('FilePath')
+ cSourceFiles = []
+ asmSourceFiles = []
+ for fileData in files:
+ filePathList = _fixRelativePaths(paths, [fileData.firstChild.data])
+ if len(filePathList) == 1:
+ filePath = filePathList[0]
+ projData.allSources.append(filePath)
+
+ _, extension = os.path.splitext(filePath)
+ if extension == '.c':
+ cSourceFiles.append(filePath)
+ elif extension == '.s':
+ asmSourceFiles.append(filePath)
+ else:
+ msg = "WARNING: this file is not '.c' or '.s'. Not added to project (user must handle this manually).\n"
+ msg += "\t" + filePath
+ print(msg)
+ else:
+ # missing file reported in _fixRelativePaths
+ msg = "WARNING: seems like none or more than one file is specified. This is not a valid Keil project syntax: "
+ msg += str(filePathList)
+ print(msg)
+
+ projData.cSources = cSourceFiles
+ print("\nC source files added:\n\t" + '\n\t'.join(cSourceFiles))
+ projData.asmSources = asmSourceFiles
+ print("\nAsm source files added:\n\t" + '\n\t'.join(asmSourceFiles) + '\n')
+
+ return projData
+
+
+def _fixRelativePaths(paths: Paths, relativePaths: list):
+ '''
+ Correct relative paths according to the folder structure as it is expected.
+ Relative paths in Keil project file are relative to the keil file path,
+ while we need paths relative to root folder where 'ideScripts' is.
+
+ Return list of a VALID relative paths paths.
+ '''
+ keilProjectAbsPath = os.path.normpath(os.path.join(paths.rootFolder, paths.keilProject))
+
+ allPaths = []
+ for relativePath in relativePaths:
+ if os.path.isabs(relativePath):
+ relativePath = os.path.normpath(relativePath)
+ relativePath = utils.pathWithForwardSlashes(relativePath)
+ allPaths.append(relativePath)
+ continue
+
+ absolutePath = os.path.normpath(os.path.join(paths.keilProjectFolder, relativePath))
+ if os.path.exists(absolutePath):
+ # path is valid, build correct relative path
+ try:
+ newRelativePath = os.path.relpath(absolutePath, paths.rootFolder)
+ newRelativePath = utils.pathWithForwardSlashes(newRelativePath)
+ allPaths.append(newRelativePath)
+ except:
+ absolutePath = utils.pathWithForwardSlashes(absolutePath)
+ allPaths.append(absolutePath)
+ else:
+ print("WARNING: unable to find file/folder:", absolutePath)
+ print("\tBuilt from relative path:", relativePath)
+
+ return allPaths
+
+
+def _getAbsolutePaths(relativePaths):
+ '''
+ Get list of relative paths and try to build absolute paths.
+ If any path does not exist, print warning message.
+ Return list of valid absolute paths.
+ '''
+ absolutePaths = []
+ for relativePath in relativePaths:
+ relativePath = relativePath.strip()
+ relativePath = os.path.normpath(os.path.join(paths.keilProjectFolder, relativePath))
+ if os.path.exists(relativePath):
+ relativePath = utils.pathWithForwardSlashes(relativePath)
+ absolutePaths.append(relativePath)
+ else:
+ print("WARNING: unable to find file/folder:", relativePath)
+
+ return absolutePaths
+
+
+def createMakefileTemplate(paths: Paths, keilProjData: KeilProjectData):
+ '''
+ Create Makefile template with CubeMX.
+ '''
+ # create script that CubeMX executes
+ paths.tmpCubeMxFolder = os.path.join(paths.rootFolder, tmpStr.cubeMxTmpFolderName)
+ paths.tmpCubeMxFolder = utils.pathWithForwardSlashes(paths.tmpCubeMxFolder)
+ if not os.path.exists(paths.tmpCubeMxFolder):
+ try:
+ os.mkdir(paths.tmpCubeMxFolder)
+ except Exception as err:
+ errorMsg = "Unable to create existing temporary folder:\n" + str(err)
+ print(errorMsg)
+
+ # even if any error occured, try to create files anyway
+ _createCubeMxTmpScript(paths, keilProjData)
+
+ # run CubeMX as subprocess with this script as a parameter
+ cmd = ['java', '-jar', paths.cubeMxExe, '-s', paths.tmpCubeMxScript]
+ if _checkCubeMxFirmwarePackage(paths, keilProjData):
+ cmd.append('-q') # no-gui mode
+ print("\tSTM32CubeMX GUI set to non-visible mode.")
+ else:
+ print("\tSTM32CubeMX GUI set to visible because of repository warning.")
+
+ try:
+ print("Generating template Makefile with STM32CubeMX...")
+ proc = subprocess.run(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+ if proc.returncode == 0:
+ print("\tSTM32CubeMX project generated.")
+ else:
+ errorMsg = "CubeMx returned non-zero exit code. Something went wrong:\n"
+ errorMsg += str(proc.stderr) + '\n'
+ errorMsg += str(proc.stdout)
+
+ utils.printAndQuit(errorMsg)
+ except Exception as err:
+ errorMsg = "Exception error while creating template Makefile with STM32CubeMX:\n" + str(err)
+ utils.printAndQuit(errorMsg)
+
+ # get makefile path
+ allGeneratedFiles = utils.getAllFilesInFolderTree(paths.tmpCubeMxFolder)
+ for theFile in allGeneratedFiles:
+ _, fileName = os.path.split(theFile)
+ if fileName == 'Makefile':
+ paths.tmpMakefile = theFile
+ print("\tMakefile found: " + paths.tmpMakefile)
+
+ _copyStartupFile(paths, keilProjData)
+ return
+ else:
+ errorMsg = "Unable to find template Makefile generated by STM32CubeMX. Was project really generated?"
+ utils.printAndQuit(errorMsg)
+
+
+def _copyStartupFile(paths: Paths, keilProjData: KeilProjectData):
+ '''
+ Get '*.s' startup file in the same folder as CubeMX template Makefile file and
+ copy it into the same location as current startup file is.
+ '''
+ # find CubeMX temporary generated startup file
+ filesInMakefileDir = os.listdir(os.path.dirname(paths.tmpMakefile))
+ for theFile in filesInMakefileDir:
+ name, ext = os.path.splitext(theFile)
+ if ext == '.s':
+ startupFile = os.path.join(os.path.dirname(paths.tmpMakefile), theFile)
+ newStartupFilePath = os.path.join(paths.rootFolder, theFile)
+ try:
+ shutil.copy(startupFile, newStartupFilePath)
+ print("Default STM32CubeMX startup file copied to:", newStartupFilePath)
+
+ relativeStartupFilePath = os.path.relpath(newStartupFilePath, paths.rootFolder)
+ relativeStartupFilePath = utils.pathWithForwardSlashes(relativeStartupFilePath)
+ break
+ except Exception as err:
+ pass
+ #print("Seems like default STM32CubeMX startup file already exist:", newStartupFilePath)
+
+ # find startup file in current keil project data and replace it with this one
+ if len(keilProjData.asmSources) == 1:
+ # no problem only one '*.s' file, assume this is the startup file
+ originalStartupFile = keilProjData.asmSources[0]
+ keilProjData.asmSources = [relativeStartupFilePath]
+
+ msg = "Default " + originalStartupFile + " source was replaced with CubeMX one: " + relativeStartupFilePath
+ print(msg)
+ return
+
+ else:
+ # more than one assembler file found, try to find file with 'startup' string or throw error
+ possibleStartupFiles = []
+ for startupFileListIndex, asmFile in enumerate(keilProjData.asmSources):
+ _, fileName = os.path.split(asmFile)
+ if fileName.lower().find('startup') != -1:
+ possibleStartupFiles.append((asmFile, startupFileListIndex)) # asm file, file index in list
+
+ if len(possibleStartupFiles) == 1:
+ # OK, only one file with startup string
+ originalStartupFile = keilProjData.asmSources[possibleStartupFiles[0][1]]
+ keilProjData.asmSources[possibleStartupFiles[0][1]] = relativeStartupFilePath
+
+ msg = "WARNING: Multiple '*.s' files found. "
+ msg += originalStartupFile + " source file was replaced with CubeMX one: " + relativeStartupFilePath
+ print(msg)
+
+ else:
+ errorMsg = "Multiple '*.s' source files listed. Can't determine startup file (searched with 'startup' string)."
+ errorMsg += "\n\tAsm files: " + str(keilProjData.asmSources)
+ utils.printAndQuit(errorMsg)
+
+
+def cleanTempMakefile(paths: Paths):
+ '''
+ Clean default generated Makefile data (sources, includes, names, ...).
+ '''
+ makefile = mkf.Makefile()
+
+ try:
+ with open(paths.tmpMakefile, 'r') as makefileHandler:
+ data = makefileHandler.readlines()
+
+ # do not change project name intentionally
+ # data = makefile.searchAndCleanData(data, makefile.mkfStr.projectName)
+
+ data = makefile.searchAndCleanData(data, makefile.mkfStr.cSources)
+ data = makefile.searchAndCleanData(data, makefile.mkfStr.asmSources)
+
+ data = makefile.searchAndCleanData(data, makefile.mkfStr.cDefines)
+ data = makefile.searchAndCleanData(data, makefile.mkfStr.asmDefines)
+
+ data = makefile.searchAndCleanData(data, makefile.mkfStr.cIncludes)
+ data = makefile.searchAndCleanData(data, makefile.mkfStr.asmIncludes)
+
+ data = makefile.searchAndCleanData(data, makefile.mkfStr.cIncludes)
+
+ print("Makefile template prepared.")
+ return data
+
+ except Exception as err:
+ errorMsg = "Exception during Makefile template preparation:\n" + str(err)
+ utils.printAndQuit(errorMsg)
+
+
+def createNewMakefile(paths: Paths, keilProjData: KeilProjectData, newMakefileData):
+ '''
+ Fill and write new makefile with data from Keil project.
+ '''
+ makefile = mkf.Makefile()
+ try:
+ # sources
+ data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.cSources, keilProjData.cSources)
+ data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.asmSources, keilProjData.asmSources)
+
+ # includes
+ data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.cIncludes, keilProjData.cIncludes, preappend='-I')
+ data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.asmIncludes, keilProjData.asmIncludes, preappend='-I')
+
+ # defines
+ data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.cDefines, keilProjData.cDefines, preappend='-D')
+ data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.asmDefines, keilProjData.asmDefines, preappend='-D')
+
+ # compiler flags
+ # TODO should import?
+ # data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.cFlags, keilProjData.cCompilerSettings)
+ # data = makefile.searchAndAppend(newMakefileData, makefile.mkfStr.asmFlags, keilProjData.asmCompilerSettings)
+ if keilProjData.cCompilerSettings:
+ print("WARNING: C compiler settings not imported (user must handle manualy):", str(keilProjData.cCompilerSettings))
+ if keilProjData.asmCompilerSettings:
+ print("WARNING: Asm compiler settings not imported (user must handle manualy):", str(keilProjData.asmCompilerSettings))
+ if keilProjData.linkerSettings:
+ print("WARNING: Linker settings not imported (user must handle manualy):", str(keilProjData.linkerSettings))
+
+ with open(paths.outputMakefile, 'w+') as newMakefileHandler:
+ newMakefileHandler.writelines(data)
+
+ print("Makefile created in: " + paths.outputMakefile)
+
+ except Exception as err:
+ errorMsg = "Exception during creating new Makefile:\n" + str(err)
+ utils.printAndQuit(errorMsg)
+
+
+def _getCPUName(paths: Paths, keilProjData: KeilProjectData):
+ '''
+ Try to get correct CPU family name from Keil project device tag.
+
+ STM32 CPU name, passed to CubeMX is not the same as Keil device name.
+ CubeMX device firmware pack must be installed so CubeMX is able to generate template Makefile.
+ '''
+ cubeMxMcuFolderPath = os.path.join(os.path.dirname(paths.cubeMxExe), 'db', 'mcu')
+ allFamiliesFilePath = os.path.join(cubeMxMcuFolderPath, 'families.xml')
+
+ allFiles = os.listdir(cubeMxMcuFolderPath)
+ for theFile in allFiles:
+ theFilePath = os.path.join(cubeMxMcuFolderPath, theFile)
+ if os.path.isfile(theFilePath):
+ if theFile.find(keilProjData.cpuName) != -1:
+ fileName, ext = os.path.splitext(theFile)
+ return fileName
+
+ errorMsg = "Unable to find matching STM32 CPU name for Keil project device: " + keilProjData.cpuName
+ utils.printAndQuit(errorMsg)
+
+ stm32McuData = minidom.parse(allFamiliesFilePath)
+
+ # build possible device family name search strings. Search order is important
+ allMcuData = stm32McuData.getElementsByTagName('Mcu')
+ minimumSearchStringLenght = len('STM32xx')
+ numOfStrippedCharacters = len(keilProjData.cpuName) - minimumSearchStringLenght
+ possibleDeviceSearchString = []
+ possibleDeviceSearchString.append(keilProjData.cpuName)
+ for charIndexFromBack in range(-1, -numOfStrippedCharacters-1, -1):
+ possibleDeviceSearchString.append(keilProjData.cpuName[:charIndexFromBack])
+
+ # find possible mcu ref names
+ allPossibleMcu = [None] * len(allMcuData)
+ subFamilyMcuData = None
+ for thisDeviceSearchString in possibleDeviceSearchString:
+ thisSearchStringPossibleMcu = []
+ for mcuData in allMcuData:
+ thisMcuName = mcuData.attributes._attrs['RPN'].value
+ if thisMcuName.find(thisDeviceSearchString) != -1:
+ thisSearchStringPossibleMcu.append(thisMcuName)
+
+ if thisSearchStringPossibleMcu:
+ if len(thisSearchStringPossibleMcu) <= len(allPossibleMcu):
+ allPossibleMcu = copy.copy(thisSearchStringPossibleMcu)
+ break
+
+ if not allPossibleMcu:
+ errorMsg = "Unable to find any (even partly) matching device name:" + keilProjData.cpuName
+ utils.printAndQuit(errorMsg)
+ allPossibleMcu = list(set(allPossibleMcu)) # remove cuplicates
+
+ # all possible MCUs are listed, ask user to select correct one
+ if len(allPossibleMcu) == 1:
+ keilProjData.stmExactCpuName = allPossibleMcu[0]
+ return allPossibleMcu[0]
+ else:
+ msg = "\n\n??? Please select exact CPU..."
+ for mcuIndex, mcu in enumerate(allPossibleMcu):
+ msg += '\n\t' + str(mcuIndex) + ': ' + mcu
+ limits = list(range(0, len(allPossibleMcu)))
+ askMsg = "Type number (0 - " + str(len(allPossibleMcu)) + ") and press Enter:"
+ print(msg + '\n' + askMsg)
+
+ while(True):
+ userAnswer = input()
+ try:
+ userNumber = int(userAnswer)
+ except:
+ print(askMsg)
+ continue
+ if userNumber not in limits:
+ print(askMsg)
+ else:
+ print("--> " + allPossibleMcu[userNumber] + " selected.")
+ keilProjData.stmExactCpuName = allPossibleMcu[userNumber]
+ return allPossibleMcu[userNumber]
+
+
+def _checkCubeMxFirmwarePackage(paths: Paths, keilProjData: KeilProjectData):
+ '''
+ Check if this cpu family firmware package can be found inside CubeMX local repository.
+ Returns True if found, False otherwise.
+ '''
+ errorMsg = ''
+ try:
+ # get all files inside local repository
+ appDataFolder = os.path.expandvars(os.environ['APPDATA'])
+ stm32CubeRepositoryFolder = os.path.join(appDataFolder, '..', '..', 'STM32Cube', 'Repository')
+ stm32CubeRepositoryFolder = os.path.normpath(stm32CubeRepositoryFolder)
+
+ # get start of package name
+ cpuFamilyName = keilProjData.cpuName[len('STM32'):len('STM32xx')]
+ fwPackageName = 'STM32Cube_FW_' + cpuFamilyName
+
+ # search if any folder name contains fwPackageName
+ for item in os.listdir(stm32CubeRepositoryFolder):
+ if os.path.isdir(os.path.join(stm32CubeRepositoryFolder, item)):
+ if item.find(fwPackageName) != -1:
+ print("Seems like STM32CubeMX " + fwPackageName + "* package is installed.")
+ return True
+
+ except Exception as err:
+ errorMsg = "\nException:\n" + str(err)
+
+ msg = "WARNING: unable to check if STM32Cube " + keilProjData.cpuName + " firmware package is installed."
+ msg += errorMsg
+ print(msg)
+ return False
+
+
+def _createCubeMxTmpScript(paths: Paths, keilProjData: KeilProjectData):
+ '''
+ Create tempory script for CubeMX Makefile generation.
+ Raises exception on error.
+ '''
+ paths.tmpCubeMxScript = os.path.join(paths.tmpCubeMxFolder, tmpStr.cubeMxTmpFileName)
+ paths.tmpCubeMxScript = utils.pathWithForwardSlashes(paths.tmpCubeMxScript)
+
+ dataToWrite = "// Temporary script for generating Base Makefile with STM32CubeMX.\n"
+ dataToWrite += "load " + _getCPUName(paths, keilProjData) + "\n"
+ dataToWrite += "project name " + keilProjData.projName + "\n"
+ dataToWrite += "project toolchain Makefile\n"
+ dataToWrite += "project path \"" + paths.tmpCubeMxFolder + "\"\n"
+ dataToWrite += "project generate\n"
+ dataToWrite += "exit"
+
+ with open(paths.tmpCubeMxScript, 'w+') as scriptHandler:
+ scriptHandler.write(dataToWrite)
+
+ print("Temporary STM32CubeMX script created.")
+
+
+def deleteTemporaryFiles(paths: Paths):
+ '''
+ Delete (clean) CubeMX temporary files.
+ '''
+ try:
+ shutil.rmtree(paths.tmpCubeMxFolder)
+ print("STM32CubeMX temporary files deleted.")
+ except Exception as err:
+ errorMsg = "Exception while deleting STM32CubeMX temporary files:\n" + str(err)
+ raise Exception(err)
+
+
+def _separateAbsoluteAndRelativePaths(pathsListToSeparate: list):
+ '''
+ This function splits pathsListToSeparate to relative and absolute paths.
+ Returns two lists: absolutePaths, relativePaths
+ '''
+ absPaths = []
+ relPaths = []
+ for path in pathsListToSeparate:
+ if os.path.isabs(path):
+ absPaths.append(path)
+ else:
+ relPaths.append(path)
+
+ return absPaths, relPaths
+
+
+def createVSCodeWorkspace(paths: Paths, keilProjData: KeilProjectData):
+ '''
+ Create VS Code workspace so user can easily run 'update.py' from ideScripts.
+ '''
+ # add non-relative source folders to VS Code workspace folders.
+ allPaths = []
+ # TODO are c and asm includes folders needed in Code workspace?
+ # allPaths.extend(keilProjData.cIncludes)
+ # allPaths.extend(keilProjData.asmIncludes)
+ cSourcesFolders = [os.path.dirname(source) for source in keilProjData.cSources]
+ allPaths.extend(list(set(cSourcesFolders)))
+ asmSourcesFolders = [os.path.dirname(source) for source in keilProjData.asmSources]
+ allPaths.extend(list(set(asmSourcesFolders)))
+ absPaths, relPaths = _separateAbsoluteAndRelativePaths(allPaths)
+
+ dataToWrite = """
+ {
+ "folders": [
+ {
+ "path": "."
+ }
+ """
+ for absPath in absPaths:
+ addToFoldersStr = ",{ \"path\": \"" + absPath + "\"}"
+ dataToWrite += addToFoldersStr
+ dataToWrite += "]"
+ dataToWrite += ",\"settings\": { }"
+ dataToWrite += "}"
+ data = json.loads(dataToWrite)
+ data = json.dumps(data, indent=4, sort_keys=False)
+
+ codeWorkspaceFileName = keilProjData.projName + '.code-workspace'
+ codeWorkspaceFilePath = os.path.join(paths.rootFolder, codeWorkspaceFileName)
+ with open(codeWorkspaceFilePath, 'w+') as fileHandler:
+ fileHandler.write(data)
+
+ print("VS Code workspace file created:", codeWorkspaceFilePath)
+
+
+if __name__ == "__main__":
+ paths = Paths()
+ thisFileAbsPath = os.path.abspath(sys.argv[0])
+ paths.rootFolder = os.path.dirname(os.path.dirname(thisFileAbsPath))
+ paths.rootFolder = utils.pathWithForwardSlashes(paths.rootFolder)
+
+ paths.cubeMxExe = getCubeMxExePath()
+ paths.keilProject = getKeilProjectPath(paths)
+ paths.keilProjectFolder = utils.pathWithForwardSlashes(os.path.dirname(paths.keilProject))
+ paths.outputMakefile = utils.pathWithForwardSlashes(os.path.join(paths.rootFolder, 'Makefile'))
+
+ keilProjData = getKeilProjectData(paths)
+
+ createMakefileTemplate(paths, keilProjData)
+ cleanMakefileData = cleanTempMakefile(paths)
+ createNewMakefile(paths, keilProjData, cleanMakefileData)
+ deleteTemporaryFiles(paths)
+
+ createVSCodeWorkspace(paths, keilProjData)
diff --git a/sw/ideScripts/templateStrings.py b/sw/ideScripts/templateStrings.py
new file mode 100755
index 0000000..623ab1c
--- /dev/null
+++ b/sw/ideScripts/templateStrings.py
@@ -0,0 +1,204 @@
+"""
+Template scripts for generating workspace files:
+ - c_cpp_properties.json
+ - tasks.json
+ - makefile strings/functions
+ - buildData.json
+"""
+import os
+
+launchName_Debug = "Cortex debug"
+launchName_Python = "Debug current Python file"
+
+taskName_build = "Build project"
+taskName_compile = "Compile current file"
+taskName_clean = "Delete build folder"
+
+taskName_CPU_buildDownloadRun = "CPU: Build, Download and run"
+taskName_CPU_downloadRun = "CPU: Download and run"
+taskName_CPU_resetRun = "CPU: Reset and run"
+taskName_CPU_halt = "CPU: Halt"
+taskName_CPU_run = "CPU: Run"
+
+taskName_Python = "Run Python file"
+taskName_OpenCubeMX = "Open CubeMX project"
+taskName_updateWorkspace = "Update workspace"
+
+#########################################################################################################
+c_cpp_template = """{
+ "env" : {
+ "____________________USER_FIELDS_CAN_BE_MODIFIED____________________": "",
+ "user_cSources": [],
+ "user_asmSources": [],
+ "user_ldSources": [],
+ "user_cIncludes": [],
+ "user_asmIncludes": [],
+ "user_ldIncludes": [],
+ "user_cDefines": [],
+ "user_asmDefines": [],
+ "user_cFlags": [],
+ "user_asmFlags": [],
+ "user_ldFlags": [],
+
+ "____________________DO_NOT_MODIFY_FIELDS_BELOW____________________": "",
+ "cubemx_sourceFiles": [],
+ "cubemx_includes": [],
+ "cubemx_defines": [],
+ "gccExePath": "",
+ "gccIncludePath": ""
+ },
+ "configurations": [
+ {
+ "name": "devTestBoard name of the project?",
+ "intelliSenseMode": "msvc-x64",
+ "includePath": [
+ "${workspaceFolder}",
+ "${cubemx_includes}",
+ "${gccIncludePath}",
+ "${user_cIncludes}",
+ "${user_asmIncludes}",
+ "${user_ldIncludes}"
+ ],
+ "browse": {
+ "path": [
+ "${workspaceFolder}",
+ "${cubemx_includes}",
+ "${gccIncludePath}",
+ "${user_cIncludes}",
+ "${user_asmIncludes}",
+ "${user_ldIncludes}"
+ ],
+ "limitSymbolsToIncludedHeaders": true
+ },
+ "defines": [
+ "${cubemx_defines}",
+ "${user_cDefines}",
+ "${user_asmDefines}"
+ ],
+ "forcedInclude": [
+ ],
+ "compilerPath": "${gccExePath}",
+ "cStandard": "c11",
+ "cppStandard": "c++17"
+ }
+ ],
+ "version": 4
+}
+"""
+#########################################################################################################
+versionString = "Version ***"
+lastRunString = "Last run: ***"
+
+#########################################################################################################
+makefileHeader = ('#' * 100) + "\n"
+makefileHeader += "# Makefile generated by updateMakefile.py\n"
+makefileHeader += "# " + versionString + " \n"
+makefileHeader += "# " + lastRunString + " \n"
+makefileHeader += ('#' * 100) + "\n"
+
+#########################################################################################################
+printMakefileVariableFunction = "print-%:"
+printMakefileDefaultString = "VARIABLE="
+printMakefileVariable = "#######################################\n"
+printMakefileVariable += "# Print makefile variables\n"
+printMakefileVariable += "#######################################\n"
+printMakefileVariable += printMakefileVariableFunction + "\n"
+printMakefileVariable += "\t@echo " + printMakefileDefaultString + "$($*)\n"
+
+#########################################################################################################
+cleanFunctionNameSearchString = "clean:"
+cleanBuildDirFunctionName = "clean-build-dir"
+cleanBuildDirFunction = "#######################################\n"
+cleanBuildDirFunction += "# Clean build directory content \n"
+cleanBuildDirFunction += "#######################################\n"
+cleanBuildDirFunction += cleanBuildDirFunctionName + ":\n"
+cleanBuildDirFunction += "\t@echo Build folder: '$(BUILD_DIR)' clean request (files with spaces and folders will not be removed):\n"
+cleanBuildDirFunction += "\t@$(foreach file, $(wildcard $(BUILD_DIR)/*), rm -f $(file))\n"
+cleanBuildDirFunction += "\t@echo OK.\n"
+
+#########################################################################################################
+taskTemplate = """{
+ "label": "Update workspace",
+ "type": "shell",
+ "command": "python",
+ "args": [
+ "${workspaceFolder}\\\\test.py"
+ ],
+ "group": "none",
+ "presentation": {
+ "reveal": "always",
+ "panel": "shared"
+ },
+ "problemMatcher": {
+ }
+ }
+"""
+
+tasksFileTemplate = """{
+ "version": "2.0.0",
+ "tasks": ["""
+tasksFileTemplate += """
+ ]
+}
+"""
+
+#########################################################################################################
+# buildData.json has template with all keys listed, since it is needed for sorting purposes. There
+# might be a better way to handle sorting (TODO)
+buildDataTemplate = """{
+ "ABOUT1": "This file holds combined user and CubeMX generated Makefile workspace dependecies.",
+ "ABOUT2": "User should not edit this fields, instead it should edit 'c_cpp_properties.json'",
+ "ABOUT3": "This file is regenerated on 'Update workspace' task.",
+ "VERSION": "",
+ "LAST_RUN": "",
+ "cSources": [],
+ "asmSources": [],
+ "ldSources": [],
+ "cIncludes": [],
+ "asmIncludes": [],
+ "ldIncludes": [],
+ "cDefines": [],
+ "asmDefines": [],
+ "cFlags" : [],
+ "asmFlags" : [],
+ "ldFlags" : [],
+ "buildDir": "",
+ "targetExecutablePath": "",
+ "cubeMxProjectPath": "",
+ "openOcdConfig": [],
+ "stm32SvdPath": "",
+ "ABOUT4": "---- Paths below are fetched from user-specific 'toolsPaths.json'. ----",
+ "gccExePath": "",
+ "gccInludePath": "",
+ "buildToolsPath": "",
+ "pythonExec": "",
+ "openOcdPath": "",
+ "openOcdInterfacePath": ""
+}
+"""
+
+#########################################################################################################
+toolsPathsTemplate = """{
+ "ABOUT1": "This file store common tools paths, shared by all VS Code ideScripts-based projects.",
+ "ABOUT2": "Delete/correct this file if paths/folder structure change on system.",
+ "VERSION": "",
+ "LAST_RUN": ""
+}
+"""
+
+#########################################################################################################
+launchFileTemplate = """{
+ "version": "0.2.0",
+ "configurations": [
+ ]
+}
+"""
+
+#########################################################################################################
+cubeMxTmpFolderName = '_tmpCubeMx'
+cubeMxTmpFileName = 'tmpCubeMx.txt'
+
+#########################################################################################################
+defaultVsCodeSettingsFolder_WIN = os.path.expandvars("%APPDATA%/Code/User/")
+defaultVsCodeSettingsFolder_UNIX = os.path.expandvars("$HOME/.config/Code/User/")
+defaultVsCodeSettingsFolder_OSX = os.path.expandvars("$HOME/Library/Application Support/Code/User/")
diff --git a/sw/ideScripts/update.py b/sw/ideScripts/update.py
new file mode 100755
index 0000000..5af3760
--- /dev/null
+++ b/sw/ideScripts/update.py
@@ -0,0 +1,103 @@
+'''
+This script runs all other updateXxx.py scripts.
+It should be called once CubeMX project was generated/re-generated or user settings were modified.
+
+- add 'print-variable' capabilities to Makefile
+- update/generate 'c_cpp_properties.json'
+- update/generate 'buildData.json' and 'toolsPaths.json'
+- update/generate 'tasks.json'
+- update/generate 'launch.json'
+'''
+import sys
+import time
+import traceback
+
+import updateWorkspaceFile as workspaceFile
+import updateLaunchConfig as launch
+import updateTasks as tasks
+import updateBuildData as build
+import updateMakefile as mkf
+import updateWorkspaceSources as wks
+import updatePaths as pth
+import utilities as utils
+
+__version__ = utils.__version__
+
+if sys.version_info[0] < 3:
+ raise Exception("Python 3 or later is required")
+
+########################################################################################################################
+if __name__ == "__main__":
+ startTime = time.time()
+ print("Update started.\n")
+ status = 'OK'
+ errorMsg = ''
+ try:
+ utils.verifyFolderStructure()
+
+ paths = pth.UpdatePaths()
+ bData = build.BuildData()
+ cP = wks.CProperties()
+ makefile = mkf.Makefile()
+ tasks = tasks.Tasks()
+ launch = launch.LaunchConfigurations()
+ wksFile = workspaceFile.UpdateWorkspaceFile()
+
+ # Makefile must exist
+ makefile.checkMakefileFile() # no point in continuing if Makefile does not exist
+ makefile.restoreOriginalMakefile()
+
+ # build data (update tools paths if neccessary)
+ buildData = bData.prepareBuildData()
+
+ # data from original makefile
+ makeExePath = buildData[bData.bStr.buildToolsPath]
+ gccExePath = buildData[bData.bStr.gccExePath]
+ makefileData = makefile.getMakefileData(makeExePath, gccExePath)
+
+ # create/update 'c_cpp_properties.json'
+ cP.checkCPropertiesFile()
+ cPropertiesData = cP.getCPropertiesData()
+ cPropertiesData = cP.addMakefileDataToCPropertiesFile(cPropertiesData, makefileData)
+ cPropertiesData = cP.addBuildDataToCPropertiesFile(cPropertiesData, buildData)
+ cPropertiesData = cP.addCustomDataToCPropertiesFile(cPropertiesData, makefileData, buildData)
+ cP.overwriteCPropertiesFile(cPropertiesData)
+
+ # update Makefile
+ makefile.createNewMakefile()
+ makefileData = makefile.getMakefileData(makeExePath, gccExePath) # get data from new Makefile
+
+ # update buildData.json
+ buildData = bData.addMakefileDataToBuildDataFile(buildData, makefileData)
+ buildData = bData.addCubeMxProjectPathToBuildData(buildData)
+ bData.overwriteBuildDataFile(buildData)
+
+ # create build folder
+ buildFolderName = makefileData[mkf.MakefileStrings.buildDir]
+ utils.createBuildFolder(buildFolderName)
+
+ # update tasks
+ tasks.checkTasksFile()
+ tasksData = tasks.getTasksData()
+ tasksData = tasks.addAllTasks(tasksData)
+ tasks.overwriteTasksFile(tasksData)
+
+ # update launch configurations
+ launch.checkLaunchFile()
+ launchData = launch.getLaunchData()
+ launchData = launch.addAllLaunchConfigurations(launchData)
+ launch.overwriteLaunchFile(launchData)
+
+ # update workspace file with "cortex-debug" specifics
+ wksFile.checkWorkspaceFile()
+ wksData = wksFile.getWorkspaceFileData()
+ wksData = wksFile.addBuildDataToWorkspaceFile(wksData, buildData)
+ wksFile.overwriteWorkspaceFile(wksData)
+
+ except Exception as err:
+ status = "ERROR"
+ errorMsg = "Unexpected error occured during 'Update' procedure. Exception:\n" + traceback.format_exc()
+
+ overallTime = int(time.time() - startTime)
+ msg = "\n" + status + " (" + str(overallTime) + " seconds).\n" + errorMsg
+ print(msg)
diff --git a/sw/ideScripts/updateBuildData.py b/sw/ideScripts/updateBuildData.py
new file mode 100755
index 0000000..3e84356
--- /dev/null
+++ b/sw/ideScripts/updateBuildData.py
@@ -0,0 +1,371 @@
+'''
+Update/generate 'buildData.json' file in '.vscode' subfolder from new Makefile.
+This file also handles 'toolsPaths.json' file.
+New Makefile is not updated by this script - it is updated with 'updateMakefile.py' or 'updateWorkspaceSources.py'
+'''
+import os
+import json
+import datetime
+
+import utilities as utils
+import templateStrings as tmpStr
+
+import updatePaths as pth
+import updateMakefile as mkf
+import updateWorkspaceSources as wks
+
+__version__ = utils.__version__
+
+
+class BuildDataStrings():
+ # project sources, includes, defines, ....
+ cSources = 'cSources'
+ asmSources = 'asmSources'
+ ldSources = 'ldSources'
+
+ cIncludes = 'cIncludes'
+ asmIncludes = 'asmIncludes'
+ ldIncludes = 'ldIncludes'
+
+ cDefines = 'cDefines'
+ asmDefines = 'asmDefines'
+
+ cFlags = 'cFlags'
+ asmFlags = 'asmFlags'
+ ldFlags = 'ldFlags'
+
+ buildDirPath = 'buildDir'
+
+ # build/interface tools paths, configuration files
+ gccInludePath = 'gccInludePath' # GCC standard libraries root folder path
+ gccExePath = 'gccExePath' # path to 'gcc.exe'
+
+ buildToolsPath = 'buildToolsPath' # path to 'make.exe'
+ targetExecutablePath = 'targetExecutablePath' # path to downloadable '*.elf' file
+
+ pythonExec = 'pythonExec'
+
+ openOcdPath = 'openOcdPath' # path to 'openocd.exe'
+ openOcdInterfacePath = "openOcdInterfacePath" # path to OpenOCD interface cofniguration file (currently 'stlink.cfg')
+
+ openOcdConfig = 'openOcdConfig' # path to target '*.cfg' file
+ stm32SvdPath = 'stm32SvdPath' # path to target '*.svd' file
+
+ cubeMxProjectPath = 'cubeMxProjectPath'
+
+ # list of paths that are automatically built (default, system or once their 'parent' paths are valid)
+ derivedPaths = [
+ pythonExec,
+ gccInludePath
+ ]
+
+ # list of target-specific configuration paths that must exist in 'buildData.json'
+ targetConfigurationPaths = [
+ openOcdConfig,
+ stm32SvdPath
+ ]
+
+ # list of paths that can be cached in 'toolsPaths.json'
+ toolsPaths = [
+ gccExePath,
+ buildToolsPath,
+ pythonExec,
+ openOcdPath,
+ openOcdInterfacePath
+ ]
+
+
+class BuildData():
+ def __init__(self):
+ self.mkfStr = mkf.MakefileStrings()
+ self.cPStr = wks.CPropertiesStrings()
+ self.bStr = BuildDataStrings()
+
+ def prepareBuildData(self, request=False):
+ '''
+ This function is used in all 'update*.py' scripts and makes sure, that 'toolsPaths.json' and 'buildData.json' with a
+ valid tools/target cofniguration paths exist. Invalid paths are updated (requested from the user).
+ Returns available, valid build data.
+
+ Note: tools paths listed in 'BuildDataStrings.toolsPaths' are stored in system local 'toolsPaths.json' file, and are
+ copied (overwritten) to 'buildData.json' on first 'Update' task run. This makes it possible for multiple code contributors.
+ '''
+ paths = pth.UpdatePaths()
+
+ self.checkBuildDataFile()
+ buildData = self.getBuildData()
+
+ if self.checkToolsPathFile(): # a valid toolsPaths.json exists
+ toolsPathsData = self.getToolsPathsData()
+
+ else:
+ # no valid data from 'toolsPaths.json' file
+ # try to get data from current 'buildData.json' - backward compatibility for paths that already exist in 'buildData.json'
+ toolsPathsData = json.loads(tmpStr.toolsPathsTemplate)
+ for path in self.bStr.toolsPaths:
+ if path in buildData:
+ if utils.pathExists(buildData[path]):
+ toolsPathsData[path] = buildData[path]
+
+ # update/overwrite tools paths file. Don't mind if paths are already valid.
+ toolsPathsData = paths.verifyToolsPaths(toolsPathsData, request)
+ self.createUserToolsFile(toolsPathsData)
+
+ buildData = self.addToolsPathsToBuildData(buildData, toolsPathsData)
+
+ templateBuildData = json.loads(tmpStr.buildDataTemplate)
+ buildData = utils.mergeCurrentDataWithTemplate(buildData, templateBuildData)
+
+ buildData = paths.verifyTargetConfigurationPaths(buildData, request)
+ buildData = paths.copyTargetConfigurationFiles(buildData)
+
+ return buildData
+
+ def checkToolsPathFile(self):
+ '''
+ Returns True if 'toolsPaths.json' file exists and is a valid JSON file.
+ If it is not a valid JSON, delete it and return False.
+ '''
+ if utils.pathExists(utils.toolsPaths):
+ # file exists, check if it loads OK
+ try:
+ with open(utils.toolsPaths, 'r') as toolsFileHandler:
+ json.load(toolsFileHandler)
+ print("Valid 'toolsPaths.json' file found.")
+ return True
+
+ except Exception as err:
+ errorMsg = "Invalid 'toolsPaths.json' file. Error:\n" + str(err)
+ print(errorMsg)
+
+ try:
+ os.remove(utils.toolsPaths)
+ msg = "\tDeleted. New 'toolsPaths.json' will be created on first workspace update."
+ print(msg)
+ except Exception as err:
+ errorMsg = "Error deleting 'toolsPaths.json'. Error:\n" + str(err)
+ utils.printAndQuit(errorMsg)
+
+ # else: toolsPaths.json does not exist
+ return False
+
+ def checkBuildDataFile(self):
+ '''
+ This function makes sure 'buildData.json' is available.
+ If existing 'buildData.json' file is a valid JSON, it returns immediately.
+ If it is not a valid JSON file OR it does not exist, new 'buildData.json' file is created from template.
+
+ Note: There is no backup file for buildData.json, since it is always regenerated on Update task.
+ '''
+ if utils.pathExists(utils.buildDataPath):
+ # file exists, check if it loads OK
+ try:
+ with open(utils.buildDataPath, 'r') as buildDataFileHandler:
+ json.load(buildDataFileHandler)
+ print("Valid 'buildData.json' file found.")
+
+ return
+
+ except Exception as err:
+ errorMsg = "Invalid 'buildData.json' file. Error:\n" + str(err)
+ print(errorMsg)
+
+ try:
+ os.remove(utils.buildDataPath)
+ msg = "\tDeleted. New 'buildData.json' will be created on first workspace update."
+ print(msg)
+ except Exception as err:
+ errorMsg = "Error deleting 'buildData.json'. Error:\n" + str(err)
+ utils.printAndQuit(errorMsg)
+
+ # else: buildData.json does not exist
+ self.createBuildDataFile()
+
+ def createUserToolsFile(self, toolsPaths):
+ '''
+ Create 'toolsPaths.json' file with current tools paths.
+ This pats are absolute and not project-specific.
+ '''
+ data = json.loads(tmpStr.toolsPathsTemplate)
+ try:
+ data["VERSION"] = __version__
+ data["LAST_RUN"] = str(datetime.datetime.now())
+
+ for path in self.bStr.toolsPaths:
+ data[path] = toolsPaths[path]
+
+ data = json.dumps(data, indent=4, sort_keys=False)
+ with open(utils.toolsPaths, 'w+') as toolsPathsFile:
+ toolsPathsFile.write(data)
+ print("'toolsPaths.json' file updated!")
+
+ except Exception as err:
+ errorMsg = "Exception error overwriting 'toolsPaths.json' file:\n"
+ errorMsg += str(err)
+ print("WARNING:", errorMsg)
+
+ def createBuildDataFile(self):
+ '''
+ Create fresh 'buildData.json' file.
+ '''
+ try:
+ data = json.loads(tmpStr.buildDataTemplate)
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+
+ with open(utils.buildDataPath, 'w+') as buildDataFile:
+ buildDataFile.truncate()
+ buildDataFile.write(dataToWrite)
+
+ print("New template 'buildData.json' file created.")
+ except Exception as err:
+ errorMsg = "Exception error creating new 'buildData.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def getToolsPathsData(self):
+ '''
+ Get data from current 'toolsPaths.json' file.
+ File existance is previoulsy checked in 'checkToolsPathFile()'.
+ '''
+ with open(utils.toolsPaths, 'r') as toolsPathsFile:
+ data = json.load(toolsPathsFile)
+
+ return data
+
+ def getBuildData(self):
+ '''
+ Get data from current 'buildData.json' file.
+ File existance is previoulsy checked in 'checkBuildDataFile()'.
+ '''
+ with open(utils.buildDataPath, 'r') as buildDataFile:
+ data = json.load(buildDataFile)
+
+ return data
+
+ def addToolsPathsToBuildData(self, buildData, toolsPaths):
+ '''
+ Get tools paths from 'toolsPaths.json' and add it to buildData
+ Returns new data.
+ '''
+ allToolsPaths = []
+ allToolsPaths.extend(self.bStr.toolsPaths)
+ allToolsPaths.extend(self.bStr.derivedPaths)
+ for path in allToolsPaths:
+ try:
+ buildData[path] = toolsPaths[path]
+ except Exception as err:
+ errorMsg = "Missing '" + path + "' key in tools paths data:\n" + str(toolsPaths)
+ print("Warning:", errorMsg)
+
+ return buildData
+
+ def addMakefileDataToBuildDataFile(self, buildData, makefileData):
+ '''
+ This function fills buildData.json file with data from 'Makefile'.
+ Returns new data.
+ '''
+ # sources
+ cSources = makefileData[self.mkfStr.cSources]
+ buildData[self.bStr.cSources] = cSources
+
+ asmSources = makefileData[self.mkfStr.asmSources]
+ buildData[self.bStr.ldSources] = asmSources
+
+ ldSources = makefileData[self.mkfStr.ldSources]
+ buildData[self.bStr.ldSources] = ldSources
+
+ # includes
+ cIncludes = makefileData[self.mkfStr.cIncludes]
+ buildData[self.bStr.cIncludes] = cIncludes
+
+ asmIncludes = makefileData[self.mkfStr.asmIncludes]
+ buildData[self.bStr.asmIncludes] = asmIncludes
+
+ ldIncludes = makefileData[self.mkfStr.ldIncludes]
+ buildData[self.bStr.ldIncludes] = ldIncludes
+
+ # defines
+ cDefines = makefileData[self.mkfStr.cDefines]
+ buildData[self.bStr.cDefines] = cDefines
+
+ asmDefines = makefileData[self.mkfStr.asmDefines]
+ buildData[self.bStr.asmDefines] = asmDefines
+
+ # compiler flags and paths
+ cFlags = makefileData[self.mkfStr.cFlags]
+ buildData[self.bStr.cFlags] = cFlags
+
+ asmFlags = makefileData[self.mkfStr.asmFlags]
+ buildData[self.bStr.asmFlags] = asmFlags
+
+ ldFlags = makefileData[self.mkfStr.ldFlags]
+ buildData[self.bStr.ldFlags] = ldFlags
+
+ # build folder must be always inside workspace folder
+ buildDirPath = makefileData[self.mkfStr.buildDir]
+ buildData[self.bStr.buildDirPath] = buildDirPath
+
+ # Target executable '.elf' file
+ projectName = makefileData[self.mkfStr.projectName]
+ targetExecutablePath = utils.getBuildElfFilePath(buildDirPath, projectName)
+ buildData[self.bStr.targetExecutablePath] = targetExecutablePath
+
+ return buildData
+
+ def addCubeMxProjectPathToBuildData(self, buildData):
+ '''
+ If utils.cubeMxProjectFilePath is not None, add/update 'cubeMxProjectPath' field to 'buildData.json'.
+ '''
+ if utils.cubeMxProjectFilePath is not None:
+ buildData[self.bStr.cubeMxProjectPath] = utils.cubeMxProjectFilePath
+ else:
+ buildData.pop(self.bStr.cubeMxProjectPath)
+ return buildData
+
+ def overwriteBuildDataFile(self, data):
+ '''
+ Overwrite existing 'buildData.json' file with new data.
+ '''
+ try:
+ with open(utils.buildDataPath, 'r+') as buildDataFile:
+ data["VERSION"] = __version__
+ data["LAST_RUN"] = str(datetime.datetime.now())
+
+ buildDataFile.seek(0)
+ buildDataFile.truncate()
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+ buildDataFile.write(dataToWrite)
+
+ print("'buildData.json' file updated!")
+
+ except Exception as err:
+ errorMsg = "Exception error overwriting 'buildData.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ utils.verifyFolderStructure()
+
+ paths = pth.UpdatePaths()
+ makefile = mkf.Makefile()
+ bData = BuildData()
+
+ # Makefile must exist - # point in continuing if Makefile does not exist
+ makefile.checkMakefileFile()
+
+ # build data (update tools paths if neccessary)
+ buildData = bData.prepareBuildData()
+
+ # data from current Makefile
+ makeExePath = buildData[bData.bStr.buildToolsPath]
+ gccExePath = buildData[bData.bStr.gccExePath]
+ makefileData = makefile.getMakefileData(makeExePath, gccExePath)
+
+ # try to add CubeMX project file path
+ buildData = bData.addCubeMxProjectPathToBuildData(buildData)
+
+ buildData = bData.addMakefileDataToBuildDataFile(buildData, makefileData)
+
+ bData.overwriteBuildDataFile(buildData)
diff --git a/sw/ideScripts/updateLaunchConfig.py b/sw/ideScripts/updateLaunchConfig.py
new file mode 100755
index 0000000..5930c02
--- /dev/null
+++ b/sw/ideScripts/updateLaunchConfig.py
@@ -0,0 +1,198 @@
+'''
+Update/generate 'launch.json' file in .vscode subfolder.
+'''
+import os
+import json
+
+import utilities as utils
+import templateStrings as tmpStr
+
+import updatePaths as pth
+import updateBuildData as build
+
+__version__ = utils.__version__
+
+
+class LaunchConfigurations():
+ def __init__(self):
+ self.bStr = build.BuildDataStrings()
+
+ def checkLaunchFile(self):
+ '''
+ Check if 'launch.json' file exists. If it does, check if it is a valid JSON file.
+ If it doesn't exist, create new according to template.
+ '''
+ if utils.pathExists(utils.launchPath):
+ # file exists, check if it loads OK
+ try:
+ with open(utils.launchPath, 'r') as launchFile:
+ json.load(launchFile)
+
+ print("Existing 'launch.json' file found.")
+ return
+
+ except Exception as err:
+ errorMsg = "Invalid 'launch.json' file. Creating backup and new one.\n"
+ errorMsg += "Possible cause: invalid json format or comments (not supported by this scripts). Error:\n"
+ errorMsg += str(err)
+ print(errorMsg)
+
+ utils.copyAndRename(utils.launchPath, utils.launchBackupPath)
+
+ self.createLaunchFile()
+
+ else: # 'launch.json' file does not exist jet, create it according to template string
+ self.createLaunchFile()
+
+ def createLaunchFile(self):
+ '''
+ Create fresh 'launch.json' file.
+ '''
+ try:
+ with open(utils.launchPath, 'w') as launchFile:
+ data = json.loads(tmpStr.launchFileTemplate)
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+
+ launchFile.seek(0)
+ launchFile.truncate()
+ launchFile.write(dataToWrite)
+
+ print("New 'launch.json' file created.")
+
+ except Exception as err:
+ errorMsg = "Exception error creating new 'launch.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def getLaunchData(self):
+ '''
+ Get data from current 'launch.json' file.
+ File existance is previoulsy checked in 'checkLaunchFile()'.
+ '''
+ with open(utils.launchPath, 'r') as launchFile:
+ data = json.load(launchFile)
+
+ return data
+
+ def overwriteLaunchFile(self, data):
+ '''
+ Overwrite existing 'launch.json' file with new data.
+ '''
+ try:
+ with open(utils.launchPath, 'r+') as launchFile:
+ launchFile.seek(0)
+ launchFile.truncate()
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+ launchFile.write(dataToWrite)
+
+ print("'launch.json' file updated!")
+
+ except Exception as err:
+ errorMsg = "Exception error overwriting 'launch.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def addOrReplaceLaunchConfiguration(self, data, launchData):
+ '''
+ Check wether launch with this "name" already exists. If it doesn't, create new launch configuration, overwrite otherwise.
+ '''
+ thisConfigurationName = launchData["name"]
+
+ configurationExist = False
+ listOfConfigurations = data["configurations"]
+ for configurationIndex, config in enumerate(listOfConfigurations):
+ if config["name"] == thisConfigurationName:
+ # launch with this name already exist, replace it's content
+ data["configurations"][configurationIndex] = launchData
+ configurationExist = True
+
+ if not configurationExist:
+ data["configurations"].append(launchData)
+
+ return data
+
+ def addAllLaunchConfigurations(self, launchData):
+ '''
+ Merge and return all combined launch configuration data.
+ '''
+ launchCfg = self.getDebugLaunchConfig()
+ launchData = self.addOrReplaceLaunchConfiguration(launchData, launchCfg)
+
+ launchCfg = self.getRunPythonLaunchConfig()
+ launchData = self.addOrReplaceLaunchConfiguration(launchData, launchCfg)
+
+ # TODO USER: User can add other launch configurations here
+ # - copy any of getXLaunchConfig() functions below, edit
+ # - add this function here as other launch configurations above
+
+ return launchData
+
+ ########################################################################################################################
+
+ ########################################################################################################################
+ def getDebugLaunchConfig(self):
+ '''
+ Create/repair 'Cortex debug' launch configuration.
+ '''
+ configurationData = """
+ {
+ "name": "will be replaced with templateStrings string",
+ "type": "cortex-debug",
+ "request": "launch",
+ "servertype": "openocd",
+ "cwd": "${workspaceFolder}",
+ "executable": "will be replaced with path from buildData.json",
+ "svdFile": "will be replaced with path from buildData.json",
+ "configFiles": ["will be replaced with path from buildData.json"],
+ "preLaunchTask": "will be replaced with templateStrings string"
+ }
+ """
+ jsonConfigurationData = json.loads(configurationData)
+
+ buildData = build.BuildData().getBuildData()
+
+ jsonConfigurationData["name"] = tmpStr.launchName_Debug
+ jsonConfigurationData["executable"] = buildData[self.bStr.targetExecutablePath]
+ jsonConfigurationData["svdFile"] = buildData[self.bStr.stm32SvdPath]
+ jsonConfigurationData["configFiles"] = [buildData[self.bStr.openOcdInterfacePath]]
+ jsonConfigurationData["configFiles"].extend(buildData[self.bStr.openOcdConfig])
+ jsonConfigurationData["preLaunchTask"] = tmpStr.taskName_build
+
+ return jsonConfigurationData
+
+ def getRunPythonLaunchConfig(self):
+ '''
+ Create 'Debug current Python file' launch configuration.
+ '''
+ configurationData = """
+ {
+ "name": "Debug current Python file",
+ "type": "python",
+ "request": "launch",
+ "cwd": "${workspaceFolder}",
+ "program": "${file}",
+ "console": "integratedTerminal"
+ }
+ """
+ jsonConfigurationData = json.loads(configurationData)
+
+ return jsonConfigurationData
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ utils.verifyFolderStructure()
+
+ paths = pth.UpdatePaths()
+ bData = build.BuildData()
+ launch = LaunchConfigurations()
+
+ # build data (update tools paths if neccessary)
+ buildData = bData.prepareBuildData()
+
+ # create taks file
+ launch.checkLaunchFile()
+ launchData = launch.getLaunchData()
+ launchData = launch.addAllLaunchConfigurations(launchData)
+
+ launch.overwriteLaunchFile(launchData)
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
diff --git a/sw/ideScripts/updatePaths.py b/sw/ideScripts/updatePaths.py
new file mode 100755
index 0000000..7599f39
--- /dev/null
+++ b/sw/ideScripts/updatePaths.py
@@ -0,0 +1,273 @@
+'''
+This script can be run to update paths to gcc, openOCD and other tools/files/folders.
+Script verify and add data to 'buildData.json' file.
+'''
+import os
+import shutil
+
+import utilities as utils
+import updateBuildData as build
+import updateTasks as tasks
+import updateLaunchConfig as launch
+import updateWorkspaceFile as workspaceFile
+
+__version__ = utils.__version__
+
+
+class UpdatePaths():
+ def __init__(self):
+ self.bStr = build.BuildDataStrings()
+
+ # list of paths with explanatory names and (optionally) default path
+ # keys must match with 'self.bStr.toolsPaths' list
+ self.pathsDescriptionsData = {
+ self.bStr.gccExePath: {
+ "name": "arm-none-eabi-gcc executable (arm-none-eabi-gcc.exe)",
+ "defaultPath": "arm-none-eabi-gcc"},
+ self.bStr.buildToolsPath: {
+ "name": "make executable (make.exe)",
+ "defaultPath": "make"},
+ self.bStr.openOcdPath: {
+ "name": "OpenOCD executable (openocd.exe)",
+ "defaultPath": "openocd"},
+ self.bStr.openOcdInterfacePath: {
+ "name": "OpenOCD ST Link interface path ('stlink.cfg')",
+ "defaultPath": "./scripts/interface/stlink.cfg"},
+ self.bStr.stm32SvdPath: {
+ "name": "STM target '*.svd' file (.../Keil*/CMSIS/SVD/STM32F0x1.svd)",
+ "defaultPath": None}
+ }
+
+ def verifyToolsPaths(self, toolsPaths, request=False):
+ '''
+ This function checks if paths in 'toolsPaths.json' are a valid paths.
+ If any path is not valid/missing, user is asked for update via updatePath().
+ If 'request' is set to True, user is asked to update path even if it is a valid path.
+
+ Returns updated valid tools paths.
+ '''
+ for pathName in self.bStr.toolsPaths:
+ try:
+ mustBeUpdated = False
+ if pathName in toolsPaths:
+ # 'toolsPaths.json' keys are not lists. Always a plain path (string)
+ if not utils.pathExists(toolsPaths[pathName]):
+ mustBeUpdated = True
+ # path not valid, check if command
+ if utils.commandExists(toolsPaths[pathName]):
+ mustBeUpdated = False
+
+ if mustBeUpdated:
+ if toolsPaths[pathName] != '':
+ # avoid reporting invalid file path, if there is an empty string
+ msg = "\n\nInvalid path detected in '" + pathName + "' key."
+ print(msg)
+ else:
+ if request:
+ msg = "\n\nValid path(s) for " + pathName + " detected: '" + toolsPaths[pathName] + "'."
+ msg += "\n\tUpdate? [y/n]: "
+ if utils.getYesNoAnswer(msg):
+ mustBeUpdated = True
+
+ else: # this key is missing in toolsPaths.json!
+ mustBeUpdated = True
+
+ if mustBeUpdated:
+ if pathName in self.bStr.derivedPaths:
+ continue
+
+ elif pathName == self.bStr.openOcdConfig:
+ # get openOcdConfig - special handler
+ toolsPaths[pathName] = utils.getOpenOcdConfig(toolsPaths[self.bStr.openOcdPath])
+
+ elif pathName in self.pathsDescriptionsData:
+ name = self.pathsDescriptionsData[pathName]['name']
+ defaultPath = self.pathsDescriptionsData[pathName]['defaultPath']
+ toolsPaths[pathName] = self.updatePath(name, defaultPath)
+
+ else:
+ toolsPaths[pathName] = self.updatePath(pathName, None)
+
+ except Exception as err:
+ toolsPaths[pathName] = self.updatePath(pathName, None)
+
+ for pathName in self.bStr.derivedPaths:
+ if pathName == self.bStr.pythonExec:
+ toolsPaths[self.bStr.pythonExec] = utils.getPython3Executable()
+
+ elif pathName == self.bStr.gccInludePath:
+ toolsPaths[self.bStr.gccInludePath] = utils.getGccIncludePath(toolsPaths[self.bStr.gccExePath])
+
+ else:
+ errorMsg = "ideScripts design error: pathName '" + pathName + "' is in 'self.bStr.derivedPaths' list, "
+ errorMsg += "but no 'get()' handler is specified."
+ utils.printAndQuit(errorMsg)
+
+ return toolsPaths
+
+ def verifyTargetConfigurationPaths(self, buildData, request=False):
+ '''
+ This function checks if 'buildData.json' contains targetConfiguration paths.
+ If any path is not valid/missing, user is asked for update via updatePath().
+ If 'request' is set to True, user is asked to update path even if it is a valid path.
+
+ Returns buildData with a valid, updated tools paths.
+ '''
+ for pathName in self.bStr.targetConfigurationPaths:
+ mustBeUpdated = False
+
+ if pathName in self.bStr.derivedPaths:
+ # derived paths, build later
+ continue
+
+ if pathName not in buildData:
+ mustBeUpdated = True
+
+ else:
+ if isinstance(buildData[pathName], list):
+ if not buildData[pathName]:
+ mustBeUpdated = True
+ else:
+ for path in buildData[pathName]:
+ if not utils.pathExists(path):
+ mustBeUpdated = True
+ break
+
+ else: # not a list, a single path expected
+ if not utils.pathExists(buildData[pathName]):
+ mustBeUpdated = True
+ # path not valid, check if command
+ if utils.commandExists(buildData[pathName]):
+ mustBeUpdated = False
+
+ if mustBeUpdated:
+ notify = True
+ # avoid reporting invalid file path, if there is an empty string/list
+ if isinstance(buildData[pathName], list):
+ if not buildData[pathName]:
+ notify = False
+ else:
+ if buildData[pathName] == '':
+ notify = False
+
+ if notify:
+ msg = "\n\nInvalid path detected in 'buildData.json' '" + pathName + "' key."
+ print(msg)
+ else:
+ if request:
+ msg = "\n\nValid path(s) for " + pathName + " detected: '" + str(buildData[pathName]) + "'."
+ msg += "\n\tUpdate? [y/n]: "
+ if utils.getYesNoAnswer(msg):
+ mustBeUpdated = True
+
+ if mustBeUpdated:
+ if pathName == self.bStr.openOcdConfig:
+ # get openOcdConfig - special handler
+ buildData[pathName] = utils.getOpenOcdConfig(buildData[self.bStr.openOcdPath])
+
+ elif pathName in self.bStr.derivedPaths:
+ name = self.bStr.derivedPaths[pathName]['name']
+ defaultPath = self.bStr.derivedPaths[pathName]['defaultPath']
+ buildData[pathName] = self.updatePath(name, defaultPath)
+
+ else:
+ buildData[pathName] = self.updatePath(pathName, None)
+
+ return buildData
+
+ def copyTargetConfigurationFiles(self, buildData):
+ '''
+ This function checks if paths to target configuration files listed in 'BuildDataStrings.targetConfigurationPaths'
+ are available, stored inside this workspace '.vscode' subfolder. Once this files are copied, paths are updated and
+ new buildData is returned.
+
+ Paths are previously checked/updated in 'verifyTargetConfigurationPaths()'
+ '''
+ for pathName in self.bStr.targetConfigurationPaths:
+ currentPaths = buildData[pathName]
+
+ if isinstance(currentPaths, list):
+ isList = True
+ else:
+ isList = False
+ currentPaths = [currentPaths]
+
+ newPaths = []
+ for currentPath in currentPaths:
+ fileName = utils.getFileName(currentPath, withExtension=True)
+ fileInVsCodeFolder = os.path.join(utils.vsCodeFolderPath, fileName)
+
+ if not utils.pathExists(fileInVsCodeFolder):
+ # file does not exist in '.vscode' folder
+ try:
+ newPath = shutil.copy(currentPath, utils.vsCodeFolderPath)
+ except Exception as err:
+ errorMsg = "Unable to copy file '" + fileName + "' to '.vscode' folder. Exception:\n" + str(err)
+ utils.printAndQuit(errorMsg)
+
+ newPath = os.path.relpath(fileInVsCodeFolder)
+ newPath = utils.pathWithForwardSlashes(newPath)
+ newPaths.append(newPath)
+
+ if isList:
+ buildData[pathName] = newPaths
+ else:
+ buildData[pathName] = newPaths[0]
+
+ return buildData
+
+ def updatePath(self, pathName, default):
+ '''
+ This function is called when a path is detected as invalid or the user requests to update paths.
+ '''
+ pathDefault = None
+
+ # check if default is a path
+ if utils.pathExists(default):
+ pathDefault = default
+
+ # not a path command, check if it's a command
+ elif utils.commandExists(default):
+ pathDefault = shutil.which(default)
+
+ if pathDefault is not None:
+ msg = "\n\tDefault path to '" + pathName + "' detected at '" + pathDefault + "'\n\tUse this path? [y/n]: "
+ if utils.getYesNoAnswer(msg):
+ return pathDefault
+
+ # default not detected or user wants custom path/command
+ newPath = utils.getUserPath(pathName)
+ return newPath
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ utils.verifyFolderStructure()
+
+ paths = UpdatePaths()
+ bData = build.BuildData()
+ tasks = tasks.Tasks()
+ launch = launch.LaunchConfigurations()
+ wksFile = workspaceFile.UpdateWorkspaceFile()
+
+ # update build data
+ buildData = bData.prepareBuildData(request=True)
+ bData.overwriteBuildDataFile(buildData)
+
+ # update tasks
+ tasks.checkTasksFile()
+ tasksData = tasks.getTasksData()
+ tasksData = tasks.addAllTasks(tasksData)
+ tasks.overwriteTasksFile(tasksData)
+
+ # update launch configurations
+ launch.checkLaunchFile()
+ launchData = launch.getLaunchData()
+ launchData = launch.addAllLaunchConfigurations(launchData)
+ launch.overwriteLaunchFile(launchData)
+
+ # update workspace file with "cortex-debug" specifics
+ wksFile.checkWorkspaceFile()
+ wksData = wksFile.getWorkspaceFileData()
+ wksData = wksFile.addBuildDataToWorkspaceFile(wksData, buildData)
+ wksFile.overwriteWorkspaceFile(wksData)
diff --git a/sw/ideScripts/updateTasks.py b/sw/ideScripts/updateTasks.py
new file mode 100755
index 0000000..16e29e0
--- /dev/null
+++ b/sw/ideScripts/updateTasks.py
@@ -0,0 +1,564 @@
+'''
+Update/generate 'tasks.json' file in .vscode subfolder.
+
+'tasks.json' fields description:
+https://code.visualstudio.com/docs/editor/tasks
+'''
+import os
+import json
+
+import utilities as utils
+import templateStrings as tmpStr
+
+import updatePaths as pth
+import updateWorkspaceSources as wks
+import updateMakefile as mkf
+import updateBuildData as build
+
+__version__ = utils.__version__
+
+
+class Tasks():
+ def __init__(self):
+ self.cPStr = wks.CPropertiesStrings()
+ self.mkfStr = mkf.MakefileStrings()
+ self.bStr = build.BuildDataStrings()
+
+ def checkTasksFile(self):
+ '''
+ Check if 'tasks.json' file exists. If it does, check if it is a valid JSON file.
+ If it doesn't exist, create new according to template.
+ '''
+ if utils.pathExists(utils.tasksPath):
+ # file exists, check if it loads OK
+ try:
+ with open(utils.tasksPath, 'r') as tasksFile:
+ json.load(tasksFile)
+
+ print("Existing 'tasks.json' file found.")
+ return
+
+ except Exception as err:
+ errorMsg = "Invalid 'tasks.json' file. Creating backup and new one.\n"
+ errorMsg += "Possible cause: invalid json format or comments (not supported by this scripts). Error:\n"
+ errorMsg += str(err)
+ print(errorMsg)
+
+ utils.copyAndRename(utils.tasksPath, utils.tasksBackupPath)
+
+ self.createTasksFile()
+
+ else: # 'tasks.json' file does not exist jet, create it according to template string
+ self.createTasksFile()
+
+ def createTasksFile(self):
+ '''
+ Create fresh 'tasks.json' file.
+ '''
+ try:
+ with open(utils.tasksPath, 'w') as tasksFile:
+ data = json.loads(tmpStr.tasksFileTemplate)
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+
+ tasksFile.seek(0)
+ tasksFile.truncate()
+ tasksFile.write(dataToWrite)
+
+ print("New 'tasks.json' file created.")
+
+ except Exception as err:
+ errorMsg = "Exception error creating new 'tasks.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def getTasksData(self):
+ '''
+ Get data from current 'tasks.json' file.
+ File existance is previoulsy checked in 'checkTasksFile()'.
+ '''
+ with open(utils.tasksPath, 'r') as tasksFile:
+ data = json.load(tasksFile)
+
+ return data
+
+ def overwriteTasksFile(self, data):
+ '''
+ Overwrite existing 'tasks.json' file with new data.
+ '''
+ try:
+ with open(utils.tasksPath, 'r+') as tasksFile:
+ tasksFile.seek(0)
+ tasksFile.truncate()
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+ tasksFile.write(dataToWrite)
+
+ print("'tasks.json' file updated!")
+
+ except Exception as err:
+ errorMsg = "Exception error overwriting 'tasks.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def addOrReplaceTask(self, data, taskData):
+ '''
+ Check wether tasks with this "label" already exists. If it doesn't, create new task, overwrite otherwise.
+ '''
+ thisTaskName = taskData["label"]
+
+ taskExist = False
+ listOfTasks = data["tasks"]
+ for taskIndex, task in enumerate(listOfTasks):
+ if task["label"] == thisTaskName:
+ # task with this name already exist, replace it's content
+ data["tasks"][taskIndex] = taskData
+ taskExist = True
+
+ if not taskExist:
+ data["tasks"].append(taskData)
+
+ return data
+
+ def addAllTasks(self, tasksData):
+ '''
+ Merge and return all combined tasks data.
+ '''
+
+ # building and compiling project tasks
+ task = self.getBuildTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ task = self.getCompileTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ task = self.getDeleteBuildFolderTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ # debugging and target control tasts
+ task = self.getBuildDownloadAndRunTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ task = self.getDownloadAndRunTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ task = self.getResetAndRunTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ task = self.getHaltTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ task = self.getRunTask()
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ # update IDE workspace tasks
+ task = self.getRunCurrentPythonFileTask() # common "run python file" task
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ if utils.cubeMxProjectFilePath is not None:
+ task = self.getOpenCubeMXTask() # open CubeMX project
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ task = self.getUpdateTask() # update all files for VS Code so it can be used as IDE
+ tasksData = self.addOrReplaceTask(tasksData, task)
+
+ # TODO USER: User can add other custom tasks here
+ # - copy any of getXTask() functions below, edit
+ # - add this function here as other tasks above
+
+ return tasksData
+
+ ########################################################################################################################
+ # Build, compile and clean tasks
+ ########################################################################################################################
+
+ def getBuildTask(self):
+ '''
+ Add build task (execute 'make' command). Also the VS Code default 'build' task.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ },
+ "type": "shell",
+ "command": "specified below",
+ "args": ["specified below"],
+ "problemMatcher": {
+ "pattern": {
+ "regexp": "^(.*):(\\\\d+):(\\\\d+):\\\\s+(warning|error):\\\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ },
+ "presentation": {
+ "focus": true
+ }
+ }
+ """
+ jsonTaskData = json.loads(taskData)
+
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData["label"] = tmpStr.taskName_build
+ jsonTaskData["command"] = buildData[self.bStr.buildToolsPath]
+
+ gccFolderPath = os.path.dirname(buildData[self.bStr.gccExePath])
+ gccFolderPath = utils.pathWithForwardSlashes(gccFolderPath)
+ jsonTaskData["args"] = ["GCC_PATH=" + gccFolderPath] # specify compiler path to make command
+
+ numOfCores = os.cpu_count()
+ parallelJobsNumber = int(numOfCores * 1.5) # https://stackoverflow.com/questions/15289250/make-j4-or-j8/15295032
+ parallelJobsStr = "-j" + str(parallelJobsNumber)
+ jsonTaskData["args"].append(parallelJobsStr) # set 'make' parallel job execution
+
+ return jsonTaskData
+
+ def getCompileTask(self):
+ '''
+ Create compile current file task (execute gcc compile command).
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "will be replaced with GCC path below",
+ "args": ["will be replaced with path from buildData.json"],
+ "problemMatcher": {
+ "pattern": {
+ "regexp": "^(.*):(\\\\d+):(\\\\d+):\\\\s+(warning|error):\\\\s+(.*)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ },
+ "presentation": {
+ "focus": true
+ }
+ }
+ """
+ jsonTaskData = json.loads(taskData)
+
+ # get compiler C flags, defines, includes, ... from 'buildData.json'
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData["label"] = tmpStr.taskName_compile
+
+ # defines
+ cDefines = buildData[self.bStr.cDefines]
+ cDefines = utils.preappendString(cDefines, '-D')
+
+ # includes
+ cIncludes = buildData[self.bStr.cIncludes]
+ cIncludes = utils.preappendString(cIncludes, '-I')
+
+ # build directory
+ buildDir = buildData[self.bStr.buildDirPath]
+
+ # c flags
+ cFlags = buildData[self.bStr.cFlags]
+ for flagIndex, flag in enumerate(cFlags):
+ if flag == "-MF":
+ newFlagString = "-MF'" + buildDir + "/${fileBasenameNoExtension}.d'"
+ cFlags[flagIndex] = newFlagString
+ continue
+
+ # output file
+ outputFilePath = "'" + buildDir + "/${fileBasenameNoExtension}.o'"
+ outputFile = ["-o"]
+ outputFile.append(outputFilePath)
+
+ # compile file string
+ fileString = "'${relativeFile}'"
+ fileString = [fileString]
+
+ jsonTaskData["command"] = buildData[self.bStr.gccExePath]
+ jsonTaskData["args"] = ["-c"] # only compile switch
+ jsonTaskData["args"].extend(cDefines)
+ jsonTaskData["args"].extend(cIncludes)
+ jsonTaskData["args"].extend(cFlags)
+ jsonTaskData["args"].extend(fileString)
+ jsonTaskData["args"].extend(outputFile)
+
+ return jsonTaskData
+
+ def getDeleteBuildFolderTask(self):
+ '''
+ Create delete task (execute 'make clean' command).
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": ["clean"],
+ "problemMatcher": [],
+ "presentation": {
+ "focus": false
+ }
+ }
+ """
+ jsonTaskData = json.loads(taskData)
+
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData["label"] = tmpStr.taskName_clean
+ jsonTaskData["command"] = buildData[self.bStr.buildToolsPath]
+
+ return jsonTaskData
+
+ ########################################################################################################################
+ # Debugging and target control tasks
+ ########################################################################################################################
+ def getBuildDownloadAndRunTask(self):
+ '''
+ Create Build + Download and run task. Use 'dependsOn' feature to avoid doubling code.
+ Note: If multiple 'dependOn' tasks are defined, these tasks are launched simultaneously,
+ not chained one after another.
+ '''
+ jsonTaskData = self.getDownloadAndRunTask()
+
+ jsonTaskData["label"] = tmpStr.taskName_CPU_buildDownloadRun
+ jsonTaskData["dependsOn"] = tmpStr.taskName_build
+
+ return jsonTaskData
+
+ def getDownloadAndRunTask(self):
+ '''
+ Create Download and run task.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": ["specified below"],
+ "problemMatcher": []
+ }
+ """
+ jsonTaskData = json.loads(taskData)
+
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData["label"] = tmpStr.taskName_CPU_downloadRun
+ jsonTaskData["command"] = buildData[self.bStr.openOcdPath]
+ jsonTaskData["args"] = []
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath])
+ for arg in buildData[self.bStr.openOcdConfig]:
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(arg)
+
+ # -c program filename [verify] [reset] [exit] [offset] ([] are optional arguments)
+ # Note: due problems with VS Code OpenOCD Tasks in case of workspace path containing spaces, target executable is passed
+ # as relative path.
+ workspacePath = utils.workspacePath
+ jsonTaskData["args"].append("-c")
+ programString = "program " + buildData[self.bStr.targetExecutablePath] + " verify reset exit"
+ jsonTaskData["args"].append(programString)
+
+ return jsonTaskData
+
+ def getResetAndRunTask(self):
+ '''
+ Create CPU: Reset and run task.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": ["specified below"],
+ "problemMatcher": []
+ }
+ """
+ jsonTaskData = json.loads(taskData)
+
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData["label"] = tmpStr.taskName_CPU_resetRun
+ jsonTaskData["command"] = buildData[self.bStr.openOcdPath]
+ jsonTaskData["args"] = []
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath])
+ for arg in buildData[self.bStr.openOcdConfig]:
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(arg)
+ jsonTaskData["args"].append("-c init") # init must be executed before other commands!
+ jsonTaskData["args"].append("-c reset")
+ jsonTaskData["args"].append("-c exit")
+
+ return jsonTaskData
+
+ def getHaltTask(self):
+ '''
+ Create Halt/stop task.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": ["specified below"],
+ "problemMatcher": []
+ }
+ """
+ jsonTaskData = json.loads(taskData)
+
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData["label"] = tmpStr.taskName_CPU_halt
+ jsonTaskData["command"] = buildData[self.bStr.openOcdPath]
+ jsonTaskData["args"] = []
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath])
+ for arg in buildData[self.bStr.openOcdConfig]:
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(arg)
+
+ jsonTaskData["args"].append("-c init") # init must be executed before other commands!
+ jsonTaskData["args"].append("-c halt")
+ jsonTaskData["args"].append("-c exit")
+
+ return jsonTaskData
+
+ def getRunTask(self):
+ '''
+ Create Run task.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": ["specified below"],
+ "problemMatcher": []
+ }
+ """
+ jsonTaskData = json.loads(taskData)
+
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData["label"] = tmpStr.taskName_CPU_run
+ jsonTaskData["command"] = buildData[self.bStr.openOcdPath]
+ jsonTaskData["args"] = []
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(buildData[self.bStr.openOcdInterfacePath])
+ for arg in buildData[self.bStr.openOcdConfig]:
+ jsonTaskData["args"].append("-f")
+ jsonTaskData["args"].append(arg)
+
+ jsonTaskData["args"].append("-c init") # init must be executed before other commands!
+ jsonTaskData["args"].append("-c resume")
+ jsonTaskData["args"].append("-c exit")
+
+ return jsonTaskData
+
+ ########################################################################################################################
+ # Other tasks
+ ########################################################################################################################
+ def getRunCurrentPythonFileTask(self):
+ '''
+ Create Run Python file task, which runs current active Python file.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": [
+ "${file}"
+ ],
+ "presentation": {
+ "focus": true
+ },
+ "problemMatcher": []
+ }
+ """
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData = json.loads(taskData)
+ jsonTaskData["label"] = tmpStr.taskName_Python
+ jsonTaskData["command"] = buildData[self.bStr.pythonExec]
+
+ return jsonTaskData
+
+ def getOpenCubeMXTask(self):
+ '''
+ Create Open CubeMX project task. Starts with default program.
+
+ Method of starting CubeMX differs across systems:
+ - WIN: use standard 'start' cmd command to start default program for '.ioc' files
+ - LINUX: does not associate itself with files by default.
+ Use a program like "Main Menu" for GNOME to add CubeMX to the applications list,
+ and then it can be selected as the default program for .ioc files.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": ["specified below"],
+ "presentation": {
+ "focus": false
+ },
+ "problemMatcher": []
+ }
+ """
+ osIs = utils.detectOs()
+ if osIs == "unix":
+ openCubeCommand = "xdg-open"
+ elif osIs == "osx":
+ openCubeCommand = "/Applications/STMicroelectronics/STM32CubeMX.app/Contents/MacOs/STM32CubeMX"
+ else:
+ openCubeCommand = "start"
+
+ jsonTaskData = json.loads(taskData)
+ jsonTaskData["label"] = tmpStr.taskName_OpenCubeMX
+ jsonTaskData["command"] = openCubeCommand
+ jsonTaskData["args"] = [utils.cubeMxProjectFilePath] # opens with default program
+
+ return jsonTaskData
+
+ def getUpdateTask(self):
+ '''
+ Create Update workspace task, which runs update.py script.
+ '''
+ taskData = """
+ {
+ "label": "will be replaced with templateStrings string",
+ "type": "shell",
+ "command": "specified below",
+ "args": [
+ "${workspaceFolder}/ideScripts/update.py"
+ ],
+ "presentation": {
+ "focus": true
+ },
+ "problemMatcher": []
+ }
+ """
+ buildData = build.BuildData().getBuildData()
+ jsonTaskData = json.loads(taskData)
+ jsonTaskData["label"] = tmpStr.taskName_updateWorkspace
+ jsonTaskData["command"] = buildData[self.bStr.pythonExec]
+
+ return jsonTaskData
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ utils.verifyFolderStructure()
+
+ paths = pth.UpdatePaths()
+ bData = build.BuildData()
+ cP = wks.CProperties()
+ makefile = mkf.Makefile()
+ tasks = Tasks()
+
+ # build data (update tools paths if neccessary)
+ buildData = bData.prepareBuildData()
+
+ # create taks file
+ tasks.checkTasksFile()
+ tasksData = tasks.getTasksData()
+ tasksData = tasks.addAllTasks(tasksData)
+
+ tasks.overwriteTasksFile(tasksData)
diff --git a/sw/ideScripts/updateWorkspaceFile.py b/sw/ideScripts/updateWorkspaceFile.py
new file mode 100755
index 0000000..80a96f2
--- /dev/null
+++ b/sw/ideScripts/updateWorkspaceFile.py
@@ -0,0 +1,105 @@
+'''
+Update existing VS Code workspace file with debug paths in "settings":
+ - "cortex-debug.armToolchainPath"
+ - "cortex-debug.openocdPath"
+'''
+import os
+import json
+
+import utilities as utils
+import updatePaths as pth
+import updateBuildData as build
+
+__version__ = utils.__version__
+
+
+class UpdateWorkspaceFile():
+ def __init__(self):
+ self.bStr = build.BuildDataStrings()
+
+ def checkWorkspaceFile(self):
+ '''
+ Check if workspace '*.code-workspace' file exists. If it does, check if it is a valid JSON file.
+ If it doesn't exist report error and quit.
+ '''
+ workspaceFiles = utils.getCodeWorkspaces()
+ if len(workspaceFiles) == 1:
+ _, fileName = os.path.split(workspaceFiles[0])
+ workspaceFileName, _ = os.path.splitext(fileName)
+ if utils.pathExists(utils.workspaceFilePath):
+ # file exists, check if it loads OK
+ try:
+ with open(utils.workspaceFilePath, 'r') as workspaceFile:
+ workspaceFileData = json.load(workspaceFile)
+
+ print("Existing " + fileName + " file found.")
+
+ except Exception as err:
+ errorMsg = "Invalid " + fileName + " file.\n"
+ errorMsg += "Possible cause: invalid json format or comments (not supported by this scripts). Error:\n"
+ errorMsg += str(err)
+ print(errorMsg)
+
+ # else: verified in 'utils.verifyFolderStructure()'
+
+ def getWorkspaceFileData(self):
+ '''
+ Get data from current '*.code-workspace' file.
+ File existance is previoulsy checked in 'checkWorkspaceFile()'.
+ '''
+ with open(utils.workspaceFilePath, 'r') as workspaceFile:
+ data = json.load(workspaceFile)
+
+ return data
+
+ def addBuildDataToWorkspaceFile(self, workspaceData, buildData):
+ '''
+ This function ads "cortex-debug.*" items to workspace file, if they don't exist yet.
+ Returns new data.
+ '''
+ armToolchainPath = os.path.dirname(buildData[self.bStr.gccExePath])
+ armToolchainPath = utils.pathWithForwardSlashes(armToolchainPath)
+
+ if 'settings' not in workspaceData:
+ workspaceData["settings"] = {}
+
+ workspaceData["settings"]["cortex-debug.armToolchainPath"] = armToolchainPath
+ workspaceData["settings"]["cortex-debug.openocdPath"] = buildData[self.bStr.openOcdPath]
+
+ return workspaceData
+
+ def overwriteWorkspaceFile(self, data):
+ '''
+ Overwrite existing '*.code-workspace' file with new data.
+ '''
+ try:
+ with open(utils.workspaceFilePath, 'r+') as workspaceFile:
+ workspaceFile.seek(0)
+ workspaceFile.truncate()
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+ workspaceFile.write(dataToWrite)
+
+ print("'*.code-workspace' file updated!")
+
+ except Exception as err:
+ errorMsg = "Exception error overwriting '*.code-workspace' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ utils.verifyFolderStructure()
+
+ paths = pth.UpdatePaths()
+ bData = build.BuildData()
+ wksFile = UpdateWorkspaceFile()
+
+ # build data (update tools paths if neccessary)
+ buildData = bData.prepareBuildData()
+
+ wksFile.checkWorkspaceFile()
+ wksData = wksFile.getWorkspaceFileData()
+ wksData = wksFile.addBuildDataToWorkspaceFile(wksData, buildData)
+
+ wksFile.overwriteWorkspaceFile(wksData)
diff --git a/sw/ideScripts/updateWorkspaceSources.py b/sw/ideScripts/updateWorkspaceSources.py
new file mode 100755
index 0000000..894a393
--- /dev/null
+++ b/sw/ideScripts/updateWorkspaceSources.py
@@ -0,0 +1,222 @@
+'''
+Update/generate 'c_cpp_properties.json' file in .vscode subfolder.
+
+See details in "README_DETAILS.md'.
+
+'c_cpp_properties.json' fields description:
+https://github.com/Microsoft/vscode-cpptools/blob/master/Documentation/LanguageServer/c_cpp_properties.json.md
+'''
+import json
+
+import utilities as utils
+import templateStrings as tmpStr
+
+import updatePaths as pth
+import updateMakefile as mkf
+import updateBuildData as build
+
+__version__ = utils.__version__
+
+
+class CPropertiesStrings():
+ user_cSources = 'user_cSources'
+ user_asmSources = 'user_asmSources'
+ user_ldSources = 'user_ldSources'
+
+ user_cIncludes = 'user_cIncludes'
+ user_asmIncludes = 'user_asmIncludes'
+ user_ldIncludes = 'user_ldIncludes'
+
+ user_cDefines = 'user_cDefines'
+ user_asmDefines = 'user_asmDefines'
+
+ user_cFlags = 'user_cFlags'
+ user_asmFlags = 'user_asmFlags'
+ user_ldFlags = 'user_ldFlags'
+
+ cubemx_sourceFiles = 'cubemx_sourceFiles'
+ cubemx_includes = 'cubemx_includes'
+ cubemx_defines = 'cubemx_defines'
+ gccExePath = 'gccExePath'
+ gccIncludePath = 'gccIncludePath'
+
+
+class CProperties():
+ def __init__(self):
+ self.cPStr = CPropertiesStrings()
+ self.mkfStr = mkf.MakefileStrings()
+ self.bStr = build.BuildDataStrings()
+
+ def checkCPropertiesFile(self):
+ '''
+ Check if 'c_cpp_properties.json' file exists. If it does, check if it is a valid JSON file.
+ If it doesn't exist, create new according to template.
+ '''
+ if utils.pathExists(utils.cPropertiesPath):
+ # file exists, check if it loads OK
+ try:
+ with open(utils.cPropertiesPath, 'r') as cPropertiesFile:
+ currentData = json.load(cPropertiesFile)
+ # this is a valid json file
+ print("Existing 'c_cpp_properties.json' file found.")
+
+ # merge current 'c_cpp_properties.json' with its template
+ templateData = json.loads(tmpStr.c_cpp_template)
+ dataToWrite = utils.mergeCurrentDataWithTemplate(currentData, templateData)
+ dataToWrite = json.dumps(dataToWrite, indent=4, sort_keys=False)
+ with open(utils.cPropertiesPath, 'w') as cPropertiesFile:
+ cPropertiesFile.write(dataToWrite)
+ print("\tKeys updated according to the template.")
+ return
+
+ except Exception as err:
+ errorMsg = "Invalid 'c_cpp_properties.json' file. Creating backup and new one.\n"
+ errorMsg += "Possible cause: invalid json format or comments (not supported by this scripts). Error:\n"
+ errorMsg += str(err)
+ print(errorMsg)
+
+ utils.copyAndRename(utils.cPropertiesPath, utils.cPropertiesBackupPath)
+
+ self.createCPropertiesFile()
+
+ else: # 'c_cpp_properties.json' file does not exist jet, create it according to template string
+ self.createCPropertiesFile()
+
+ def createCPropertiesFile(self):
+ '''
+ Create fresh 'c_cpp_properties.json' file.
+ '''
+ try:
+ with open(utils.cPropertiesPath, 'w') as cPropertiesFile:
+ data = json.loads(tmpStr.c_cpp_template)
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+
+ cPropertiesFile.seek(0)
+ cPropertiesFile.truncate()
+ cPropertiesFile.write(dataToWrite)
+
+ print("New 'c_cpp_properties.json' file created.")
+
+ except Exception as err:
+ errorMsg = "Exception error creating new 'c_cpp_properties.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def getCPropertiesData(self):
+ '''
+ Get data from current 'c_cpp_properties.json' file.
+ File existance is previoulsy checked in 'checkCPropertiesFile()'.
+ '''
+ with open(utils.cPropertiesPath, 'r') as cPropertiesFile:
+ data = json.load(cPropertiesFile)
+
+ return data
+
+ def getCPropertiesKeyData(self, cPropertiesData, keyName):
+ '''
+ Try to get data of keyName field from 'c_cpp_properties.json' file.
+ Return list of data or empty list.
+ '''
+ try:
+ cPropEnvData = cPropertiesData['env']
+ return cPropEnvData[keyName]
+ except Exception as err:
+ errorMsg = "Unable to get '" + str(keyName) + "' data from 'c_cpp_properties.json' file."
+ print("WARNING:", errorMsg)
+ return []
+
+ def addMakefileDataToCPropertiesFile(self, cPropertiesData, makefileData):
+ '''
+ Add data from Makefile to 'cubemx_...' fields in 'c_cpp_properties.json' file.
+ Returns new data to be written to 'c_cpp_properties.json' file.
+ '''
+ # source files
+ sourceFiles = makefileData[self.mkfStr.cSources]
+ sourceFiles += makefileData[self.mkfStr.asmSources]
+ cPropertiesData["env"][self.cPStr.cubemx_sourceFiles] = sourceFiles
+
+ # includes
+ includes = makefileData[self.mkfStr.cIncludes]
+ # includes += makefileData[self.mkfStr.asmIncludes] # TODO Should assembler includes be included here?
+ cPropertiesData["env"][self.cPStr.cubemx_includes] = includes
+
+ # defines
+ defines = makefileData[self.mkfStr.cDefines]
+ # defines += makefileData[self.mkfStr.asmDefines] # TODO Should assembler defines be included here?
+ cPropertiesData["env"][self.cPStr.cubemx_defines] = defines
+
+ return cPropertiesData
+
+ def addBuildDataToCPropertiesFile(self, cPropertiesData, buildData):
+ '''
+ Add data from buildData to tools fields in 'c_cpp_properties.json' file.
+ Returns new data to be written to 'c_cpp_properties.json' file.
+ '''
+ # gcc
+ cPropertiesData["env"][self.cPStr.gccExePath] = buildData[self.bStr.gccExePath]
+ cPropertiesData["env"][self.cPStr.gccIncludePath] = buildData[self.bStr.gccInludePath]
+
+ return cPropertiesData
+
+ def overwriteCPropertiesFile(self, data):
+ '''
+ Overwrite existing 'c_cpp_properties.json' file with new data.
+ '''
+ try:
+ with open(utils.cPropertiesPath, 'r+') as cPropertiesFile:
+ cPropertiesFile.seek(0)
+ cPropertiesFile.truncate()
+ dataToWrite = json.dumps(data, indent=4, sort_keys=False)
+ cPropertiesFile.write(dataToWrite)
+
+ print("'c_cpp_properties.json' file updated!")
+
+ except Exception as err:
+ errorMsg = "Exception error overwriting 'c_cpp_properties.json' file:\n"
+ errorMsg += str(err)
+ utils.printAndQuit(errorMsg)
+
+ def addCustomDataToCPropertiesFile(self, cProperties, makefileData, buildData):
+ '''
+ TODO USER Add custom data to 'c_cpp_properties.json' file.
+ '''
+ cProperties["configurations"][0]["name"] = utils.getWorkspaceName()
+
+ # TODO USER can add other specific here
+ # Note: be careful not to override other parameters that are added from 'Makefile' and 'buildData.json'
+
+ return cProperties
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ utils.verifyFolderStructure()
+
+ paths = pth.UpdatePaths()
+ cP = CProperties()
+ makefile = mkf.Makefile()
+ bData = build.BuildData()
+
+ # Makefile must exist
+ makefile.checkMakefileFile() # no point in continuing if Makefile does not exist
+ makefile.restoreOriginalMakefile()
+
+ # build data (update tools paths if neccessary)
+ buildData = bData.prepareBuildData()
+
+ # data from original makefile
+ makeExePath = buildData[bData.bStr.buildToolsPath]
+ gccExePath = buildData[bData.bStr.gccExePath]
+ makefileData = makefile.getMakefileData(makeExePath, gccExePath)
+
+ # create 'c_cpp_properties.json' file
+ cP.checkCPropertiesFile()
+ cPropertiesData = cP.getCPropertiesData()
+ cPropertiesData = cP.addBuildDataToCPropertiesFile(cPropertiesData, buildData)
+ cPropertiesData = cP.addMakefileDataToCPropertiesFile(cPropertiesData, makefileData)
+ cPropertiesData = cP.addCustomDataToCPropertiesFile(cPropertiesData, makefileData, buildData)
+ cP.overwriteCPropertiesFile(cPropertiesData)
+
+ # create build folder if it does not exist jet
+ buildFolderName = makefileData[mkf.MakefileStrings.buildDir]
+ utils.createBuildFolder(buildFolderName)
diff --git a/sw/ideScripts/utilities.py b/sw/ideScripts/utilities.py
new file mode 100755
index 0000000..aca7e02
--- /dev/null
+++ b/sw/ideScripts/utilities.py
@@ -0,0 +1,614 @@
+'''
+Common utilities for 'update*.py' scripts.
+
+This script can be called standalone to verify if folder structure is correct and to print out all workspace
+paths.
+'''
+
+import os
+import shutil
+import subprocess
+import sys
+import traceback
+import platform
+
+import templateStrings as tmpStr
+
+__version__ = '1.7' # this is inherited by all 'update*.py' scripts
+
+########################################################################################################################
+# Global utilities and paths
+########################################################################################################################
+
+workspacePath = None # absolute path to workspace folder
+workspaceFilePath = None # absolute file path to '*.code-workspace' file
+cubeMxProjectFilePath = None # absolute path to *.ioc STM32CubeMX workspace file
+ideScriptsPath = None # absolute path to 'ideScripts' folder
+vsCodeFolderPath = None # absolute path to workspace '.vscode' folder
+
+makefilePath = None
+makefileBackupPath = None
+cPropertiesPath = None
+cPropertiesBackupPath = None
+buildDataPath = None
+toolsPaths = None # absolute path to toolsPaths.json with common user settings
+tasksPath = None
+tasksBackupPath = None
+launchPath = None
+launchBackupPath = None
+
+
+def printAndQuit(msg):
+ '''
+ Unrecoverable error, print and quit with system
+ '''
+ msg = "\n**** ERROR (unrecoverable) ****\n" + str(msg)
+ print(msg)
+
+ if sys.exc_info()[2]: # was exception raised?
+ print("\nTraceback:")
+ traceback.print_exc()
+ sys.exit(1)
+
+
+def pathExists(path):
+ '''
+ Checks if a path exists.
+ '''
+ if path is not None:
+ return os.path.exists(path)
+ else:
+ return False
+
+
+def commandExists(command):
+ '''
+ Checks if a command exists.
+ '''
+ if command is not None:
+ if shutil.which(command):
+ return True
+
+ return False
+
+
+def getFileName(path, withExtension=False, exception=True):
+ '''
+ Returns file name of a given 'path', with or without extension.
+ If given path is not a file, exception is raised if 'exception' is set to True. Otherwise, None is returned.
+ '''
+ if os.path.isfile(path):
+ _, fileNameExt = os.path.split(path)
+ if withExtension:
+ return fileNameExt
+ else:
+ fileName, _ = os.path.splitext(fileNameExt)
+ return fileName
+ else:
+ if exception:
+ errorMsg = "Cannot get a file name - given path is not a file:\n\t" + path
+ raise Exception(errorMsg)
+ else:
+ return None
+
+
+def detectOs():
+ '''
+ This function detects the operating system that python is running in. We use this for OS specific operations
+ '''
+ if platform.system() == "Darwin":
+ osIs = "osx"
+ elif os.name == "nt":
+ osIs = "windows"
+ elif os.name == "java":
+ osIs = "java"
+ elif os.name == "posix":
+ release = platform.release() # get system release
+ release = release.lower()
+ if release.endswith("microsoft"): # Detect windows subsystem for linux (wsl)
+ osIs = "wsl"
+ else:
+ osIs = "unix"
+ return osIs
+
+
+def copyAndRename(filePath, newPath):
+ '''
+ Copy file from 'filePath' to a new 'newPath'.
+ '''
+ if not pathExists(filePath):
+ errorMsg = "Can't copy non-existing file: " + str(filePath)
+ printAndQuit(errorMsg)
+
+ shutil.copyfile(filePath, newPath)
+ newFileName = getFileName(newPath)
+ msg = "Copy of file (new name: " + newFileName + "): " + str(filePath)
+ print(msg)
+
+
+def verifyFolderStructure():
+ '''
+ Verify if folder structure is correct.
+ 'ideScript' folder must be placed in the root of the project, where:
+ - exactly one '*.code-workspace' file must exist (this is also Workspace name)
+ - '.vscode' folder is present (it is created if it doesn't exist jet)
+
+ If this requirements are met, all paths are built - but not checked (they are checked in their respective .py files).
+ - build, launch, tasks, cpp properties files
+ - Makefile
+ - STM32CubeMX '.ioc'
+ - backup file paths
+ '''
+ global workspacePath
+ global workspaceFilePath
+ global cubeMxProjectFilePath
+ global ideScriptsPath
+ global vsCodeFolderPath
+
+ global makefilePath
+ global makefileBackupPath
+ global cPropertiesPath
+ global cPropertiesBackupPath
+ global buildDataPath
+ global toolsPaths
+ global tasksPath
+ global tasksBackupPath
+ global launchPath
+ global launchBackupPath
+
+ thisFolderPath = os.path.dirname(sys.argv[0])
+ workspacePath = pathWithForwardSlashes(os.path.dirname(thisFolderPath))
+ ideScriptsPath = pathWithForwardSlashes(os.path.join(workspacePath, 'ideScripts'))
+
+ codeWorkspaces = getCodeWorkspaces()
+ if len(codeWorkspaces) == 1:
+ # '*.code-workspace' file found
+ workspaceFilePath = codeWorkspaces[0] # file existance is previously checked in getCodeWorkspaces()
+ else:
+ errorMsg = "Invalid folder/file structure:\n"
+ errorMsg += "Exactly one VS Code workspace ('*.code-workspace') file must exist "
+ errorMsg += "in the root folder where 'ideScripts' folder is placed.\n"
+ errorMsg += "Expecting one '*.code-workspace' file in: " + workspacePath
+ printAndQuit(errorMsg)
+
+ vscodeFolder = pathWithForwardSlashes(os.path.join(workspacePath, ".vscode"))
+ if not pathExists(vscodeFolder):
+ try:
+ os.mkdir(vscodeFolder)
+ print("'.vscode' folder created.")
+ except Exception as err:
+ errorMsg = "Exception error creating '.vscode' subfolder:\n" + str(err)
+ printAndQuit(errorMsg)
+ else:
+ print("Existing '.vscode' folder used.")
+ vsCodeFolderPath = vscodeFolder
+
+ # 'ideScripts' folder found in the same folder as '*.code-workspace' file. Structure seems OK.
+ cPropertiesPath = os.path.join(workspacePath, '.vscode', 'c_cpp_properties.json')
+ cPropertiesPath = pathWithForwardSlashes(cPropertiesPath)
+ cPropertiesBackupPath = cPropertiesPath + ".backup"
+
+ makefilePath = os.path.join(workspacePath, 'Makefile')
+ makefilePath = pathWithForwardSlashes(makefilePath)
+ makefileBackupPath = makefilePath + ".backup"
+
+ buildDataPath = os.path.join(workspacePath, '.vscode', 'buildData.json')
+ buildDataPath = pathWithForwardSlashes(buildDataPath)
+ # does not have backup file, always regenerated
+
+ osIs = detectOs()
+ if osIs == "windows":
+ vsCodeSettingsFolderPath = tmpStr.defaultVsCodeSettingsFolder_WIN
+ elif osIs == "unix":
+ vsCodeSettingsFolderPath = tmpStr.defaultVsCodeSettingsFolder_UNIX
+ elif osIs == "osx":
+ vsCodeSettingsFolderPath = tmpStr.defaultVsCodeSettingsFolder_OSX
+ toolsPaths = os.path.join(vsCodeSettingsFolderPath, 'toolsPaths.json')
+ toolsPaths = pathWithForwardSlashes(toolsPaths)
+
+ tasksPath = os.path.join(workspacePath, '.vscode', 'tasks.json')
+ tasksPath = pathWithForwardSlashes(tasksPath)
+ tasksBackupPath = tasksPath + ".backup"
+
+ launchPath = os.path.join(workspacePath, '.vscode', 'launch.json')
+ launchPath = pathWithForwardSlashes(launchPath)
+ launchBackupPath = launchPath + ".backup"
+
+ cubeMxFiles = getCubeMXProjectFiles()
+ if len(cubeMxFiles) == 1:
+ cubeMxProjectFilePath = cubeMxFiles[0]
+ print("One STM32CubeMX file found: " + cubeMxProjectFilePath)
+ else: # more iocFiles:
+ cubeMxProjectFilePath = None
+ print("WARNING: None or more than one STM32CubeMX files found. None or one expected.")
+
+
+def printWorkspacePaths():
+ print("\nWorkspace root folder:", workspacePath)
+ print("VS Code workspace file:", workspaceFilePath)
+ print("CubeMX project file:", cubeMxProjectFilePath)
+ print("'ideScripts' folder:", ideScriptsPath)
+
+ print("\n'Makefile':", makefilePath)
+ print("'Makefile.backup':", makefileBackupPath)
+
+ print("\n'c_cpp_properties.json':", cPropertiesPath)
+ print("'c_cpp_properties.json.backup':", cPropertiesBackupPath)
+ print("\n'tasks.json':", tasksPath)
+ print("'tasks.json.backup':", tasksBackupPath)
+ print("\n'launch.json':", launchPath)
+ print("'launch.json.backup':", launchBackupPath)
+
+ print("\n'buildData.json':", buildDataPath)
+ print("'toolsPaths.json':", toolsPaths)
+ print()
+
+
+def getCubeMXProjectFiles():
+ '''
+ Returns list of all STM32CubeMX '.ioc' files in root directory.
+ Since only root directory is searched, all files (paths) are relative to root dir.
+ '''
+ iocFiles = []
+ for theFile in os.listdir(workspacePath):
+ if theFile.endswith('.ioc'):
+ iocFiles.append(theFile)
+
+ return iocFiles
+
+
+def createBuildFolder(folderName='build'):
+ '''
+ Create (if not already created) build folder with specified name where objects are stored when 'make' is executed.
+ '''
+ buildFolderPath = os.path.join(workspacePath, folderName)
+ buildFolderPath = pathWithForwardSlashes(buildFolderPath)
+ if not pathExists(buildFolderPath):
+ os.mkdir(buildFolderPath)
+ print("Build folder created: " + buildFolderPath)
+ else:
+ print("Build folder already exist: '" + buildFolderPath + "'")
+
+
+def getCodeWorkspaces():
+ '''
+ Search workspacePath for files that ends with '.code-workspace' (VS Code workspaces).
+ Returns list of all available VS Code workspace paths.
+
+ Only root directory is searched.
+ '''
+ codeFiles = []
+
+ for theFile in os.listdir(workspacePath):
+ if theFile.endswith(".code-workspace"):
+ theFilePath = os.path.join(workspacePath, theFile)
+ codeFiles.append(pathWithForwardSlashes(theFilePath))
+
+ return codeFiles
+
+
+def getWorkspaceName():
+ '''
+ Return name (without extension) for this project '.code-workspace' file.
+
+ Return first available file name without extension.
+ '''
+ return getFileName(workspaceFilePath)
+
+
+def stripStartOfString(dataList, stringToStrip):
+ newData = []
+
+ for data in dataList:
+ if data.find(stringToStrip) != -1:
+ item = data[len(stringToStrip):]
+ newData.append(item)
+ else:
+ newData.append(data)
+
+ return newData
+
+
+def preappendString(data, stringToAppend):
+ if type(data) is list:
+ for itemIndex, item in enumerate(data):
+ data[itemIndex] = stringToAppend + item
+ else:
+ data = stringToAppend + data
+
+ return data
+
+
+def stringToList(string, separator):
+ '''
+ Get list of unparsed string items into list. Strip any redundant spaces.
+ '''
+ allItems = []
+ items = string.split(separator)
+ for item in items:
+ item = item.strip()
+ allItems.append(item)
+
+ return allItems
+
+
+def mergeCurrentDataWithTemplate(currentData, templateData):
+ '''
+ Merge all fields from both, currentData and templateData and return merged dict.
+ This is needed for backward compatibility and adding missing default fields.
+ '''
+ def recursiveClone(template, data):
+ for key, value in data.items():
+ if key not in template:
+ template[key] = {} # create a dict in case it must be copied recursively
+
+ if isinstance(value, dict):
+ template[key] = recursiveClone(template[key], value)
+ else:
+ template[key] = value
+ return template
+
+ mergedData = recursiveClone(templateData, currentData)
+
+ return mergedData
+
+
+def getYesNoAnswer(msg):
+ '''
+ Asks the user a generic yes/no question.
+ Returns True for yes, False for no
+ '''
+ while(True):
+ resp = input(msg).lower()
+ if resp == 'y':
+ return True
+ elif resp == 'n':
+ return False
+ else:
+ continue
+
+
+def getUserPath(pathName):
+ '''
+ Get path or command from user (by entering path in terminal window).
+ Repeated as long as user does not enter a valid path or command to file/folder/executable.
+ '''
+ while True:
+ msg = "\n\tEnter path or command for '" + pathName + "':\n\tPaste here and press Enter: "
+ path = input(msg)
+ path = pathWithoutQuotes(path)
+ path = pathWithForwardSlashes(path)
+
+ if pathExists(path):
+ break
+ elif commandExists(path):
+ break
+ else:
+ print("\tPath/command not valid: ", path)
+
+ return path
+
+
+def pathWithoutQuotes(path):
+ path = path.replace('\"', '') # remove " "
+ path = path.replace('\'', '') # remove ' '
+ path = path.strip() # remove any redundant spaces
+
+ return path
+
+
+def pathWithForwardSlashes(path):
+ path = os.path.normpath(path)
+ path = path.replace("\\", "/")
+ return path
+
+
+def getGccIncludePath(gccExePath):
+ '''
+ Get path to '...\include' folder from 'gccExePath', where standard libs and headers. Needed for VS Code Intellisense.
+
+ If ARM GCC folder structure remains the same as official, the executable is located in \bin folder.
+ Other headers can be found in '\lib\gcc\arm-none-eabi\***\include' folder, which is found by searching for
+ <stdint.h>.
+ '''
+ gccExeFolderPath = os.path.dirname(gccExePath)
+ gccFolderPath = os.path.dirname(gccExeFolderPath)
+ searchPath = os.path.join(gccFolderPath, "lib", "gcc", "arm-none-eabi")
+
+ fileName = "stdint.h"
+ filePath = findFileInFolderTree(searchPath, fileName)
+ if filePath is None:
+ errorMsg = "Unable to find " + fileName + " file on path: " + searchPath
+ errorMsg += "\nOfficial GCC folder structure must remain intact!"
+ printAndQuit(errorMsg)
+
+ folderPath = os.path.dirname(filePath)
+ return folderPath
+
+
+def getPython3Executable():
+ '''
+ Uses detectOs() to determine the correct python command to use for python related tasks
+ '''
+ osIs = detectOs()
+
+ if osIs == "unix" or osIs == "wsl" or osIs=="osx": # detected unix based system
+ pythonExec = "python3"
+ else: # windows or other system
+ pythonExec = "python"
+
+ if not commandExists(pythonExec):
+ msg = "\n\tPython version 3 or later installation not detected, please install or enter custom path/command below."
+ print(msg)
+ pythonExec = getUserPath(pythonExec)
+
+ return pythonExec
+
+
+def getOpenOcdInterface(openOcdPath):
+ '''
+ Try to get OpenOCD interface file (TODO: currently hard-coded 'stlink.cfg') from 'openocd.exe' (openOcdPath) path.
+ If such path can't be found ask user for update.
+ Returns absolute path to 'stlink.cfg' file.
+ '''
+ openOcdExeFolderPath = os.path.dirname(openOcdPath) # ../bin
+ openOcdRootPath = os.path.dirname(openOcdExeFolderPath) # ../
+ # interfaceFolderPath = os.path.join(openOcdRootPath, 'scripts', 'interface') # only on windwos, linux has different structure
+
+ # get openOcdInterfacePath from
+ # TODO here of once anything other than stlink will be supported
+ fileName = "stlink.cfg"
+ openOcdInterfacePath = findFileInFolderTree(openOcdRootPath, fileName)
+ if openOcdInterfacePath is None:
+ openOcdInterfacePath = getUserPath("stlink.cfg interface")
+
+ return openOcdInterfacePath
+
+
+def getOpenOcdConfig(openOcdInterfacePath):
+ '''
+ Get openOCD configuration files from user, eg. 'interface/stlink.cfg, target/stm32f0x.cfg'
+ Paths can be passed in absolute or relative form, separated by comma. Optionally enclosed in " or '.
+ Returns the list of absolute paths to these config files.
+ '''
+ openOcdScriptsPath = os.path.dirname(os.path.dirname(openOcdInterfacePath))
+
+ while(True):
+ msg = "\n\tEnter path(s) to OpenOCD configuration file(s):\n\t\t"
+ msg += "Example: 'target/stm32f0x.cfg'. Absolute or relative to OpenOCD /scripts/ folder.\n\t\t"
+ msg += "If more than one file is needed, separate with comma.\n\t\t"
+ msg += "Paste here and press Enter: "
+ configFilesStr = input(msg)
+
+ allConfigFiles = []
+ configFiles = configFilesStr.split(',')
+ for theFile in configFiles:
+ # ex.: " C:/asd/foo bar/fail.cfg " , ' C:/asd/bar foo/fail.cfg' ,
+ theFile = theFile.strip()
+ theFile = theFile.strip('\'')
+ theFile = theFile.strip('\"')
+ theFile = theFile.strip()
+ theFile = pathWithForwardSlashes(theFile)
+
+ if pathExists(theFile): # file is an absolute path
+ allConfigFiles.append(theFile)
+ else:
+ # arg is a relative path. Must be relative to OpenOCD 'scripts' folder
+ theFileAbs = os.path.join(openOcdScriptsPath, theFile)
+ theFileAbs = pathWithForwardSlashes(theFileAbs)
+ if pathExists(theFileAbs):
+ allConfigFiles.append(theFileAbs)
+ else:
+ msg = "\tConfiguration invalid (file not found): \'" + theFileAbs + "\'"
+ print(msg)
+ break
+ else:
+ break # break loop if config detected successfully
+ continue # continue if unsuccessful
+
+ return allConfigFiles
+
+
+def getStm32SvdFile(stm32SvdPath):
+ ''' # TODO HERE - deprecated? no use cases?
+ Get stm32SvdFile from user, eg. 'STM32F042x.svd'
+ Validates that file exists
+ '''
+ while True:
+ msg = "\n\tEnter SVD File name (eg: 'STM32F042x.svd'), or 'ls' to list available SVD files.\n\tSVD file name: "
+ fileName = input(msg)
+
+ if fileName == "ls":
+ print(os.listdir(stm32SvdPath))
+ continue
+
+ stm32SvdFilePath = os.path.join(stm32SvdPath, fileName)
+ stm32SvdFilePath = pathWithForwardSlashes(stm32SvdFilePath)
+
+ if pathExists(stm32SvdFilePath):
+ break
+ else:
+ print("\tSVD File '" + fileName + "' not found")
+ continue
+
+ return fileName
+
+
+def getBuildElfFilePath(buildDirPath, projectName):
+ '''
+ Returns .elf file path.
+ '''
+ elfFile = projectName + ".elf"
+ buildFileName = os.path.join(buildDirPath, elfFile)
+ buildFileName = pathWithForwardSlashes(buildFileName)
+
+ return buildFileName
+
+
+def getAllFilesInFolderTree(pathToFolder):
+ '''
+ Get the list of all files in directory tree at given path
+ '''
+ allFiles = []
+ if os.path.exists(pathToFolder):
+ for (dirPath, dirNames, fileNames) in os.walk(pathToFolder):
+ for theFile in fileNames:
+ filePath = os.path.join(dirPath, theFile)
+ filePath = pathWithForwardSlashes(filePath)
+ allFiles.append(filePath)
+
+ return allFiles
+
+
+def findFileInFolderTree(searchPath, fileName):
+ '''
+ Find a file in a folder or subfolders, and return absolute path to the file.
+ Returns None if unsuccessful.
+ '''
+
+ for root, dirs, files in os.walk(searchPath, topdown=False):
+ if fileName in files:
+ filePath = os.path.join(root, fileName)
+ filePath = pathWithForwardSlashes(filePath)
+ return filePath
+
+ return None
+
+
+def findExecutablePath(extension, raiseException=False):
+ '''
+ Find default associated path of a given file extension, for example 'pdf'.
+ '''
+ arguments = "for /f \"delims== tokens=2\" %a in (\'assoc "
+ arguments += "." + extension
+ arguments += "\') do @ftype %a"
+
+ errorMsg = "Unable to get associated program for ." + extension + "."
+ try:
+ proc = subprocess.run(arguments, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
+ if proc.returncode == 0:
+ returnString = str(proc.stdout)
+ path = returnString.split('=')[1]
+ path = path.split('\"')[0]
+ path = path.strip()
+ path = os.path.normpath(path)
+ if os.path.exists(path):
+ return path
+ else:
+ print(errorMsg)
+
+ except Exception as err:
+ errorMsg += "Exception:\n" + str(err)
+
+ if raiseException:
+ raise Exception(errorMsg)
+ else:
+ return None
+
+
+########################################################################################################################
+if __name__ == "__main__":
+ print("Workspace generation script version: " + __version__)
+ verifyFolderStructure()
+ print("This workspace name:", getWorkspaceName())
+ printWorkspacePaths()