summaryrefslogtreecommitdiff
path: root/sw/ideScripts/utilities.py
diff options
context:
space:
mode:
Diffstat (limited to 'sw/ideScripts/utilities.py')
-rwxr-xr-xsw/ideScripts/utilities.py614
1 files changed, 614 insertions, 0 deletions
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()