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 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 _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 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 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) workspaceData["settings"][ "cortex-debug.armToolchainPath"] = armToolchainPath workspaceData["settings"]["cortex-debug.openocdPath"] = buildData[ self.bStr.openOcdPath] return workspaceData
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 _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 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 _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 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 return jsonTaskData
def addDownloadAndRunTask(self, tasksData): ''' Create/repair 'CPU: Download and run' task. ''' # User edit BEGIN taskData = """ { "label": "CPU: Download and run", "type": "shell", "command": "specified below", "args": ["specified below"], "problemMatcher": [] } """ jsonTaskData = json.loads(taskData) buildData = build.BuildData().getBuildData() jsonTaskData["command"] = buildData[self.bStr.openOCDPath] jsonTaskData["args"] = [] jsonTaskData["args"].append("-f") jsonTaskData["args"].append(buildData[self.bStr.openOCDInterfacePath]) jsonTaskData["args"].append("-f") jsonTaskData["args"].append(buildData[self.bStr.openOCDTargetPath]) # -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. Not a problem since VS Code shell is started from workspace folder. workspacePath = utils.workspacePath targetExecutablePath = buildData[self.bStr.targetExecutablePath] relativeTargetExecutablePath = os.path.relpath(targetExecutablePath, workspacePath) relativeTargetExecutablePath = utils.pathWithForwardSlashes( relativeTargetExecutablePath) jsonTaskData["args"].append("-c") programString = "program " + relativeTargetExecutablePath + " verify reset exit" jsonTaskData["args"].append(programString) # User edit END tasksData = self.addOrReplaceTask(tasksData, jsonTaskData) return tasksData
def addBuildTask(self, tasksData): ''' Add build task (execute 'make' command). ''' # User edit BEGIN taskData = """ { "label": "Build project", "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["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 # User edit END tasksData = self.addOrReplaceTask(tasksData, jsonTaskData) return tasksData
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. Not a problem since VS Code shell is started from workspace folder. workspacePath = utils.workspacePath targetExecutablePath = buildData[self.bStr.targetExecutablePath] relativeTargetExecutablePath = os.path.relpath(targetExecutablePath, workspacePath) relativeTargetExecutablePath = utils.pathWithForwardSlashes( relativeTargetExecutablePath) jsonTaskData["args"].append("-c") programString = "program " + relativeTargetExecutablePath + " verify reset exit" jsonTaskData["args"].append(programString) return jsonTaskData
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)